栈在递归中的应用
递归的精髓在于能否将原始问题转换为属性相同但规模较小的问题。
- 函数调用的特点:最后被调用的函数最先执行结束(LIFO)。
- 函数调用时,需要用一个栈存储:
① 调用返回地址
② 实参
③ 局部变量- 必须注意递归模型不能是循环定义的,其必须满足下面的两个条件:
①递归表达式(递归体)
②边界条件(递归出口)
斐波那契数列:
F
i
b
(
n
)
{
F
i
b
(
n
−
1
)
+
F
i
b
(
n
−
1
)
,
n
>
1
1
,
n
=
1
0
,
n
=
0
Fib(n)\begin{cases} Fib(n-1)+Fib(n-1),n>1\\ 1,n=1\\ 0,n=0 \end{cases} \\
Fib(n)⎩⎪⎨⎪⎧Fib(n−1)+Fib(n−1),n>11,n=10,n=0
int Fib(int n) //斐波那契数列的实现
{
if (n == 0) return 0; //边界条件
else if (n == 1) return 1; //边界条件
else return Fib(n - 1) + Fib(n - 2); //递归表达式
}
阶乘:
f
a
c
t
o
r
i
a
l
(
n
)
{
n
×
f
a
c
t
o
r
i
a
l
(
n
−
1
)
,
n
>
1
1
,
n
=
1
1
,
n
=
0
factorial (n)\begin{cases} n×factorial(n−1) ,n>1\\ 1,n=1\\ 1,n=0 \end{cases} \\
factorial(n)⎩⎪⎨⎪⎧n×factorial(n−1),n>11,n=11,n=0
int factorial(int n) //阶乘的实现
{
if (n == 0 || n == 1) return 1; //边界条件
else return n*factorial(n-1); //递归表达式
}
利用一个栈实现以下递归函数的非递归计算:
P
n
(
x
)
{
2
x
P
n
−
1
(
x
)
−
2
(
n
−
1
)
P
n
−
2
(
x
)
,
n
>
1
2
x
,
n
=
1
1
,
n
=
0
P_n(x)\begin{cases} 2xP_{n-1}(x)-2(n-1)P_{n-2}(x) ,n>1\\ 2x,n=1\\ 1,n=0 \end{cases} \\
Pn(x)⎩⎪⎨⎪⎧2xPn−1(x)−2(n−1)Pn−2(x),n>12x,n=11,n=0
算法实现
- 算法思想:设置一个栈用于保存n和对应的 P n ( x ) P_n(x) Pn(x)值,找中相邻元素的 P n ( x ) P_n(x) Pn(x)有题中关系。然后边出栈边计算 P n ( x ) P_n(x) Pn(x),栈空后该值就计算出来了。
#include<stdio.h>
#define MaxSize 100
double recursion(int n, double x)
{
struct stack
{
int no;
double val;
}St[MaxSize];
int top = -1, i;
double fv1 = 1, fv2 = 2 * x;
for (i = n; i >= 2; i--)
{
top++;
St[top].no = i;
}
while (top >= 0)
{
St[top].val = 2 * x * fv2 - 2 * (St[top].no - 1) * fv1;
fv1 = fv2;
fv2 = St[top].val;
top--;
}
if (n == 0) return fv1;
return fv2;
}
int main()
{
printf("%lf", recursion(4, 3.2));
return 0;
}
//输出结果:1198.201600
栈在迷宫问题中的应用
- 给定一个M×N的迷宫图,求一条从指定入口到出口的迷宫路径。假设一个迷宫图(这里M=8,N=8),其中的每个方块用空白表示通道,用阴影表示障碍物。一般情况下,所求迷宫路径是简单路径,即在求得的迷宫路径上不会重块。一个迷宫图的迷宫路径可能有多条,这些迷宫路径有长有短,这里仅仅考虑用栈求一条从指定入口到出口的迷宫路径。
- 算法思想:为了表示迷宫,设置一个数组mg[ ],其中每个元素表示一个方块的状态,为0时表示对应方块是通道,为1时表示对应方块是障碍物(不可走)。为了算法方便,一般在迷官的外围加一条围墙。(由于迷宫四周加了一条围墙,故mg数组的行数和列数均加上2)
算法实现:
#include<iostream>
using namespace std;
#include<malloc.h>
#define MaxSize 200
#define M 8
#define N 8
typedef struct Box
{
int i; //当前方块的行号
int j; //当前方块的列号
int di; //di是下一个相邻可走方位的方位号
};
typedef struct StType
{
Box data[MaxSize];
int top;
};
bool InitStack(StType*& S)
{
S = (StType*)malloc(sizeof(StType)); //分配一个顺序栈空间,首地址存放在s中
if (S == NULL) return false; //内存不足,分配失败
S->top = -1; //栈顶指针置为-1
return true;
}
bool StackEmpty(StType* S)
{
return S->top == -1;
}
bool Push(StType*& S, Box x)
{
if (S->top == MaxSize - 1) return false; //栈满的情况,即栈上溢出
S->top++; //栈顶指针增1
S->data[S->top] = x; //元素x放在栈顶指针处
return true;
}
bool Pop(StType*& S, Box& x)
{
if (S->top == -1) return false; //栈为空的情况,即栈下溢出
x = S->data[S->top]; //取栈顶元素
S->top--; //栈顶指针减1
return true;
}
bool GetTop(StType* S, Box& x)
{
if (S->top == -1) return false; //栈为空的情况,即栈下溢出
x = S->data[S->top]; //取栈顶元素
return true;
}
void DestoryStack(StType*& S) //销毁栈
{
free(S);
}
bool mgpath(int xi, int yi, int xe, int ye) //求解路径为(xi, yi)→ (xe, ye)
{
int mg[M + 2][N + 2] = { {1,1,1,1,1,1,1,1,1,1},{1,0,0,1,0,0,0,1,0,1},
{1,0,0,1,0,0,0,1,0,1},{1,0,0,0,0,1,1,0,0,1},
{1,0,1,1,1,0,0,0,0,1},{1,0,0,0,1,0,0,0,1},
{1,0,1,0,0,0,1,0,0,1},{1,0,1,1,1,0,1,1,0,1},
{1,1,0,0,0,0,0,0,0,1},{1,1,1,1,1,1,1,1,1,1} };
Box path[MaxSize], e;
int i, j, di, i1, j1, k;
bool find;
StType* st; //定义栈st
InitStack(st); //初始化栈顶指针
e.i = xi; e.j = yi; e.di = -1; //设置e为入口
Push(st, e); //方块e进栈
mg[xi][yi] = -1; //将入口的迷宫值置为-1,避免重复走到该方块
while (!StackEmpty(st)) //栈不空时循环
{
GetTop(st, e); //取栈顶方块e
i = e.i; j = e.j; di = e.di;
if (i == xe && j == ye) //找到了出口, 输出该路径
{
printf("一条迷宫路径如下:\n");
k = 0;
while (!StackEmpty(st))
{
Pop(st, e); //出栈方块e
path[k++] = e; //将e添加到path数组中
}
while (k >= 1)
{
k--;
printf("\t(%d,%d)", path[k].i, path[k].j);
if ((k + 1) % 5 == 0) printf("\n"); //每输出5个方块后换一行
}
printf("\n");
DestoryStack(st); //销毁栈
return true; //输出一条迷宫路径后返回true
}
find = false;
while (di < 4 && !find)
{
di++; //找方块(i,j)的下一个相邻可走方块(i1, j1)
switch (di)
{
case 0: i1 = i - 1; j1 = j; break;
case 1: i1 = i; j1 = j + 1; break;
case 2: i1 = i + 1; j1 = j; break;
case 3: i1 = i; j1 = j - 1; break;
}
if (mg[i1][j1] == 0) find = true; //找到一个相邻可走方块,设置find为真
}
if (find) //找到了一个相邻可走方块(i1,j1)
{
st->data[st->top].di = di; //修改原栈顶元素的di值
e.i = i1; e.j = j1; e.di = -1;
Push(st, e); //相邻可走方块e进栈
mg[i1][j1] = -1; //将(i1,j1)迷宫值置为一1,避免重复走到该方块
}
else //没有路径可走,则退栈
{
Pop(st, e); //将栈顶方块退栈
mg[e.i][e.j] = 0; //让退栈方块的位置变为其他路径可走方块
}
}
DestoryStack(st); //销毁栈
return false; //表示没有可走路径,返回 false
}
int main()
{
if (!mgpath(1, 1, M, N)) printf("该迷宫问题没有解!");
return 0;
}
程序分析:
- 运行结果:
队列在迷宫问题中的应用
- 算法思想:从入口(xi,yi)开始,利用队列的特点,一层一层向外扩展查找可走的方块,直到找到出口为止,这个方法就是广度优先搜索方法。
算法实现:
//用队列求解迷宫问题
#include <stdio.h>
#include <malloc.h>
#define MaxSize 100
#define M 8
#define N 8
int mg[M + 2][N + 2] = { {1,1,1,1,1,1,1,1,1,1}, {1,0,0,1,0,0,0,1,0,1},
{1,0,0,1,0,0,0,1,0,1}, {1,0,0,0,0,1,1,0,0,1},
{1,0,1,1,1,0,0,0,0,1}, {1,0,0,0,1,0,0,0,0,1},
{1,0,1,0,0,0,1,0,0,1}, {1,0,1,1,1,0,1,1,0,1},
{1,1,0,0,0,0,0,0,0,1}, {1,1,1,1,1,1,1,1,1,1} };
typedef struct
{
int i, j; //方块的位置
int pre; //本路径中上一方块在队列中的下标
} Box; //方块类型
typedef struct
{
Box data[MaxSize];
int front, rear; //队头指针和队尾指针
} QuType; //顺序队类型
void InitQueue(QuType *&q) //初始化队列
{ q=(QuType *)malloc (sizeof(QuType));
q->front = q->rear = -1;
}
void DestroyQueue(QuType *&q) //销毁队列
{
free(q);
}
bool QueueEmpty(QuType *q) //判断队列是否为空
{
return(q->front == q->rear);
}
bool enQueue(QuType*& q, Box e) //进队列
{
if (q->rear==MaxSize-1) return false; //队满上溢出,返回假
q->rear++; //队尾增1
q->data[q->rear] = e; //rear位置插入元素e
return true; //返回真
}
bool deQueue(QuType*& q, Box& e) //出队列
{ if (q->front==q->rear) return false; //队空下溢出
q->front++;
e = q->data[q->front];
return true;
}
void print(QuType* qu, int front) //从队列qu中输出路径
{
int k = front, j, ns = 0;
printf("\n");
do //反向找到最短路径,将该路径上的方块的pre成员设置成-1
{
j=k;
k = qu->data[k].pre;
qu->data[j].pre = -1;
} while (k != 0);
printf("一条迷宫路径如下:\n");
k=0;
while (k < MaxSize) //正向搜索到pre为-1的方块,即构成正向的路径
{
if (qu->data[k].pre==-1)
{ ns++;
printf("\t(%d,%d)", qu->data[k].i, qu->data[k].j);
if (ns % 5 == 0) printf("\n"); //每输出每5个方块后换一行
}
k++;
}
printf("\n");
}
bool mgpath1(int xi,int yi,int xe,int ye) //搜索路径为:(xi,yi)->(xe,ye)
{
Box e;
int i, j, di, i1, j1;
QuType *qu; //定义顺序队指针qu
InitQueue(qu); //初始化队列qu
e.i = xi; e.j = yi; e.pre = -1;
enQueue(qu,e); //(xi,yi)进队
mg[xi][yi] = -1; //将其赋值-1,以避免回过来重复搜索
while (!QueueEmpty(qu)) //队不空且循环
{
deQueue(qu, e); //出队方块e,由于不是环形队列,该出队元素仍在队列中
i = e.i; j = e.j;
if (i==xe && j==ye) //找到了出口,输出路径
{
print(qu, qu->front); //调用print函数输出路径
DestroyQueue(qu); //销毁队列
return true; //找到一条路径时返回真
}
for (di = 0; di < 4; di++) //循环扫描每个方位,把每个可走的方块插入队列中
{
switch(di)
{
case 0:i1=i-1; j1=j; break;
case 1:i1=i; j1=j+1; break;
case 2:i1=i+1; j1=j; break;
case 3:i1=i; j1=j-1; break;
}
if (mg[i1][j1]==0)
{
e.i = i1; e.j = j1;
e.pre = qu->front; //指向路径中上一个方块的下标
enQueue(qu, e); //(i1,j1)方块进队
mg[i1][j1] = -1; //将其赋值-1,以避免回过来重复搜索
}
}
}
DestroyQueue(qu); //销毁队列
return false; //未找到任何路径时返回假
}
int main()
{
mgpath1(1,1,M,N);
return 0;
}
程序分析:
- 这里使用的顺序队列qu不是环形队列,因为在找到出口时需要利用队列中的所有方块査找一条迷宫路径。如果采用环形队列,出队的方块可能被新进队的方块覆盖,从而无法求出迷宫路径。这里要求非环形队列qu有足够大的空间。
- 运行结果:
- 显然这个解是最优解,也就是最短路径。
队列在计算机系统中的应用
- 1.解决主机与外部设备之间速度不匹配的问题
- 以主机和打印机之间速度不匹配的问题为例。
主机输出数据给打印机打印,输出数据的速度比打印数据的速度要快得多,由于速度不匹配,若直接把输出的数据送给打印机打印显然是不行的。解决的方法是设置一个打印数据缓冲区,主机把要打印输出的数据依次写入这个缓冲区,写满后就暂停输出,转去做其他的事情。打印机就从缓冲区中按照先进先出的原则依次取出数据并打印,打印完后再向主机发出请求,主机接到请求后再向缓冲区写入打印数据。这样做既保证了打印数据的正确,又使主机提高了效率。由此可见,打印数据缓冲区中所存储的数据就是一个队列。
- 2.解決由多用户引起的资源竞争问题
- CPU(即中央处理器,它包括运算器和控制器)资源的竞争就是一个典型的例子。
在一个带有多终端的计算机系统上,有多个用户需要CPU各自运行自己的程序,它们分别通过各自的终端向操作系统提出占用CPU的请求。操作系统通常按照每个请求在时间上的先后
顺序,把它们排成一个队列,每次把CPU分配给队首请求的用户使用。当相应的程序运行结束或用完规定的时间间隔后,令其出队,再把CPU分配给新的队首请求的用户使用。这样既能满足每个用户的请求,又使CPU能够正常运行。