函数的调用
程序运行后有一个特殊的内存区供函数调用使用
- 用于保存函数中的形参,局部变量,临时变量,等
- 从起始地址开始往一个方向增长(如:高地址->低地址)
- 有一个专用 "指针" 标识当前已使用内存的 "顶部"
程序中的栈区
- 一段特殊的专用内存区
实例分析:逆序打印单链表中的偶数节点
编程实验:函数调用栈分析
#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表示边界
方向
- 水平: (-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 == SIZE一次查找成功,即 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)
只检查 3 个方向(左下,正下,右下),因为棋盘被初始化为空,且起始为有规律放置。
说明:回溯尝试
void run(int j)
当发现 j 行的任意位置都不能放置,则说明 j -1 行的的位置是错误的,依次不断进行回溯尝试。
小结
- 程序运行后的栈存储区专供函数调用使用
- 栈存储区用于保存形参,局部变量,临时变量,等
- 利用栈存储区能够方便的实现回溯算法
- 八皇后问题是栈回溯的经典应用
以上内容整理于狄泰软件学院系列课程,请大家保护原创!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。