【算法与数据结构】栈和队列

1. 栈(逻辑结构)

1.1  定义和基本操作

1.1.1 定义 

        栈:只允许在栈顶Top进行插入和删除操作的线性表。

        操作特性:后进先出。

        数学性质:n个不同元素进栈,出栈元素不同排列的个数为   \frac{1}{n+1}C_{2n}^{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(a_{i,j}) = Loc(a_{0,0}) + ( i * (h2 + 1) + j ) * L

        列优先:Loc(a_{i,j}) = Loc(a_{0,0}) + ( j * (h1 + 1) + i ) * L

4.2 矩阵的压缩存储 !!!

4.2.1 对称矩阵 

设n阶方阵A[1...n][1..n],分为上三角,主对角线,下三角,其中上三角a_{i,j} = 下三角a_{j,i},现存放于数组B[n(n+1)/2]中,数组下标从0开始,求a_{i,j}在数组中的下标k: 

存放下三角(含主对角):

        行优先:k = \left\{\begin{matrix} \frac{i * (i - 1)}{2} + j - 1, i >= j\\ \frac{j * (j - 1)}{2} + i - 1, i < j \end{matrix}\right.

        列优先:k = \left\{\begin{matrix} \frac{(2n - j + 2) * ( j - 1)}{2} + i - j, i >= j\\ \frac{(2n - i + 2) * ( i - 1)}{2} + j - i, i < j \end{matrix}\right.

4.2.2 三角矩阵

下三角矩阵:上三角元素为同一个,存放于B[( 1 + n) * n / 2 + 1];

        行优先:k = \left\{\begin{matrix} \frac{i * (i - 1)}{2} + j - 1, i >= j\\ \frac{n * (n- 1)}{2} , i < j \end{matrix}\right.

上三角矩阵:下三角元素为同一个,存放于B[( 1 + n ) * n / 2 + 1];

        行优先: k = \left\{\begin{matrix} \frac{(2n - i + 2) * ( i - 1)}{2} + j - i, i <= j \\ \frac{n(n+1)}{2}, i > j \end{matrix}\right.

4.2.3 三对角矩阵

        定义:以对角线为中心的3条对角线的区域,其他区域的元素都为0。

设三对角矩阵A[1...n][1...n],按行优先方式存储在数组B[3n - 2]中,数组下标从0开始:

        a_{i,j}对应下标 k = 2i + j - 3;

        由 k 得到 i 和 j :i=\left \lfloor\frac{k+1}{3} + 1\right \rfloor        j = k + 3 - 2i 

4.2.4 稀疏矩阵

        稀疏矩阵的压缩存储方法:三元组十字链表法 

        稀疏矩阵在压缩存储后就失去了随取存取的特性! 

之后会继续补充哒! 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值