
进栈顺序:a1 > a2 > a3 > a4 > a5
出栈顺序:a5 > a4 > a3 > a2 > a1
InitStack(&S):初始化栈。构造一个空栈 S,分配内存空间。
DestroyStack(&S):销毁栈。销毁并释放栈 S 所占用的内存空间。
Push(&S, x):进栈。若栈 S 未满,则将 x 加入使其成为新的栈顶元素。
Pop(&S, &x):出栈。若栈 S 非空,则弹出(删除)栈顶元素,并用 x 返回。
GetTop(S, &x):读取栈顶元素。若栈 S 非空,则用 x 返回栈顶元素。
StackEmpty(S):判空。断一个栈 S 是否为空,若 S 为空,则返回 true,否则返回 false。

【顺序栈的定义】
- #define MaxSize 10 //定义栈中元素的最大个数
-
- typedef struct{
- ElemType data[MaxSize]; //静态数组存放栈中元素
- int top; //栈顶元素
- }SqStack;
-
- void testStack(){
- SqStack S; //声明一个顺序栈(分配空间)
- //连续的存储空间大小为 MaxSize*sizeof(ElemType)
- }
【顺序栈的初始化】
- #define MaxSize 10
- typedef struct{
- ElemType data[MaxSize];
- int top;
- }SqStack;
-
- // 初始化栈顶为-1,栈顶指针指向栈顶
- void InitStack(SqStack &S){
- S.top = -1; //初始化栈顶指针
- }
-
- // 判断栈是否为空
- bool StackEmpty(SqStack S){
- if(S.top == -1)
- return true;
- else
- return false;
- }
-
- // 初始化栈顶为0,栈顶指针指向栈顶的下一个空位
- void InitStack(SqStack &S){
- S.top = 0; //初始化栈顶指针
- }
-
- // 判断栈是否为空
- bool StackEmpty(SqStack S){
- if(S.top == 0)
- return true;
- else
- return false;
- }
【顺序栈的入栈出栈】
- 初始化为-1时
- // 新元素进栈
- bool Push(SqStack &S, ElemType x){
- if(S.top == MaxSize - 1) // 判断栈是否已满
- return false;
- S.data[++S.top] = x;
- return true;
- }
-
- // 出栈
- bool Pop(SqStack &x, ElemType &x){
- if(S.top == -1) // 判断栈是否为空
- return false;
- x = S.data[S.top--];
- return true;
- }
-
- 初始化为0时
- // 新元素进栈
- bool Push(SqStack &S, ElemType x){
- if(S.top == MaxSize) // 判断栈是否已满
- return false;
- S.data[S.top++] = x;
- return true;
- }
-
- // 出栈
- bool Pop(SqStack &x, ElemType &x){
- if(S.top == 0) // 判断栈是否为空
- return false;
- x = S.data[--S.top];
- return true;
- }
【读取栈顶元素】
- // 读栈顶元素
- 初始化为-1时
- bool GetTop(SqStack S, ElemType &x){
- if(S.top == -1) 先判空,非空读取才有意义
- return false;
- x = S.data[S.top];
- return true;
- }
-
- 初始化为-1时
- bool GetTop(SqStack S, ElemType &x){
- if(S.top == 0)
- return false;
- x = S.data[S.top-1];
- return true;
- }
【读取栈的长度】
- // 获取当前栈长
- 当初始化为-1
- int GetSize(SqStack S){
- return S.top + 1;
- }
-
- 当初始化为0
- int GetSize(SqStack S){
- return S.top;
- }
共享栈(两个栈共享同一片空间)】
- #define MaxSize 10 //定义栈中元素的最大个数
-
- typedef struct{
- ElemType data[MaxSize]; //静态数组存放栈中元素
- int top0; //0号栈栈顶指针
- int top1; //1号栈栈顶指针
- }ShStack;
-
- //初始化栈
- void InitSqStack(ShStack &S){
- S.top0 = -1; //初始化栈顶指针
- S.top1 = MaxSize;
- }
【链栈的定义】
链表的头部作为栈顶,意味着:
因此,链栈实际上就是一个只能采用头插法插入或删除数据的链表;
栈的链式存储结构可描述为:
【链栈的定义】
- typedef struct Linknode{
- ElemType data; //数据域
- Linknode *next; //指针域
- }Linknode,*LiStack;
-
- void testStack(){
- LiStack L; //声明一个链栈
- }
【链栈的初始化】
- typedef struct Linknode{
- ElemType data;
- Linknode *next;
- }Linknode,*LiStack;
-
- // 初始化栈
- bool InitStack(LiStack &L){ // 生成虚拟头节点,并将其next指针置空
- L = (Linknode *)malloc(sizeof(Linknode));
- if(L == NULL)
- return false;
- L->next = NULL;
- return true;
- }
-
- // 判断栈是否为空
- bool isEmpty(LiStack &L){
- if(L->next == NULL)
- return true;
- else
- return false;
- }
【入栈出栈】
- // 新元素入栈
- bool pushStack(LiStack &L,ElemType x){
- Linknode *s = (Linknode *)malloc(sizeof(Linknode));
- if(s == NULL)
- return false;
- s->data = x;
- // 头插法
- s->next = L->next;
- L->next = s;
- return true;
- }
-
- // 出栈
- bool popStack(LiStack &L, int &x){
- // 栈空不能出栈
- if(L->next == NULL)
- return false;
- Linknode *s = L->next;
- x = s->data;
- L->next = s->next;
- free(s);
- s = NULL;
- return true;
- }

InitQueue(&Q):初始化队列,构造一个空队列Q。
QueueEmpty(Q):判队列空,若队列Q为空返回true,否则返回false。
EnQueue(&Qx):入队,若队列Q未满,则将x加入使之成为新的队尾。
DeQueue(&Q&x):出队,若队列Q非空,则删除队头元素,并用x返回。
GetHead(Q&x):读队头元素,若队列Q非空则用x返回队头元素。
ClearQueue(&Q):销毁队列,并释放队列Q占用的内存空间。
【队列的顺序存储实现 】
队头指针:指向队头元素
队尾指针:指向队尾元素或者队尾的下一个位置
【顺序队列的定义】
- #define MaxSize 10; //定义队列中元素的最大个数
-
- typedef struct{
- ElemType data[MaxSize]; //用静态数组存放队列元素
- int front, rear; //队头指针和队尾指针
- }SqQueue;
-
- void test{
- SqQueue Q; //声明一个队列
- }
【顺序队列的初始化】
- #define MaxSize 10;
-
- typedef struct{
- ElemType data[MaxSize];
- int front, rear;
- }SqQueue;
-
- // 初始化队列
- void InitQueue(SqQueue &Q){
- // 初始化时,队头、队尾指针指向0
- // 队尾指针指向的是即将插入数据的数组下标
- // 队头指针指向的是队头元素的数组下标
- Q.rear = Q.front = 0;
- }
-
- // 判断队列是否为空
- bool QueueEmpty(SqQueue Q){
- if(Q.rear == Q.front)
- return true;
- else
- return false;
- }
【入队出队(循环队列)】
- // 新元素入队
- bool EnQueue(SqQueue &Q, ElemType x){
- // 如果队列已满直接返回
- if((Q.rear+1)%MaxSize == Q.front) //牺牲一个单元区分队空和队满
- return false;
- Q.data[Q.rear] = x;
- Q.rear = (Q.rear+1)%MaxSize;
- return true;
- }
-
- // 出队
- bool DeQueue(SqQueue &Q, ElemType &x){
- // 如果队列为空直接返回
- if(Q.rear == Q.front)
- return false;
- x = Q.data[Q.front];
- Q.front = (Q.front+1)%MaxSize;
- return true;
- }
解决方法一:牺牲一个单元来区分队空和队满,即将(Q.rear+1)%MaxSize == Q.front作为判断队列是否已满的条件。(主流方法)
解决方法二:设置 size 变量记录队列长度。
- #define MaxSize 10;
-
- typedef struct{
- ElemType data[MaxSize];
- int front, rear;
- int size;
- }SqQueue;
-
- // 初始化队列
- void InitQueue(SqQueue &Q){
- Q.rear = Q.front = 0;
- Q.size = 0;
- }
-
- // 判断队列是否为空
- bool QueueEmpty(SqQueue 0){
- if(Q.size == 0)
- return true;
- else
- return false;
- }
-
- // 新元素入队
- bool EnQueue(SqQueue &Q, ElemType x){
- if(Q.size == MaxSize)
- return false;
- Q.size++;
- Q.data[Q.rear] = x;
- Q.rear = (Q.rear+1)%MaxSize;
- return true;
- }
-
- // 出队
- bool DeQueue(SqQueue &Q, ElemType &x){
- if(Q.size == 0)
- return false;
- Q.size--;
- x = Q.data[Q.front];
- Q.front = (Q.front+1)%MaxSize;
- return true;
- }
解决方法三:设置 tag 变量记录队列最近的操作。(tag=0:最近进行的是删除操作;tag=1 :最近进行的是插入操作)
- #define MaxSize 10;
-
- typedef struct{
- ElemType data[MaxSize];
- int front, rear;
- int tag;
- }SqQueue;
-
- // 初始化队列
- void InitQueue(SqQueue &Q){
- Q.rear = Q.front = 0;
- Q.tag = 0;
- }
-
- // 判断队列是否为空,只有tag==0即初始化或者出队后才可能为空
- bool QueueEmpty(SqQueue 0){
- if(Q.front == Q.rear && Q.tag == 0)
- return true;
- else
- return false;
- }
-
- // 新元素入队 判断队列是否满,只有tag==1即入队后才可能满
- bool EnQueue(SqQueue &Q, ElemType x){
- if(Q.rear == Q.front && tag == 1)
- return false;
- Q.data[Q.rear] = x;
- Q.rear = (Q.rear+1)%MaxSize;
- Q.tag = 1;
- return true;
- }
-
- // 出队
- bool DeQueue(SqQueue &Q, ElemType &x){
- if(Q.rear == Q.front && tag == 0)
- return false;
- x = Q.data[Q.front];
- Q.front = (Q.front+1)%MaxSize;
- Q.tag = 0;
- return true;
- }
【获得队头元素】
- // 获取队头元素并存入x
- bool GetHead(SqQueue &Q, ElemType &x){
- if(Q.rear == Q.front)
- return false;
- x = Q.data[Q.front];
- return true;
- }
【链队列的定义】
- // 链式队列结点
- typedef struct LinkNode{
- ElemType data;
- struct LinkNode *next;
- }
-
- // 链式队列
- typedef struct{
- // 头指针和尾指针
- LinkNode *front, *rear;
- }LinkQueue;
【 链队列的初始化(带头结点)】
- typedef struct LinkNode{
- ElemType data;
- struct LinkNode *next;
- }LinkNode;
-
- typedef struct{
- LinkNode *front, *rear;
- }LinkQueue;
-
- // 初始化队列
- void InitQueue(LinkQueue &Q){
- // 初始化时,front、rear都指向头结点
- Q.front = Q.rear = (LinkNode *)malloc(sizeof(LinkNode));
- Q.front -> next = NULL;
- }
-
- // 判断队列是否为空
- bool IsEmpty(LinkQueue Q){
- if(Q.front == Q.rear)
- return true;
- else
- return false;
- }
【入队出队(带头结点)】
- // 新元素入队
- void EnQueue(LinkQueue &Q, ElemType x){ // 不存在满的情况
- LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
- s->data = x;
- s->next = NULL;
- Q.rear->next = s;
- Q.rear = s;
- }
-
- // 队头元素出队
- bool DeQueue(LinkQueue &Q, ElemType &x){
- if(Q.front == Q.rear) // 判空
- return false;
- LinkNode *p = Q.front->next;
- x = p->data;
- Q.front->next = p->next;
- // 如果p是最后一个结点,此时Q.rear已经要被删除了,则将队尾指针也指向队首指针
- if(Q.rear == p)
- Q.rear = Q.front;
- free(p);
- p = NULL;
- return true;
- }
【不带头结点的链队列操作】
- typedef struct LinkNode{
- ElemType data;
- struct LinkNode *next;
- }LinkNode;
-
- typedef struct{
- LinkNode *front, *rear;
- }LinkQueue;
-
- // 初始化队列
- void InitQueue(LinkQueue &Q){
- // 不带头结点的链队列初始化,头指针和尾指针都指向NULL
- Q.front = NULL;
- Q.rear = NULL;
- }
-
- // 判断队列是否为空
- bool IsEmpty(LinkQueue Q){
- if(Q.front == NULL)
- return true;
- else
- return false;
- }
-
- // 新元素入队
- void EnQueue(LinkQueue &Q, ElemType x){
- LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
- s->data = x;
- s->next = NULL;
- // 第一个元素入队时需要特别处理
- if(Q.front == NULL){
- Q.front = s;
- Q.rear = s;
- }else{
- Q.rear->next = s;
- Q.rear = s;
- }
- }
-
- //队头元素出队
- bool DeQueue(LinkQueue &Q, ElemType &x){
- if(Q.front == NULL)
- return false;
- LinkNode *s = Q.front;
- x = s->data;
- if(Q.front == Q.rear){
- Q.front = Q.rear = NULL;
- }else{
- Q.front = Q.front->next;
- }
- free(s);
- return true;
- }
双端队列定义

双端队列考点:判断输出序列的合法化
- #define MaxSize 10
- typedef struct{
- char data[MaxSize];
- int top;
- }SqStack;
-
- void InitStack(SqStack &S);
- bool StackEmpty(SqStack &S);
- bool Push(SqStack &S, char x);
- bool Pop(SqStack &S, char &x);
-
- // 判断长度为length的字符串str中的括号是否匹配
- bool bracketCheck(char str[], int length){
- SqStack S;
- InitStack(S);
- // 遍历str
- for(int i=0; i
- // 扫描到左括号,入栈
- if(str[i] == '(' || str[i] == '[' || str[i] == '{'){
- Push(S, str[i]);
- }else{
- // 扫描到右括号且栈空直接返回
- if(StackEmpty(S))
- return false;
- char topElem;
- // 用topElem接收栈顶元素
- Pop(S, topElem);
- // 括号不匹配
- if(str[i] == ')' && topElem != '(' )
- return false;
- if(str[i] == ']' && topElem != '[' )
- return false;
- if(str[i] == '}' && topElem != '{' )
- return false; }
- }
- // 扫描完毕若栈空则说明字符串str中括号匹配
- return StackEmpty(S);
- }
3.2栈在表达式求值中的应用
中缀表达式:中缀表达式是一种通用的算术或逻辑公式表示方法,运算符以中缀形式处于操作数的中间。对于计算机来说中缀表达式是很复杂的,因此计算表达式的值时,通常需要先将中缀表达式转换为前缀或后缀表达式,然后再进行求值。
前缀表达式(波兰表达式):前缀表达式的运算符位于两个操作数之前。
后缀表达式(逆波兰表达式):后缀表达式的运算符位于两个操作数之后。
中缀表达式转后缀表达式-手算
步骤1: 确定中缀表达式中各个运算符的运算顺序
步骤2: 选择下一个运算符,按照[左操作数 右操作数 运算符]的方式组合成一个新的操作数
步骤3: 如果还有运算符没被处理,继续步骤2
“左优先”原则: 只要左边的运算符能先计算,就优先算左边的 (保证运算顺序唯一);
- 中缀:A + B - C * D / E + F
- ① ④ ② ③ ⑤
- 后缀:A B + C D * E / - F +
后缀表达式转中缀的计算—手算:
从左往右扫描,每遇到一个运算符,就让运算符前面最近的两个操作数执行对应的运算,合体为一个操作数
后缀表达式的计算—机算
用栈实现后缀表达式的计算(栈用来存放当前暂时不能确定运算次序的操作数)
步骤1: 从左往后扫描下一个元素,直到处理完所有元素;
步骤2: 若扫描到操作数,则压入栈,并回到步骤1;否则执行步骤3;
步骤3: 若扫描到运算符,则弹出两个栈顶元素,执行相应的运算,运算结果压回栈顶,回到步骤1;
中缀表达式转后缀表达式(机算)
初始化一个栈,用于保存暂时还不能确定运算顺序的运算符从左到右处理各个元素,直到末尾。可能遇到三种情况:
1.遇到操作数:直接加入后缀表达式。
2.遇到界限符:遇到“(”直接入栈;遇到“)”则依次弹出栈内运算符并加入后缀表达式,直到 弹出“(”为止。注意:“(”不加入后缀表达式。
3.遇到运算符:依次弹出栈中优先级高于或等于当前运算符的所有运算符,并加入后缀表达式, 若碰到“(” 或栈空则停止。之后再把当前运算符入栈。
- #define MaxSize 40
- typedef struct{
- char data[MaxSize];
- int top;
- }SqStack;
-
- typedef struct{
- char data[MaxSize];
- int front,rear;
- }SqQueue;
-
- void InitStack(SqStack &S);
- bool StackEmpty(SqStack S);
- bool Push(SqStack &S, char x);
- bool Pop(SqStack &S, char &x);
- void InitQueue(SqQueue &Q);
- bool EnQueue(LQueue &Q, char x);
- bool DeQueue(LQueue &Q, char &x);
- bool QueueEmpty(SqQueue Q);
-
- // 判断元素ch是否入栈
- int JudgeEnStack(SqStack &S, char ch){
- char tp = S.data[S->top];
- // 如果ch是a~z则返回-1
- if(ch >= 'a' && ch <= 'z')
- return -1;
- // 如果ch是+、-、*、/且栈顶元素优先级大于等于ch则返回0
- else if(ch == '+' && (tp == '+' || tp == '-' || tp == '*' || tp == '/'))
- return 0;
- else if(ch == '-' && (tp == '+' || tp == '-' || tp == '*' || tp == '/'))
- return 0;
- else if(ch == '*' && (tp == '*' || tp == '/'))
- return 0;
- else if(ch == '/' && (tp == '*' || tp == '/'))
- return 0;
- // 如果ch是右括号则返回2
- else if(ch == ')')
- return 2;
- // 其他情况ch入栈,返回1
- else return 1;
- }
-
- // 中缀表达式转后缀表达式
- int main(int argc, char const *argv[]) {
- SqStack S;
- SqQueue Q;
- InitStack(S);
- InitQueue(Q);
- char ch;
- printf("请输入表达式,以“#”结束:");
- scanf("%c", &ch);
- while (ch != '#'){
- // 当栈为空时
- if(StackEmpty(&S)){
- // 如果输入的是数即a~z,直接入队
- if(ch >= 'a' && ch <= 'z')
- EnQueue(Q, ch);
- // 如果输入的是运算符,直接入栈
- else
- Puch(S, ch);
- }else{
- // 当栈非空时,判断ch是否需要入栈
- int n = JudgeEnStack(S, ch);
- // 当输入是数字时直接入队
- if(n == -1){
- EnQueue(Q, ch);
- }else if(n == 0){
- // 当输入是运算符且运算符优先级不高于栈顶元素时
- while (1){
- // 取栈顶元素入队
- char tp;
- Pop(S, tp);
- EnQueue(Q, tp);
- // 再次判断是否需要入栈
- n = JudgeEnStack(S, ch);
- // 当栈头优先级低于输入运算符或者栈头为‘)’时,入栈并跳出循环
- if(n != 0){
- EnStack(S, ch);
- break;
- }
- }
- }else if(n == 2){
- // 当出现‘)’时 将()中间的运算符全部出栈入队
- while(1){
- char tp;
- Pop(S, tp);
- if(tp == '(')
- break;
- else
- EnQueue(Q, tp);
- }
- }else{
- // 当运算符优先级高于栈顶元素或出现‘(’时直接入栈
- Push(S, ch);
- }
- }
- scanf("%c", &ch);
- }
- // 将最后栈中剩余的运算符出栈入队
- while (!StackEmpty(S)){
- char tp;
- Pop(S, tp);
- EnQueue(Q, tp);
- }
- // 输出队中元素
- while (!QueueEmpety(Q)){
- printf("%c ", DeQueue(Q));
- }
- return 0;
- }
用栈实现中缀表达式的计算:
1.初始化两个栈,操作数栈和运算符栈;
2.若扫描到操作数,压入操作数栈;
3.若扫描到运算符或界限符,则按照“中缀转后缀”相同的逻辑压入运算符栈(期间也会弹出运算符,每当弹出一个运算符时,就需要再弹出两个操作数栈的栈顶元素并执行相应运算,运算结果再压回操作数栈)
3.3栈在递归中的应用
函数调用的特点:最后被调用的函数最先执行结束(LIFO)
函数调用时,需要用一个栈存储:
- 调用返回地址
- 实参
- 局部变量
递归调用时,函数调用栈称为 “递归工作栈”:
每进入一层递归,就将递归调用所需信息压入栈顶;
每退出一层递归,就从栈顶弹出相应信息;
缺点:太多层递归可能回导致栈溢出;适合用“递归”算法解决:可以把原始问题转换为属性相同,但规模较小的问题
3.4队列的应用
- 队列应用:树的层次遍历
- 队列应用:图的广度优先遍历
- 队列应用:操作系统中多个进程争抢着使用有限的系统资源时,先来先服务算法(First Come First Service)是是一种常用策略。
3.5 特殊矩阵的压缩存储

3.5.1 数组的存储
一维数组的存储:各数组元素大小相同,且物理上连续存放。设起始地址为LOC,则数组元素
的存放地址 = LOC + i * sizeof(ElemType) (0≤i<10)

二维数组的存储 :
1. M行N列的二维数组b[M][N]中,设起始地址为LOC,若按行优先存储,则b[i][j]的存储地址 =LOC+(i∗N+j)∗sizeof(ElemType)
2. M行N列的二维数组b[M][N]中,设起始地址为 LOC,若按列优先存储,则b[i][j]
的存储地址 = LOC+(i+j*M)∗sizeof(ElemType)


3.5.2对称矩阵的压缩存储
对称矩阵的压缩存储:若n阶方阵中任意一个元素
,都有
则该矩阵为对称矩阵,对于对称矩阵,只需存储主对角线+下三角区。若按照行优先原则将各元素存入一维数组中,即
存入到数组
中,那么数组
共有
个元素。对于k,有:


3.5.3三角矩阵的压缩存储
-
下三角矩阵:除了主对角线和下三角区,其余的元素都相同。
-
上三角矩阵:除了主对角线和上三角区,其余的元素都相同。
-
压缩存储策略:按行优先原则将主对角线+下三角区存入一维数组中,并在最后一个位置存储常量。即
存入到数组
中,那么数组
共有
+ 1个元素。对于k,有:

按行优先原则将主对角线+上三角区存入一维数组中,并在最后一个位置存储常量。即
存入到数组
中,那么数组
共有
+ 1个元素。对于k,有:

推导:k= n+ (n-1) + ...+ (n - i + 2) + (j -i+1)-1= 
3.5.4三对角矩阵的压缩存储
三对角矩阵,又称带状矩阵: 当
时,有
。对于三对角矩阵,按行优先原则,只存储带状部分,即
存入到数组
中,那么
。若已知数组下标k,则
,
。

k值推导:前i-1行有2 + (i- 2)* 3个元素,第i行j-i + 2个元素,共有2*i+j- 2个元素,数组中下标为2*i+j- 3。
稀疏矩阵的压缩存储
稀疏矩阵的非零元素远远少于矩阵元素的个数。压缩存储策略:
- 顺序存储:三元组 <行,列,值>为了运算方便,矩阵的行数,列数,非零元素的个数也同时存储。

- 链式存储:十字链表法
