一、栈的定义和特点
1) 栈是一个特殊的线性表,是限定仅在表尾(栈顶)进行插入和删除操作的线性表
2) 栈又称为后进先出的线性表,简称LIFO结构
3) 表尾称为栈顶Top,表头称为栈底Base
4) 插入元素到栈顶,称为入栈;从栈顶删除最后一个元素的操作,称为出栈
5) 栈的逻辑结构: 与线性表相同,仍为一对一关系
6) 栈的存储结构: 顺序存储或链式存储
二、栈的顺序表示及基本操作
1) 利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素;栈底一般在低地址端
2) 附设top指针,指示栈顶元素在顺序栈中的位置;另设base指针,指示栈底元素在顺序栈中的位置
//通常为了方便操作,top指针指向栈顶元素的下一个地址
3) 另外,用stacksize表示栈可使用的最大容量
1.顺序栈的表示
#define SIZE 10
typedef int Datatype;
typedef struct Stack
{
Datatype *base; //栈底指针
Datatype *top; //栈顶指针
int stacksize; //栈可用的最大容量
}SqStack;
2.顺序栈的初始化
void InitStack(SqStack *p) //顺序栈的初始化
{
p->base = (Datatype*)malloc(SIZE*sizeof(Datatype));
if(!p->base)
{
printf("内存分配失败!\n"); //存储分配失败
return;
}
p->top = p->base; //栈顶指针等于栈底指针
p->stacksize = SIZE;
}
3.判断栈是否为空
int IsEmpty(SqStack *p) //判断栈是否为空
{
if (p->base == p->top) //若栈为空返回1
return 1;
return 0; //不为空返回0
}
4.求顺序栈的长度
int GetLength(SqStack *p) //求顺序栈长度
{
return p->top - p->base; //栈顶指针和栈底指针的差值
}
5.清空顺序栈
void ClearStack(SqStack *p) //清空顺序栈
{
if (p->base) //如果栈存在,将栈顶指针指向栈底
p->top = p->base;
}
6.销毁顺序栈
void DestroyStack(SqStack *p) //销毁顺序栈
{
if (p->base) //判断栈是否存在
{
free(p->base); //销毁顺序栈
p->stacksize = 0;
p->base = p->top = NULL;
}
}
7.入栈
Datatype PopStack(SqStack *p) //出栈
{
if (IsEmpty(p)) //判断是否栈空
{
printf("栈空!\n");
return 0;
}
p->top--; //栈顶元素-1
return *(p->top); //返回栈顶元素
}
8.出栈
void PushStack(SqStack *p, Datatype x) //入栈
{
if (p->top - p->base == p->stacksize) //判断是否栈满
{
printf("栈满!\n");
return;
}
*(p->top) = x; //元素入栈
p->top++; //栈顶指针+1
}
9.打印
void PrintStack(SqStack *p) //打印
{
for (int i = 0; i < p->top - p->base; i++)
{
printf("%d ", p->base[i]);
}
putchar('\n');
}
10.完整代码如下:
#include<stdio.h>
#include<stdlib.h>
#define SIZE 10
typedef int Datatype;
typedef struct Stack
{
Datatype *base; //栈底指针
Datatype *top; //栈顶指针
int stacksize; //栈可用的最大容量
}SqStack;
void InitStack(SqStack *p); //顺序栈的初始化
int IsEmpty(SqStack *p); //判断栈是否为空
int GetLength(SqStack *p); //求顺序栈长度
void ClearStack(SqStack *p); //清空顺序栈
void DestroyStack(SqStack *p); //销毁顺序栈
void PushStack(SqStack *p, Datatype x); //入栈
Datatype PopStack(SqStack *p); //出栈
void PrintStack(SqStack *p); //打印
int main()
{
SqStack S;
InitStack(&S);
PushStack(&S, 1);
PushStack(&S, 2);
PushStack(&S, 3);
PushStack(&S, 4);
PushStack(&S, 5);
printf("栈长为: %d\n", GetLength(&S));
PrintStack(&S);
while (!IsEmpty(&S))
{
printf("%d ", PopStack(&S));
}
putchar('\n');
DestroyStack(&S);
return 0;
}
void InitStack(SqStack *p) //顺序栈的初始化
{
p->base = (Datatype*)malloc(SIZE * sizeof(Datatype));
if (!p->base)
{
printf("内存分配失败!\n"); //存储分配失败
return;
}
p->top = p->base; //栈顶指针等于栈底指针
p->stacksize = SIZE;
}
int IsEmpty(SqStack *p) //判断栈是否为空
{
if (p->base == p->top) //若栈为空返回1
return 1;
return 0; //不为空返回0
}
int GetLength(SqStack *p) //求顺序栈长度
{
return p->top - p->base; //栈顶指针和栈底指针的差值
}
void ClearStack(SqStack *p) //清空顺序栈
{
if (p->base) //如果栈存在,将栈顶指针指向栈底
p->top = p->base;
}
void DestroyStack(SqStack *p) //销毁顺序栈
{
if (p->base) //判断栈是否存在
{
free(p->base); //销毁顺序栈
p->stacksize = 0;
p->base = p->top = NULL;
}
}
void PushStack(SqStack *p, Datatype x) //入栈
{
if (p->top - p->base == p->stacksize) //判断是否栈满
{
printf("栈满!\n");
return;
}
*(p->top) = x; //元素入栈
p->top++; //栈顶指针+1
}
Datatype PopStack(SqStack *p) //出栈
{
if (IsEmpty(p)) //判断是否栈空
{
printf("栈空!\n");
return 0;
}
p->top--; //栈顶元素-1
return *(p->top); //返回栈顶元素
}
void PrintStack(SqStack *p) //打印
{
for (int i = 0; i < p->top - p->base; i++)
{
printf("%d ", p->base[i]);
}
putchar('\n');
}
11.输出结果:
三、栈的链式表示及基本操作
1) 链栈是运算受限的单链表,只能在链表头部进行操作
2) 链表的头指针就是栈顶;不需要头结点;基本不存在栈满的情况,空栈相当于头指针指向空
1.链栈的表示
typedef int Datatype;
typedef struct Node
{
Datatype data;
struct Node *next;
}Node,*pNode;
2.链栈的初始化
void InitStack(pNode *p) //链栈的初始化
{
*p = NULL; //头指针指向空
}
3.判断链栈是否为空
int IsEmpty(pNode *p) //判断链栈是否为空
{
if (*p == NULL) //为空返回1
return 1;
return 0; //不为空返回0
}
4.入栈
void PushStack(pNode *p, Datatype x) //入栈
{
pNode pnode = (pNode)malloc(sizeof(Node));
pnode->data = x; //生成数据域为x的新结点
pnode->next = (*p); //将新结点插入栈顶
(*p) = pnode; //修改栈顶指针
}
5.出栈
Datatype PopStack(pNode *p) //出栈
{
pNode pL;
Datatype temp; //暂存出栈的元素数据
if (IsEmpty(p)) //判断栈是否为空
{
printf("栈空!\n");
return 0;
}
pL = *p;
(*p) = (*p)->next; //将栈顶指针后移
temp = pL->data;
free(pL); //释放出栈的元素内存
return temp; //返回结点数据
}
6.销毁链栈
void Destroy(pNode *p) //销毁链栈
{
pNode pL;
while ((*p) != NULL)
{
pL = *p;
(*p) = (*p)->next;
free(pL); //依次释放结点
}
}
7.求链栈长度
int GetLength(pNode *p) //求链栈长度
{
int i = 0;
pNode pL = *p;
while (pL != NULL)
{
i++;
pL = pL->next;
}
return i;
}
8.完整代码如下
#include<stdio.h>
#include<stdlib.h>
typedef int Datatype;
typedef struct Node
{
Datatype data;
struct Node *next;
}Node,*pNode;
void InitStack(pNode *p); //链栈的初始化
int IsEmpty(pNode *p); //判断链栈是否为空
void PushStack(pNode *p, Datatype x); //入栈
Datatype PopStack(pNode *p); //出栈
void Destroy(pNode *p); //销毁链栈
int GetLength(pNode *p); //求链栈长度
int main()
{
pNode S;
InitStack(&S);
PushStack(&S, 6);
PushStack(&S, 7);
PushStack(&S, 8);
PushStack(&S, 9);
PushStack(&S, 10);
int num = GetLength(&S);
for (int i = 0; i < num; i++)
{
printf("%d ", PopStack(&S));
}
putchar('\n');
Destroy(&S);
return 0;
}
void InitStack(pNode *p) //链栈的初始化
{
*p = NULL; //头指针指向空
}
int IsEmpty(pNode *p) //判断链栈是否为空
{
if (*p == NULL) //为空返回1
return 1;
return 0; //不为空返回0
}
void PushStack(pNode *p, Datatype x) //入栈
{
pNode pnode = (pNode)malloc(sizeof(Node));
pnode->data = x; //生成数据域为x的新结点
pnode->next = (*p); //将新结点插入栈顶
(*p) = pnode; //修改栈顶指针
}
Datatype PopStack(pNode *p) //出栈
{
pNode pL;
Datatype temp; //暂存出栈的元素数据
if (IsEmpty(p)) //判断栈是否为空
{
printf("栈空!\n");
return 0;
}
pL = *p;
(*p) = (*p)->next; //将栈顶指针后移
temp = pL->data;
free(pL); //释放出栈的元素内存
return temp; //返回结点数据
}
void Destroy(pNode *p) //销毁链栈
{
pNode pL;
while ((*p) != NULL)
{
pL = *p;
(*p) = (*p)->next;
free(pL); //依次释放结点
}
}
int GetLength(pNode *p) //求链栈长度
{
int i = 0;
pNode pL = *p;
while (pL != NULL)
{
i++;
pL = pL->next;
}
return i;
}
9.输出结果
四、栈与递归
1.递归的定义
1) 若一个对象部分地包含它自己,或用它自己给自己定义,则称这个对象是递归的
2) 若一个过程直接地或间接地调用自己,则称这个过程是递归的过程
2.递归问题-----用分治法求解
1) 分治法:对于一个较为复杂的问题,能够分解成几个相对简单的且解法相同或类似的子问题来求解
2) 必备的三个条件:
*能将一个问题转变成一个新问题,而新问题与原问题的解法相同或类同,不同的仅是处理的对象,且这些处理对象是变化有规律的
*可以通过上述转化而使问题简化
*必须有一个明确的递归出口,或称递归的边界
3.分治法求解递归问题算法的一般形式:
void p(参数表){
if (递归结束条件) 可直接求解步骤; //基本项
else p(较小的参数); //归纳项
}
例:求解阶乘问题
long Fact(long n){
if(n==0)
return 1; //基本项
else
return n*Fact(n-1); //归纳项
}
4.函数调用过程:
调用前,系统完成:
1) 将实参,返回地址等传递给被调用函数
2) 为被调用函数的局部变量分配存储区
3) 将控制转移到被调用函数的入口
调用后,系统完成:
1) 保存被调用函数的计算结果
2) 释放被调用函数的数据区
3) 依照被调用函数保存的返回地址将控制转移到调用函数
5.递归的优缺点:
1) 优点: 结构清晰,程序易读
2) 缺点: 每次调用要生成工作记录,保存状态信息,入栈;返回时要出栈,恢复状态信息;时间开销大
3) 递归->非递归
方法1: 尾递归、单向递归->循环结构
方法2: 自用栈模拟系统的运行时栈
6.借助栈改写递归:
*递归程序在执行时需要系统提供栈来实现
*仿照递归算法执行过程中递归工作栈的状态变化可写出相应的非递归程序
*改写后的非递归算法与原来的递归算法相比,结构不够清晰,可读性较差,有的还需要经过一系列优化