2 栈和队列

1 栈

1.1 栈的定义

  • 栈是一种重要的线性结构,是线性表的一种具体形式。

  • 栈(Stack)是一个后进先出(Last in first out,LIFO)的线性表,它要求只在表尾进行删除和插入操作。

  • 表尾称为栈的栈顶(top),表头称为栈底(bottom)。

  • 栈的插入操作(Push),叫做进栈,也称为压栈,入栈。栈的删除操作(Pop),叫做出栈,也称为弹栈。

  • 因为栈的本质是一个线性表,线性表有两种存储形式,那么栈也有分为栈的顺序存储结构和栈的链式存储结构

1.2 栈的顺序存储结构

  • 最开始栈中不含有任何数据,叫做空栈,此时栈顶就是栈底。然后数据从栈顶进入,栈顶栈底分离,整个栈的当前容量变大。数据出栈时从栈顶弹出,栈顶下移,整个栈的当前容量变小。

    这里写图片描述

  • 定义

typedef struct
{
    ElemType *base;
    ElemType *top;
    int stackSize;
}sqStack;

//定义了一个顺序存储的栈,它包含了三个元素:base,top,stackSize。
//其中base是指向栈底的指针变量,top是指向栈顶的指针变量,stackSize指示栈的当前可使用的最大容量。
  • 创建
#define STACK_INIT_SIZE 100
initStack(sqStack *s)
{
    s->base = (ElemType *)malloc( STACK_INIT_SIZE * sizeof(ElemType) );
    if( !s->base )
    exit(0);
    s->top = s->base;   // 最开始,栈顶就是栈底
    s->stackSize = STACK_INIT_SIZE;
}
  • 入栈:要在栈顶进行,每次向栈中压入一个数据,top指针就要+1,到栈满为止。
#define SATCKINCREMENT 10

Push(sqStack *s, ElemType e)
{
    // 如果栈满,追加空间
    if( s->top – s->base >= s->stackSize )
    {
        s->base = (ElemType *)realloc(s->base, (s->stackSize + STACKINCREMENT) * sizeof(ElemType));
        if( !s->base )
            exit(0);

        s->top = s->base + s->stackSize;              // 设置栈顶
        s->stackSize = s->stackSize + STACKINCREMENT; // 设置栈的最大容量
    }

    *(s->top) = e;
    s->top++;
}
  • 出栈:就是在栈顶取出数据,栈顶指针随之下移的操作。每当从栈内弹出一个数据,栈的当前容量就-1。
Pop(sqStack *s, ElemType *e)
{
if( s->top == s->base )  // 栈已空空是也
return;
*e = *--(s->top);
}
  • 清空栈:将栈中的元素全部作废,但栈本身物理空间并不发生改变(不是销毁)。单纯地清空没有覆盖。
ClearStack(sqStack *s){
s->top = s->base;
}
  • 销毁栈:销毁一个栈是要释放掉该栈所占据的物理内存空间。
DestroyStack(sqStack *s){
    int i, len;
    len = s->stackSize;
    for( i=0; i < len; i++ ){
    free( s->base );
    s->base++;
}
s->base = s->top = NULL;
s->stackSize = 0;
}
  • 计算栈中元素个数

    计算栈的当前容量也就是计算栈中元素的个数,因此只要返回s.top-s.base即可。

    PS:栈的最大容量是指该栈占据内存空间的大小,其值是s.stackSize,它与栈的当前容量不是一个概念。

int StackLen(sqStack s)
{
    return(s.top – s.base);  // 初学者需要重点讲解
}

1.3 栈的链式存储结构

  • 定义

    栈的链式存储结构,简称栈链。(通常我们用的都是栈的顺序存储结构存储)

2 队列

1 定义

  • 队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。

  • 队列是一种先进先出(First In First Out, FIFO)的线性表。

  • 实现一个队列同样需要顺序表或链表作为基础。

    这里写图片描述

  • 跟栈相反的是,栈一般我们用顺序表来实现,而队列我们常用链表来实现,简称为链队列。

  • 代码

typedef struct QNode {
ElemType data;      
struct QNode *next;
} QNode, *QueuePrt;

typedef struct {
QueuePrt front, rear; // 队头、尾指针
} LinkQueue;

2 队列的链式存储结构

  • 我们将队头指针指向链队列的头结点,而队尾指针指向终端结点。(注:头结点不是必要的,但为了方便操作,我们加上了。)

    这里写图片描述

  • 空队列:front和rear都指向头结点。

    这里写图片描述

  • 创建一个队列

    创建一个队列要完成两个任务:一是在内存中创建一个头结点,二是将队列的头指针和尾指针都指向这个生成的头结点,因为此时是空队列。

initQueue(LinkQueue *q)
{
q->front=q->rear=(QueuePtr)malloc(sizeof(QNode));
if( !q->front )
        exit(0);
q->front->next = NULL;
}
  • 入队列

    这里写图片描述

InsertQueue(LinkQueue *q, ElemType e)
{
    QueuePtr p;
    p = (QueuePtr)malloc(sizeof(QNode));
    if( p == NULL )
    exit(0);
    p->data = e;
    p->next = NULL;
    q->rear->next = p;
    q->rear = p;
}
  • 出队列

    出队列操作是将队列中的第一个元素移出,队头指针不发生改变,改变头结点的next指针即可。

    这里写图片描述

    如果原队列只有一个元素,那么我们就应该处理一下队尾指针。

    这里写图片描述

DeleteQueue(LinkQueue *q, ELemType *e)
{
    QueuePtr p;
    if( q->front == q->rear )
    return;
    p = q->front->next;
    *e = p->data;
    q->front->next = p->next;
    if( q->rear == p )
    q->rear = q->front;
    free(p);
}
  • 销毁一个队列

    由于链队列建立在内存的动态区,因此当一个队列不再有用时应当把它及时销毁掉,以免过多地占用内存空间。

DestroyQueue(LinkQueue *q)
{
    while( q->front ) {
        q->rear = q->front->next;
        free( q->front );
        q->front = q->rear;
    }
}
  • 例:编写一个链队列,任意输入一串字符,以#作为结束标志,然后将队列中的元素显示到屏幕上。
#include <stdio.h>
#include <stdlib.h>

typedef char ElemType;

typedef struct QNode
{
    ElemType data;
    struct QNode *next;
} QNode, *QueuePtr;

typedef struct
{
    QueuePtr front, rear;
} LinkQueue;

initQueue(LinkQueue *q)
{
    q->front = q->rear = (QueuePtr)malloc(sizeof(QNode));
    if( !q->front )
        exit(0);

    q->front->next = NULL;
}

InsertQueue(LinkQueue *q, ElemType e)
{
    QueuePtr p;

    p = (QueuePtr)malloc(sizeof(QNode));
    if( !q->front )
        exit(0);

    p->data = e;
    p->next = NULL;
    q->rear->next = p;
    q->rear = p;
}

DeleteQueue(LinkQueue *q, ElemType *e)
{
    QueuePtr p;

    if( q->front == q->rear )
        return;

    p = q->front->next;
    *e = p->data;
    q->front->next = p->next;

    if( q->rear == p )
    {
        q->rear = q->front;
    }

    free(p);
}

int main()
{
    ElemType e;
    LinkQueue q;

    initQueue(&q);
    printf("请输入一个字符串,以井号键结束输入:");
    scanf("%c", &e);

    while( e != '#' )
    {
        InsertQueue( &q, e );
        scanf("%c", &e);
    }

    printf("打印队列中的元素:");
    while( q.front != q.rear )
    {
        DeleteQueue( &q, &e );
        printf("%c", e);
    }

    return 0;
}

2 队列的顺序存储结构与循环队列

  • 我们更愿意用链式存储结构来存储队列

  • 由于队列的顺序存储会出现很多问题,采用循环队列更好

  • 循环队列它的容量固定,并且队头和队尾指针都可以随着元素入出队列而发生改变,逻辑上就好像是一个环形存储空间。(在实际的内存当中,不可能有真正的环形存储区,我们只是用顺序表模拟出来的逻辑上的循环。)

  • 也就是让front或rear指针不断加1,即时超出了地址范围,也会自动从头开始。我们可以采取取模运算处理:
    (rear+1) % QueueSize
    (front+1) % QueueSize

  • 定义一个循环队列

#define MAXSIZE 100
typedef struct
{
    ElemType *base; // 用于存放内存分配基地址
                // 这里你也可以用数组存放
    int front;
    int rear;
}
  • 初始化一个循环队列
initQueue(cycleQueue *q)
{
    q->base = (ElemType *) malloc (MAXSIZE * sizeof(ElemType));
    if( !q->base )
    exit(0);
    q->front = q->rear = 0;
}
  • 入队列操作
InsertQueue(cycleQueue *q, ElemType e)
{
    if( (q->rear+1)%MAXSIZE == q->front )
    return; // 队列已满
    q->base[q->rear] = e;
    q->rear = (q->rear+1) % MAXSIZE;
}
  • 出队列操作
DeleteQueue(cycleQueue *q, ElemType *e)
{
    if( q->front == q->rear )
    return ; // 队列为空
    *e = q->base[q->front];
    q->front = (q->front+1) % MAXSIZE;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值