第3章 栈和队列

106 阅读7分钟

第3章 栈和队列

3.1 栈和队列的特点

队列
定义限定只能在表的一端进行插入和删除运算的线性表(栈顶操作)头删尾插
逻辑结构与线性表相同,一对一关系~
存储结构顺序栈/链栈顺序队/栈队
运算规则采用后进先出(LIFO)原则采用先进先出(FIFO)原则
实现方式主要是掌握入栈和出栈函数的书写主要是掌握入队和出队操作

3.2 栈的表示和操作的实现

ADT Stack{
    数据关系:D
    数据关系:S
    基本操作:
    InitStack(&S)//初始化操作,建立一个空栈
    DestroyStack(&S)//销毁栈操作
    StackEmpty(S);
    StackLength;
    ClearStack(&S);
    GetTop(S,&e)//取栈顶元素  
    Push(&S,e);
    Pop(&S,&e);  
}ADT Stack;

3.3 顺序栈的表示和实现

顺序栈的初始化

① 为顺序栈动态分配一个最大容量为MAXSIZE的数组空间,使base指向这段空间的基地址,即栈底。

② 栈顶指针top初始为base,表示栈为空。

③ stacksize置为栈的最大容量MAXSIZE

//顺序栈的初始化
Status InitStack(SqStack& S) {
    //分配栈空间,方式一
    S.base = (ElemType*)malloc(MaxSize * sizeof(ElemType));
    //分配栈空间,方式二
    //S.base = new ElemType(MaxSize);
    if (S.base) exit(OVERFLOW);//空间分配失败
    S.top = S.base;
    S.stacksize = MaxSize;
    return OK;
}
//顺序栈的存储结构
# define MaxSize 100//顺序栈存储空间的初始容量
typedef struct {
    ElemType* base, * top;//栈底指针(指向第一个元素,下标一般为0)和栈尾指针
    int stacksize;//栈可用最大容量
}SqStack;

顺序栈的基本操作

//顺序栈是否为空栈
Status StackEmpty(SqStack S) {
    //若栈为空,则栈顶等于栈底
    if (S.top == S.base) return 1;
    return 0;
}
​
//求顺序栈的长度
int StackLength(SqStack S) {
    return S.top - S.base;
}
​
​
//取栈顶元素
ElemType GetTop(SqStack S) {
    //栈非空
    if (S.top != S.base) return *(S.top - 1);
}
​
//清空顺序栈
Status ClearStack(SqStack &S) {
    if (S.base) S.top = S.base;
    printf("顺序栈已清空!\n");
    return OK;
}
​
//销毁顺序栈
Status ClearStack(SqStack& S) {
    if (S.base)
    {
        free(S.base);
        S.stacksize = 0;
        S.top = S.base = NULL;
    }
    printf("顺序栈已销毁!\n");
    return OK;
}

顺序栈的入栈

① 判断是否栈满,若栈满则出错(上溢)

② 元素e压入栈底

③ 栈顶指针+1

//入栈
Status Push(SqStack& S, ElemType  e) {
    if (S.top - S.base == S.stacksize) return ERROR;//栈满
    *S.top = e;//先将元素压栈
    S.top++;//栈顶指针再++
    //以上两步可以合并为:*S.top++=e;
    return OK;
}

顺序栈的出栈

① 判断是否为空栈,若为空栈则出错(下溢)

② 栈顶指针-1

③ 获取栈顶元素e

//出栈
Status Pop(SqStack& S, ElemType& e) {
    if (S.top == S.base) return ERROR;//栈空
    --S.top;//指针先减
    e = *S.top;//再取值
    //以上两步可以合并为:e=*--S.top;
    return OK;
}

进制转换

//数制转化(十进制转任意进制)
void conversion(SqStack S,int num, int target) {
    ElemType e;
    //① 将N于目标进制取余数压栈再商目标进制
    while (num != 0) {
        Push(S, num % target);
        num /= target;
    }
    //② 当栈非空,取出数据
    while (!StackEmpty(S))
    {
        Pop(S, e);
        printf("%d\t", e);
    }
}   

3.4 链栈的表示和实现

链栈的初始化

//链栈的初始化
Status InitStack(LinkStack& S) {
    //构造一个空栈,栈顶指针置为空
    S = NULL;
    return OK;
}
//链栈的存储结构
typedef struct StackNode{
    ElemType data;
    struct StackNode* next;
}StackNode,*LinkStack;

链栈的基本操作

//链栈判空
Status StackEmpty(LinkStack S) {
    if (S) return 0;//非空
    return 1;
}
​
//取栈顶元素
ElemType GetTop(LinkStack S) {
    if (S == NULL) return ERROR;
    return S->data;
}
​
//链栈的遍历
void StackPrint(LinkStack S) {
    if (S==NULL) return;//递归终止
    else {
        printf("%d\t", S->data);
        StackPrint(S->next);//递归调用
    }
}

链栈的入栈

① 为入栈元素 e 分配空间, 用指针 p 指向。

② 将新结点数据域置为e。

③ 将新结点插入栈顶。

④ 修改栈顶指针为 p

//链栈的入栈
Status PushStack(LinkStack& S, ElemType e) {
    StackNode* p;
    p = new StackNode;//生成新结点p
    if (!p) exit(OVERFLOW);//空间分配失败
    p->data = e;//将新结点数据域置为e
    p->next = S;//将新结点插入到栈顶
    S = p;//修改栈顶指针位置
    return OK;
}

链栈的出栈

① 判断栈是否为空 , 若空则返回ERROR。

② 将栈顶元素赋给e。

③ 临时保存栈顶元素的空间, 以备释放。

④ 修改栈顶指针, 指向新的栈顶元素。

⑤ 释放原栈顶元素的空间。

//链栈的出栈
Status Pop(LinkStack& S, ElemType& e) {
    StackNode* p;
    if (S) return ERROR;
    e = S->data;//删除之前,先把要删除的元素返回
    p = S;//将要删除元素的地址备份一下,以备释放
    S = S->next;//指向下一个元素;
    delete (p); 
    return OK;
}

3.5 队列的表示和操作的实现

ADT Stack{
        数据关系:D
        数据关系:S
        基本操作:
        InitQueue(&Q);
        DestroyQueue(&Q);
        ClearQueue(&Q);
        QueueLength(Q);
        GetHead(Q,&e);
        EnQueue(&Q,e);
        DeQueue(&Q,&e)
}ADT Stack;

3.6 队列的顺序表示和实现

//队列的顺序表示
#define MAXQSIZE 100//最大数列的长度
#define QElemType int
typedef struct {
    QElemType* data;//初始化动态分配空间
    int front;//头指针,若队列不空,指向队列头元素
    int rear;//尾指针,若队列不空,指向队列尾元素的下一个位置
}SqQueue;

队列的顺序表示存在两种问题:(rear = MAXQSIZE,发生溢出)

  • ① 若,front =0,rear=MAXQSIZE时再入队,此时为真溢出。
  • ② 若,front≠0,rear=MAXQSIZE时再入队,此时为假溢出。

解决假溢出的方法:

  • ① 将队中元素依次向队头方向移动(缺点:浪费时间)
  • ② 使用循环列表(需少用一个空间解决队满时的判断)

data[0]接在data[MAXQSIZE-1]之后,若rear+1==M;则令rear=0;

  • ① 实现方法:利用取模运算;
  • ② 队空:front==rear;
  • ③ 队满:(rear+1)%MAXQSIZE==front
  • ④ 插入元素:Q.data[s.rear] = e; Q.rear = (rear+1)%MAXQSIZE;
  • ⑤ 删除元素:x = Q.data[s.front]; Q.front= (Q.front+1)%MAXQSIZE;

队列的初始化操作

初始化:

  • ① 为队列分配 一 个最大容量为 M 心 CSIZE 的数组空间, base 指向数组空间的首地址。
  • ② 头指针和尾指针置为零, 表示队列为空。
//循环队列初始化
Status InitQueue(SqQueue& Q) {
    Q.data = new ElemType[MAXSIZE];//分配数组空间
    if (Q.data == NULL) exit(OVERFLOW);//空间分配失败
    Q.front = Q.rear = 0;//头指针和尾指针置为0,队列为空
    return OK;
}
//求循环队列的长度
int QueueLength(SqQueue Q) {
    return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}
//取队头的元素
ElemType GetHead(SqQueue Q) {
    if (Q.front != Q.rear) return Q.data[Q.front];
    return 0;
}

队列的入队操作

① 判断队列是否满,若满则返回ERROR。

② 将新元素插入队尾。

③ 队尾指针加1。

//循环队列入队
Status EnQueue(SqQueue& Q, ElemType e) {
    if ((Q.rear + 1) % MAXSIZE == Q.front) return ERROR;//队满
    Q.data[Q.rear] = e;
    Q.rear = (Q.rear + 1) % MAXSIZE;//队尾指针+1
    return OK;
}

队列的出队操作

① 判断队列是否为空,若空则返回ERROR。

② 保存队头元素。

③ 队头指针加1。

//循环队列出队
Status DeQueue(SqQueue& Q, ElemType& e) {
    if (Q.front == Q.rear) return ERROR;//队空
    e = Q.data[Q.front];//保存队头元素
    Q.front = (Q.front + 1) % MAXSIZE;//队头指针+1
    return OK;
}

3.7 队列的链式表示与实现

链队的初始化操作

//链队的存储结构
typedef struct QNode {
	ElemType data;
	struct QNode* next;
}QNode,*QueuePtr;

typedef struct {
	QueuePtr front;//队头指针
	QueuePtr rear;//队尾指针
}LinkQueue;

初始化:

  • ① 生成新结点作为头结点, 队头和队尾指针指向此结点。
  • ② 头结点的指针域置空。
//链队的初始化
Status InitQueue(LinkQueue& Q) {
    //生成新结点作为头结点,队头和队尾指针指向此结点
    Q.front = Q.rear = new QNode;
    if (!Q.front) exit(OVERFLOW);//分配空间失败
    Q.front->next = NULL;
    return OK;
}
​
//链队的销毁操作
Status DestroyQueue(LinkQueue& Q) {
    QNode* p;
    while (Q.front) {
        p = Q.front->next;
        free(Q.front);
        Q.front = p;
        //为了节省指针变量,也可以将所有的指针变量p变为Q.rear
    }
    return OK;
}
​
//取队头元素
Status GetHead(LinkQueue Q, ElemType& e) {
    if (Q.front == Q.rear) return ERROR;
    e = Q.front->next->data;
    return OK;
}

链队的入队

① 为入队元素分配结点空间,用指针p指向。

② 将新结点数据域置为e。

③ 将新结点插入到队尾 。

④ 修改队尾指针为p。

//入队
Status EnQueue(LinkQueue& Q, ElemType e) {
    QNode* p = new QNode;//为入队元素分配结点空间,用指针p指向
    p->data = e;
    p->next = NULL;Q.rear->next = p;//将新结点插入到队尾
    Q.rear = p;//修改队尾指针
    return OK;
}

链队的出队

① 判断队列是否为空,若空则返回ERROR。

② 临时保存队头元素的空间,以备释放。

③ 修改队头指针,指向下 一 个结点。

④ 判断出队元素是否为最后 一 个元素,若是,则将队尾指针重新赋值, 指向头结点。

⑤ 释放原队头元素的空间。

//出队
Status DeQueue(LinkQueue& Q, ElemType& e) {
    if (Q.front == Q.rear) return ERROR;//空队列
    QNode* p;
    p = Q.front->next;
    e = p->data;
    Q.front->next = p->next;//修改头指针
    if (Q.rear == p) Q.rear = Q.front;//最后一个元素被删,队尾指针指向头结点
    delete p;
    return OK;
}