3.1 栈(stack)
3.1.1 栈的基本概念
栈的定义
-
栈是特殊的线性表:只允许在一端进行插入或删除操作, 其逻辑结构与普通线性表相同;
-
操作特性:后进先出LIFO^[LIFO:Last In First Out,后进栈的元素先出栈]
-
缺点:栈的大小不可变,解决方法——共享栈;
-
相关概念
-
栈顶:允许进行插入和删除的一端 (最上面的为栈顶元素);
-
栈底:不允许进行插入和删除的一端 (最下面的为栈底元素);
-
空栈:不含任何元素的空表;
-
栈的基本操作
-
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;
栈的常见题型
-
个不同元素进栈,出栈元素不同排列的个数为 (卡特兰数) 考前记一记
-
给定进栈顺序,判断有哪些合法的出栈顺序;
-
给定进展顺序和出栈顺序,判断出栈顺序是否合法;
3.1.2 栈的顺序存储
顺序栈的基本操作
顺序栈的定义
顺序栈结构体 算法题
#define MaxSize 10 //定义栈中元素的最大个数
typedef struct{
ElemType data[MaxSize]; //静态数组存放栈中元素
int top; //栈顶指针。从零开始,-1代表空栈
}SqStack;顺序栈的初始化
//初始化栈
void InitStack(SqStack &S){
S.top = -1; //初始化栈顶指针
}
void testStack(){
SqStack S; //声明一个顺序栈(分配空间)
InitStack(S);
//...
}注:也可以初始化时定义 S.top = 0 :top指针指向下一个可以插入元素的位置(栈顶元素的后一个位置),此时栈的基本操作略有不同;
-
判空:
if(S.top == 0) -
进栈使用:
S.data[S.top++] = x,先赋值再改下标 -
出栈使用:
x = S.data[--S.top],先改下标再赋值 -
判断栈满:
s.top == MaxSize
顺序栈的进栈操作
bool Push(SqStack &S, ElemType x){
if(S.top == MaxSize - 1)//栈满
return false;
S.top = S.top + 1; //指针先加1
S.data[S.top] = x; //新元素入栈
/*或者
S.data[++S.top] = x;
*/
return true;
}顺序栈的出栈操作
bool Pop(SqStack &S, ElemType &x){
if(S.top == -1) //栈空
return false;
x = S.data[S.top]; //先出栈
S.top = S.top - 1; //栈顶指针减1
/*或者
x = S.data[S.top--];
*/
return true;
}注:这里只是逻辑上的删除,数据依然残留在内存里
顺序栈读栈顶元素
bool GetTop(SqStack S, ElemType &x){
if(S.top == -1)
return false;
x = S.data[S.top]; //x记录栈顶元素
return true;
}顺序栈判断栈空
bool StackEmpty(SqStack S){
if(S.top == -1) //栈空
return true;
else //栈不空
return false;
}共享栈
共享栈:两个栈共享同一片空间
#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;
}栈满条件:top0 + 1 == top1
3.1.3 栈的链式存储结构
用链式存储方式实现的栈
-
进栈和出栈都只能在栈顶一端进行(链头作为栈顶)
-
链表的头部作为栈顶,意味着:
-
在实现数据“入栈”操作时,需要将数据从链表的头部插入;
-
在实现数据“出栈”操作时,需要删除链表头部的首元节点;
-
因此,链栈实际上就是一个只能采用头插法插入或删除数据的链表
typedef struct Linknode{
ElemType data; //数据域
struct Linknode *next; //指针域
}*LiStack; //栈类型的定义链栈的基本操作
参考:单链表上基本操作的实现
带头结点链栈的基本操作
定义
#include<stdio.h>
struct Linknode{
int data; //数据域
Linknode *next; //指针域
}Linknode,*LiStack;
typedef Linknode *Node; //结点结构体指针变量
typedef Node List; //结点结构体头指针变量初始化
void InitStack(LiStack &L){ //L为头指针
L = new Linknode;
L->next = NULL;
}判栈空
bool isEmpty(LiStack &L){
if(L->next == NULL)
return true;
else
return false;
}进栈操作:即使用头插法插入结点
注:链栈基本上不会出现栈满的情况,故不需要判断栈满
void pushStack(LiStack &L, int x){
Linknode s; //创建存储新元素的结点
s = new Linknode;
s->data = x;
//头插法
s->next = L->next;
L->next = s;
}出栈操作:删除头结点的下一个结点,即头删法
bool popStack(LiStack &L, int &x){
Linknode s;
if(L->next == NULL) //栈空不能出栈
return false;
s = L->next;
x = s->data;//x返回栈顶元素
L->next = L->next->next;
delete(s);
return true;
}不带头结点链栈的基本操作
定义
#include<stdio.h>
struct Linknode{
int data; //数据域
Linknode *next; //指针域
}Linknode,*LiStack;
typedef Linknode *Node; //结点结构体指针变量
typedef Node List; //结点结构体头指针变量初始化
void initStack(LiStack &L){
L=NULL;
}判栈空
bool isEmpty(LiStack &L){
if(L == NULL)
return true;
else
return false;
}进栈操作
void pushStack(LiStack &L, int x){
Linknode s; //创建存储新元素的结点
s = new Linknode;
s->next = L;
L = s;
}出栈操作
bool popStack(LiStack &L, int &x){
Linknode s;
if(L = NULL) //栈空不出栈
return false;
s = L;
x = s->data;
L = L->next;
delete(s);
return true;
}3.1.4 本节习题精选
3.2 队列(Queue)
3.2.1 队列的基本概念
队列的定义
-
队列是操作受限的线性表,只允许在一端进行插入 (入队),另一端进行删除 (出队)
-
操作特性:先进先出 FIFO^[First In First Out,先进队列的元素先出队列]
-
相关术语
-
队头:允许删除的一端
-
队尾:允许插入的一端
-
空队列:不含任何元素的空表
-
队列的基本操作
-
InitQueue(&Q):初始化队列,构造一个空列表Q -
DestroyQueue(&Q):销毁队列,并释放队列Q所占用的内存空间 -
EnQueue(&Q, x):入队,若队列Q未满,将x加入,使之成为新的队尾 -
DeQueue(&Q, &x):出队,若队列Q非空,删除队头元素,并用x返回 -
GetHead(Q,&x):读队头元素,若队列Q非空,则将队头元素赋值给x -
QueueEmpty(Q):判队列空,若队列Q为空,则返回true
3.2.2 队列的顺序存储结构
-
队头指针:指向队头元素;
-
队尾指针:指向队尾元素的后一个位置(下一个元素应该插入的位置)
\begin{document}
\begin{tikzpicture}[scale=2,line width=1.5pt]
\tikzstyle{every node}=[font=\huge,very thick,rectangle,minimum width=1cm, minimum height=1cm]
% 队列结点
\def\mystring{$q_{1}$,$q_{2}$,$q_{3}$,$q_{4}$,$q_{5}$,$q_{6}$}
\foreach \char [count=\i] in \mystring {
\node[draw=black,fill=gray!20] at (\i,0) (\i){\char};
}
\node [draw=black, dashed] at(7,0) (7){};
\draw[-,line width=1pt] (0.5,0.6) -- (7.5,0.6);
\draw[-,line width=1pt] (0.5,-0.6) -- (7.5,-0.6);
\node at(0,1) (out) {output};
\node at(8,1) (in) {input};
\draw[-latex, line width=2pt] (in) -- (out);
% 指针
\node at(1,-1) (front){front};
\node at(7,-1) (rear){rear};
\draw[->] (front) -- (1);
\draw[->] (rear) -- (7);
\end{tikzpicture}
\end{document}顺序队列的基本操作
定义
//队列的顺序存储类型
define MaxSize 10; //定义队列中元素的最大个数
typedef struct{
ElemType data[MaxSize]; //用静态数组存放队列元素
//连续的存储空间,大小为——MaxSize*sizeof(ElemType)
int front, rear; //队头指针和队尾指针
}SqQueue;初始化
void InitQueue(SqQueue &Q){
//初始化时,队头、队尾指针指向0
//队尾指针是最后一个元素的下标+1
Q.rear = Q.front = 0;
}
void test{
SqQueue Q; //声明一个队列
InitQueue(Q);
//...
}入队
bool EnQueue(SqQueue $Q, ElemType x){
if(队列已满)
return false; //队满则报错
Q.data[Q.rear] = x; //将x插入队尾
Q.rear = Q.rear + 1; //队尾指针后移
/*
Q.rear = (Q.rear+1)%MaxSize //队尾指针+1取模(循环队列中使用)
*/
return true;
}判空
bool QueueEmpty(SqQueue 0){
if(Q.rear == Q.front)
return true;
else
return false;
}循环队列
Q:能否用 Q.rear == MaxSize 作为队列满的条件?
A:不能!会有假溢出,所以需要用 模运算 将存储空间 {0,1,2,…,MaxSize} 在逻辑上变成“环状”——循环队列
-
初始:
Q.front = Q.rear = 0; -
队首指针进1:
Q.front = (Q.front + 1) % MaxSize -
队尾指针进1:
Q.rear = (Q.rear + 1) % MaxSize—— 队尾指针后移,当移到最后一个后,下次移动会到第一个位置 -
队列元素个数:
循环队列判满
Q:能否用Q.rear == Q.front 作为队列满的条件?
A:不能!这已经作为队列空的判断条件了
方案一:牺牲一个单元来区分队空和队满
队尾指针的再下一个位置就是队头,即 (Q.rear+1)%MaxSize == Q.front(队尾指针是最后一个元素的下标+1)
\begin{document}
\begin{tikzpicture}[scale=2,line width=1.5pt]
\tikzstyle{every node}=[font=\huge,very thick,rectangle,minimum width=1cm, minimum height=1cm]
\node[draw=black,fill=gray!20] at (1,0) (1){$q_{5}$};
\node[draw=black,fill=gray!20] at (2,0) (2){$q_{6}$};
\node[draw=black,dashed] at (3,0) (3){};
\node[draw=black,fill=gray!20] at (4,0) (4){$q_{1}$};
\node[draw=black,fill=gray!20] at (5,0) (5){$q_{2}$};
\node[draw=black,fill=gray!20] at (6,0) (6){$q_{3}$};
\node[draw=black,fill=gray!20] at (7,0) (7){$q_{4}$};
\draw[-,line width=1pt] (0.5,0.6) -- (7.5,0.6);
\draw[-,line width=1pt] (0.5,-0.6) -- (7.5,-0.6);
\draw[-latex] (1) -- (0,0) -- (0,1) -- (8,1) -- (8,0) -- (7);
\node at(4,-1) (front){front};
\node at(3,-1) (rear){rear};
\draw[->] (front) -- (4);
\draw[->] (rear) -- (3);
\end{tikzpicture}
\end{document}-
循环队列获取元素个数:元素个数=
(rear+MaxSize-front)%MaxSize -
循环队列——入队:只能从队尾插入(判满使用方案一)
bool EnQueue(SqQueue &Q, ElemType x){
if((Q.rear+1)%MaxSize == Q.front) //队满
return false;
Q.data[Q.rear] = x; //将x插入队尾
Q.rear = (Q.rear + 1) % MaxSize; //队尾指针加1取模
return true;
}- 循环队列——出队:只能让队头元素出队
//出队,删除一个队头元素,用x返回
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;
}- 循环队列——获得队头元素
bool GetHead(SqQueue &Q, ElemType &x){
if(Q.rear == Q.front) //队空报错
return false;
x = Q.data[Q.front];
return true;
}方案二:不牺牲存储空间,设置 size
定义一个变量 size用于记录队列此时记录了几个数据元素,初始化 size = 0,进队成功 size++,出队成功size--,根据 size 的值判断队满与队空
-
队满条件:
size == MaxSize -
队空条件:
size == 0
define MaxSize 10;
typedef struct{
ElemType data[MaxSize];
int front, rear;
int size; //队列当前长度
}SqQueue;
//初始化队列
void InitQueue(SqQueue &Q){
Q.rear = Q.front = 0;
size = 0;
}方案三:不牺牲存储空间,设置 tag
定义一个变量 tag用来表示最近进行的操作
-
每次删除操作成功时,都令
tag = 0; -
每次插入操作成功时,都令
tag = 1;
队满条件:Q.front == Q.rear && tag == 1(只有插入操作,才可能导致队满)
队空条件:Q.front == Q.rear && tag == 0(只有删除操作,才可能导致队空)
define MaxSize 10;
typedef struct{
ElemType data[MaxSize];
int front, rear;
int tag; //最近进行的是删除or插入
}SqQueue;其他出题方法——队尾指针指向队尾元素
-
判空
(Q.rear + 1) % MaxSize == Q.front -
判满
-
方案一:牺牲一个存储单元
-
方案二:增加辅助变量
size或tag
-
-
入队操作
Q.rear = (Q.rear + 1) % MaxSize; //后移一位
Q.data[Q.rear] = x;3.2.3 队列的链式存储结构
typedef struct LinkNode{ //链式队列结点
ElemType data;
struct LinkNode *next;
}
typedef struct{ //链式队列
LinkNode *front, *rear; //队列的队头和队尾指针
}LinkQueue;\begin{document}
\begin{tikzpicture}[scale=2,line width=1.5pt]
\tikzstyle{every node}=[font=\huge,very thick,rectangle,minimum width=1cm, minimum height=1cm]
% 队列结点
\def\mystring{$q_{1}$,$q_{2}$,$q_{3}$,$q_{4}$,$q_{5}$,$q_{6}$}
\foreach \char [count=\i] in \mystring {
\node[draw=black,fill=gray!20] at (\i,0) (\i){\char};
}
\node [draw=black, dashed] at(7,0) (7){};
\draw[-,line width=1pt] (0.5,0.6) -- (7.5,0.6);
\draw[-,line width=1pt] (0.5,-0.6) -- (7.5,-0.6);
\node at(0,1) (out) {output};
\node at(8,1) (in) {input};
\draw[-latex, line width=2pt] (in) -- (out);
% 指针
\node at(1,-1) (front){front};
\node at(7,-1) (rear){rear};
\draw[->] (front) -- (1);
\draw[->] (rear) -- (7);
%链式关系
\draw[-latex, gray!50] (1) -- (2);
\draw[-latex, gray!50] (2) -- (3);
\draw[-latex, gray!50] (3) -- (4);
\draw[-latex, gray!50] (4) -- (5);
\draw[-latex, gray!50] (5) -- (6);
\draw[-latex, gray!50, dashed] (6) -- (7);
\end{tikzpicture}
\end{document}链式队列的基本操作
判断队满
-
顺序存储:预分配存储空间
-
链式存储:一般不会队满,除非内存不足
链队长度:设置一个int length 记录链式队列长度,再遍历链队
带头结点链对的基本操作
- 初始化 & 判空
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) //也可用 Q.front -> next == NULL
return true;
else
return false;
}- 入队操作
//新元素入队 (表尾进行)
void EnQueue(LinkQueue &Q, ElemType x){
LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode)); //申请一个新结点
s->data = x;
s->next = NULL; //s作为最后一个结点,指针域指向NULL
Q.rear->next = s; //新结点插入到当前的rear之后
Q.rear = s; //表尾指针指向新的表尾
}- 出队操作
//队头元素出队(表头进行)
bool DeQueue(LinkQueue &Q, ElemType &x){
if(Q.front == Q.rear)
return false; //空队
LinkNode *p = Q.front->next; //p指针指向即将删除的结点 (头结点所指向的结点)
x = p->data;
Q.front->next = p->next; //修改头结点的next指针
//如果p是最后一个结点的话,p->next为NULL,所以这个关系也是正确的
if(Q.rear == p) //此次是最后一个结点出队
Q.rear = Q.front; //修改rear指针,释放后队列变为空队列
free(p); //释放结点空间
return true;
}不带头结点链对的基本操作
- 初始化 & 判空
void InitQueue(LinkQueue &Q){
//初始化时,front、rear都指向NULL
Q.front = NULL;
Q.rear = NULL;
}
//判断队列是否为空
bool IsEmpty(LinkQueue Q){
if(Q.front == NULL) //也可以用 Q.rear == 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){ //在空队列中插入第一个元素,此时rear和front都指向NULL
Q.front = s; //修改队头队尾指针
Q.rear = s;
}else{
Q.rear->next = s; //新结点插入到rear结点之后
Q.rear = s; //修改rear指针指向新的表尾结点
}
}- 出队
//队头元素出队(表头进行)
bool DeQueue(LinkQueue &Q, ElemType &x){
if(Q.front == NULL)
return false; //空队
LinkNode *p = Q.front; //p指针指向即将删除的结点
x = p->data;
Q.front = p->next; //修改next指针
if(Q.rear == p) { //此次是最后一个结点出队
Q.front = NULL;
Q.rear = NULL;
}
free(p); //释放结点空间
return true;
}3.2.4 双端队列
双端队列定义
-
双端队列允许从两端插入、两端删除的线性表;
-
如果只使用其中一端的插入、删除操作,则等同于栈;
-
输入受限的双端队列:允许一端插入,两端删除的线性表;
-
输出受限的双端队列:允许两端插入,一端删除的线性表;
判断输出序列的合法性
例: 数据元素输入序列为 1,2,3,4,判断 个输出序列的合法性
PS: 栈中合法的序列,双端队列中一定也合法
| 栈 | 输入受限的双端队列 | 输出受限的双端队列 |
|---|---|---|
| 14个合法(卡特兰数) | 验证在栈中不合法的序列 | 验证在栈中不合法的序列 |
| 只有 4213 和 4231 不合法 | 只有 4132 和 4231 不合法 |
3.2.5 本节习题精选
3.3 栈的应用
3.3.1 栈在括号匹配中的应用
用栈实现括号匹配:( ( ( ) ) ) 最后出现的左括号最先被匹配(栈的特性—LIFO);
实现思路:
-
初始时栈为空,从左往右遍历算术表达式中的各个括号
-
当遍历到左括号时,将其压入栈顶;
-
当遍历到右括号时,将栈顶的一个左括号弹出,并判断出栈的左括号是否与当前遍历到的右括号匹配
-
如果栈空而没有左括号弹出,则匹配失败
-
如果匹配,则继续遍历下一个括号
-
如果不匹配,则匹配失败。
-
-
当遍历过所有的括号后
-
若栈空,则匹配成功
-
若干非空,则匹配失败
-
-
#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)
//栈顶元素出栈,用x返回
bool Pop(SqStack &S, char &x)
bool bracketCheck(char str[], int length){
SqStack S; //声明
InitStack(S); //初始化栈
for(int i=0; i<length; i++){
if(str[i] == '(' || str[i] == '[' || str[i] == '{'){
Push(S, str[i]); //扫描到左括号,入栈
}
else{
if(StackEmpty(S)) //扫描到右括号,且当前栈空
return false; //匹配失败
char topElem; //存储栈顶元素
Pop(S, topElem); //栈顶元素出栈
if(str[i] == ')' && topElem != '(' )
return false;
if(str[i] == ']' && topElem != '[' )
return false;
if(str[i] == '}' && topElem != '{' )
return false;
}
}
return StackEmpty(S); //栈空说明匹配成功
}3.3.2 栈在表达式求值中的应用
后缀表达式(逆波兰表达式)
- 运算符在两个操作数后面
中缀表达式转后缀表达式-手算
-
确定中缀表达式中各个运算符的运算顺序
-
选择下一个运算符,按照
[左操作数 右操作数 运算符]的方式组合成一个新的操作数 -
如果还有运算符没被处理,继续步骤2
\begin{document}
\begin{tikzpicture}[scale=2,line width=1.5pt]
\tikzstyle{every node}=[font=\huge,very thick,minimum width=1cm, minimum height=1cm]
% 中缀表达式
\def\infix{$A$, $+$, $B$, $-$, $C$, $\times$, $D$, $/$, $E$, $+$, $F$}
\foreach \char [count=\i] in \infix {
\node at (\i/2,0) {\char};
}
% 符号生效次序
\foreach \idx [count=\i] in {1,4,2,3,5}{
\node at(\i,-0.5) {\idx};
}
% 后缀表达式
\def\postfix{$A$, $B$, $+$, $C$, $D$, $\times$, $E$, $/$, $-$, $F$, $+$}
\foreach \char [count=\i] in \postfix {
\node at (\i/2,-1) {\char};
}
% 符号生效次序
\foreach \idx [count=\i] in {3,6,8,9,11} {
\node at (\idx/2,-1.5) {\i};
}
\end{tikzpicture}
\end{document}“左优先”原则:只要左边的运算符能先计算,就优先算左边的(保证运算顺序唯一),如左图所示;但客观来看左右两种方式都是正确的
\begin{document}
\begin{tikzpicture}[scale=2,line width=1.5pt]
\tikzstyle{every node}=[font=\huge,very thick,minimum width=1cm, minimum height=1cm]
% 中缀表达式
\def\infix{$A$, $+$, $B$, $\times$, $(C$, $-$, $D)$, $-$, $E$, $/$, $F$}
\foreach \char [count=\i] in \infix {
\node at (\i/2,0) {\char};
}
% 符号生效次序
\foreach \idx [count=\i] in {3,2,1,5,4}{
\node at(\i,-0.5) {\idx};
}
% 后缀表达式
\def\postfix{$A$, $B$, $C$, $D$, $-$, $\times$, $+$, $E$, $F$, $/$, $-$}
\foreach \char [count=\i] in \postfix {
\node at (\i/2,-1) {\char};
}
% 符号生效次序
\foreach \idx [count=\i] in {5,6,7,10,11} {
\node at (\idx/2,-1.5) {\i};
}
\end{tikzpicture}
\hspace{5em}
\begin{tikzpicture}[scale=2,line width=1.5pt]
\tikzstyle{every node}=[font=\huge,very thick,minimum width=1cm, minimum height=1cm]
% 中缀表达式
\def\infix{$A$, $+$, $B$, $\times$, $(C$, $-$, $D)$, $-$, $E$, $/$, $F$}
\foreach \char [count=\i] in \infix {
\node at (\i/2,0) {\char};
}
% 符号生效次序
\foreach \idx [count=\i] in {5,3,2,4,1}{
\node at(\i,-0.5) {\idx};
}
% 后缀表达式
\def\postfix{$A$, $B$, $C$, $D$, $-$, $\times$, $+$, $E$, $F$, $/$, $-$}
\foreach \char [count=\i] in \postfix {
\node at (\i/2,-1) {\char};
}
% 符号生效次序
\node at (5/2,-1.5) {2};
\node at (6/2,-1.5) {3};
\node at (9/2,-1.5) {1};
\node at (10/2,-1.5) {4};
\node at (11/2,-1.5) {5};
\end{tikzpicture}
\end{document}中缀表达式转后缀表达式-机算
栈用于保存暂时还不能确定运算顺序的运算符 考前记一记
-
从左到右处理各个元素,直到末尾。
-
遇到操作数:直接加入后缀表达式;
-
遇到界限符:遇到
'('直接入栈;遇到')'则依次弹出栈内运算符并加入后缀表达式,直到弹出'('为止。注意:'('不加入后缀表达式; -
遇到运算符:依次弹出栈中优先级高于或等于当前运算符的所有运算符 ^[提示: ÷ 高于 ],并加入后缀表达式,若碰到
'('或栈空则停止。之后再把当前运算符入栈。
-
-
按上述方法处理完所有字符后,将栈中剩余运算符依次弹出,并加入后缀表达式。
后缀表达式求值-手算
从左往右扫描,每遇到一个运算符,就让运算符前面最近的两个操作数执行对应的运算,合体为一个操作数
注意: 两个操作数的左右顺序
后缀表达式求值-机算
栈用于存放当前暂时不能确定运算次序的操作数 考前记一记
-
从左往右扫描下一个元素,直到处理完所有元素
-
若扫描到操作数,则压入栈;
-
若扫描到运算符,则弹出两个栈顶元素,执行相应的运算,运算结果压回栈顶;
-
-
若表达式合法,则最后栈中只会留下一个元素,就是最终结果
注意:先出栈的是“右操作数”(先出栈的原本在栈的右边/上面)
前缀表达式 (波兰表达式)
运算符在两个操作数前面
中缀表达式转前缀表达式-手算
-
确定中缀表达式中各个运算符的运算顺序
-
选择下一个运算符,按照
[运算符 左操作数 右操作数]的方式组合成一个新的操作数 -
如果还有运算符没被处理,就继续执行步骤2
“右优先”原则:只要右边的运算符能先计算,就优先算右边的(保证运算顺序唯一),如左图所示;但客观来看左右两种方式都是正确的
\begin{document}
\begin{tikzpicture}[scale=2,line width=1.5pt]
\tikzstyle{every node}=[font=\huge,very thick,minimum width=1cm, minimum height=1cm]
% 中缀表达式
\def\infix{$A$, $+$, $B$, $\times$, $(C$, $-$, $D)$, $-$, $E$, $/$, $F$}
\foreach \char [count=\i] in \infix {
\node at (\i/2,0) {\char};
}
% 符号生效次序
\foreach \idx [count=\i] in {5,3,2,4,1}{
\node at(\i,-0.5) {\idx};
}
% 前缀表达式
\def\prefix{$+$, $A$, $-$, $\times$, $B$, $-$, $C$, $D$, $/$, $E$, $F$}
\foreach \char [count=\i] in \prefix {
\node at (\i/2,-1) {\char};
}
% 符号生效次序
\foreach \idx [count=\i] in {1,3,4,6,9} {
\pgfmathtruncatemacro{\result}{6-\i};
\node at (\idx/2,-1.5) {\result};
}
\end{tikzpicture}
\hspace{5em}
\begin{tikzpicture}[scale=2,line width=1.5pt]
\tikzstyle{every node}=[font=\huge,very thick,minimum width=1cm, minimum height=1cm]
% 中缀表达式
\def\infix{$A$, $+$, $B$, $\times$, $(C$, $-$, $D)$, $-$, $E$, $/$, $F$}
\foreach \char [count=\i] in \infix {
\node at (\i/2,0) {\char};
}
% 符号生效次序
\foreach \idx [count=\i] in {3,2,1,5,4}{
\node at(\i,-0.5) {\idx};
}
% 前缀表达式
\def\prefix{$-$, $+$, $A$, $\times$, $B$, $-$, $C$, $D$, $/$, $E$, $F$}
\foreach \char [count=\i] in \prefix {
\node at (\i/2,-1) {\char};
}
% 符号生效次序
\node at (1/2,-1.5) {5};
\node at (2/2,-1.5) {3};
\node at (4/2,-1.5) {2};
\node at (6/2,-1.5) {1};
\node at (9/2,-1.5) {4};
\end{tikzpicture}
\end{document}前缀表达式求值-机算
用栈实现前缀表达式的计算
-
从右往左扫描下一个元素,直到处理完所有元素;
-
若扫描到操作数则压入栈;
-
若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶;
注意:先出栈的是“左操作数”
中缀表达式(需要界限符)
-
由三个部分组成:操作数、运算符、界限符组成。
-
运算符在两个操作数中间:
a + b
a + b - c
a + b - c*d
((15 ÷ (7-(1+1)))×3)-(2+(1+1))中缀表达式求值-机算
两个算法的结合: 中缀转后缀+ 后缀表达式的求值,二者都是从左往右的扫描
-
初始化两个栈,运算符栈和操作数栈
-
从左往右扫描中缀表达式,若扫描到操作数,压入操作数栈
-
若扫描到运算符或界限符,则按照“中缀转后缀”相同的逻辑压入运算符栈 (期间也会弹出运算符,每当弹出一个运算符时
3.3.3 栈在递归中的应用
函数调用的特点:最后被调用的函数最先执行结束(LIFO[
函数调用时,需要用一个栈存储:
-
调用返回地址:返回到上一层递归的该地址处,继续执行此层递归在该地址之后的代码
-
实参:递归返回值
-
局部变量:递归所需参数
递归算法特点:可以把原始问题转换为属性相同,但规模较小的问题,如:求阶乘、斐波那契数列
递归调用时,函数调用栈称为 “递归工作栈”:
-
每进入一层递归,就将递归调用所需信息压入栈顶;
-
每退出一层递归,就从栈顶弹出相应信息;
缺点:效率低,太多层递归可能回导致栈溢出;可能包含很多次重复计算
3.3 队列的应用
3.3.1 树的层次遍历
详见树的层序遍历
3.3.2 图的广度优先遍历
3.3.3 队列在操作系统中的应用
-
CPU资源分配,先来先服务(FCFS)中采用就绪进程队列
-
打印数据缓冲区,SPOOLing技术中采用缓冲区队列
3.3.6 本节习题精选
3.4 特殊矩阵的压缩存储
3.4.1 数组的存储结构
-
一维数组
-
各数组元素大小相同,物理上连续存放;
-
起始地址:
LOC -
数组下标:默认从0开始!
-
数组元素
a[i]的存放地址 =LOC + i × sizeof(ElemType)
-
Elemtype a[10]; //ElemType型一维数组-
二维数组
-
起始地址:
LOC -
行 列的二维数组
b[M][N]中,b[i][j]的存储地址:-
行优先存储:
LOC + (i×N + j) × sizeof(ElemType) -
列优先存储:
LOC + (j×M + i) × sizeof(ElemType)
-
-
行优先/列优先存储优点:实现随机存储
-
Elemtype b[2][4]; //2行4列的二维数组\begin{document}
\begin{tikzpicture}[scale=2, line width=1.5pt,
every node/.style={draw=black, rectangle, very thick, font =\huge, rectangle, minimum width=6em, minimum height=3em},
]
\foreach \i in {0,1}{
\foreach \j in {0,1,2,3}{
\ifnum\i=1
\node[fill=blue!20] at(\j*2,-\i) {b[\i][\j]};
\else
\node[fill=green!20] at(\j*2,-\i) {b[\i][\j]};
\fi
}
}
\end{tikzpicture}
\end{document}行优先存储:
\begin{document}
\begin{tikzpicture}[scale=2, line width=1.5pt,
every node/.style={draw=black, rectangle, very thick, font =\huge, rectangle, minimum width=6em, minimum height=3em},
]
\foreach \i in {0,1}{
\foreach \j in {0,1,2,3}{
\ifnum\i=1
\node[fill=blue!20] at(\i*8+\j*2,0){b[\i][\j]};
\else
\node[fill=green!20] at(\i*8+\j*2,0){b[\i][\j]};
\fi
}
}
\end{tikzpicture}
\end{document}列优先存储:
\begin{document}
\begin{tikzpicture}[scale=2, line width=1.5pt,
every node/.style={draw=black, rectangle, very thick, font =\huge, rectangle, minimum width=6em, minimum height=3em},
]
%\idx=1
\foreach \i in {0,1}{
\foreach \j in {0,1,2,3}{
\ifnum\i=1
\node[fill=blue!20] at(\i*2+\j*4,0) {b[\i][\j]};
\else
\node[fill=green!20] at(\i*2+\j*4,0) {b[\i][\j]};
\fi
}
}
\end{tikzpicture}
\end{document}3.4.2 普通矩阵的存储
可用二维数组存储
-
描述矩阵元素时,行、列号通常从
1开始; -
描述数组时,通常下标从
0开始;
3.4.3 特殊矩阵的存储
特殊矩阵——压缩存储空间
对称矩阵
对称矩阵(方阵),
-
只存储主对角线和下(上)三角区的数据
-
可以使用一维数组存储各个元素,数组大小为
-
映射函数:矩阵下标→一维数组下标,
-
当以行优先存储主对角线和下三角区数据时
-
若 ,则 对应一维数组下标
-
若 ,则 对应一维数组下标
-
-
当以列优先存储主对角线和下三角区数据时
-
若 ,则 对应一维数组下标
-
若 ,则 对应一维数组下标
-
-
Example
例:以存储对称矩阵主对角线和下三角区数据为例,行优先存储和列优先存储如下所示
\begin{document}
\def\myMatrix{{
{0,5,3,3,4},
{5,0,2,6,7},
{3,2,0,8,9},
{3,6,8,0,6},
{4,7,9,6,0}
}}
\begin{tikzpicture}[scale=1, ultra thick,
every node/.style={font=\huge, draw=black, very thick, rectangle, minimum width=1cm, minimum height=1cm},
]
\def\rowfirst{0};
\foreach \row in {0,1,2,3,4} {
\foreach \col in {0,1,2,3,4} {
\pgfmathtruncatemacro{\value}{\myMatrix[\row][\col]};
\ifnum\row=\col
\node[fill=blue!20] at (\col, -\row) {\value};
\node[fill=blue!20] at (\rowfirst+8, -1) {\value};
\xdef\rowfirst{\rowfirst+1};
\else
\ifnum\row>\col
\node[fill=green!20] at (\col, -\row) {\value};
\node[fill=green!20] at (\rowfirst+8, -1) {\value};
\xdef\rowfirst{\rowfirst+1};
\else
\node at (\col, -\row) {\value};
\fi
\fi
}
}
\def\colfirst{0};
\foreach \col in {0,1,2,3,4} {
\foreach \row in {0,1,2,3,4} {
\pgfmathtruncatemacro{\value}{\myMatrix[\row][\col]};
\ifnum\row=\col
\node[fill=blue!20] at (\colfirst+8, -3) {\value};
\xdef\colfirst{\colfirst+1};
\else
\ifnum\row>\col
\node[fill=green!20] at (\colfirst+8, -3) {\value};
\xdef\colfirst{\colfirst+1};
\else
\fi
\fi
}
}
% 序号
\foreach \i in{1,2,3,4,5}{
\node[draw=none, font=\Large] at (\i-1, 1) {\i};
\node[draw=none, font=\Large] at (-1, -\i+1) {\i};
}
\foreach \i in{0,1,…,14}{
\node[draw=none, font=\Large] at(\i+8, -2){\i};
\node[draw=none, font=\Large] at(\i+8, -4){\i};
}
\end{tikzpicture}
\end{document}
三角矩阵
三角矩阵(方阵):除了主对角线和下(上)三角区,其余元素都相同
-
重点存储主对角线和下(上)三角区的数据,并在最后一个位置存储常量
-
映射函数:矩阵下标→一维数组下标,
-
当以行优先存储主对角线和下三角区数据时
-
若 ,则 对应一维数组下标
-
若 ,则 对应一维数组下标
-
-
当以列优先存储主对角线和下三角区数据时
-
若 ,则 对应一维数组下标
-
若 ,则 对应一维数组下标
-
-
Example
例:以 为例,行优先存储和列优先存储如下所示
\begin{document}
\def\myMatrix{{
{1,0,0,0,0},
{5,1,0,0,0},
{3,2,1,0,0},
{3,6,8,1,0},
{4,7,9,6,1}
}}
\begin{tikzpicture}[scale=1, ultra thick,
every node/.style={font=\huge, draw=black, very thick, rectangle, minimum width=1cm, minimum height=1cm},
]
\def\rowfirst{0};
\foreach \row in {0,1,2,3,4} {
\foreach \col in {0,1,2,3,4} {
\pgfmathtruncatemacro{\value}{\myMatrix[\row][\col]};
\ifnum\row=\col
\node[fill=blue!20] at (\col, -\row) {\value};
\node[fill=blue!20] at (\rowfirst+8, -1) {\value};
\xdef\rowfirst{\rowfirst+1};
\else
\ifnum\row>\col
\node[fill=green!20] at (\col, -\row) {\value};
\node[fill=green!20] at (\rowfirst+8, -1) {\value};
\xdef\rowfirst{\rowfirst+1};
\else
\node at (\col, -\row) {\value};
\fi
\fi
}
}
\def\colfirst{0};
\foreach \col in {0,1,2,3,4} {
\foreach \row in {0,1,2,3,4} {
\pgfmathtruncatemacro{\value}{\myMatrix[\row][\col]};
\ifnum\row=\col
\node[fill=blue!20] at (\colfirst+8, -3) {\value};
\xdef\colfirst{\colfirst+1};
\else
\ifnum\row>\col
\node[fill=green!20] at (\colfirst+8, -3) {\value};
\xdef\colfirst{\colfirst+1};
\else
\fi
\fi
}
}
% 存储常量c
\node at(23,-1) {0};
\node at(23,-3) {0};
% 序号
\foreach \i in{1,2,3,4,5}{
\node[draw=none, font=\Large] at (\i-1, 1) {\i};
\node[draw=none, font=\Large] at (-1, -\i+1) {\i};
}
\foreach \i in{0,1,…,15}{
\node[draw=none, font=\Large] at(\i+8, -2){\i};
\node[draw=none, font=\Large] at(\i+8, -4){\i};
}
\end{tikzpicture}
\end{document}
三对角矩阵
三对角矩阵(方阵):当 时,有,即只需存储主对角线以及上下左右相邻元素
* 第一行和最后一行两个元素,其余三个元素,共 $3n-2$ 个元素
稀疏矩阵
稀疏矩阵:非零元素远远少于矩阵元素的个数
-
顺序存储——三元组
<行,列,值> -
链式存储——十字链表法
-
向下域
down:指针指向第 列的各个元素 -
向右域
right:指针指向第 行的各个元素
-