LinuxC編程一站式學習——堆棧

本科學習數據結構時實踐比較少,因此最近開始複習,看到網站http://learn.akae.cn/media/ch12s03.html,既可以打基礎,又可以順便學習Linux的知識。

第12章講“堆棧”,第3節講“深度優先算法”尋找路徑迷宮。代碼已經給出了,爲了方便其他同學下載調試,我這裏也附上:

原始代碼:

#include <stdio.h>
#define MAX_ROW 5
#define MAX_COL 5

struct point {
  int row;
  int col;
} stack[512];
int top = 0;

int maze[5][5] = {
  {0, 1, 0, 0, 0},
  {0, 1, 0, 1, 0},
  {0, 0, 0, 0, 0},
  {0, 1, 1, 1, 0},
  {0, 0, 0, 1, 0}
};

void push(struct point p)
{
  stack[top++] = p;
}

struct point pop()
{
  return stack[--top];
}

int is_empty()
{
  return top == 0;
}

void print_maze(void)
{
  int i, j;
  for (i = 0; i < MAX_ROW; i++) {
    for (j = 0; j < MAX_COL; j++)
      printf("%d ", maze[i][j]);
    putchar('\n');
  }
  printf("*********\n");
}

struct point predecessor[MAX_ROW][MAX_COL] = {
  {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}},
  {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}},
  {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}},
  {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}},
  {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}}
};

void visit(int row, int col, struct point pre)
{
  struct point visit_point = { row, col };
  maze[row][col] = 2;
  predecessor[row][col] = pre;
  push(visit_point);
}

int main()
{
  struct point p = { 0, 0 };
  maze[p.row][p.col] = 2;
  push(p);

  while (!is_empty()) {
    p = pop();
    if (p.row == MAX_ROW - 1 && p.col == MAX_COL - 1)
      break;
    if (p.col + 1 < MAX_COL && maze[p.row][p.col + 1] == 0)
      visit(p.row, p.col + 1, p);
    if (p.row + 1 < MAX_ROW && maze[p.row + 1][p.col] == 0)
      visit(p.row + 1, p.col, p);
    if (p.col > 0 && maze[p.row][p.col - 1] == 0)
      visit(p.row, p.col - 1, p);
    if (p.row > 0 && maze[p.row - 1][p.col] == 0)
      visit(p.row - 1, p.col, p);
    print_maze();
  }
  if (p.row == MAX_ROW - 1 && p.col == MAX_COL - 1) {
    printf("(%d, %d)\n", p.row, p.col);
    while (predecessor[p.row][p.col].row != -1) {
      p = predecessor[p.row][p.col];
      printf("(%d, %d)\n", p.row, p.col);
    }
  } else
    printf("No path!\n");
  return 0;
}

下面有三道習題。

1. 正向打印路徑

文中已經分析“正向打印路徑”原理上的不可行,因此我想到的方法是:

A:把輸出到路徑壓到一個新的堆棧中,然後pop出來——這很好理解。

B:取巧的做法——把迷宮出口(4,4)當作起點,入口(0,0)當作終點,找到的路徑(4,4)->(0,0)逆序輸出即爲(0,0)->(4,4),倘若沒有路徑,那麼只會輸出“No pathing!”

B方法代碼更改的地方很少:

#include <stdio.h>
#define MAX_ROW 5
#define MAX_COL 5

struct point {
  int row;
  int col;
} stack[512];
int top = 0;

int maze[5][5] = {
  {0, 1, 0, 0, 0},
  {0, 1, 0, 1, 0},
  {0, 0, 0, 0, 0},
  {0, 1, 1, 1, 0},
  {0, 0, 0, 1, 0}
};

void push(struct point p)
{
  stack[top++] = p;
}

struct point pop()
{
  return stack[--top];
}

int is_empty()
{
  return top == 0;
}

void print_maze(void)
{
  int i, j;
  for (i = 0; i < MAX_ROW; i++) {
    for (j = 0; j < MAX_COL; j++)
      printf("%d ", maze[i][j]);
    putchar('\n');
  }
  printf("*********\n");
}

struct point predecessor[MAX_ROW][MAX_COL] = {
  {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}},
  {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}},
  {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}},
  {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}},
  {{-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1}}
};

void visit(int row, int col, struct point pre)
{
  struct point visit_point = { row, col };
  maze[row][col] = 2;
  predecessor[row][col] = pre;
  push(visit_point);
}

int main()
{
  struct point p = { MAX_ROW-1, MAX_COL-1 };	// 起點爲迷宮終點
  maze[p.row][p.col] = 2;
  push(p);

  while (!is_empty()) {
    p = pop();
    if (p.row == 0 && p.col == 0)	// 終止條件爲達到起點
      break;
    if (p.col + 1 < MAX_COL && maze[p.row][p.col + 1] == 0)
      visit(p.row, p.col + 1, p);
    if (p.row + 1 < MAX_ROW && maze[p.row + 1][p.col] == 0)
      visit(p.row + 1, p.col, p);
    if (p.col > 0 && maze[p.row][p.col - 1] == 0)
      visit(p.row, p.col - 1, p);
    if (p.row > 0 && maze[p.row - 1][p.col] == 0)
      visit(p.row - 1, p.col, p);
    print_maze();
  }
  if (p.row == 0 && p.col == 0) {
    printf("(%d, %d)\n", p.row, p.col);
    while (predecessor[p.row][p.col].row != -1) {	// 最後只剩下起點以及未被訪問到點到predecessor值爲(-1,-1)
      p = predecessor[p.row][p.col];
      printf("(%d, %d)\n", p.row, p.col);
    }
  } else
    printf("No path!\n");
  return 0;
}

讀者會發現,這種方法最終的結果maze矩陣中出現了0,原因是:本算法是深度優先算法,假如第一條路徑就能夠到達終點,那麼算法就不會再計算了。

由此讀者可以想到延伸的問題:求迷宮有幾條路徑可以達到終點?進而延伸:每條路徑的長度是多少?這兩個問題等到我以後再研究時會再練習,本文就先略去了。


2. 縮小空間

原來predecessor存儲的是point的二維數組,我首先想到的是將其改爲二維整形數據——代表指向前一個位置的“方向”。visit函數需要傳入這個方向值;最後逆序輸出時也需要根據方向值找到下一個位置點。首先,四個方向宏定義。

#include <stdio.h>
#define MAX_ROW 5
#define MAX_COL 5
#define RIGHT	1
#define LEFT	2
#define UP	3
#define DOWN	4

struct point {
	int row;
	int col;
} stack[512];
int top = 0;

int maze[5][5] = {
	{0, 1, 0, 0, 0},
	{0, 1, 0, 1, 0},
	{0, 0, 0, 0, 0},
	{0, 1, 1, 1, 0},
	{0, 0, 0, 1, 0}
};

void push(struct point p)
{
	stack[top++] = p;
}

struct point pop()
{
	return stack[--top];
}

int is_empty()
{
	return top == 0;
}

void print_maze(void)
{
	int i, j;
	for (i = 0; i < MAX_ROW; i++) {
		for (j = 0; j < MAX_COL; j++)
			printf("%d ", maze[i][j]);
		putchar('\n');
	}
	printf("*********\n");
}

int predecessor[MAX_ROW][MAX_COL] = {
	{0, 0, 0, 0, 0},
	{0, 0, 0, 0, 0},
	{0, 0, 0, 0, 0},
	{0, 0, 0, 0, 0},
	{0, 0, 0, 0, 0}
};

void visit(int row, int col, struct point pre, int Dir)
{
	struct point visit_point = { row, col };
	maze[row][col] = 2;
	predecessor[row][col] = Dir;
	push(visit_point);
}

int main()
{
	struct point p = { 0, 0 };
	maze[p.row][p.col] = 2;
	push(p);

	while (!is_empty()) {
		p = pop();
		if (p.row == MAX_ROW - 1 && p.col == MAX_COL - 1)
			break;
		if (p.col + 1 < MAX_COL && maze[p.row][p.col + 1] == 0)
			visit(p.row, p.col + 1, p, LEFT);
		if (p.row + 1 < MAX_ROW && maze[p.row + 1][p.col] == 0)
			visit(p.row + 1, p.col, p, UP);
		if (p.col > 0 && maze[p.row][p.col - 1] == 0)
			visit(p.row, p.col - 1, p, RIGHT);
		if (p.row > 0 && maze[p.row - 1][p.col] == 0)
			visit(p.row - 1, p.col, p, DOWN);
		print_maze();
	}
	if (p.row == MAX_ROW - 1 && p.col == MAX_COL - 1) {
		printf("(%d, %d)\n", p.row, p.col);
		while (predecessor[p.row][p.col] != 0) {
			switch (predecessor[p.row][p.col]) {
			case UP:
				p.row -= 1;
				break;
			case DOWN:
				p.row += 1;
				break;
			case LEFT:
				p.col -= 1;
				break;
			case RIGHT:
				p.col += 1;
				break;
			}
			// p = predecessor[p.row][p.col];
			printf("(%d, %d)\n", p.row, p.col);
		}
	} else
		printf("No path!\n");
	return 0;
}
起先設置predecessor的初始值都是0,最終找到的路徑上點到predecessor的值則爲LEFT/UP/RIGHT/DOWN,因此沿着已知的方向依次判斷下個座標點到位置,直到preecessor值=0的起點。


更進一步,我想,predecessor和maze都是2維整形數組,爲什麼不能把兩者合併呢?於是修改如下:(爲了防止數據混淆,修改原來到宏定義)

#include <stdio.h>
#define MAX_ROW 5
#define MAX_COL 5
#define RIGHT	3
#define LEFT	4
#define UP	5
#define DOWN	6

struct point {
	int row;
	int col;
} stack[512];
int top = 0;

int maze[5][5] = {
	{0, 0, 0, 0, 0},
	{1, 0, 1, 1, 0},
	{0, 0, 1, 1, 1},
	{0, 1, 0, 0, 0},
	{0, 0, 0, 1, 0}
};

void push(struct point p)
{
	stack[top++] = p;
}

struct point pop()
{
	return stack[--top];
}

int is_empty()
{
	return top == 0;
}

void print_maze(void)
{
	int i, j;
	for (i = 0; i < MAX_ROW; i++) {
		for (j = 0; j < MAX_COL; j++)
			printf("%d ", maze[i][j]);
		putchar('\n');
	}
	printf("*********\n");
}

/*
int predecessor[MAX_ROW][MAX_COL] = {
	{0, 0, 0, 0, 0},
	{0, 0, 0, 0, 0},
	{0, 0, 0, 0, 0},
	{0, 0, 0, 0, 0},
	{0, 0, 0, 0, 0}
};
*/

void visit(int row, int col, struct point pre, int Dir)
{
	struct point visit_point = { row, col };
	maze[row][col] = Dir;
	// predecessor[row][col] = Dir;
	push(visit_point);
}

int main()
{
	struct point p = { 0, 0 };
	maze[p.row][p.col] = 2;	// 起始點的方向設置爲2,代表無方向。
	push(p);

	while (!is_empty()) {
		p = pop();
		if (p.row == MAX_ROW - 1 && p.col == MAX_COL - 1)
			break;
		if (p.col + 1 < MAX_COL && maze[p.row][p.col + 1] == 0)
			visit(p.row, p.col + 1, p, LEFT);
		if (p.row + 1 < MAX_ROW && maze[p.row + 1][p.col] == 0)
			visit(p.row + 1, p.col, p, UP);
		if (p.col > 0 && maze[p.row][p.col - 1] == 0)
			visit(p.row, p.col - 1, p, RIGHT);
		if (p.row > 0 && maze[p.row - 1][p.col] == 0)
			visit(p.row - 1, p.col, p, DOWN);
		print_maze();
	}	// 倘若有路徑,那麼最後pop出的p即(4,4)
	if (p.row == MAX_ROW - 1 && p.col == MAX_COL - 1) {
		printf("(%d, %d)\n", p.row, p.col);
		// 起點的方向是2,其他皆爲1/3/4/5/6
		// 0代表尚未訪問到點:當第一條路徑就打到終點時,其它路徑也就沒有再訪問了
		while (maze[p.row][p.col] != 2) {	
			switch (maze[p.row][p.col]) {
			case UP:
				p.row -= 1;
				break;
			case DOWN:
				p.row += 1;
				break;
			case LEFT:
				p.col -= 1;
				break;
			case RIGHT:
				p.col += 1;
				break;
			}
			// p = predecessor[p.row][p.col];
			printf("(%d, %d)\n", p.row, p.col);
		}
	} else	// 倘若最後pop出的非(4,4),那麼就沒有路徑
		printf("No path!\n");
	return 0;
}


3. 堆棧變遞歸

待續。。。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章