數據結構一直都是專業課裏面比較難的一門課程,因爲裏面涉及到了很多算法知識。這也給大家造成了一個困擾,是不是智商不行就學不了數據結構?
顯然不是!算法知識確實很難,但是我們在學習的過程中很少會去開發新的算法,基本上都是在別人的成果上加以探索。其實只要是願意花時間,善於歸納總結,我們還是能發現很多算法的規律。看的多了,就很容易在解決實際問題的時候聯想到對應的算法。
直奔主題吧。棧結構我們在之前的課堂上給大家講過,還寫了用棧解決四則運算的代碼,代碼不簡單,我相信也讓不少小夥伴奔潰。我們今天繼續用棧來解決問題,而且解決的是一個非常著名的算法–深度優先算法(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來解決,那題目就會變得非常簡單,但是如果你想不到,用自己的辦法解決,那就會麻煩的多。
趕緊把上面的代碼敲一遍吧!
更多精彩視頻、文章、嵌入式學習資料,微信關注公衆號 『學益得智能硬件』