数据结构不挂科 3 栈和队列


数据结构不挂科 3 栈和队列


正文

模块1 栈

⼩节1 栈的类型定义

栈的定义

也称为堆栈,是一种先进后出,删除和插⼊都在栈顶操作的线性表。

栈的插入

push():在栈顶插⼊元素。

栈的删除

pop():在栈顶移除一个元素,并将栈数-1。

  • 例题3-1

若元素a,b,c,d,e,f依次进栈,允许进栈、退栈操作交替进行,但不允许连续三次进行退栈工作,则不可能得到的出栈序列是().

A.dcebfa B.cbdaef C.dbcaef D.afedcb

栈的基本操作

InitStack(&S) 
DestroyStack(&S) 
StackLength(S) 
StackEmpty(S) 
GetTop(S,&e) 
ClearStack(&S) 
Push(&S,e) 
Pop(&S,&e) 
StackTravers(S,visit())

InitStack(&S) 操作结果:构造一个空栈S。

void Init(SqStack s)
{
    s.base = (int *)malloc(size*sizeof(int));
    s.top = s.base;
    s.stacksize = size;
}

Push(&S,e) 初始条件:栈S已存在 操作结果:插⼊元素e为新的栈顶元素

void push(SqStack s,ini e)
{
    if (s.top - s.base > s.stacksize) {
        s.base = (int*)realloc(s.base, (s.stacksize + incresize) * sizeof(int));
        s.top = s.base + s,stacksize;
        s.stacksize += incresize;
    }
    
    *s.top ++= e;
}

Pop(&S,&e) 初始条件:栈S已存在且非空。 操作结果:删除S 的栈顶元素,并用e返回其值

void pop(SqStack s,ini e)
{
    if (s.top != s.base) {
        e = *(--s.top);
    }
    
    count<<e<<endl;
}

打印函数:

void Print(SqStack *s)
{
    int *temp;
    temp = s->top;
    while(temp != s->base)
    {
        temp--;
        printf("%d ", *temp);
    }
}

⼩节2 栈的应用举例

数制转换

算法原理:

N = (N div d)×d + N mod d

⼗进制转⼋进制: (1348)10 = (2504)8

代码实现:

void conversion() {
    InitStack(S);
    scanf("%d", N);
    while (N) {
        Push(S, N % 8);
        N = N / 8;
    }
    
    while (!StackEmpty(S)) {
        Pop(S, e);
        printf("%d", e);
    }
}
2    <---- 栈顶
5
0
4

括号匹配的检验

[ ( [ ] [ ] ) ]
1 2 3 4 5 6 7 8

检验括号是否匹配的方法可用 “期待的急迫程度”这个概念来描述。

出现不匹配的三种可能情况:

1.到来的右括弧不是所“期待”的;

2.到来的是“不速之客”;

3.直到结束,也没有到来所“期待”的括弧;

1.凡出现左括弧,则进栈;
2.凡出现右括弧,⾸先检查栈是否为空
若栈空,则表明该“右括弧”多余
否则和栈顶元素比较
若相匹配,则“左括弧出栈” 
否则表明不匹配;
3.表达式检验结束时,
若栈空,则表明表达式中匹配正确,
否则表明“左括弧”有余;

表达式求值

表达式的三种标识方法:

Exp = S1 + OP + S2

前缀表示法:OP+S1+S2

中缀表示法:S1+OP+S2

后缀表示法:S1+S2+OP

表达式求值举例

Exp = a × b + (c − d / e) × f
前缀式: + × a b × − c / d e f
中缀式: a × b + c − d / e × f
后缀式: a b × c d e / − f × +

后缀式求值

先找运算符,再找操作数

ab × cde/ − f × +
a×b 
     d/e
     c − d/e
     (c − d/e) × f
= (a × b) + (c − d/e) × f 
  • 例题3-2 表达式a*(b+c)-d的后缀表达式是

A.abcd*+- B. abc+*d- C. abc*+d- D. -+*abcd

栈的递归调用

递归函数:一个直接调用⾃⼰或通过一系列的调用语句间接地调用⾃⼰的函数。

#include <stdio.h>
int f(int m)
{
    if (m == 1) {
        return 1;
    } else {
        printf("m = %d\n", m);
        return f(m - 1);
    }
}

int main()
{
    int n;
    int f(int m);
    printf("请输入一个大于1的数:");
    scanf("%d", &n);
    printf("%d\n", f(n));
    return 0;
}

⼩节3 栈类型的实现

顺序栈

类似于线性表的顺序映象实现,指向表尾的指针可以作为栈顶指针。

栈的顺序存储表示:

#define STACK_INIT_SIZE 100
typedef struct {
    SElemType *base;
    SElemType *top;
    int stacksize;
} SqStack;

构造一个最大空间为 maxsize 的空顺序栈 S:

Status InitStack (SqStack &S, int maxsize)
{
    s.base = new ElemType[maxsize];
    if (!S.base) {
        exit(OVERFLOW);  // 存储分配失败
    }
    S.top = S.base;
    S.stacksize = maxsize;
    return OK;
}

顺序栈的基本操作:

Status Push (SqStack &S, ElenType e)
{
    // 若栈不满,则将e插入栈顶
    if (S.top - S.base >= S.stacksize) {
        // 栈满
        return OVERFLOW;
    }
    *S.top++ = e;
    return ok;
}
Status Pop (SqStack &S, ElenType &e)
{
    // 若栈不空,则删除S的栈顶元素,
    // 用e返回其值,并返回ok,
    // 否则返回失败
    if (S.top == S.base) {
        return ERROR;
    }
    e = *--S.top;
    return ok;
}

链栈

线性表有顺序存储结构和链式存储结构,栈属于线性表的一种,也具有顺序存储结构和链式存储结构。

模块2 队列

⼩节1 队列的类型定义

队列

和栈相反,是一种先进先出的线性表,它只允许在表的一端进行插⼊,而在另一端删除元素。

队尾:允许插⼊的一端

队头:允许删除的一端

队列的类型定义

ADT Queue {
    数据对象:
        D = {ai | aiElemSet, i=1,2...,n, n≥0}
    数据关系:
        R1 = {<ai-1, ai> | ai-1, ai ED, i=2,...,n}
        约定其中a1端为队列头, an端为队列尾
    基本操作:
        ...
} ADT Queue

队列的八大基本操作

1.InitQueue(&Q)

操作结果:构造一个空队列Q。

2.DestroyQueue(&Q)

初始条件:队列Q已存在。

操作结果:队列Q被销毁,不再存在。

3.QueueEmpty(Q)

初始条件:队列Q已存在。

操作结果:若Q为空队列,则返回TRUE,否则返回FALSE。

4.QueueLength(Q)

初始条件:队列Q已存在。

操作结果:返回Q的元素个数,即队列的长长度。

5.GetHead(Q, &e)

初始条件:Q为非空队列。

操作结果:用e返回Q的队头元素。

6.ClearQueue(&Q)

初始条件:队列Q已存在。

操作结果:将Q清为空队列。

7.EnQueue(&Q, e)

初始条件:队列Q已存在。

操作结果:插⼊入元素e为Q的新的队尾元素。

8.DeQueue(&Q, &e)

初始条件:Q为非空队列。

操作结果:删除Q的队头元素,并用用e返回其值。

⼩节2 队列类型的实现

链队列

用链表表示的队列称为链队列。一个链队列显然需要两个分别指示对头和对尾的指针才能唯一确定。

链队列的类型定义

typedef struct QNode {  // 结点类型
    QElemType       data;
    struct QNode    *next;
} QNode, *QueuePtr;
Status EnQueue(LinkQueue &Q, QElemType e)
{
    // 插入元素e为Q的新的队尾元素
    p = new QNode;
    if (!p) {
        // 存储分配失败
        exit(OVERFLOW);
    }
    p->data = e;
    p->next = NULL;
    Q.rear->next = p;
    Q.rear = p;
    
    return OK;
}
Status DeQueue(LinkQueue &Q, QElemType &e) 
{
    if (Q.front == Q.raer) {
        return ERROR;
    }
    p = Q.front->next;
    e = p->data;
    Q.front->next = p->next;
    delete(p);
    
    return OK;
}

循环队列

在顺序队列中,当队尾指针已经到数组的上界,不能再有入队操作,但其实数组中还有空位置, 这就叫做“假溢出”,解决假溢出的途径—-采用用循环队列。

队空条件:front ==rear

队满条件:front==(rear+1)%N

队列长度:L=(N+rear-front)%N

循环队列的类型定义

#define MAXQSIZE 100  // 最大队列长度

typedef struct {
    QElemType *base;  // 动态分配存储空间
    int front;  // 头指针,若队列不为空,指向队列头元素
    int rear;  // 尾指针,若队列不为空,指向队列尾元素的下一个位置
    int queuesize;
} SqQueue;
Status InitQueue(SqQueue &Q, int maxsize)
{
    Q.base = new ElemType[maxsize];
    if (!Q.base) {
        exit(OVERFLOW);
    }
    Q.queuesize = maxsize;
    Q.front = Q.rear = 0;
    
    return OK;
}
Status EnQueue(SqQueue &Q, ElemType e)
{
    // 插入元素e为Q的新的队尾元素
    if ((Q.rear + 1) % Q.queuesize == Q.front) {
        // 队列满
        return ERROR;
    }
    Q.base[Q.rear] = e;
    Q.rear = (Q.rear + 1) % Q.queuesize;
    
    return OK;
}
Status DeQueue(SqQueue &Q, ElemType &e)
{
    if (Q.front == Q.rear) {
        return ERROR;
    }
    e = Q.base[Q.front];
    Q.front = (Q.front + 1) % Q.queuesize;
    
    return OK;
}

循环队列与循环链表的区别

队列(包括循环队列)是一个逻辑概念,而链表是一个存储概念。一个队列是否是循环队列, 不取决于它将采用何种存储结构,根据实际的需要,循环队列可以采用顺序存储结构, 也可以采用链式存储结构,包括采用循环链表作为存储结构。

  • 例题3-3

栈和队列的相同之处是

A.元素的进出满⾜先进后出 B.元素的进出满⾜先进先出 C.只允许在端点进行插入和删除操作 D.无无共同点

  • 例题3-4 Q是一个队列,S是一个空栈,实现将队列中的元素逆置的算法。

  • 例题3-5 简述栈和线性表的差别

例题解析

  • 解析3-1

  • 解析3-2 B

  • 解析3-3 C

本题考查堆栈和队列的特点。堆栈和队列是常用的两种数据结构,其中队列只允许在一端进行插入, 另一端进行删除操作,具有先进先出的特征。而堆栈只允许在同一端进行插⼊和删除运算,具有后进先出的特征。 因此,堆栈和队列的相同之处是允许在端点进行插⼊和删除操作。

  • 解析3-4

算法思想:将队列中的元素逐个地出队列,入栈:全部⼊栈后再逐个出栈,入队列。

void inverse (Stack &S, Queue &Q)
{
    ElemType x;
    while (!QueueEmpty(Q)) {
        DeQueue(Q, x);  // 队列中全部元素依次出队
        Push(S, x);  // 元素依次入栈
    }
    while(!StackEmpty(S)) {
        pop(S, x);  // 栈中全部元素依次出栈
        EnQueue(Q, x);  // 再入队        
    }
}
  • 解析3-5

线性表是具有相同特性的数据元素的一个有限序列。栈是限定仅在表尾进行插入和删除操作的线性表。






参考资料


返回