1. 栈(逻辑结构)
1.1 定义和基本操作
1.1.1 定义
栈:只允许在栈顶Top进行插入和删除操作的线性表。
操作特性:后进先出。
数学性质:n个不同元素进栈,出栈元素不同排列的个数为 (卡特兰数)
1.1.1 基本操作
InitStack(&S) //初始化一个空栈S
StackEmpty(S) //判断一个栈是否为空
Push(&S,x) //入栈,若栈【未满】,则将x加入使之称为新栈顶
Pop(&S,&x) //出栈,若栈【非空】,则弹出栈顶元素,并用x返回
GetTop(S,&x) //读取栈顶元素,若栈S非空,则用x返回栈顶元素
DestroyStack(&S) //销毁栈,并释放栈S占用的存储空间
1.2 栈的顺序存储结构(顺序栈)
1.2.1 顺序栈的结构体定义
#define MaxSize 50
typedef struct {
ElemType data[MaxSize];
int top;
} SqStack;
栈顶指针:S.top 初始化:S.top = -1; 栈顶元素:S.data[S.top];
进栈操作:若栈不满,栈顶指针先+1,再将值赋给栈顶元素
出栈操作:若栈非空,先取栈顶元素值,再将栈顶指针-1
栈空条件:S.top == -1; 栈满条件:S.top == MaxSize - 1; 栈长:S.top+1
1.2.2 顺序栈的基本运算
初始化
void InitStack (SqStack &S) {
S.top = -1;
}
判栈空
bool StackEmpty (SqStack S) {
if (S.top == -1) return true;
else return false;
}
进栈(初始化:S.top = -1)
bool Push(SqStack &S, ElemType x) {
if (S.top == MaxSize - 1) return false;
S.data[++S.top] = x; // 先指针+1,再传数据
return true;
}
出栈(初始化:S.top = -1):只是逻辑上删除,内存中还有残留
bool Pop(SqStack &S, ElemType &x) {
if (S.top == -1) return false;
x = S.data[S.top--]; // 先取数据,再指针-1
return true;
}
读栈顶元素
bool GetTop(SqStack S, ElemtType &x) {
x = S.data[S.top - 1];
return true;
}
【注意】
若栈的初始化为 S.top = 0
则进栈:S.data[S.top++] = x; 先传值,后指针+1
则出栈:x = S.data[--S.top]; 先指针-1,再取值
1.2.3 共享栈
两个栈的栈底分别设置为共享栈的两端
栈满:top1 - top0 == 1 栈空:top0 == -1 && top1 == MaxSize
1.3 栈的链式存储结构(链栈)
相当于一个单链表,推荐不带头结点,区别是只能在表头进行插入和删除操作。
优点:便于多个栈共享存储空间,提高效率,不存在栈满上溢。
1.3.1 链栈的结构体定义
typedef struct LinkNode {
ElemType data;
struct LinkNode *next;
} *LinkStack;
2.队列(逻辑结构)
2.1 定义和基本操作
2.1.1 定义
队列:只允许在一端插入(入队),在另一端删除(出队)。
操作特性:先进先出。队头删除,队尾插入
2.1.2 基本操作
【注意】不可以随便读取栈或队列中间的某个数据。
InitQueue(&Q) //初始化队列,构造一个空队列
QueueEmpty(Q) //判断队列是否为空
EnQueue(&Q,x) //入队,若队列Q【未满】,将x加入,使之称为新的队尾
DeQueue(&Q,&x) //出队,若队列Q【非空】,删除队头元素,并用x返回
GetHead(Q,&x) //读取队头元素,若队列【非空】,则将队头元素赋值给x
2.2 队列的顺序存储结构(顺序队列)
2.2.1 顺序队列的结构体定义
#define MaxSize 50
typedef struct {
ElemType data[MaxSize];
int front, rear; // front指向队头元素,rear指向队尾元素的下一个位置
} SqQueue;
初始状态(队空条件):Q.front == Q.rear == 0
进队操作:若队列不满,先传值,再队尾指针+1
出队操作:若队列非空,先取值,再对头指针+1
【注意】不能用Q.rear == MaxSize 判队满,这会发生“假溢出”。
2.2.2 循环队列
定义:把存储队列元素的表从逻辑上视为一个环,称为循环列表。当Q.front = MaxSize - 1后,再前进一个位置就自动到 0 。
初始时: Q.front = Q.rear = 0
队首指针进1:Q.front = (Q.front + 1) % MaxSize
队尾指针进1:Q.rear = (Q.rear + 1) % MaxSize
队列长度:(Q.rear + MaxSize - Q.front) % MaxSize
!!!区分队空还是队满的三种处理方式:
牺牲一个单元
队空:Q.front == Q.rear
队满:(Q.rear + 1) % MaxSize == Q.front
结构体中增设表示元素个数的数据成员
队空:Q.size == 0
队满:Q.size == MaxSize
结构体中增设tag数据成员
tag = 0 :因删除导致的Q.front == Q.rear (队空)
tag = 1 :因插入导致的Q.front == Q.rear (队满)
基本操作
初始化
void InitQueue(SqQueue &Q) {
Q.front = Q.rear = 0;
}
判队空
bool QueueEmpty(SqQueue Q) {
if ( Q.rear == Q.rear) 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.front == Q.rear ) return false;
x = Q.data[Q.front];
Q.front = (Q.front + 1) % MaxSize;
return true;
}
2.3队列的链式存储结构(链队列)
2.3.1 链队列结构体定义
typedef struct LinkNode{
ElemType data;
struct LNode *next;
} LinkNode;
typedef struct {
LinkNode *front, *rear;
} LinkQueue;
链队列为空:Q.front == null && Q.rear == null
初始化
void InitQueue ( LinkQueue &Q ) {
Q.front = Q.rear = (LinkNode *)malloc(sizeof(LinkNode));
Q.rear->next = null;
}
判队空
bool QueueEmpty(LinkQueue Q) {
if ( Q.front == Q.rear ) return true;
else return false;
}
入队
bool EnQueue( LinkQueue &Q, ElemType x) {
LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
s->data = x; s->next = null;
Q.rear->next = s;
Q.rear = s;
return true;
}
出队
bool DeQueue (LinkQueue &Q, ElemType &x) {
if ( Q.front == Q.rear ) return false;
LinkNode *p = (LinkNode *)malloc(sizeof(LinkNode));
p = Q.front->next;
x = p->data;
Q.front->next = p->next;
if (Q.rear == p)
Q.rear = Q.front;
free(p);
return true;
}
2.4 双端队列
允许两端都可以进行插入和删除操作。
【注意】栈满足的序列必满足于双端队列 !实际考题中代入验证即可。
双端队列---输入序列为1,2,3,4,则
能由输入受限的双端队列得到,但不能由输出受限的双端队列得到的是4,1,3,2。
能由输入受限的双端队列得到,但不能由输入受限的双端队列得到的是4,2,1,3。
既不能由输入受限的双端队列得到,又不能由输出受限的双端队列得到的是4,2,3,1。
3. 栈和队列的应用
3.1 栈的应用
3.1.1 括号匹配
简言之,遇到左括号入栈,遇到右括号弹出栈顶元素,检查是否匹配成功。
匹配失败情况:
(1) 左括号单身:括号序列扫描完毕,栈非空
(2) 右括号单身:扫描到右括号,栈为空
(3) 左右括号不匹配:扫描过程中,括号不匹配
3.1.2 栈在表达式求值中的应用
中缀转前缀表达式(波兰表达式):遵循“左优先”原则,保证手算和计算机结果相同。
中缀转后缀表达式(逆波兰表达式):遵循“右优先”原则
例:中缀表达式:A + B * (C - D) - E / F
转前缀:-+A*B-CD/EF
转后缀:ABCD-*+EF/-
前缀表达式的计算过程:
从右往左扫描,操作数入栈,遇到运算符<op>,则弹出两个栈顶元素X和Y,执行运算X<op>Y,将结果压回栈顶,最后栈顶元素就是最后的计算结果。
【注意】此处先出栈的为“左操作数”。
后缀表达式的计算过程:
从左到右扫描,操作数入栈,遇到操作符<op>,则从栈中退出两个操作数Y和X,形成运算指令X<op>Y,最后将计算结果重新压入栈中,即栈顶元素就是最后的计算结果。
【注意】先出栈的是“右操作数”。
3.1.3 栈在递归中的应用
函数调用的特点:最后被调用的函数最先执行结束(LIFO)。
【注意】递归模型不能是循环定义的,必须满足两个条件:递归表达式和边界条件。
递归算法的效率不高,是因为递归调用有太多重复的计算。
递归算法转化为非递归算法,通常需要借助栈来实现这种转换。
3.1 队列的应用
层次遍历;
在计算机系统中 一方面解决主机和外部设备之间速度不匹配的问题;
另一方面解决有多用户引起的资源竞争问题。
4. 特殊矩阵的压缩存储
4.1 多维数组的压缩存储
以二维数组【行:[0, h1] 列:[0, h2]】为例,L = sizeof(ElemType),数组下标从0开始。
行优先:Loc() = Loc(
) + ( i * (h2 + 1) + j ) * L
列优先:Loc() = Loc(
) + ( j * (h1 + 1) + i ) * L
4.2 矩阵的压缩存储 !!!
4.2.1 对称矩阵
设n阶方阵A[1...n][1..n],分为上三角,主对角线,下三角,其中上三角 = 下三角
,现存放于数组B[n(n+1)/2]中,数组下标从0开始,求
在数组中的下标k:
存放下三角(含主对角):
行优先:
列优先:
4.2.2 三角矩阵
下三角矩阵:上三角元素为同一个,存放于B[( 1 + n) * n / 2 + 1];
行优先:
上三角矩阵:下三角元素为同一个,存放于B[( 1 + n ) * n / 2 + 1];
行优先:
4.2.3 三对角矩阵
定义:以对角线为中心的3条对角线的区域,其他区域的元素都为0。
设三对角矩阵A[1...n][1...n],按行优先方式存储在数组B[3n - 2]中,数组下标从0开始:
对应下标 k = 2i + j - 3;
由 k 得到 i 和 j : j = k + 3 - 2i
4.2.4 稀疏矩阵
稀疏矩阵的压缩存储方法:三元组、十字链表法
稀疏矩阵在压缩存储后就失去了随取存取的特性!
之后会继续补充哒!