vlambda博客
学习文章列表

如何用栈实现深度优先算法


数据结构一直都是专业课里面比较难的一门课程,因为里面涉及到了很多算法知识。这也给大家造成了一个困扰,是不是智商不行就学不了数据结构?

如何用栈实现深度优先算法


显然不是!算法知识确实很难,但是我们在学习的过程中很少会去开发新的算法,基本上都是在别人的成果上加以探索。其实只要是愿意花时间,善于归纳总结,我们还是能发现很多算法的规律。看的多了,就很容易在解决实际问题的时候联想到对应的算法。


直奔主题吧。栈结构我们在之前的课堂上给大家讲过,还写了用栈解决四则运算的代码,代码不简单,我相信也让不少小伙伴奔溃。我们今天继续用栈来解决问题,而且解决的是一个非常著名的算法--深度优先算法( Depth First Search),简称DFS。


DFS是图里面的一种遍历算法。提到图,大家又被吓的一激灵,这可是比树还要难的模型啊。别紧张,图确实很难,但是早就有人把它总结出来了,只要套用固定的算法,还是可以解决的。我们还是先看看DFS是怎么回事吧。


DFS简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个 节点只能访问一次,等到发现不能再深入了,再回头换一条路探索。啥玩意?看看下面的例子:


如何用栈实现深度优先算法

对于这种结构, DFS方法首先从根节点1开始,其搜索节点顺序是1,2,3,4,5,6,7,8。


步骤如下:
1、访问第一个节点,并且把第一个节点入栈 如何用栈实现深度优先算法


2、 找出与此点邻接的且尚未遍历的点,进行标记,然后放入stack中,依次进行;

如何用栈实现深度优先算法


3、 如果此点没有尚未遍历的邻接点,则将此点从stack中弹出,再按照2依次进行。比如第5个节点进栈后,找不到合适的下一个节点,把第5个节点弹出,再去寻找第4个节点的另一条出路。

如何用栈实现深度优先算法

如何用栈实现深度优先算法

4、 直到遍历完整个树,stack里的元素都将弹出,最后栈为空,DFS遍历完成。

如何用栈实现深度优先算法


我们再来看一个深度优先算法的典型应用:迷宫问题。简单来看一下问题描述,然后直接上代码。


问题描述:


以一个m*n的长方阵表示迷宫,0和1分别表示迷宫中的通路和障碍。迷宫问题要求求出从入口(1,1)到出口(m,n)的一条通路,或得出没有通路的结论。基本要求:首先实现一个以链表作存储结构的栈类型,然后编写一个求迷宫问题的非递归程序,求得的通路,其中:(x,y)指示迷宫中的一个坐标,dir表示走到下一坐标的方向。左上角(1,1)为入口,右下角(m,n)为出口。 


#include <stdio.h>
#define SIZE 1024
struct Box { int x; //点的横坐标 int y; //点的纵坐标 int dir; //下一个点的方向};typedef struct Box Box;
typedef struct{ Box data[SIZE]; int top;}Stack;
//用二位数组表示迷宫,0表示可以走,1表示不可以走int map[6][6] = { {0, 0, 0, 1, 0, 1}, {0, 1, 0, 0, 1, 0}, {0, 0, 1, 0, 0, 0}, {0, 1, 0, 1, 1, 0}, {0, 1, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 0}};
//初始化顺序栈int InitStack(Stack *s) { if (!s) return -1; s->top = -1; return 0;}
//检查该点是否可以走,1表示不能走,超出地图也不能走int check(Box b){ if (b.x < 0 || b.x > 5 || b.y < 0 || b.y > 5) //点不在迷宫内 return -1; if (map[b.x][b.y] != 0) //点不能走 return -1; return 1;}
//进栈操作int push(Stack *s, Box b){ if (s->top == SIZE - 1 || !s) return -1; s->top++; s->data[s->top] = b; return 0;}
//判断栈是否为空int EmptyStack(Stack *s){ if (!s) return -1; return (s->top == -1) ? 1 : 0;}
//获取栈顶元素int GetTop(Stack *s, Box *b){ if (!s || s->top == -1) return -1; *b = s->data[s->top]; return 0;}
//显示一条可以走得通的路径(路径保存在栈中,遍历栈可以得到路径)void ShowPath(Stack *s){ int i; for (i = 0; i <= s->top; i++) { printf("(%d %d)", s->data[i].x, s->data[i].y); if (i != s->top) { printf("->"); } } printf("\n");}
//出栈操作int pop(Stack *s, Box *b){ if (!s || s->top == -1) return -1; *b = s->data[s->top]; s->top--; return 0;}
//修改栈顶元素,下一个点的位置int ChangeDir(Stack *s, int dir){ if (!s || s->top == -1) return -1; s->data[s->top].dir = dir; return 0;}
//x1 y1表示起点 x2,y2表示终点int Walk(Stack *s, int x1, int y1, int x2, int y2){ Box now, t; int i, j;
now.x = x1; now.y = y1; now.dir = -1;
if (check(now) == 1) {        push(s, now);                  //如果该点可以走,则进栈 map[now.x][now.y] = -1; //表示点走过 }
while (EmptyStack(s) != 1) { GetTop(s, &now); //获取栈顶的点 if (now.x == x2 && now.y == y2) //栈顶元素就是终点 { ShowPath(s); map[now.x][now.y] = 0; //表示点没走过 pop(s, &now);            GetTop(s, &now); } else { int k; for (k = now.dir + 1; k < 4; k++) //判断每个方向是否可走 { switch(k) { case 0: //向上走 i = now.x - 1; j = now.y; break; case 1: i = now.x; j = now.y + 1; break; case 2: //向下 i = now.x + 1; j = now.y; break; case 3: //向左 i = now.x; j = now.y - 1; break; } t.x = i; t.y = j; t.dir = -1; if (check(t) == 1) { ChangeDir(s, k); push(s, t); map[i][j] = -1; break; } } if (k == 4) //没有方向可以走 { pop(s, &now); //无路可走,出栈 map[now.x][now.y] = 0; } } }}
int main(){ Stack stack;
InitStack(&stack);
Walk(&stack, 0, 0, 5, 5);
return 0;}


DFS也是比较符合人的正常思维的算法,沿着一条路一直走下去,发现行不通了再回来,寻找下一个出路。比如上面的迷宫问题其实就是这样解决的。当然如果我们用递归算法也能解决,思想都是一样的。大家再平时的学习过程中也要多去积累一些案例,当你看到一个面试题的时候,如果能够立马想到这个问题用DFS来解决,那题目就会变得非常简单,但是如果你想不到,用自己的办法解决,那就会麻烦的多。


赶紧把上面的代码敲一遍吧!