程序運行後有一個特殊的內存區供函數調用使用ios
- 用於保存函數中的形參,局部變量,臨時變量,等
- 從起始地址開始往一個方向增加(如:高地址->低地址)
- 有一個專用 "指針" 標識當前已使用內存的 "頂部"
- 一段特殊的專用內存區
#include <iostream> using namespace std; struct Node { int value; Node *next; }; Node *create_list(int v, int len) { Node *ret = nullptr; Node *slider = nullptr; for (int i=0; i<len; ++i) { Node *n = new Node(); n->value = v++; n->next = nullptr; if (slider == nullptr) { slider = n; ret = n; } else { slider->next = n; slider = n; } } return ret; }; void destroy_list(Node *list) { while (list) { Node *del = list; list = list->next; delete del; } } void print_list(Node *list) { while (list) { cout << list->value << "->"; list = list->next; } cout << "NULL" << endl; } void r_print_even(Node *list) { if (list != nullptr) { r_print_even(list->next); if ((list->value % 2) == 0) { cout << list->value << " "; } } } int main() { Node *list = create_list(2, 5); print_list(list); r_print_even(list); destroy_list(list); cout << endl; return 0; }
輸出:算法
2->3->4->5->6->NULL 6 4 2
退棧打印的過程就是回溯的過程。
遞歸調用時,將數據保存在棧空間,棧返回時再使用。編程
在一個 8x8 的國際象棋盤上,有 8 個皇后,每一個皇后佔一格;要求皇后間不會出現相互 "攻擊" 的現象(不能有兩個皇后處在同一行)。數組
棋盤:二維數組 (10 * 10)數據結構
- 0 表示位置爲空,1表示皇后,2表示邊界
方向ide
- 水平: (-1, 0),(1, 0)
- 垂直: (0, -1),(0, 1)
- 對角線: (-1, 1),(-1,-1),(1,-1),(1,1)
- 位置: Struct Pos;
struct Pos { int x; int y; };
- 初始化: i = 1
- 初始化: j = 1
- 從第 j 行開始,恢復 i 的有效值(經過函數調用棧進行回溯), 判斷 i 的位置
a. 位置 i 可放入皇后:標記位置(i, j), j++, 轉步驟 2
b. 位置 i 不可放入皇后:i++, 轉步驟 a
c. 當 i > 8 時, j--, 轉步驟 3- 結束: 第 8 行有位置可放入皇后
#include <iostream> #include <list> using namespace std; template <int SIZE> class QueueSolution { protected: enum { N = SIZE + 2}; // 包含邊界元素 struct Pos { Pos(int px = 0, int py = 0) : x(px), y(py) {} int x; int y; }; int m_chessboard[N][N]; // 棋盤 Pos m_direction[3]; // 3方向數組 (左下、正下、右下) list<Pos> m_solution; // 放置皇后位置的解決方案 int m_count; // 解的數量 void init() { m_count = 0; // 邊界填充 for (int i=0; i<N; i+=(N-1)) { for (int j=0; j<N; ++j) { m_chessboard[i][j] = 2; m_chessboard[j][i] = 2; } } // 棋位填充 for (int i=1; i<=SIZE; ++i) { for (int j=1; j<=SIZE; ++j) { m_chessboard[i][j] = 0; } } // 方向數組填充 m_direction[0].x = -1; // ↙ m_direction[0].y = -1; m_direction[1].x = 0; // ↓ m_direction[1].y = -1; m_direction[2].x = 1; // ↘ m_direction[2].y = -1; } void print() { // 當前解的八皇座標 for (auto iter = m_solution.cbegin(); iter != m_solution.cend(); ++iter) { cout << "(" << iter->x << "," << iter->y << ")" << " "; } cout << endl; for (int i=0; i<N; ++i) { for (int j=0; j<N; ++j) { switch (m_chessboard[i][j]) { case 0 : cout << "."; break; case 1 : cout << "#"; break; case 2 : cout << "*"; break; } //cout << hex << m_chessboard[i][j]; } cout << endl; } cout << endl; } bool check(int x, int y, int d) { bool flag = true; do { x += m_direction[d].x; y += m_direction[d].y; flag = flag && (m_chessboard[x][y] == 0); }while (flag); // 當棋位不爲空(到達邊界或有棋子)退出 return (m_chessboard[x][y] == 2); // 檢查是否到達棋盤邊界 } // 核心算法 !! void run(int j) { if (j <= SIZE) // 1. 位置查找 { for (int i=1; i<=SIZE; ++i) // 1.1 嘗試 j 行的每一列 { if (check(i, j, 0) && check(i, j, 1) && check(i, j, 2)) // 1.1。1 檢查棋位是否可用 { m_chessboard[i][j] = 1; // 1.1.2 棋位標記 m_solution.push_back(Pos(i, j)); run(j + 1); // 1.1.3 查找 j +1 行 // 1.1.4 擦除棋位標記 // 當查找成功時,run 會不斷遞歸查找 j + 1 行,直到 j == SIZE 時,在出口退出; // 當運行到這裏,說明 j + 1 行位置查找失敗,即 j 行的位置是錯誤的,擦除標記,繼續嘗試 j 行的下一列元素(for(...)) m_chessboard[i][j] = 0; m_solution.pop_back(); } } } else // 2. 遞歸出口,[1-SIZE] 找到解決方案 { m_count ++; print(); } } public: QueueSolution() { init(); } void run() { run(1); cout << "Total : " << m_count << endl; } }; int main() { QueueSolution<8> qs; qs.run(); return 0; }
輸出:函數
... ... ... (8,1) (2,2) (5,3) (3,4) (1,5) (7,6) (4,7) (6,8) ********** *....#...* *.#......* *...#....* *......#.* *..#.....* *.......#* *.....#..* *#.......* ********** (8,1) (3,2) (1,3) (6,4) (2,5) (5,6) (7,7) (4,8) ********** *..#.....* *....#...* *.#......* *.......#* *.....#..* *...#....* *......#.* *#.......* ********** (8,1) (4,2) (1,3) (3,4) (6,5) (2,6) (7,7) (5,8) ********** *..#.....* *.....#..* *...#....* *.#......* *.......#* *....#...* *......#.* *#.......* ********** Total : 92
說明:棋位檢查
bool check(int x, int y, int d)
spa
只檢查 3 個方向(左下,正下,右下),由於棋盤被初始化爲空,且起始爲有規律放置。指針
說明:回溯嘗試
void run(int j)
當發現 j 行的任意位置都不能放置,則說明 j -1 行的的位置是錯誤的,依次不斷進行回溯嘗試。code
- 程序運行後的棧存儲區專供函數調用使用
- 棧存儲區用於保存形參,局部變量,臨時變量,等
- 利用棧存儲區可以方便的實現回溯算法
- 八皇后問題是棧回溯的經典應用
以上內容整理於狄泰軟件學院系列課程,請你們保護原創!