数据结构一直都是专业课里面比较难的一门课程,因为里面涉及到了很多算法知识。这也给大家造成了一个困扰,是不是智商不行就学不了数据结构?
显然不是!算法知识确实很难,但是我们在学习的过程中很少会去开发新的算法,基本上都是在别人的成果上加以探索。其实只要是愿意花时间,善于归纳总结,我们还是能发现很多算法的规律。看的多了,就很容易在解决实际问题的时候联想到对应的算法。
直奔主题吧。栈结构我们在之前的课堂上给大家讲过,还写了用栈解决四则运算的代码,代码不简单,我相信也让不少小伙伴奔溃。我们今天继续用栈来解决问题,而且解决的是一个非常著名的算法–深度优先算法(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来解决,那题目就会变得非常简单,但是如果你想不到,用自己的办法解决,那就会麻烦的多。
赶紧把上面的代码敲一遍吧!
更多精彩视频、文章、嵌入式学习资料,微信关注公众号 『学益得智能硬件』