栈
栈 stack 限定仅在表尾进行插入删除操作的线性表,表尾称栈顶 top 表头称栈底 bottom
栈又称先进后出的线性表 LIFO
栈抽象数据类型定义
ADT Stack{
数据对象
数据关系
基本操作
InitStack(&S) //构造空栈
DestroyStack(&S) //销毁栈
ClearStack(&S) //清空栈
StackEmpty(S) //判断是否为空栈
StackLength(S) //求栈长
GetTop(S,&e) //求栈顶
Push(&S,e) //入栈
Pop(&S,&e) //出栈
StackTraverse(S,visit()) //从栈底到栈顶依次对每个元素调用 visit()
}
类似线性表,栈也有顺序栈与链栈两种存储方式
顺序栈即栈的顺序存储结构是利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素,同时附设指针top指示栈顶元素是顺序栈的位置
一般 top = 0 表示空栈,但是C中数组从下标 0 开始,故实际情况下可设 top = -1 表示空栈 top = 0 表示有一个元素
顺序栈在使用前要预估大小,一般可以先分配一个基本容量,不够时在逐段扩大为此可设两个常量 STACK_INIT_SIZE 和 STACKINCREMENT(增量)
//顺序栈定义
typedef struct {
SElemType *base; //栈底指针
SElemType *top; //栈顶指针
int stacksize; //指示栈的当前可使用的最大容量
}SqStack;
/*类似的数组表示
typedef struct {
SElemType data[];
int top;
}SqStack
*/
栈的初始化操作:按设定的初始分配量进行第一次存储分配,base 为栈底指针,顺序栈中始终指向栈底位置,若 base = NULL 则栈结构不存在
top 为栈顶指针,初值指向栈底即 top = base 可作为栈空的标记,每入栈一个元素 top 增 1,出栈一个则减 1
非空栈的栈顶指针始终在栈顶元素的下一个位置上
//ADT Stack 的表示与实现
//栈的顺序表示
#define STACK_INIT_SIZE 100
#define STACKINCREMENT 10
typedef struct {
SElemType *base;
SElemType *top;
int stacksize;
}SqStack;
//基本操作函数原型
Status InitStack(SqStack &S) //构造一个空栈
Status DestroyStack(SqStack &S) //销毁栈 S
Status ClearStack(SqStack &S) //清空栈 S
Status StackEmpty(SqStack S) //空栈返回 true 否则返回 false
int StackLength(SqStack S) //返回栈的长度
Status GetTop(SqStack S,SElemType &e) //用 e 返回栈顶元素
Status Push(SqStack &S,SElemType e) //入栈元素
Status Pop(SqStack &S,SElemType &e) //出栈元素 用 e 返回其值
Status StackTraverse(SqStack S,Status(*visit)()) //从栈底到栈顶依次对每个元素调用 visit()
//基本操作的算法描述
Status InitStack(SqStack &S)
{
S.base = (SElemType *)malloc(sizeof(SElemType)*STACK_INIT_SIZE);
if (!S.base)
exit(0);
S.top = S.base;
S.stacksize = STACK_INIT_SIZE;
return OK;
}
Status GetTop(SqStack S, SElemType &e)
{
//若栈不空,则用 e 返回S的栈顶元素,并返回 OK,否则返回 ERROR
if (S.top == S.base)
return ERROR;
e = *(S.top - 1);
return OK;
}
Status Push(SqStack &S, SElemType e)
{
//插入 e 为新的栈顶元素
if (S.top - S.base >= S.stacksize) //栈满追加新空间
{
S.base = (SElemType *)realloc(S.base, S.stacksize + STACKINCREMENT * sizeof(SElemType));
if (!S.base)
exit(0);
S.top = S.base + S.stacksize;
S.stacksize += STACKINCREMENT;
}
*S.top++ = e;
return OK;
}
Status Pop(SqStack &S, SElemType &e)
{
//若栈不空,则删除栈顶元素用 e 返回其值
if (S.top == S.base)
return ERROR;
e = *--S.top;
return OK;
}
//测试代码
#include<stdio.h>
#include<stdlib.h>
#define STACKMAX 100
#define OK 1
#define ERROR 0
typedef struct { //栈的结构
int *base; //栈底指针
int *top; //栈顶指针
int stacksize; //当前栈的最大存储空间
}SqStack;
SqStack *initStack(void); //构造空栈
void destroyStack(SqStack *S); //销毁栈
void printStack(SqStack *S); //从栈底开始遍历栈 debug 用
int push(SqStack *S, int e); //入栈
int pop(SqStack *S, int *e); //出栈
SqStack *initStack(void)
{
SqStack *S = (SqStack *)malloc(sizeof(SqStack)); //创建栈并分配空间
S->base = (int *)malloc(sizeof(int)*STACKMAX);
if (!S->base) //分配内存失败
exit(0);
S->top = S->base; //初始化栈顶指针
S->stacksize = STACKMAX;
return S;
}
void destroyStack(SqStack *S)
{
free(S->base);
free(S);
}
void printStack(SqStack *S)
{
int *p = S->base;
while (p != S->top) //从栈底开始,未到栈顶就一直向下遍历
{
printf("%d ", *p);
p++;
}
}
int push(SqStack *S, int e)
{
if (S->top - S->base >= S->stacksize) //栈的空间以满,返回 ERROR
{
printf("the stack is full\n");
return ERROR;
}
*S->top++ = e; //更新栈顶元素,值得一提的是 该操作等价于 *S->top = e; S->top++; 的合并
return OK;
}
int pop(SqStack *S, int *e)
{
if (S->top == S->base) //空栈--不能再出栈
{
printf("the stack is empty\n");
return ERROR;
}
*e = *--(S->top);
return OK;
}
int main(void) //测试功能
{
freopen("r4.txt", "r", stdin);
SqStack *S = initStack();
int e;
while (scanf("%d", &e) != EOF)
push(S, e);
printStack(S);
printf("\n");
int *s = (int *)malloc(sizeof(int));
pop(S, s);
printf("*s = %d\n", *s);
printStack(S);
printf("\n");
while (S->base != S->top)
{
pop(S, s);
printf("*s = %d\n", *s);
}
printf("\n");
printStack(S);
destroyStack(S);
return 0;
}
/*************************************************************************/
栈的应用
1 数制转换
十进制数 N 与其他 d 进制的转换的方法举例
N = (N div d) * d + N mod d
对输入任意一个十进制数转换,依次对每位取模运算,产生的数是底位到高位,而打印则是从高位打印到低位,正好符合栈先进后出的原则
void conversion(int N) // conversion 转换
{
SqStack *S = initStack();
while (N != 0)
{
push(S, N % 8);
N = N / 8;
}
while (S->top != S->base)
{
int *e;
pop(S, e);
printf("%d", *e);
}
}
//范例代码--十进制转八进制
void conversion()
{
//对于输入的任意一个非负十进制数,打印与其等值的八进制数
initStack(S);
scanf("%d", &N);
while (N)
{
push(S, N % 8);
N = N / 8;
}
while (!StackEmpty(S))
{
pop(S, e);
printf("%d", *e);
}
}
/*********************测试程序********************/
//进制转换
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define STACKMAX 100
#define ERROR 0
#define OK 1
#define MAXSIZE 10000
typedef struct {
int *base;
int *top;
int stacksize;
}SqStack;
SqStack *initStack(void);
int push(SqStack *S, int e);
int pop(SqStack *S, int *e);
int *random(void); //生成随机数,提供测试数据
int *random(void)
{
srand(time(NULL));
int *a = (int *)malloc(sizeof(int)*MAXSIZE);
for (int i = 0; i<MAXSIZE; i++)
{
a[i] = rand() % MAXSIZE;
}
printf("十进制原数据\n");
for (int i = 0; i<MAXSIZE / 100; i++)
{
if (i % 10 == 0 && i != 0)
printf("\n");
printf("%d ", a[i]);
}
printf("\n************************\n");
printf("八进制结果\n");
return a;
}
int push(SqStack *S, int e)
{
if (S->top - S->base >= S->stacksize)
{
printf("the stack is full\n");
return ERROR;
}
*S->top++ = e;
return OK;
}
int pop(SqStack *S, int *e)
{
if (S->base == S->top)
{
printf("the stack is empty\n");
return ERROR;
}
*e = *--(S->top);
return OK;
}
SqStack *initStack(void)
{
SqStack *S = (SqStack *)malloc(sizeof(SqStack));
S->base = (int *)malloc(sizeof(int)*STACKMAX);
if (!S->base)
exit(0);
S->top = S->base;
S->stacksize = STACKMAX;
return S;
}
void converse(SqStack *S, int N)
{
while (N != 0)
{
push(S, N % 8);
N = N / 8;
}
while (S->top != S->base)
{
int *e = (int *)malloc(sizeof(int));
pop(S, e);
printf("%d", *e);
free(e);
}
}
int main(void)
{
SqStack *S = initStack();
int *a = random();
for (int i = 0; i<MAXSIZE / 100; i++)
{
if (i % 10 == 0 && i != 0)
printf("\n");
converse(S, a[i]);
printf(" ");
}
free(a);
free(S->base);
free(S);
return 0;
}
2 括号匹配的检验
问题描述:有一数字串,中间含有两种括号()[],嵌套顺序任意,现在需要检查其括号的使用是否正确
typedef struct {
char *base;
char *top;
}SqStack;
int bracket(char *a,int N) //a 是从原始串中保持相对顺序的仅有括号的串
{
SqStack *S = initStack();
int flag = 0; //判断变量,直到最后还是 0 的时候则说明匹配
for (int i = 0; i < N; i++)
{
if (a[i] == '(' || a[i] == '[') //左括号都入栈
{
push(S, a[i]);
flag++;
}
else if (a[i] == ')' || a[i] == ']') //右括号向前找
{
if (a[i] == ')'&&*(S->top - 1) == '(') //前一个是匹配的,则栈顶元素出栈,标志变量减 1
{
pop(S);
flag--;
}
else if (a[i] == ']'&&*(S->top - 1) == '[')
{
pop(S);
flag--;
}
else //遇到右括号不匹配时结束
return ERROR;
}
if (flag < 0) //中间有任意一次为负都不匹配
return ERROR;
}
free(S->base);
free(S);
if (flag == 0)
return OK;
else
return ERROR;
}
//测试代码
#include<stdio.h>
#include<stdlib.h>
#define MAXSIZE 100
#define ERROR 0
#define OK 1
typedef struct {
char *base;
char *top;
}SqStack;
int bracket(char *a, int N);
char *str(char *a, int N, int *count);
SqStack *initStack(void);
int push(SqStack *S, char e);
int push(SqStack *S, char e)
{
*S->top++ = e;
return OK;
}
int pop(SqStack *S)
{
S->top--;
return OK;
}
SqStack *initStack(void)
{
SqStack *S = (SqStack*)malloc(sizeof(SqStack)*MAXSIZE);
S->base = (char *)malloc(sizeof(char)*MAXSIZE);
if (!S->base)
exit(0);
S->top = S->base;
return S;
}
char *str(char *a, int N, int *count) //抽取括号串
{
char *s = (char *)malloc(sizeof(char)*MAXSIZE);
int c = 0;
for (int i = 0; i<N; i++)
{
if (a[i] == '(' || a[i] == ')' || a[i] == '[' || a[i] == ']')
{
s[c] = a[i];
c++;
}
}
*count = c;
return s;
}
int bracket(char *a, int N) //括号匹配
{
SqStack *S = initStack();
int flag = 0;
for (int i = 0; i < N; i++)
{
if (a[i] == '(' || a[i] == '[')
{
push(S, a[i]);
flag++;
}
else if (a[i] == ')' || a[i] == ']')
{
if (a[i] == ')'&&*(S->top - 1) == '(')
{
pop(S);
flag--;
}
else if (a[i] == ']'&&*(S->top - 1) == '[')
{
pop(S);
flag--;
}
else
return ERROR;
}
if (flag < 0)
return ERROR;
}
free(S->base);
free(S);
if (flag == 0)
return OK;
else
return ERROR;
}
int main(void)
{
freopen("r6.txt", "r", stdin);
char *a = (char *)malloc(sizeof(char)*MAXSIZE);
if (!a)
exit(0);
int i = 0;
while (scanf("%c", &a[i]) != EOF)
i++;
/*debug
for(int j = 0;j<i;j++)
printf("%c",a[j]);
*/
int *count = (int *)malloc(sizeof(int));
if (!count)
exit(0);
char *s = str(a, i, count);
//printf("\n*count = %d\n",*count);
//printf("\n");
//for(int j = 0;j<i;j++)
//printf("%c",s[j]);
int flag = bracket(s, *count);
if (flag == ERROR)
printf("ERROR\n");
else
printf("OK\n");
free(a);
free(s);
free(count);
return 0;
}
//范例代码
void check()
{
//对于输入的任意一个字符串,检验括号是否匹配
SqStack s;
SElemType ch[80], *p, e;
if (InitStack(S)) //初始化栈成功
{
printf("please input the string\n");
gets(ch);
p = ch;
while (*p) //没到串尾
{
switch (*p)
{
case '(':
case '[':Push(S, *p++);
break; //左括号入栈且 p++
case')':
case']':
if (!StackEmpty(S)) //栈不空
{
Pop(S, e); //弹出栈顶元素
if (*p == ')'&&e != '(' || *p == ']'&&e != '[') //弹出的栈顶元素与 *p 不匹配
{
printf("左右括号不配匹\n");
exit(ERROR);
}
else
{
p++;
break;
}
}
else //栈空
{
printf("缺左括号\n");
exit(ERROR);
}
default:p++; //其他字符不处理,指针向后移
}
}
if (StackEmpty(S)) //字符串结束时栈空
printf("括号匹配\n");
else
printf("缺右括号\n");
}
}
3 行编辑程序
一个简单的行编辑程序的功能是:接受用户从终端输入的程序或数据,并存入用户的数据区。由于用户在终端输入时可能出错
因此在编辑程序中较好的做法是设立一个输入缓冲区,用以接受用户输入的一行字符,然后逐行存入用户数据区,
允许用户输入出差错,并在发现有误时进行更正。如当用户发现刚刚键入的一个字符是错的时,可以补进一个退格符"#"表示前一个字符无效
键入"@"表示当前行字符均无效。如下
whli##ilr#e(s#*s)
outcha@putchar(*s = #++);
实际有效的是下列两行
while (*s)
putchar(*s++);
//范例描述
void LineEdit()
{
//利用字符栈S,从终端接收一行并传送至调用过程的数据区
InitStack(S);
ch = getchar(); //从终端接受第一个字符
while (ch != EOF)
{
while (ch != EOF&&ch != '\n')
{
switch (ch)
{
case'#':Pop(S, c); break; //仅当栈非空时退栈
case'@':ClearStack(S); break; //重置栈为空栈
default:Push(S, ch); break; //有效字符进栈,未考虑栈满情形
}
ch = getchar(); //从终端接受下一个字符
}
//将从栈底到栈顶的栈内字符传送至调用过程的数据区
ClearStack(S); //重置S为空栈
if (ch != EOF)
ch = getchar();
}
DestroyStack(S);
}
//处理函数
void edit(char a, SqStack *S)
{
if (a != '#'&&a != '@') //非删除字符依次入栈
push(S, a);
else if (a == '#') //退格符,出栈栈顶元素
pop(S);
else if (a == '@') //删除一行,清空栈
clearStack(S);
}
//简单实现--测试代码
#include<stdio.h>
#include<stdlib.h>
#define MAXSIZE 100
#define ERROR 0
#define OK 1
typedef struct {
char *base;
char *top;
}SqStack;
void edit(char a); //编辑函数
SqStack *initStack(void);
void push(SqStack *S, char a);
void pop(SqStack *S);
void clearStack(SqStack *S);
void destroyStack(SqStack *S);
void printStack(SqStack *S);
void destroyStack(SqStack *S)
{
free(S->base);
free(S);
}
void printStack(SqStack *S)
{
char *p = S->base;
while (p != S->top)
{
printf("%c", *p);
p++;
}
}
SqStack *initStack(void)
{
SqStack *S = (SqStack *)malloc(sizeof(SqStack));
S->base = (char *)malloc(sizeof(char)*MAXSIZE);
if (!S->base)
{
printf("ERROR\n");
exit(0);
}
S->top = S->base;;
return S;
}
void push(SqStack *S, char a)
{
*S->top++ = a;
}
void pop(SqStack *S)
{
if (S->top == S->base)
{
printf("the stack is empty\n");
}
else
S->top--;
}
void clearStack(SqStack *S)
{
S->top = S->base;
}
void edit(char a, SqStack *S)
{
if (a != '#'&&a != '@') //非删除字符依次入栈
push(S, a);
else if (a == '#') //退格符,出栈栈顶元素
pop(S);
else if (a == '@') //删除一行,清空栈
clearStack(S);
//else if(a=='\n') //为什么这个不会出现?
// printf("sssss\n");
}
int main(void)
{
freopen("r7.txt", "r", stdin);
char a;
SqStack *S = initStack();
while ((a = getchar()) != EOF)
{
if (a == '\n' || a == '\r') //如果a是换行符,打印目前栈中的所有元素,打印换行符并清空栈
{
printStack(S);
printf("\n");
clearStack(S);
}
else //编辑栈
edit(a, S);
}
printStack(S);
destroyStack(S);
return 0;
}
4 迷宫求解
求迷宫从入口到出口的所有路径
计算机求解时常采用穷举法,从入口出发,延某一方向向前探索,若能走通继续向前探索,否则沿原路退回,换一个方向继续探索,为例保证
在任何位置上都能沿原路退回,需要先进后出的结构来保存行走路线,这显然符合栈的存储结构
//范例--简单的算法描述
设定当前位置的初值为入口位置
do {
若当前位置可通
则{
将当前位置插入栈顶, //纳入路径
若该位置是出口位置则结束; //求得路径存放在栈中
否则切换当前位置的东邻方块为新的当前位置;
}
否则
若栈不空且栈顶位置尚有其他方向未经探索,
则设定新的当前位置为顺时针方向旋转找到的栈顶位置的下一相邻块;
若栈不空但栈顶位置的四周均不可通,
则{
删除栈顶位置;
若栈不空则重新测试新的栈顶位置直到找到一个可通的相邻块或出栈至空栈;
}
}while(栈不空)
NOTE :当前位置可通指的是未曾走过的通道块,即要求该方块位置不仅是通道块而且既不在当前路径上(否则所求路径就不是简单路径)
也不是曾经纳入过的路径通道块(否则只能在死胡同内转圈)
typedef struct {
int ord; //通道块在路径上的序号
PosType seat; //通道块在迷宫中的坐标位置
int di; //从此通道块走向下一通道块的方向
}SElemType; //栈的元素类型
Status MazePath(MazeType maze, PosType start, PosType end)
{
//若迷宫maze中存在入口start到出口end的通道,则求得一条存放在栈中并返回true否则返回false
InitStack(S);
curpos = start; //设定当前位置为入口位置
curstep = 1; //探索第一步
do {
if (Pass(curpos)) //当前位置可以通过,即是未曾走到过的通道块
{
FootPrint(curpos); //留下足迹
e = (curstep, curpos, 1);
Push(S, e); //加入路径
if (curpos == end) //到达出口
return TRUE;
curpos = NextPos(curpos, 1); //下一位置是当前位置的东邻
curstep++; //探索下一步
}
else //当前位置不能通过
{
if (!StackEmpty(S))
{
Pop(S, e);
while (e.di == 4 && !StackEmpty(S))
{
MarkPrint(e.seat); //留下不能通过的标记并退回一步
Pop(S, e);
}
if (e.di < 4)
{
e.di++; //换下一个方向探索
Push(S, e);
curpos = NextPos(e.seat, e.di); //设定当前位置是该方向上的相邻块
}
}
}
}
return FALSE;
}
栈与递归的实现
一个直接调用自己或通过一系列的调用语句简介地调用自己的函数称为递归函数
例 Hanoi塔问题
//算法描述
//将塔座x按直径由小到大且自上而下编号为 1 至 n 的 n 个圆盘按规则搬到塔座 z 上,y可做辅助塔座
//搬动操作move(x,n,z)可定义为(c 是初值为 0 的全局变量,对搬动次数计数)
//printf("% i. Move disk % i from %c to %c \n",++c,n,x,z);
void hanoi(int n, char x, char y, char z)
{
if (n == 1)
move(x, 1, z); //将编号为 1 的圆盘从 x 移到 z
else
{
hanoi(n - 1, x, z, y); //将x上编号为1至n-1的圆盘移到 y,z作辅助塔
move(x, n, z); //将编号为 n 的圆盘移至 z
hanoi(n - 1, y, x, z); //将 y 上编号为 1 至 n-1 的圆盘移至 z,x 做辅助塔
}
}