数据结构王道笔记②栈队列数组

16 阅读9分钟

一、栈

(一)特征

后进先出LIFO
栈的大小不可变
n个不同元素入栈,出栈元素不同排列个数为(1/(n-1))C(n,2n)
基本操作:

InitStack(&S):初始化一个空栈S。
StackEmpty(S):判断一个栈是否为空,若栈s为空则返回true,否则返回false。
Push(&S,x):入栈,若栈S未满,则将x加入使之成为新栈顶。
Pop(&S,&X):出栈,若栈S非空,则弹出栈顶元素,并用x返回。
GetTop(S,&x):读栈顶元素,但不出栈,若栈s非空,则用x返回栈顶元素。
DestroyStack(&S):销毁栈,并释放栈S占用的存储空间

(二)顺序栈

采用顺序存储的栈,一组地址连续的存储单元存放元素
以下操作时间复杂度均为O(1)

//1.定义
#define MaxSize 50 //定义栈中元素的最大个数
typedef struct{
    ElemType data[MaxSize]; //存放栈中元素
    int top; //栈顶指针,指向当前栈顶元素
}SqStack;
//声明一个栈:SqStack S;
//栈顶元素:S.data[S.top]

//2.初始化
void InitStack(SqStack &S){
    s.top=-1;
}

//3.判空
bool StackEmpty(SqStack S){
    if (S.top == -1//栈空
        return trueelse //不空
    return false; 
}

//4.进栈
bool Push(SqStack &S, ElemType x) {
    if (S.top == MaxSize - 1) //栈满,报错
        return false;
    S.data[++S.top] = x; //指针先加1,再入栈
    return true; 
}

//5.出栈
bool Pop(SqStack &S, ElemType &x) {
    if (S.top == -1) //栈空,报错 
        return false;
    x = S.data[S.top--]; //先出栈,指针再减1
    return true; 
}

//6.获取栈顶元素
bool GetTop(SqStack S, ElemType &x) {
    if (S.top == -1) //栈空,报错 
        return false;
    x = S.data[S.top]; //x记录栈顶元素
    return true; 
}

//若top初始为0,即指向栈顶元素的下一位置,则入栈为S.data[S.top++] = x;出栈为x = S.data[--S.top];栈满条件为S.top == MaxSize;栈空条件为S.top == 0

共享栈:两个顺序栈共享一个一维数组空间,两个栈底分别设置在共享空间两端,两个栈顶向共享空间的中间延伸
存取数据时间复杂度O(1)

#define MaxSize 50 //定义栈中元素的最大个数
typedef struct{
    ElemType data[MaxSize]; //存放栈中元素
    int top0; //0号栈栈顶指针
    int top1; //1号栈栈顶指针
}ShStack;

//初始化
void InitStack(ShStack &S){
    s.top0=-1;//0判空
    s.top1=MAxSize;//1判空
}

//栈满条件:top0+1=top1
//0号入栈:S.data[++S.top0] = x
//1号入栈:S.data[--S.top1] = x
//0号出栈:S.data[S.top0--] = x
//0号入栈:S.data[S.top1++] = x//////////??

(三)链栈

链式存储,便于多个栈共享存储空间,效率高,不存在栈满上溢情况。用单链表,规定只能在表头进行操作。
1.带头结点
2.没有头结点,Lhead指向栈顶元素(推荐)

ypedef struct Linknode{
    ElemType data; //数据域
    struct Linknode *next; //指针域
}*LiStack;

//初始化 进栈 出栈 获取栈顶元素 判空 判满。。??写一遍

(四)栈的应用

1.括号匹配

91a64ba376549b5a95ff6b0199de437.jpg

2.表达式(这里的机算代码没写*****//////////)

机算: 6ff4335e5f8099381538a1c9f3c0d31.jpg 中缀表达式必须有括号 a+b
后缀表达式(逆波兰式)无括号 ab+
前缀表达式(波兰式)+ab

(1) 中缀表达式转后缀

左优先原则

栈的深度=栈中元素个数

(2)后缀转中缀
(3)计算后缀表达式

①从左往右扫描下一个元素,直到处理完所有元素
②若扫描到操作数则压入栈,并回到①,否则执行③
③若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶
先出栈的是右操作数

(4)中缀转前缀

①确定中缀表达式中各个运算符的运算顺序
②选择下一个运算符,按照「运算符 左操作数 右操作数」的方式组合成一个新的操作数
③如果还有运算符没被处理,就继续②
右优先原则 image.png

3.递归

1741871361022.png

(5)计算前缀表达式

①从右往左扫描下一个元素,直到处理完所有元素
②若扫描到操作数则压入栈,并回到①;否则执行③
③若扫描到运算符,则弹出两个栈顶元素,执行相应运算,运算结果压回栈顶,回到①
先出栈的是左操作数

int Fib(n){//斐波那契数列实现
    if(n==0)
        return 0;//边界条件
    else if(n==1)
        return 1;//边界条件
    else
        return Fib(n-1)+Fib(n-2);//递归表达式 
}

二、队列

(一)特征

在队头删除,在队尾插入。操作受限的线性表,先进先出FIFO
基本操作:

InitQueue(&Q):初始化队列,构造一个空队列Q。
QueueEmpty(Q):判队列空,若队列Q为空返回true,否则返回false。
EnQueue(&Q,x):入队,若队列Q未满,将x加入,使之成为新的队尾。
DeQueue(&Q,&x):出队,若队列Q非空,删除队首元素,并用x返回。
GetHead(Q,&x):读队首元素,若队列Q非空,则将队首元素赋值给x。

(二)顺序队列

顺序存储,用数组
循环队列

//定义
#define MaxSize 50 //定义队列中元素的最大个数 
typedef struct {
    ElemType data[MaxSize]; //存放队列元素
    int front, rear; //队头指针指向队首元素,队尾指针指向队尾元素的下一个位置 
} SqQueue;

//初始化*循环队列*
void InitQueue(SqQueue &Q) {
    Q.rear = Q.front = 0; //初始化队首、队尾指针 
}

//声明
void tstQueue() {
    SqQueue Q;
    InitQueue(Q);
}

//判空
bool isEmpty(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; //队尾指针加1取模
    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; //队头指针加1取模
    return true; 
}

//获取队头元素
bool DeQueue(SqQueue Q, ElemType &x) {
    if (Q.rear == Q.front) 
        return false; //队空报错 
    x = Q.data[Q.front];
    return true; 
}

//求队列长度
(Q.rear+MaxSize-Q.front)%MaxSize

//以上判断队空满的方式牺牲了1个存储空间。
//法2:设结构体size=0数据成员表示元素个数,删除成功size-1,插入成功size+1.则队空为Q.size==0;队满为Q.size==MaxSize,两种情况都有Q.front==Q.rear
//法3:设tag成员,删除成功置tag=0,若导致Q.front==Q.rear则为队空;插入成功置tag=1,若导致Q.front==Q.rear则为队满

若rear指向队尾元素

//初始化
 Q.rear = MaxSize-1;
 Q.front = 0; 
 
//入队
    Q.rear = (Q.rear + 1) % MaxSize;
    Q.data[Q.rear] = x;
    
//判空:(Q.rear+1)%MaxSize==Q.front
//判满:牺牲头指针前面的元素位置,不可以存放,(Q.rear+2)%MaxSize==Q.front
//用size和tag也行

(三)链队列

链式存储,单链表

1.带头结点

队首指针指向队头结点,队尾指针指向队尾结点,即单链表最后一个结点

//定义
typedef struct{
    ElemType data;
    struct LinkNode *next
}LinkNode;

typedef struct {
    LinkNode *front, *rear; //队列的对头和队尾指针 
} LinkQueue;

//初始化
void InitQueue(LinkQueue &Q) {
    Q.front = Q.rear = (LinkNode *)malloc(sizeof( LinkNode));//建立头结点,front和rear都指向头结点
    Q.front->next = NULL; //初始为空 
}

void testLinkQueue() {
    LinkQueue Q;
    InitQueue(Q);
}

//判空
bool IsEmpty(LinkQueue Q) {
    if(Q.front==Q.rear)
        return true;
    else
        return false
 }//或者如果Q.front->next==NULL,则队空

//入队
void EnQueue(LinkQueue &Q, ElemType x) {
    LinkNode *s = (LinkNode *)malloc(sizeof(LinkNode));
    s->data = x;
    s->next = 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;
    x = p->data;
    Q.front->next = p->next
    if (Q.rear == p)
    Q.rear = Q.front; //若原队列中只有一个结点,删除后 变空
    free(p);
    return true
}

//队满:一般不会满
2.不带头结点
//初始化
void InitQueue(LinkQueue &Q) {
    Q.front = NULL;
    Q.rear= NULL;  
}

//判空
bool IsEmpty(LinkQueue Q) {
    if(Q.front==NULL)
        return true;
    else
        return false
 }//或者如果Q.rear==NULL,则队空
 
 //入队,第一个元素入队要有特殊处理
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;//新结点查到rear之后
        Q.rear = s; //修改表尾指针
    }
}

//出队
bool DeQueue(LinkQueue &Q, ElemType &x) {
    if (Q.front == NULL) 
        return false;//空队 
    LinkNode *p = Q.front;//p指向此次出队的结点
    x = p->data;
    Q.front = p->next
    if (Q.rear == p)//若是最后一个结点出队
        Q.front == NULL; 
        Q.rear == NULL;
    free(p);
    return true
}

//队满:一般不会满

(三)双端队列

两端插入,两端删除,左前右后
输出受限:一端插入和删除,另一端插入
输入受限:一端插入和删除,另一端删除

(四)队列的应用

1.二叉树层次遍历
2.图的广度优先遍历
3.计算机系统
  1. 解决主机与外部设备之间速度不匹配的问题(缓冲区)
  2. 解决由多用户引起的资源竞争问题

三、数组和特殊矩阵

(一)一维数组

以一维数组A[0.…n-1]为例,其存储结构关系式为LOC(ai)=LOC(a0)+i×L(0≤i<n),其中,L是每个数组元素所占的存储单元

(二)二维数组

1.行优先

先行后列,先存储行号较小的元素,行号相等先存储列号较小的元素。设二维数组的行下标与列下标的范围分别为[0,h₁]与[0,h₂],则存储结构关系式为LOC(ai,j)=LOC(a0,0)+[i×(h2+1)+j]×L

2.列优先

LOC(ai,j)=LOC(a0,0)+[jx(h1+1)+i]xL

(三)矩阵的存储

1.普通矩阵
  1. 用二维数组,矩阵元素行列从a1,1开始,数组一般从0开始
  2. 压缩存储:为值相同的矩阵元素只分配一个空间,对零元不分配存储空间
2.对称矩阵

aij=aji
只存上三角,或者只存下三角(含主对角线)

(1)行优先,存主对角线+下三角区

n阶对称矩阵存放在一维数组B[n(n+1)/2]中,ai,j存放到B[k]中(数组下标从0开始) 1741959856442.png 若数组下标从1开始,则上图不用-1

(2)列优先,存主对角线+下三角区

n阶对称矩阵存放在一维数组B[n(n+1)/2]中,ai,j存放到B[k]中(数组下标从0开始)

(3)行优先,存主对角线+上三角区
(2)列优先,存主对角线+上三角区
3.三角矩阵
(1)上三角矩阵,行优先,数组下标从0开始

i>j时,aij = 常量c 1741960813213.png

(2)下三角矩阵,行优先,数组下标从0开始

i<j时,aij = 常量c 1741960611807.png

3.三对角矩阵

任意一个元素 aij,当|i-j|>1时,若有aij=0(1<i,j<n),则称为三对角矩阵.在三对角矩阵中,所有非零元素都集中在以主对角线为中心的3条对角线的区域,其他区域的元素都为零。
压缩存储:将3条对角线上的元素按行优先方式存放在一维数组B中,a11放在B[0]中,共存放3n-2个元素,则为B[0]~B[3n-3] 1741961073392.png

4.稀疏矩阵(零元多,分布无规则)

压缩存储:保存三元组表、行数、列数、非零元素个数
(1)顺序存储:用数组,三元组线性表(非零元素的行i、列j、值aij),稀疏矩阵压缩存储后失去了随机存取特性。
(2)十字链表存储(见6.2节)