C语言数据结构【手抄版】第三章 栈和队列

注意:文中彩色代码均在Visual Studio 2022编译器中编写,本文为C语言数据结构手抄版,文中有部分改动,非原创。

目录

注意:文中彩色代码均在Visual Studio 2022编译器中编写,本文为C语言数据结构手抄版,文中有部分改动,非原创。

第三章 栈和队列

3.1.栈

3.1.1.栈的定义及其运算

3.1.2.栈的存储表示和实现

1.栈的顺序存储结构

2.顺序栈基本运算的实现

3.顺序栈封装

4.栈的链式存储结构及基本操作

5.链栈封装

3.2.栈的应用举例

3.2.1.圆括号匹配的检验

3.2.2.字符串回文的判断

3.2.3.数制转换

3.2.4.栈与递归

3.2.5.中缀后缀表达式★★★

3.3 队列

3.3.1.队列的定义及其运算

3.3.2.顺序队列

1.循环队列

2.顺序队列封装

3.3.3.链队列

1.构建

2.链队列封装

3.4栈和队列的应用实例

3.4.1.中缀表达式到后缀表达式的转换(算法见3.2.5)

3.4.2.后缀表达式的计算(算法见3.2.5)

小  结


第三章 栈和队列

学习目标:

1.掌握栈和队列这两种数据结构的特点,懂得在什么问题中应该使用哪种结构

2.熟悉栈、队列与线性表的关系,顺序栈(顺序队列)与顺序表的关系,以及链栈(链队列)与链表的关系。

3.重点掌握在顺序栈和链栈上实现的栈的五种基本运算,特别是栈满和栈空的判断条件及它们的描述。

4.重点掌握在循环队列和链队列上实现队列的五种基本运算,特别注意队满和队空的描述方法。

5.熟悉栈和队列的“下溢”和“上溢”的概念,循环队列是如何消除“假上溢”的

堆和栈的区别主要有五大点,分别是:

1.申请方式的不同。栈由系统自动分配,而堆是人为申请开辟;

2.申请大小的不同。栈获得的空间较小,而堆获得的空间较大;

3.申请效率的不同。栈由系统自动分配,速度较快,而堆一般速度比较慢;

4.存储内容的不同。栈在函数调用时,函数调用语句的下一条可执行语句的地址第一个进栈,然后函数的各个参数进栈,其中静态变量是不入栈的。而堆一般是在头部用一个字节存放堆的大小,堆中的具体内容是人为安排;

5.底层不同。栈是连续的空间,而堆是不连续的空间。

3.1.

3.1.1.栈的定义及其运算

    (Stack)是限定在表的一端进行插入和删除运算的线性表通常将插入、删除的端称为栈项(top)另一端称为栈底(bottom)不含元素的空表称为空栈

    根据上述栈的定义,每次删除(退栈)的总是当前栈中最后插入(进栈)的元素而最先进栈的元素在栈底要到最后才能删除。假设栈S= (a1, a2,... ,an),若栈中元素按a1, a2,... ,an的次序进栈,其中a1为栈底元素, an为栈顶元素,而退栈的次序却是an,an-1, … a1。也就是说,栈的修改是按后进先出的原则进行的,如图3.1所示。因此,栈又称为后进先出(Last In First Out)的线性表简称为LIFO

    栈的基本运算除了在栈顶进行插入或删除运算外,还有的初始化、判栈空及取栈顶元素等运算。栈的运算主要有以下几种。

    (1)置空栈InitStack (&S):构造一个空栈S.

    (2)判栈空StackEmpty (S):若栈S为空栈,则返回TRUE,否则返回FALSE

    (3)判栈满StackFull (S):若栈S为满栈,则返回TRUE,否则返回FALSE

    (4)进栈(又称入栈或插入) Push (&S,x):将元素x插入S栈的栈顶。

    (5)退栈(又称出栈或删除) Pop (&S):若栈S为非空,则将S的栈顶元素删除,并返回栈顶元素。

    (6)取栈顶元素GetTop (S):若S栈为非空,则返回栈顶元素,但不改变栈的状态。

同线性表一样,利用以上六种栈的基本运算,就可以实现有关的应用要求。

3.1.2.栈的存储表示和实现

1.栈的顺序存储结构

    栈的顺序存储结构称为顺序栈。由于运算受限线性因此线性表的存储结构对栈也适用。类似于顺序表的定义顺序栈也是用数组实现的。因为栈底位置是固定不变的故可以将栈底位置设置在数组的最低端(即下标为0);栈顶位置是随着进栈和退栈操作而变化的故需用一个整型量top来指示当前栈顶位置通常称top栈顶指针因此,顺序栈的类型定义只需将顺序表类型定义中的长度属性length改为top,将顺序表改为顺序栈即可。顺序栈定义如下:

#define StackSize 100                 //栈空间的大小应根据实际需要来定义,这里假设为100

typedef char DataType;                //DataType的类型可根据实际情况而定,这里假设为char

typedef strut {

   DataType data[StackSize];           //数组data用来存放表结点

   int top;                          //表示栈顶指针

}SeqStack;

SeqStack S;

  • 上溢下溢

SSeqStack类型的顺序栈S.data[0]是栈底元素那么栈顶S.data[top]正向增长即进栈时需将S.top加1退栈时需将S.top减1。因此 S.top<0表示空栈 S.top=StackSize-1表示栈满当栈满时再做进栈运算必定产生空间溢出简称“上溢”;当栈空时再做退栈运算也将产生溢出简称“下溢

【例3.1】对于一个栈,给出输入序列为abc,试写出全部可能的输出序列。

分析:因为栈是受限在一端输入或输出,而且有“后进先出”的特点,所以本题有如下几种情况:

    (1) a进 a出 b进 b出c进 c出  产生输出序列abc

    (2) a进 a出 b进 c进 c出 b出  产生输出序列acb

    (3) a进 b进 b出 a出 c进 c出  产生输出序列bac

    (4) a进 b进 b出 c进 c出 a出  产生输出序列bca

    (5) a进 b进 c进 c出 b出 a出  产生输出序列cba

不可能产生的输出序列cab。

本题的答案是: abc, acb, bac, bca, cba。

    【例3.2】设一个栈的输入序列为: 1, 2, 3, 4, 5,则下列序列中不可能是栈的输出序列的是(  )

    A. 1, 2, 3, 4, 5    B. 5, 4, 3, 2, 1

    C. 2, 3, 4, 5, 1    D. 4, 1, 2, 3, 5

    分析:像这类题目,一是要知道栈的操作规则,即后进先出;再一个就是排除可能的,那么剩下的自然是不可能的。例如,该题就是按照入栈退栈的规则,一一排除可能的序列:序列A很显然不可能是,因为1进1出, 2进2出, …,故得到出栈序列1, 2,3, 4, 5;序列B也显然不可能是,先1, 2, 3, 4, 5入栈,出栈是5, 4, 3, 2, 1;序列C是1,2进栈, 2出栈, 3进3出, 4进4出, 5进5出,最后1出栈。因此本题的答案是D。

2.顺序栈基本运算的实现

    顺序栈基本运算的时间方法有以下六种。

1.置空栈

void InitStack(SeqStack * S)

{                         //置空顺序栈。由于C语言数组下标是从0开始,所以栈中元素亦从0开始

                          //存储,因此空栈时栈顶指针不能时0,而只能时-1

   S->top=-1;

}

2.判栈空

int StackEmpty(SeqStack * S)

{

   return S->top==-1;

}

3.判栈满

int StackFull(SeqStack * S)

{

   return S->top==StackSize-1;

}

4.进栈(入栈)

void Push(SeqStack * S, DataType x)

{   if(StackFull(S))

      printf(“stack overflow”);

   else {

  1. >top=S->top+1;                //栈顶指针加1

S->data[S->top]=x;               //将x入栈

}

}

5.退栈(出栈)

DataType Pop(SeqStack * S)

{   if(StackEmpty(S)){

  printf(“stack underflow”);

  exit(0);                                     //出错退出处理

}else

  return S->data[S->top--];                       //返回栈顶元素后栈顶指针减1

}

6.取栈顶元素(不改变栈顶指针)

DataType GetTop(SeqStack * S)

{   if(StackEmpty(S)){

  printf(“stack empty”);

  exit(0);                                  //出错退出处理

}else

  return S->data[S->top];                     //返回栈顶元素

}

    由于顺序栈必须预先分配存储空间,因此在应用中要考虑溢出问题。另外,在实际应用中还可能同时使用多个栈,为了防止溢出,需要为每个栈分配一个较大的空间,这样做往往会产生空间上的浪费,因为当某一个栈发生溢出的同时,其余的栈还可能有很多的未用空间。如果将多个栈分配在同一个顺序存储空间内,即让多个栈共享存储空间,则可以相互进行调节,既节约了空间,又可降低发生溢出的频率。

    当程序中同时使用两个栈时,可以将两个栈的栈底分别设在顺序存储空间两端,让两个栈顶各自向中间延伸。当一个栈中的元素较多而栈使用的空间超过共享空间的一半时,只要另一个栈中的元素不多,就可以让第一个栈占用第二个栈的部分存储空间。只有当整个存储空间被两个栈占满时(即两栈顶相遇),才会产生溢出

3.顺序栈封装

    对已经编写好的C程序做一些小步的重构调整,以便于后面很方便的复用顺序栈。

    借鉴Java的封装思想,将顺序栈的创建与运用过程封装起来不对外暴露,将对顺序栈的创建与操作功能对外暴露,为了防止函数名称相同导致无法使用的问题,我们将对外暴露的操作功能封装到一个结构体当中,使用起来有点类似Java的对象使用方式。

编码采用Visual Studio 2022编译器编写,头文件serialStack.h:

#pragma once

#ifndef SERIAL_STACK_H

#define SERIAL_STACK_H

struct SequenceStack

{

    struct SerialStack* serialStack;

    void (*push)(void* data, struct SequenceStack* sequenceStack);

    void (*pop)(struct SequenceStack* sequenceStack);

    void* (*top)(struct SequenceStack* sequenceStack);

    int (*size)(struct SequenceStack* sequenceStack);

    int (*isEmpty)(struct SequenceStack* sequenceStack);

    int (*isNotEmpty)(struct SequenceStack* sequenceStack);

    void** (*clear)(struct SequenceStack* sequenceStack);

    void** (*destroy)(struct SequenceStack* sequenceStack);

};

struct SequenceStack* initSequenceStack();

#endif

头文件逻辑实现serialStack.c:

#define _CRT_SECURE_NO_WARNINGS //规避C4996告警

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include "serialStack.h"

#define MAX 1024

struct SerialStack* initSerialStack();

void pushSerialStack(void* data, struct SequenceStack* sequenceStack);

void popSerialStack(struct SequenceStack* sequenceStack);

void* topSerialStack(struct SequenceStack* sequenceStack);

int sizeSerialStack(struct SequenceStack* sequenceStack);

int isEmptySerialStack(struct SequenceStack* sequenceStack);

int isNotEmptySerialStack(struct SequenceStack* sequenceStack);

void** clearSerialStack(struct SequenceStack* sequenceStack);

void** destroySerialStack(struct SequenceStack* sequenceStack);

struct SequenceStack* initSequenceStack()

{

      struct SequenceStack* sequenceStack = malloc(sizeof(struct SequenceStack));

      struct SerialStack* serialStack = initSerialStack();

      if (sequenceStack == NULL || serialStack == NULL)

      {

            printf("栈操作初始化失败!\n");

            return NULL;

      }

      sequenceStack->serialStack = serialStack;

      sequenceStack->push = pushSerialStack;

      sequenceStack->pop = popSerialStack;

      sequenceStack->top = topSerialStack;

      sequenceStack->size = sizeSerialStack;

      sequenceStack->isEmpty = isEmptySerialStack;

      sequenceStack->isNotEmpty = isNotEmptySerialStack;

      sequenceStack->clear = clearSerialStack;

      sequenceStack->destroy = destroySerialStack;

      return sequenceStack;

}

struct SerialStack

{

      void* data[MAX];

      int size;

};

struct SerialStack* initSerialStack()

{

      struct SerialStack* serialStack = malloc(sizeof(struct SerialStack));

      if (serialStack == NULL)

      {

            printf("栈初始化失败!\n");

            return NULL;

      }

      memset(serialStack->data, 0, sizeof(void*) * MAX);

      serialStack->size = 0;

      return serialStack;

}

void pushSerialStack(void* data, struct SequenceStack* sequenceStack)

{

      struct SerialStack* serialStack = sequenceStack->serialStack;

      if (serialStack == NULL)

      {

            printf("栈空间不存在!\n");

            return;

      }

      if (data == NULL)

      {

            printf("入栈数据不存在!\n");

            return;

      }

      if (serialStack->size >= MAX)

      {

            printf("栈空间满了!\n");

            return;

      }

      serialStack->data[serialStack->size] = data;

      serialStack->size++;

}

void popSerialStack(struct SequenceStack* sequenceStack)

{

      struct SerialStack* serialStack = sequenceStack->serialStack;

      if (serialStack == NULL)

      {

            printf("栈空间不存在!\n");

            return;

      }

      if (serialStack->size <= 0)

      {

            printf("栈空间空了!\n");

            return;

      }

      serialStack->data[serialStack->size - 1] = NULL;

      serialStack->size--;

}

void* topSerialStack(struct SequenceStack* sequenceStack)

{

      struct SerialStack* serialStack = sequenceStack->serialStack;

      if (serialStack == NULL)

      {

            printf("栈空间不存在!\n");

            return;

      }

      if (serialStack->size <= 0)

      {

            printf("栈空间空了!\n");

            return NULL;

      }

      return serialStack->data[serialStack->size - 1];

}

int sizeSerialStack(struct SequenceStack* sequenceStack)

{

      struct SerialStack* serialStack = sequenceStack->serialStack;

      if (serialStack == NULL)

      {

            printf("栈空间不存在!\n");

            return;

      }

      return serialStack->size;

}

int isEmptySerialStack(struct SequenceStack* sequenceStack)

{

      struct SerialStack* serialStack = sequenceStack->serialStack;

      if (serialStack == NULL)

      {

            printf("栈空间不存在!\n");

            return -1;

      }

      if (serialStack->size <= 0)

      {

            return 1;

      }

      return 0; // false => not empty stack

}

int isNotEmptySerialStack(struct SequenceStack* sequenceStack)

{

      return !isEmptySerialStack(sequenceStack);

}

void** clearSerialStack(struct SequenceStack* sequenceStack)

{

      struct SerialStack* serialStack = sequenceStack->serialStack;

      if (serialStack == NULL)

      {

            printf("栈空间不存在!\n");

            return NULL;

      }

      void** result = malloc(sizeof(void*) * serialStack->size);

      int size = serialStack->size;

      for (int i = 0; i < size; i++)

      {

            result[i] = topSerialStack(sequenceStack);

            popSerialStack(sequenceStack);

      }

      return result;

}

void** destroySerialStack(struct SequenceStack* sequenceStack)

{

      struct SerialStack* serialStack = sequenceStack->serialStack;

      if (serialStack == NULL)

      {

            printf("栈空间不存在!\n");

            return;

      }

      void** result = clearSerialStack(sequenceStack);

      free(serialStack);

      serialStack = NULL;

      free(sequenceStack);

      sequenceStack = NULL;

      return result;

}

测试:

#define _CRT_SECURE_NO_WARNINGS //规避C4996告警

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include "serialStack.h"

struct Person

{

    char name[64];

    int age;

};

int  main()

{

    struct SequenceStack* stack = initSequenceStack();

    struct Person p1 = { "唐三藏", 24 }; stack->push(&p1, stack);

    struct Person p2 = { "白龙马", 502 }; stack->push(&p2, stack);

    struct Person p3 = { "孙悟空", 650 }; stack->push(&p3, stack);

    struct Person p4 = { "猪八戒", 800 }; stack->push(&p4, stack);

    struct Person p5 = { "沙和尚", 320 }; stack->push(&p5, stack);

    struct Person p6 = { "小龙女", 950 }; stack->push(&p6, stack);

    printf("栈的元素个数为:%d\n", stack->size(stack));

    while (stack->isNotEmpty(stack))

    {

        struct Person* person = stack->top(stack);

        printf("姓名:%s--年龄:%d\n", person->name, person->age);

        stack->pop(stack);

    }

    printf("栈的元素个数为:%d\n", stack->size(stack));

    void** clear = stack->clear(stack);

    printf("清空栈->栈的元素个数为:%d\n", stack->size(stack));

    printf("销毁栈!\n");

    void** destroy = stack->destroy(stack);

    return 0;

}

运行结果:

栈的元素个数为:6

姓名:小龙女--年龄:950

姓名:沙和尚--年龄:320

姓名:猪八戒--年龄:800

姓名:孙悟空--年龄:650

姓名:白龙马--年龄:502

姓名:唐三藏--年龄:24

栈的元素个数为:0

清空栈->栈的元素个数为:0

销毁栈!

4.栈的链式存储结构及基本操作

    为了克服这种由顺序存储分配固定空间所产生的溢出空间浪费问题可以采用链式存储结构存储栈。栈的链式存储结构称为链栈它是运算受限的单链表其插入和删除操作仅限制在表头位置上(栈顶)进行因此不必设置头结点将单链表的头指针head改为栈顶指针top即可。因此,链栈的类型定义如下:

typedef struct stacknode {

DataType data;

struct stacknode * next;

}StackNode;

typedef StackNode * LinkStack;

LinkStack top;

    由于链栈是限制表头一端(即栈顶)进行插入删除的单链表因此对链栈的运算要比单链表的运算简单。空链栈和非空链栈的结构如图3.2所示。

  

 下面给出链栈上的基本运算。

1.判栈空

int StackEmpty(LinkStack top)

{

    return top==NULL;

}

2.进栈(入栈)

LinkStack Push(LinkStack top, DataType x)

{                                                //将元素x插入栈项

StackNode * p;

p=(StackNode *)malloc(sizeof(StackNode));         //申请新结点

p->data=x;

p->next=top;                                 //将新结点*p插入栈顶

top=p;                                      //使top指向新的栈项

return top;                                  //返回新栈顶指针

}

3.退栈(出栈)

LinkStack Pop(LinkStack top, DataType * x)

{   StackNode * p = top;                           //保存栈顶指针

   if(StackEmpty(top)){

   printf(“stack empty”);                         //栈为空

   exit(0);                                     //出错退出处理

}else{

   * x=p->data;                                 //保存删除结点值,并带回

   top=p->next;                                //栈顶指针指向下一个结点

   free(p);                                    //删除p指向的结点

   return top;                                 //并返回删除后的栈顶指针

}

}

4.取栈顶元素

DataType GetTop(LinkStack top)

{                                              //取栈顶元素

   if(StackEmpty(top)){

     printf(“stack empty”);                        //栈为空

     exit(0);                                    //出错退出处理

}else{

   retuen top->data;                           //返回栈顶结点值

}

}

5.链栈封装

    对已经编写好的C程序做一些小步的重构调整,以便于后面很方便的复用顺序栈。

    借鉴Java的封装思想,将链栈的创建与运用过程封装起来不对外暴露,将对链栈的创建与操作功能对外暴露,为了防止函数名称相同导致无法使用的问题,我们将对外暴露的操作功能封装到一个结构体当中,使用起来有点类似Java的对象使用方式。

    编码采用Visual Studio 2022编译器编写,构建头文件linkStack.h:

#pragma once

#ifndef LINK_STACK_H

#define LINK_STACK_H

struct LinkStack

{

    struct Stack* stack;

    void (*push)(void* data, struct LinkStack* linkStack);

    void (*pop)(struct LinkStack* linkStack);

    void* (*top)(struct LinkStack* linkStack);

    int (*size)(struct LinkStack* linkStack);

    int (*isEmpty)(struct LinkStack* linkStack);

    int (*isNotEmpty)(struct LinkStack* linkStack);

    void** (*clear)(struct LinkStack* linkStack);

    void** (*destroy)(struct LinkStack* linkStack);

};

struct LinkStack* initLinkStack();

#endif

实现头文件声明linkStack.c:

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include "linkStack.h"

struct Stack* initStack();

void pushLinkStack(void* data, struct LinkStack* linkStack);

void popLinkStack(struct LinkStack* linkStack);

void* topLinkStack(struct LinkStack* linkStack);

int sizeLinkStack(struct LinkStack* linkStack);

int isEmptyLinkStack(struct LinkStack* linkStack);

int isNotEmptyLinkStack(struct LinkStack* linkStack);

void** clearLinkStack(struct LinkStack* linkStack);

void** destroyLinkStack(struct LinkStack* linkStack);

struct LinkStack* initLinkStack()

{

    struct LinkStack* linkStack = malloc(sizeof(struct LinkStack));

    struct Stack* stack = initStack();

    if (linkStack == NULL || stack == NULL)

    {

        printf("链栈操作功能初始化失败!\n");

        return NULL;

    }

    linkStack->stack = stack;

    linkStack->push = pushLinkStack;

    linkStack->pop = popLinkStack;

    linkStack->top = topLinkStack;

    linkStack->size = sizeLinkStack;

    linkStack->isEmpty = isEmptyLinkStack;

    linkStack->isNotEmpty = isNotEmptyLinkStack;

    linkStack->clear = clearLinkStack;

    linkStack->destroy = destroyLinkStack;

}

struct StackNode

{

    struct StackNode* next;

};

struct Stack

{

    struct StackNode header;

    int size;

};

struct Stack* initStack()

{

    struct Stack* stack = malloc(sizeof(struct Stack));

    if (stack == NULL)

    {

        printf("链栈初始化失败!\n");

        return NULL;

    }

    stack->header.next = NULL;

    stack->size = 0;

    return stack;

}

void pushLinkStack(void* data, struct LinkStack* linkStack)

{

    struct Stack* stack = linkStack->stack;

    if (stack == NULL)

    {

        printf("链栈为空!\n");

        return;

    }

    if (data == NULL)

    {

        printf("入栈数据为空!\n");

        return;

    }

    struct StackNode* stackNode = data;

    stackNode->next = stack->header.next;

    stack->header.next = stackNode;

    stack->size++;

}

void popLinkStack(struct LinkStack* linkStack)

{

    struct Stack* stack = linkStack->stack;

    if (stack == NULL)

    {

        printf("链栈为空!\n");

        return;

    }

    if (stack->size == 0)

    {

        printf("链栈中数据已空!\n");

        return;

    }

    struct StackNode* stackNode = stack->header.next;

    stack->header.next = stackNode->next;

    stackNode->next = NULL;

    stack->size--;

}

void* topLinkStack(struct LinkStack* linkStack)

{     

    struct Stack* stack = linkStack->stack;

    if (stack == NULL)

    {

        printf("链栈为空!\n");

        return;

    }

    if (stack->size == 0)

    {

        printf("链栈中数据已空!\n");

        return NULL;

    }

    return stack->header.next;

}

int sizeLinkStack(struct LinkStack* linkStack)

{

    struct Stack* stack = linkStack->stack;

    if (stack == NULL)

    {

        printf("链栈为空!\n");

        return;

    }

    return stack->size;

}

int isEmptyLinkStack(struct LinkStack* linkStack)

{

    struct Stack* stack = linkStack->stack;

    if (stack == NULL)

    {

        printf("链栈为空!\n");

        return -1;

    }

    if (stack->size == 0)

    {

        printf("链栈中数据已空!\n");

        return 1;

    }

    return 0;

}

int isNotEmptyLinkStack(struct LinkStack* linkStack)

{

    return !isEmptyLinkStack(linkStack);

}

void** clearLinkStack(struct LinkStack* linkStack)

{

    struct Stack* stack = linkStack->stack;

    if (stack == NULL)

    {

        printf("链栈为空!\n");

        return NULL;

    }

    void** result = malloc(sizeof(void*) * stack->size);

    for (int i = 0; i < stack->size; i++)

    {

        result[i] = topLinkStack(linkStack);

        popLinkStack(linkStack);

    }

    return result;

}

void** destroyLinkStack(struct LinkStack* linkStack)

{

    struct Stack* stack = linkStack->stack;

    if (stack == NULL)

    {

        printf("链栈为空!\n");

        return NULL;

    }

    void** result = clearLinkStack(linkStack);

    free(stack);

    stack = NULL;

    free(linkStack);

    linkStack = NULL;

    return result;

}

测试:

#define _CRT_SECURE_NO_WARNINGS //规避C4996告警

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include "linkStack.h"

struct Person

{

    void* index;

    char name[64];

    int age;

};

int  main()

{

    struct LinkStack* stack = initLinkStack();

    struct Person p1 = { NULL, "唐三藏", 24 }; stack->push(&p1, stack);

    struct Person p2 = { NULL, "白龙马", 502 }; stack->push(&p2, stack);

    struct Person p3 = { NULL, "孙悟空", 650 }; stack->push(&p3, stack);

    struct Person p4 = { NULL, "猪八戒", 800 }; stack->push(&p4, stack);

    struct Person p5 = { NULL, "沙和尚", 320 }; stack->push(&p5, stack);

    struct Person p6 = { NULL, "小龙女", 950 }; stack->push(&p6, stack);

    printf("栈的元素个数为:%d\n", stack->size(stack));

    while (stack->isNotEmpty(stack))

    {

        struct Person* person = stack->top(stack);

        printf("姓名:%s--年龄:%d\n", person->name, person->age);

        stack->pop(stack);

    }

    printf("栈的元素个数为:%d\n", stack->size(stack));

    void** clear = stack->clear(stack);

    printf("清空栈->栈的元素个数为:%d\n", stack->size(stack));

    printf("销毁栈!\n");

    void** destroy = stack->destroy(stack);

    return 0;

}

运行结果:

栈的元素个数为:6

姓名:小龙女--年龄:950

姓名:沙和尚--年龄:320

姓名:猪八戒--年龄:800

姓名:孙悟空--年龄:650

姓名:白龙马--年龄:502

姓名:唐三藏--年龄:24

链栈中数据已空!

栈的元素个数为:0

清空栈->栈的元素个数为:0

销毁栈!

3.2.栈的应用举例

    栈的应用非常广泛,只要问题满足LIFO原则,都可使用栈作为数据结构。例如,在程序设计语言的编译系统中,经常要用到对输入算术表达式进行语法检查,如判断括号是否匹配、表达式的运算等问题。下面将举几个栈应用的例子,在以后的章节中还会经常借助栈来解决各种问题。

3.2.1.圆括号匹配的检验

    对于输入的一个算术表达式字符串,试写一算法判断其中圆括号是否匹配,若匹配则返回TRUE,否则返回FALSE

    可利用栈的操作来实现:循环读入表达式中的字符,如遇左括号"("就进栈;遇右括号")”则判断栈是否为空,若为空,则返回FALSE,否则退栈;循环结束后再判断栈是否为空,若栈空则说明括号匹配,否则说明不匹配。

    几乎所有的编译器都具有检测括号是否匹配的能力,那么如何实现编译器中的符号成对检测?如字符串:5+5*(6)+9/3*1)-(1+3(。

    算法思路:

        • 从第一个字符开始扫描

        • 当遇见普通字符时忽略,

        • 当遇见左括号时压入栈中

        • 当遇见右括号时从栈中弹出栈顶符号,并进行匹配

        • 匹配成功:继续读入下一个字符

        • 匹配失败:立即停止,并报错

    结束:

        • 成功: 所有字符扫描完毕,且栈为空

        • 失败:匹配失败或所有字符扫描完毕但栈非空

    总结:当需要检测成对出现但又互不相邻的事物时可以使用栈"后进先出"的特性,栈非常适合于需要"就近匹配"的场合。

    其实现算法如下:栈结构使用3.1.2小节构建的顺序栈结构serialStack.h:

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include "serialStack.h"

int left(char ch)

{

    return ch == '(';

}

int right(char ch)

{

    return ch == ')';

}

void printError(char* str, char* message, char* pointer)

{

    printf(message);

    printf("%s\n", str);

    int index = pointer - str;

    for (int i = 0; i < index; i++)

    {

        printf(" ");

    }

    printf("↑\n");

}

int main()

{

    char* str = "5+5*(6)+9/3*1-(1+3(";

    char* pointer = str;

    char* currentLift = NULL;

    struct SequenceStack* sequenceStack = initSequenceStack();

    while (*pointer != '\0')

    {

        if (left(*pointer))

        {

            sequenceStack->push(pointer, sequenceStack);

        }

        if (right(*pointer))

        {

            if (sequenceStack->size(sequenceStack) > 0)

            {

                currentLift = (char*)sequenceStack->top(sequenceStack);

                if (left(*currentLift))

                {

                    sequenceStack->pop(sequenceStack);

                }

                else

                {

                    printError(str, "出栈左括号匹配校验异常:\n", currentLift);

                    sequenceStack->pop(sequenceStack);

                    break;

                }

            }

            else

            {

                printError(str, "左括号与右括号匹配不成对:\n", pointer);

                break;

            }

        }

        pointer++;

    }

    while (sequenceStack->size(sequenceStack) > 0)

    {

        printError(str, "左括号没有匹配到右括号:\n", sequenceStack->top(sequenceStack));

        sequenceStack->pop(sequenceStack);

    }

    sequenceStack->destroy(sequenceStack);

    return 0;

}

运行结果:

左括号没有匹配到右括号:

5+5*(6)+9/3*1-(1+3(

                  ↑

左括号没有匹配到右括号:

5+5*(6)+9/3*1-(1+3(

              ↑

3.2.2.字符串回文的判断

    利用顺序栈的基本运算,试设计一个算法,判断一个输入字符串是否具有中心对称(也就是所谓的“回文”,即正读和反读均相同的字符序列),例如ababbaba和abcba都是中心对称的字符串。分析:所谓“中心对称”,首先要知道中心在哪儿,有了中心位置之后,就可以从中间向两头进行比较,若完全相同,则该字符串是中心对称,否则不是。这就要首先求出字符串串的长度,然后将前一半字符入栈,再利用退栈操作将其与后一半字符进行比较。可设计算法如下:

int symmetry(char str[])

{  SeqStack S;

  int j,k,i=0;

  InitStack(&S);

  while(str[i]!=’\0’) i++;                               //求字符串长度

  for(j=0;j<i/2;j++)

Push(&S,str[j]);                                  //前一半字符入栈

  k=(i+1)/2;                                        //后一半字符在字符串的起始位置

  for(j=k;j<i;j++)                                    //后一半字符与栈中字符比较

if(str[j]!=Pop(&S))

   return 0;                                   //有不相同字符,即不对称

  return 1;                                        //完全相同,即对称

}

3.2.3.数制转换

    将一个非负的十进制整数N转换成d进制的方法很多,其中的一种方法基于原理:

                                                              N=(N/d)*d+N%d。

例如(3553)10=(6741)8,其运算过程如下:

                      N      N/8      N%8

                      3553     444        1

                      444       55        4

                      55         6        7

                      6          0        6

    这个例子是从十进制数到八进制数的转换。要将一个非负的十进制数转换成任意的d进制数,原理都是一样的。由于上述计算过程是从低位到高位顺序产生八进制的各个数位,而打印输出一般应从高位到低位进行,正好与计算过程相反。因此,应将计算过程中得到的d进制数的各位数字顺序进栈,则按出栈序列顺序输出得到的数就是十进制数所对应的d进制数。这种转换算法如下:

void conversion(int N, int d)

{                                      //将一个非负的十进制数N转换成任意的d进制数

   SeqStack S;

   InitStack(&S)

   While(N){

   Push(&S,N%d);

   N=N/d;

}

While(!StackEmpty(&S)){

   i=Pop(&S);

   printf(“%d”,i);

}

}

例如,要将十进制数1348转换成八进制数,按上述算法,栈的变化情况如图3.3所示:

3.2.4.栈与递归

    栈还有一个非常重要的应用就是在程序设计语言中实现递归一个直接调用自己或间接调用自己的函数称为递归函数

    递归是程序设计中一个强有力的工具,虽然不用递归可以求解许多数学问题,但有很多数学问题不用递归计算是很难求解的。递归算法的设计一般分为两步:第一步是将规模较大的原问题分解为一个或多个规模较小的而又类似于原问题特性的子问题即将较大的问题递归地用较小的子问题来描述解原问题的方法同样可以用来解决子问题;第二步是确定一个或多个不需要分解、可直接求解的最小子问题。第一步称为递归步骤第二步中的最小子问题称为递归的终止条件

例如,求n的阶乘可递归地定义为

2阶的Fibonacci数列:

而像八皇后、Hanoi塔等问题本身没有明显的递归特征,但用递归求解更简单。

    【例3.3】试分析求阶乘的递归函数。

    解:求阶乘的递归函数定义如下:

long int fact(int n)

{   int temp;

   If(n==0)

return 1;

   else t

temp=n*fact(n-1);

   r12:return temp;

}

    如果用一个如下所示的C语言主函数来调用上述递归函数:

void main()

{   long int n;

   N=fact(5);

   R11 printf(“5!=%1d”,n);

}

    它的执行过程就必须利用递归工作栈来保存返回地址和中间参数及结果。在上面的程序中,用了两个标号r11和r12分别表示主函数调用递归函数和递归函数递归调用的返回地址,由此,可用如图3.4所示的递归工作栈的变化来描述整个程序的执行过程。

    【例3.4】已知函数

    试写一个递归算法,实现其功能。

    分析:用递归算法来实现,是比较简单的,其算法如下:

float fu(int n)

{

   if(n<2)

      return (n+1);

   else

      return fu(n/2)*fu(n/4);

}

或者为

float fu(int n)

{  float fi,f2;

if(n<2)

  return (n+1);

else

  f1=fu(n/2);

  f2=fu(n/4);

  return(f1*f2);

}

这样可能更容易理解些。现假设有如下主函数:

main()

{  int n=10;

   printf(“fu()=%f\n”,fu(n));

}

其调用和执行过程如图3.5所示。

    最后的结果应为8.0000,体现了栈的“后进先出”特点。

    另外,程序设计语言中的函数调用和返回也是利用栈来实现的。例如,图3.6所示是一个主函数和三个子函数调用、返回及返回地址入栈退栈的变化情况。

3.2.5.中缀后缀表达式★★★

    后缀表达式(由波兰科学家在20世纪50年代提出,将运算符放在数字后面,符合计算机运算。我们习惯的数学表达式叫做中缀表达式,符合人类思考习惯

中缀转后缀算法

    • 遍历中缀表达式中的数字和符号:

    • 对于数字:直接输出;

    • 对于符号:

        ○ 左括号:进栈;

        ○ 运算符号:与栈顶符号进行优先级比较;

            □ 若栈顶符号优先级低:此符号进栈(默认栈顶若是左括号,左括号优先级最低);

            □ 若栈顶符号优先级不低:将栈顶符号弹出并输出,当前符号进栈;

        ○ 右括号:将栈顶符号弹出并输出,直到匹配左括号,将左括号和右括号同时舍弃;

    • 遍历结束:将栈中的所有符号弹出并输出;

    其实现算法如下:栈结构使用3.1.2小节构建的顺序栈结构serialStack.h:

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include "SerialStack.h"

int isLeft(char ch)

{

    return ch == '(';

}

int isRight(char ch)

{

    return ch == ')';

}

int isSymbol(char ch)

{

    if (ch == '+')

    {

        return 1;

    }

    if (ch == '-')

    {

        return 1;

    }

    if (ch == '*')

    {

        return 1;

    }

    if (ch == '/')

    {

        return 1;

    }

    return 0;

}

int priority(char ch)

{

    switch (ch)

    {

    case '*':

    case '/': return 3;

    case '+':

    case '-': return 2;

    case '(': return 1;

    default: return 0;

    }

}

int main()

{

    char* str = "8 + ( 3 - 1 ) * 5";

    char* pointer = str;

    struct SequenceStack* sequenceStack = initSequenceStack();

    while (*pointer != '\0')

    {

        if (*pointer == ' ')

        {

            pointer++;

            continue;

        }

        if (isLeft(*pointer))

        {

            sequenceStack->push(pointer, sequenceStack);

            pointer++;

            continue;

        }

        else if (isSymbol(*pointer))

        {

            if (sequenceStack->size(sequenceStack) <= 0)

            {

                sequenceStack->push(pointer, sequenceStack);

                pointer++;

                continue;

            }

            else if (priority(*pointer) > priority(*(char*)sequenceStack->top(sequenceStack)))

            {

                sequenceStack->push(pointer, sequenceStack);

                pointer++;

                continue;

            }

            else

            {

                printf("%c ", *(char*)sequenceStack->top(sequenceStack));

                sequenceStack->pop(sequenceStack);

                sequenceStack->push(pointer, sequenceStack);

                pointer++;

                continue;

            }

        }

        else if (isRight(*pointer))

        {

            char* topChar = (char*)sequenceStack->top(sequenceStack);

            while (!isLeft(*topChar))

            {

                printf("%c ", *topChar);

                sequenceStack->pop(sequenceStack);

                topChar = (char*)sequenceStack->top(sequenceStack);

            }

            sequenceStack->pop(sequenceStack);

            pointer++;

            continue;

        }

        printf("%c ", *pointer);

        pointer++;

    }

    while (sequenceStack->size(sequenceStack) > 0)

    {

        printf("%c ", *(char*)sequenceStack->top(sequenceStack));

        sequenceStack->pop(sequenceStack);

        pointer++;

    }

    return 0;

}

运行结果:

8 3 1 - 5 * +

    思考:计算机是如何基于后缀表达式计算的?

    例如:8 3 1 - 5 * +      3 1 - 5 * 8 +

    计算规则:

    遍历后缀表达式中的数字和符号

        • 对于数字:进栈

        • 对于符号:

            ○ 从栈中弹出右操作数

            ○ 从栈中弹出左操作数

            ○ 根据符号进行运算

            ○ 将运算结果压入栈中

    遍历结束:栈中的唯一数字为计算结果

    计算后缀表达式结果(栈结构使用3.1.2小节构建的顺序栈结构serialStack.h,动态数组使用2.2.2小节构建的dynamicArray.h动态数组):

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include "serialStack.h"

#include "dynamicArray.h"

int isSymbol(char ch)

{

    if (ch == '+')

    {

        return 1;

    }

    if (ch == '-')

    {

        return 1;

    }

    if (ch == '*')

    {

        return 1;

    }

    if (ch == '/')

    {

        return 1;

    }

    return 0;

}

void operation(int* result, int left, char symbol, int right)

{

    switch (symbol)

    {

    case '*':

        *result = left * right;

        break;

    case '/':

        *result = left / right;

        break;

    case '+':

        *result = left + right;

        break;

    case '-':

        *result = left - right;

        break;

    default: return 0;

    }

}

void printArray(struct ArrayList* arrayList)

{

    int size = arrayList->size(arrayList);

    void** array = arrayList->destroy(arrayList);

    for (int i = 0; i < size; i++)

    {

        // 释放运算过程中产生的零时内存空间

        free(array[i]);

        array[i] = NULL;

    }

}

int main()

{

    char* str = "8 3 1 - 5 * +";

    char* pointer = str;

    int left = 0;

    int right = 0;

    struct ArrayList* arrayList = initArrayList(0);

    struct SequenceStack* sequenceStack = initSequenceStack();

    while (*pointer != '\0')

    {

        if (*pointer == ' ')

        {

            pointer++;

            continue;

        }

        if (isSymbol(*pointer))

        {

            right = atoi((char*)sequenceStack->top(sequenceStack));

            sequenceStack->pop(sequenceStack);

            left = atoi((char*)sequenceStack->top(sequenceStack));

            sequenceStack->pop(sequenceStack);

            int* result = malloc(sizeof(int));

            if (result == NULL)

            {

                return 0;

            }

            operation(result, left, *pointer, right);

            // 创建临时存储空间 需要运行结束借助动态数组释放内存空间

            char* resultChar = malloc(sizeof(char) * 10);

            if (resultChar == NULL)

            {

                return 0;

            }

            arrayList->insert(0, resultChar, arrayList);

            sprintf(resultChar, "%d", *result);

            sequenceStack->push(resultChar, sequenceStack);

            free(result);

            result = NULL;

            pointer++;

            continue;

        }

        sequenceStack->push(pointer, sequenceStack);

        pointer++;

    }

    printf("[%s]计算结果:%s\n", str, (char*)sequenceStack->top(sequenceStack));

    printArray(arrayList);

    return 0;

}

运行结果:

[8 3 1 - 5 * +]计算结果:18

3.3 队列

3.3.1.队列的定义及其运算

    队列(Queue)也是一种操作受限线性表它只允许在表的一端进行元素插入而在另一端进行元素删除。允许插入的一端称为队尾(rear)允许删除的一端称为队头(font)

    在队列中,通常把元素的插入称为入队,而元素的删除称为出队。队列的概念与现实生活中的排队相似,新来的成员总是加入队尾,排在队列最前面的总是最先离开队列,即先进先出,因此又称队列为先进先出(First In First Out FIFO)表

    假设队列q=(a1, a2, ..,an),是在空队列情况下依次加入元素a1, a2, ..,an之后形成的, a1是队头元素, an是队尾元素,则退出队列也必须按此次序进行;也就:只有在a1, a2, .., an-1都出队之后, an才能出队列。队列的示意图如图3.7所示。

队列的操作运算与栈类似,有关队列的基本运算如下:

  1. 置空队列InitQueue (Q),构造一个空队列Q
  2. 判队空QueueEmpty (Q),若Q为空队列,则返回TRUE,否则返回FALSE
  3. 入队列EnQueue (Q, x),若队列不满,则将数据x插入到Q的队尾。
  4. 出队列DeQueue (Q),若队列不空,,则删除队头元素,并返回该元素。
  5. 取队头GetFront (Q),若队列不空,则返回队头元素。

3.3.2.顺序队列

1.循环队列

    队列的顺序存储结构称为顺序队列。队列的顺序存储结构也是利用一块连续的存储单元存放队列中的元素的,实际上是一个受限线性表。由于队列的队头和队尾的位置是变化的,因此需要设置两个指针frontrear分别指示队头和队尾元素在表中的位置,它们的初值在队列初始化时置为0。顺序队列的类型定义如下:

#define QueueSize 100

typedef struct{

DataType data[QueueSize];

int front,rear;

}SeqQueue;

SeqQueue Q;                                            //定义一个顺序队列Q

    这种顺序队列的操作十分简单:入队时,先将新元素x插入rear所指的位置,再将rear加1;出队时,将front加1,并返回被删除的元素。由此可见,当头尾指针相等时队列为空。在非空队列中,头指针始终指向队头元素,而队尾指针却始终指向队尾元素的下一个位置。因此,入队运算可描述为:

                                Q.data[Q.rear]=x;Q.rear=Q.rear+1;

而出队运算为:

                                x=Q.data[Q.front]; Q.front=Q.front+1; return x;

    图3.8给出了一个入队和出队操作的例子,可说明头、尾指针和队列中元素之间的关系。假设队列分配的最大空间为5,当队列处于如图3.8 (f)所示的状态时,如果再继续插入新的元素就会产生上溢,而出队时空出的一些存储单元无法使用;如将队列的存储空间定义得太大,则会产生存储空间的浪费。

    为了充分利用数组空间,克服上溢,可将数组空间想象为一个环状空间,如图3.9所示,并称这种环状数组表示的队列为循环队列。在这种循环队列中进行入队、出队运算时,头尾指针仍然要加1,只不过当头尾指针指向数组上界(QueueSize-1)时,如果按正常的加1运算,数组就会产生越界溢出,因此,需要判断加1后是否超过数组上界,若是则使其指向数组下界0。如果用i来表示Q.front或Q.rear,那么,这种循环意义上的加1运算可描述为: 

if(i+1==QueueSize)                                 //i表示Q.rear或Q.front

   i=0;

else

   i=i+1;

可利用求余(%)运算将上述的操作简化为 :

i=(i+1)%QueueSize;

    在这样定义的循环队列中,出队元素的空间可以重新被利用。所以,一般情况下真正实用的顺序队列是循环队列。在循环队列的运算中,要涉及一些边界条件的处理问题。如图3.9所示的循环队列中,由于入队时的尾指针Q.rear向前追赶队头指针Q.front,出队时头指针向前追赶尾指针,如果是尾指针追赶上头指针,说明队满,否则若头指针追赶上尾指针,说明队空。因此,队列无论是空还是满, Q.rear==Q.front都成立。由此可见,仅凭队列的头尾指针是否相等是无法判断队列是“空”还是“满”的。解决这个问题有多种方法,常用的方法一般有三种:其一是另设一个标志位,以区别队列是“空”还是“满”,其二是设置一个计数器记录队列中元素个数;第三种方法是少用一个元素空间,约定入队前,测试尾指针在循环意义下加1后是否等于头指针,若相等则认为队列满,即尾指针Q.rear所指向的单元始终为空。下面介绍的有关循环队列的运算都是用第三种方法来实现的。该方法实现的循环队列的存储结构与前面定义的顺序队列基本上是一样的,循环队列的顺序存储类型定义如下:

#define QueueSize 100                               //假设数据为字符型

typedef char DataType;

typedef struct{

DataType data[QueueSize];

int front,rear;

}CirQueue;

因此,循环队列基本运算的各算法描述如下:

1.置空队列

void InitQueue(CirQueue * Q)

{

   Q->front=Q->rear=0;

}

2.判对空

int QueueEmpty(CirQueue * Q)

{

    return Q->rear==Q->front;

}

3.判对满

int QueueFull(CirQueue * Q)

{

    return (Q->rear+1)%QueueSize==Q->front;

}

4.入队列

void EnQueue(CirQueue * Q, DataType x)

{                                                   //插入元素x为队列Q新的队尾元素

if(QueueFull(Q))

  printf(“Queue overflow”);

else{

  1. >data[Q->rear]=x;

Q->rear=(Q->rear+1)%QueueSize;                  //循环意义下的加1

}

}

5.取队头元素

DataType GetFront(CirQueue * Q)

{                                            //获取Q的队头元素值

   if(QueueEmpty(Q)){

     printf(“Queue empty”);

     exit(0);                                  //出错退出处理

}else

   return Q->data[Q->front];                   //返回队头元素值

}

6.出队列

DataType DeQueue(CirQueue * Q)

{                                            //删除Q的队头元素,并返回其值

DataType x;

if(QueueEmpty(Q)){

  printf(“Queue empty”);

  exit(0);                                 //出错退出处理

}else{

  x=Q->data[Q->fromt];                     //保存待删除的元素

  Q->front=(Q->front+1)%QueueSize;          //头指针加1

  return x;                                //返回删除元素

}

}

【例3.5】Q是一个有11个元素存储空间的顺序循环队列,初始状态Q.front=Q.rear=0,写出下列操作后头、尾指针的变化情况,若不能入队,请说明理由。

                   d,e,b,g,h入队; d,e出队;

                    i,j,k,l,m入队; b出队;

                    n,o,p,q,r入队。

    分析:本题的入队和出队的变化情况是这样的:当元素d,e,b,g,h入队后,Q.rear=5,Q.front=0;元素d,e出队,Q.rear=5,Q.front=2;元素i,j,k,l,m入队后,Q.rear=10, Q.front=2;元素b出队后,Q.rear=10, Q.front=3;此时n,o,p入队,由于Q.rear=2, Q.front=3,当q入队时,(Q.rear+ 1)% QueueSize=Q.front。故队列满将产生溢出。

    【例3.6】 设栈S= (1, 2, 3, 4, 5, 6, 7),其中7为栈顶元素。请写出调用函数Algo (S)后栈S的状态。其函数为

void Algo(SeqStack S)

{  int i=1;

  CirQueue Q; SeqStack T;                               //定义一个循环队列和一个栈

  InitQueue(&Q);InitStack(&T);                           //初始化队列和栈

  while(!StackEmpty(&S)){

   if(i%2!=0)Push(&T,Pop(&S));

   else EnQueue(&Q,Pop(&S));

   i++;

}

while(!QueueEmpty(&Q))

   Push(&S,DeQueue(&Q));

while(!StackEmpty(&T))

   Push(&S,Pop(&T));

}

    分析:函数的第一个循环的作用是当S栈不空时,从栈顶开始将栈中序号为奇数的元素压入T栈中,序号为偶数的元素入队列Q中,循环结束时, S为空栈,栈T中有元素7、5、3、1, 1为栈顶元素,队列Q中有元素6、4、2, 6是队头元素;第二个循环把队列Q中的元素全部压入S栈中,此时的S栈中有元素6、4、2, 2是栈顶元素;第三个循环将栈T中的所有元素都压入S栈中,因此,最后的s=(6, 4, 2, 1, 3, 5, 7),tdcos1e-g-=159tc-0 ntuJer其中栈顶元素为7。

2.顺序队列封装

    队列是一种特殊的线性表,可以用线性表顺序存储来模拟队列。

    新建sequenceQueue.h定义队列功能,数组使用2.2.2小节构建的动态数组dynamicArray.h。

#pragma once

#ifndef SERIAL_QUEUE_H

#define SERIAL_QUEUE_H

struct SequenceQueue

{

    struct ArrayList* arrayList;

    void (*push)(void* data, struct SequenceQueue* sequenceQueue);

    void* (*pop)(struct SequenceQueue* sequenceQueue);

    int (*size)(struct SequenceQueue* sequenceQueue);

    int (*isEmpty)(struct SequenceQueue* sequenceQueue);

    int (*isNotEmpty)(struct SequenceQueue* sequenceQueue);

    void* (*front)(struct SequenceQueue* sequenceQueue);

    void* (*back)(struct SequenceQueue* sequenceQueue);

    void** (*clear)(struct SequenceQueue* sequenceQueue);

    void** (*destroy)(struct SequenceQueue* sequenceQueue);

    void (*ruin)(struct SequenceQueue* sequenceQueue);

};

struct SequenceQueue* initSequenceQueue();

#endif

sequenceQueue.c实现队列功能。

#define _CRT_SECURE_NO_WARNINGS //规避C4996告警

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include "dynamicArray.h"

#include "sequenceQueue.h"

#define MAX 1024

struct SequenceQueue* initSerialStack();

void pushSequenceQueue(void* data, struct SequenceQueue* sequenceQueue);

void* popSequenceQueue(struct SequenceQueue* sequenceQueue);

int sizeSequenceQueue(struct SequenceQueue* sequenceQueue);

int isEmptySequenceQueue(struct SequenceQueue* sequenceQueue);

int isNotEmptySequenceQueue(struct SequenceQueue* sequenceQueue);

void* frontSequenceQueue(struct SequenceQueue* sequenceQueue);

void* backSequenceQueue(struct SequenceQueue* sequenceQueue);

void** clearSequenceQueue(struct SequenceQueue* sequenceQueue);

void** destroySequenceQueue(struct SequenceQueue* sequenceQueue);

void ruinSequenceQueue(struct SequenceQueue* sequenceQueue);

struct SequenceQueue* initSequenceQueue()

{

    struct SequenceQueue* sequenceQueue = malloc(sizeof(struct SequenceQueue));

    struct ArrayList* arrayList = arrayList = initArrayList(MAX);

    if (sequenceQueue == NULL || arrayList == NULL)

    {

        printf("创建顺序队列,分配内存空间失败!");

        return NULL;

    }

    sequenceQueue->arrayList = arrayList;

    sequenceQueue->push = pushSequenceQueue;

    sequenceQueue->pop = popSequenceQueue;

    sequenceQueue->size = sizeSequenceQueue;

    sequenceQueue->isEmpty = isEmptySequenceQueue;

    sequenceQueue->isNotEmpty = isNotEmptySequenceQueue;

    sequenceQueue->front = frontSequenceQueue;

    sequenceQueue->back = backSequenceQueue;

    sequenceQueue->clear = clearSequenceQueue;

    sequenceQueue->destroy = destroySequenceQueue;

    return sequenceQueue;

}

void pushSequenceQueue(void* data, struct SequenceQueue* sequenceQueue)

{

    struct ArrayList* arrayList = sequenceQueue->arrayList;

    if (arrayList == NULL)

    {

        printf("顺序队列中动态数组不存在!");

        return;

    }

    if (data == NULL)

    {

        printf("入队数据为空!");

        return;

    }

    if (arrayList->size(arrayList) >= MAX)

    {

        printf("队列已满,不允许再插入数据!");

        return;

    }

    arrayList->insert(arrayList->size(arrayList), data, arrayList);

}

void* popSequenceQueue(struct SequenceQueue* sequenceQueue)

{

    struct ArrayList* arrayList = sequenceQueue->arrayList;

    if (arrayList == NULL)

    {

        printf("顺序队列中动态数组不存在!");

        return NULL;

    }

    if (arrayList->size(arrayList) <= 0)

    {

        printf("队列已空,无法再提供数据!");

        return NULL;

    }

    return arrayList->removeByIndex(0, arrayList);

}

int sizeSequenceQueue(struct SequenceQueue* sequenceQueue)

{

    struct ArrayList* arrayList = sequenceQueue->arrayList;

    if (arrayList == NULL)

    {

        printf("顺序队列中动态数组不存在!");

        return -1;

    }

    return arrayList->size(arrayList);

}

int isEmptySequenceQueue(struct SequenceQueue* sequenceQueue)

{

    struct ArrayList* arrayList = sequenceQueue->arrayList;

    if (arrayList == NULL)

    {

        printf("顺序队列中动态数组不存在!");

        return -1;

    }

    if (arrayList->size(arrayList) <= 0)

    {

        return 1;

    }

    return 0;

}

int isNotEmptySequenceQueue(struct SequenceQueue* sequenceQueue)

{

    return !isEmptySequenceQueue(sequenceQueue);

}

void* frontSequenceQueue(struct SequenceQueue* sequenceQueue)

{

    struct ArrayList* arrayList = sequenceQueue->arrayList;

    if (arrayList == NULL)

    {

        printf("顺序队列中动态数组不存在!");

        return NULL;

    }

    return arrayList->get(0, arrayList);

}

void* backSequenceQueue(struct SequenceQueue* sequenceQueue)

{

    struct ArrayList* arrayList = sequenceQueue->arrayList;

    if (arrayList == NULL)

    {

        printf("顺序队列中动态数组不存在!");

        return NULL;

    }

    return arrayList->get(arrayList->size(arrayList) - 1, arrayList);

}

void** clearSequenceQueue(struct SequenceQueue* sequenceQueue)

{

    struct ArrayList* arrayList = sequenceQueue->arrayList;

    if (arrayList == NULL)

    {

        printf("顺序队列中动态数组不存在!");

        return NULL;

    }

    return arrayList->clear(arrayList);

}

void** destroySequenceQueue(struct SequenceQueue* sequenceQueue)

{

    struct ArrayList* arrayList = sequenceQueue->arrayList;

    if (arrayList == NULL)

    {

        printf("顺序队列中动态数组不存在!");

        return NULL;

    }

    return arrayList->destroy(arrayList);

}

void ruinSequenceQueue(struct SequenceQueue* sequenceQueue)

{

    struct ArrayList* arrayList = sequenceQueue->arrayList;

    if (arrayList == NULL)

    {

        printf("顺序队列中动态数组不存在!");

        return NULL;

    }

    arrayList->ruin(arrayList);

}

测试。

#define _CRT_SECURE_NO_WARNINGS //规避C4996告警

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include "sequenceQueue.h"

struct Person

{

    char name[64];

    int age;

};

void printArray(void* data)

{

    struct Person* person = data;

    printf("姓名:%s--年龄:%d\n", person->name, person->age);

}

int compare(void* firstData, void* secondData)

{

    struct Person* firstPerson = firstData;

    struct Person* secondPerson = secondData;

    return strcmp(firstPerson->name, secondPerson->name) == 0 && firstPerson->age == secondPerson->age;

}

int main()

{

    struct SequenceQueue* sequenceQueue = initSequenceQueue();

    printf("新建队列长度%d\n", sequenceQueue->size(sequenceQueue));

    printf("数据入队,长度:%d\n", sequenceQueue->size(sequenceQueue));

    struct Person p1 = { "唐三藏", 24 }; sequenceQueue->push(&p1, sequenceQueue);

    struct Person p2 = { "白龙马", 502 }; sequenceQueue->push(&p2, sequenceQueue);

    struct Person p3 = { "孙悟空", 650 }; sequenceQueue->push(&p3, sequenceQueue);

    struct Person p4 = { "猪八戒", 800 }; sequenceQueue->push(&p4, sequenceQueue);

    struct Person p5 = { "沙和尚", 320 }; sequenceQueue->push(&p5, sequenceQueue);

    struct Person p6 = { "小龙女", 950 }; sequenceQueue->push(&p6, sequenceQueue);

    printf("数据入队后,长度:%d\n", sequenceQueue->size(sequenceQueue));

    printf("数据出队,长度:%d\n", sequenceQueue->size(sequenceQueue));

    while (sequenceQueue->isNotEmpty(sequenceQueue))

    {

        struct Person* front = sequenceQueue->front(sequenceQueue);

        printf("队头=>姓名:%s--年龄:%d\n", front->name, front->age);

        struct Person* back = sequenceQueue->back(sequenceQueue);

        printf("队尾=>姓名:%s--年龄:%d\n", back->name, back->age);

        struct Person* person = sequenceQueue->pop(sequenceQueue);

        // break;

    }

    int size = sequenceQueue->size(sequenceQueue);

    printf("数据出队后,长度:%d\n", sequenceQueue->size(sequenceQueue));

    void** result = sequenceQueue->destroy(sequenceQueue);

    printf("销毁队列,展示队列中剩余数据!\n");

    for (int i = 0; i < size; i++)

    {

        printArray(result[i]);

    }

    return 0;

}

运行结果。

新建队列长度0

数据入队,长度:0

数据入队后,长度:6

数据出队,长度:6

队头=>姓名:唐三藏--年龄:24

队尾=>姓名:小龙女--年龄:950

队头=>姓名:白龙马--年龄:502

队尾=>姓名:小龙女--年龄:950

队头=>姓名:孙悟空--年龄:650

队尾=>姓名:小龙女--年龄:950

队头=>姓名:猪八戒--年龄:800

队尾=>姓名:小龙女--年龄:950

队头=>姓名:沙和尚--年龄:320

队尾=>姓名:小龙女--年龄:950

队头=>姓名:小龙女--年龄:950

队尾=>姓名:小龙女--年龄:950

数据出队后,长度:0

销毁队列,展示队列中剩余数据!

3.3.3.链队列

1.构建

    队列的链式存储结构称为链队列。它是一种限制在表头删除表尾插入操作的单链表显然,仅有单链表的头指针是不便在表尾作插入操作的,为此再增加一个尾指针,使其指向链表上的最后一个结点,于是,一个链队列就由一个头指针一个尾指针唯一确定。同顺序队列定义类似,我们也将队列的两个指针封装在一起,链队列的类型定义如下:

typedef struct qnode{

   DataType data;

   struct qnode * next;                             //链队列结点类型

}QueueNode;

typedef struct{

   QueueNode * front;                             //队头指针

   QueueNede * rear;                              //队尾指针

}LinkQueue;                                       //定义一个链队列Q

LinkQueue Q;

    链队列一般不带头结点的,但和单链表类似,为了简化边界条件的处理,队头结点之前也附加一个头结点,并设队头指针指向此结点。因此,空的链队列和非空链队列的结构如图3.10所示。

在这里要特别注意的是队列的队头结点头结点的区别。下面给出的都是带头结点链队列的基本运算。

1.构造空队列

void InitQueue(LInkQueue * Q)

{

  Q->front=(QueueNode *)malloc(sizeof(QueueNode));           //申请头结点

  Q->rear=Q->front;                                       //尾指针也指向头结点

  Q->rear->next->NULL;

}

2.判队空

int QueueEmpty(LinkQueue * Q)

{

   return Q->rear==Q->front;                                //头尾指针相等队列为空

}

3.入队列

void EnQueue(LinkQueue * Q, DataType x)

{                                                        //将元素x插入链队列尾部

   QueueNode * p=(QueueNode *)malloc(sizeof(QueueNode));    //申请新结点

   p->data=x;

   p->next=NULL;

   Q->rear->next=p;                                       //*p链到原队尾结点之后

   Q->raer=p;                                            //队尾指针指向新的队尾结点

}

4.取队头元素

DataType GetFront(LinkQueue * Q)

{                                                       //取链队列的队头元素值

   if(QueueEmpty(Q)){

   printf(“Queue underflow”);

   exit(0);                                             //出错退出处理

}else

   return Q->front->next->data;                           //返回原队头元素值

}

5.出队列

链队列的出队操作有两种不同情况要分别考虑。

当队列长度大于1时,则出队操作只需要修改头结点的指针域即可,尾指针不变,操作步骤如下:

s=Q->front->next;

Q->front->next=s->next;

x=s->data;

free(s); return x;                                          //释放队头结点,并返回其值

若列队长度等于1,则出队时不仅要修改头结点的指针域,而且还要修改尾指针。

s=Q->front->next;

Q->front->next=NULL;

Q->rear=Q->front;

x=s->data;

free(s);return x;                                          //释放队头结点,并返回其值

这样,在写算法时对于长度等于1和长度大于1的情况要分别处理。为了使得在长度等于1和长度大于1的情况下处理操作一致,可以改进出队算法,使得出队时只修改头指针,删除队列头结点(不是队头结点),使链队列的队头结点成为新的链队列的头结点。因此,链队列的出队算法描述如下:

DataType DeQueue(LinkQueue * Q)

{                                                 //删除链队列的头结点,并返回头结点的元素值

   QueueNode * p;

   if(QueueEmpty(Q)){

   printf(“Queue underflow”);

   exit(0);                                       //出错退出处理

}else{

   p=Q->front;                                   //p指向头结点

   Q->front=Q->front->next;                        //头指针指向原队头结点

   free(p);                                      //删除释放原头结点

   return(Q->front->data);                         //返回原队头结点的数据值

}

}

【例3.7】 假设用一个带头结点的循环单链表表示队列(称为循环链队列),该队列只设一个指向队尾结点的指针rear,不设头指针,试编写相应的初始化队列、入队(即入)和出队(即删除)算法。循环链队列的结构如图3.11所示。

按题目的已知条件和假设,该循环链队列的类型定义为:

typedef struct queuenode{

   DataTpye data;

   struct queuenode * next;

}QueueNode;

QueueNode * rear;

分析:

1.初始化空队列:

QueueNode * InitQueue(QueueNode * rear)

{

   rear=(QueueNode *)malloc(sizeof(QueueNode));                      //申请头结点

   rear->next=NULL;

   return rear;

}

2.入队列(插入一个结点):在队列中插入一个结点操作是在队尾进行的,所以应在该循环链队列的尾部插入一个结点。插入的过程应该是:首先生成一个新结点S,因为链表带头结点,所以队空与否对插入没有影响。插入操作是简单的:将尾的指针域值赋给新结点的指针域(即s->next=rear->next);把新结点指针S赋给原尾指针rear所指结点的指针域(即rear->next=s);再把S赋给rear (即rear=s),因此,该入队算法如下:

void EnQueue(QueueNode * rear, DataType x)

{

   QueueNode * s=(QueueNode *)malloc(sizeof(QueueNode));  //申请新结点

   s->data=x;

   s->next=rear->next;

   rear->next=s;

   rear=s;

}

3.出队列(删除一个结点):在队列中删除一个结点,首先要判断队列是否为空,若不为空,则可进行删除操作;否则,显示出错。删除的思想是将原表头结点删掉,把队头结点作为新的头结点,实现算法如下(要特别注意头结点和队头结点的区别):

DataType DelQueue(QueueNode * rear)

{  QueueNode * s, * t;

   DataType x;

   if(rear->next==NULL){

   printf(“Queue Empty”);

   exit(0);                                           //出错退出处理

}else{

   s=rear->next;                                      //S指向头结点

   rear->next=s->next;                                 //删除头结点

   t=s->next;                                         //使t指向队头结点

   x=t->data;                                         //保存队头节点的数据域值

   free(s);                                           //释放被删除的头结点

   return x;                                          //返回删除的数据

}

}

2.链队列封装

队列也可以用线性表链式存储来模拟队列的链式存储。

新建linkQueue.h定义队列功能。

#pragma once

#ifndef LINK_QUEUE_H

#define LINK_QUEUE_H

struct LinkQueue

{

    struct Queue* queue;

    void (*push)(void* data, struct LinkQueue* linkQueue);

    void* (*pop)(struct LinkQueue* linkQueue);

    int (*size)(struct LinkQueue* linkQueue);

    int (*isExist)(void* data, int (*compare)(void* first, void* second), struct LinkQueue* linkQueue);

    int (*isNotExist)(void* data, int (*compare)(void* first, void* second), struct LinkQueue* linkQueue);

    int (*isEmpty)(struct LinkQueue* linkQueue);

    int (*isNotEmpty)(struct LinkQueue* linkQueue);

    void* (*front)(struct LinkQueue* linkQueue);

    void* (*back)(struct LinkQueue* linkQueue);

    void** (*clear)(struct LinkQueue* linkQueue);

    void** (*destroy)(struct LinkQueue* linkQueue);

};

struct LinkQueue* initLinkQueue();

#endif

linkQueue.c实现队列功能。

#define _CRT_SECURE_NO_WARNINGS //规避C4996告警

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include "linkQueue.h"

struct Queue* initQueue();

void pushLinkQueue(void* data, struct LinkQueue* linkQueue);

void* popLinkQueue(struct LinkQueue* linkQueue);

int sizeLinkQueue(struct LinkQueue* linkQueue);

int isExistLinkQueue(void* data, int (*compare)(void* first, void* second), struct LinkQueue* linkQueue);

int isNotExistLinkQueue(void* data, int (*compare)(void* first, void* second), struct LinkQueue* linkQueue);

int isEmptyLinkQueue(struct LinkQueue* linkQueue);

int isNotEmptyLinkQueue(struct LinkQueue* linkQueue);

void* frontLinkQueue(struct LinkQueue* linkQueue);

void* backLinkQueue(struct LinkQueue* linkQueue);

void** clearLinkQueue(struct LinkQueue* linkQueue);

void** destroyLinkQueue(struct LinkQueue* linkQueue);

struct LinkQueue* initLinkQueue()

{

    struct LinkQueue* linkQueue = malloc(sizeof(struct LinkQueue));

    struct Queue* queue = initQueue();

    if (linkQueue == NULL || queue == NULL)

    {

        printf("初始化链队列操作失败,分配链队列操作空间失败!");

        return NULL;

    }

    linkQueue->queue = queue;

    linkQueue->push = pushLinkQueue;

    linkQueue->pop = popLinkQueue;

    linkQueue->size = sizeLinkQueue;

    linkQueue->isExist = isExistLinkQueue;

    linkQueue->isNotExist = isNotExistLinkQueue;

    linkQueue->isEmpty = isEmptyLinkQueue;

    linkQueue->isNotEmpty = isNotEmptyLinkQueue;

    linkQueue->front = frontLinkQueue;

    linkQueue->back = backLinkQueue;

    linkQueue->clear = clearLinkQueue;

    linkQueue->destroy = destroyLinkQueue;

    return linkQueue;

}

struct QueueNode

{

    struct QueueNode* next;

};

struct Queue

{

    struct QueueNode queueHeader;

    struct QueueNode* queueTail;

    int size;

};

struct Queue* initQueue()

{

    struct Queue* queue = malloc(sizeof(struct Queue));

    if (queue == NULL)

    {

        printf("初始化链队列失败,分配链队列空间失败!");

        return NULL;

    }

    queue->queueHeader.next = NULL;

    queue->queueTail = &queue->queueHeader;

    queue->size = 0;

    return queue;

}

void pushLinkQueue(void* data, struct LinkQueue* linkQueue) // 尾指针尾插数据

{

    struct Queue* queue = linkQueue->queue;

    if (queue == NULL)

    {

        printf("链队列为空!");

        return;

    }

    if (data == NULL)

    {

        printf("入队列数据为空!");

        return;

    }

    struct QueueNode* queueNode = data;

    queue->queueTail->next = queueNode;

    queueNode->next = NULL;

    queue->queueTail = queueNode;

    queue->size++;

}

void* popLinkQueue(struct LinkQueue* linkQueue) // 头删除

{

    struct Queue* queue = linkQueue->queue;

    if (queue == NULL)

    {

        printf("链队列为空!");

        return NULL;

    }

    if (queue->size <= 0)

    {

        printf("链队中已没有数据结点!");

        return NULL;

    }

    if (queue->size == 1)

    {

        queue->queueTail = &queue->queueHeader;

    }

    struct QueueNode* headerNode = queue->queueHeader.next;

    queue->queueHeader.next = headerNode->next;

    queue->size--;

    return headerNode;

}

int sizeLinkQueue(struct LinkQueue* linkQueue)

{

    struct Queue* queue = linkQueue->queue;

    if (queue == NULL)

    {

        printf("链队列为空!");

        return -1;

    }

    return queue->size;

}

int isExistLinkQueue(void* data, int (*compare)(void* first, void* second), struct LinkQueue* linkQueue)

{

    if (linkQueue == NULL || linkQueue->queue == NULL)

    {

        printf("链队列为空!");

        return -1;

    }

    struct QueueNode header = linkQueue->queue->queueHeader;

    struct QueueNode* node = &header;

    while (node != NULL)

    {

        if (compare(data, node))

        {

            return 1;

        }

        node = node->next;

    }

    return 0;

}

int isNotExistLinkQueue(void* data, int (*compare)(void* first, void* second), struct LinkQueue* linkQueue)

{

    return !isExistLinkQueue(data, compare, linkQueue);

}

int isEmptyLinkQueue(struct LinkQueue* linkQueue)

{

    struct Queue* queue = linkQueue->queue;

    if (queue == NULL)

    {

        printf("链队列为空!");

        return -1;

    }

    if (queue->size <= 0)

    {

        return 1;

    }

    return 0;

}

int isNotEmptyLinkQueue(struct LinkQueue* linkQueue)

{

    struct Queue* queue = linkQueue->queue;

    if (queue == NULL)

    {

        printf("链队列为空!");

        return -1;

    }

    return !isEmptyLinkQueue(linkQueue);

}

void* frontLinkQueue(struct LinkQueue* linkQueue)

{

    struct Queue* queue = linkQueue->queue;

    if (queue == NULL)

    {

        printf("链队列为空!");

        return -1;

    }

    return queue->queueHeader.next;

}

void* backLinkQueue(struct LinkQueue* linkQueue)

{

    struct Queue* queue = linkQueue->queue;

    if (queue == NULL)

    {

        printf("链队列为空!");

        return -1;

    }

    return queue->queueTail;

}

void** clearLinkQueue(struct LinkQueue* linkQueue)

{

    struct Queue* queue = linkQueue->queue;

    if (queue == NULL)

    {

        printf("链队列为空!");

        return -1;

    }

    void** result = malloc(sizeof(void*) * queue->size);

    if (result == NULL)

    {

        printf("清空链队列失败,没有足够的临时空间存放链队列中的数据!");

        return NULL;

    }

    struct QueueNode* currentPointer = &queue->queueHeader;

    for (int i = 0; i < queue->size; i++)

    {

        currentPointer = currentPointer->next;

        result[i] = currentPointer;

    }

    queue->queueHeader.next = NULL;

    queue->queueTail = &queue->queueHeader;

    queue->size = 0;

    return result;

}

void** destroyLinkQueue(struct LinkQueue* linkQueue)

{

    struct Queue* queue = linkQueue->queue;

    if (queue == NULL)

    {

        printf("链队列为空!");

        return NULL;

    }

    void** result = clearLinkQueue(linkQueue);

    free(queue);

    queue = NULL;

    free(linkQueue);

    linkQueue = NULL;

    return result;

}

测试。

#define _CRT_SECURE_NO_WARNINGS //规避C4996告警

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include "linkQueue.h"

struct Person

{

    void* index;

    char name[64];

    int age;

};

void printArray(void* data)

{

    struct Person* person = data;

    printf("姓名:%s--年龄:%d\n", person->name, person->age);

}

int compare(void* firstData, void* secondData)

{

    struct Person* firstPerson = firstData;

    struct Person* secondPerson = secondData;

    return strcmp(firstPerson->name, secondPerson->name) == 0 && firstPerson->age == secondPerson->age;

}

int main()

{

    struct LinkQueue* linkQueue = initLinkQueue();

    printf("新建队列长度%d\n", linkQueue->size(linkQueue));

    printf("数据入队,长度:%d\n", linkQueue->size(linkQueue));

    struct Person p1 = { NULL, "唐三藏", 24 }; linkQueue->push(&p1, linkQueue);

    struct Person p2 = { NULL, "白龙马", 502 }; linkQueue->push(&p2, linkQueue);

    struct Person p3 = { NULL, "孙悟空", 650 }; linkQueue->push(&p3, linkQueue);

    struct Person p4 = { NULL, "猪八戒", 800 }; linkQueue->push(&p4, linkQueue);

    struct Person p5 = { NULL, "沙和尚", 320 }; linkQueue->push(&p5, linkQueue);

    struct Person p6 = { NULL, "小龙女", 950 }; linkQueue->push(&p6, linkQueue);

    printf("数据入队后,长度:%d\n", linkQueue->size(linkQueue));

    printf("数据出队,长度:%d\n", linkQueue->size(linkQueue));

    while (linkQueue->isNotEmpty(linkQueue))

    {

        struct Person* front = linkQueue->front(linkQueue);

        printf("队头=>姓名:%s--年龄:%d\n", front->name, front->age);

        struct Person* back = linkQueue->back(linkQueue);

        printf("队尾=>姓名:%s--年龄:%d\n", back->name, back->age);

        struct Person* person = linkQueue->pop(linkQueue);

        // break;

    }

    int size = linkQueue->size(linkQueue);

    printf("数据出队后,长度:%d\n", linkQueue->size(linkQueue));

    void** result = linkQueue->destroy(linkQueue);

    printf("销毁队列,展示队列中剩余数据!\n");

    for (int i = 0; i < size; i++)

    {

        printArray(result[i]);

    }

    return 0;

}

运行结果。

新建链式队列长度0

数据入链式队列,长度:0

数据入链式队列后,长度:6

数据出链式队列,长度:6

链式队列头=>姓名:唐三藏--年龄:24

链式队列尾=>姓名:小龙女--年龄:950

链式队列头=>姓名:白龙马--年龄:502

链式队列尾=>姓名:小龙女--年龄:950

链式队列头=>姓名:孙悟空--年龄:650

链式队列尾=>姓名:小龙女--年龄:950

链式队列头=>姓名:猪八戒--年龄:800

链式队列尾=>姓名:小龙女--年龄:950

链式队列头=>姓名:沙和尚--年龄:320

链式队列尾=>姓名:小龙女--年龄:950

链式队列头=>姓名:小龙女--年龄:950

链式队列尾=>姓名:小龙女--年龄:950

数据出链式队列后,长度:0

销毁链式队列,展示链式队列中剩余数据!

3.4栈和队列的应用实例

    我们在很早就开始学习如何写和计算表达式。大部分小学生开始在计算诸如8+5x(7-3)之类的表达式时,都碰到了一些困难。但通过一段的学习之后,就能够掌握和描述计算步骤了,例如上述表达式的计算可描述为:先用7减去3得4,再用5乘以4得20,然后再用8加20得到28,因此表达式的值为28。如果要问为什么要以这种次序计算表达式,因为这是人们一直沿袭下来的手工运算规则:有括号先算括号内的运算,无括号时先做乘、除法,再做加、减法;对于相同的级别的运算则按从左到右次序计算。如今,计算机已成为人们生活中不可缺少的工具,那么计算机能否计算给定的算术表达式的值呢?回答是肯定的。表达式计算是实现程序设计语言的基本问题之一,也是栈和队列应用的一个典型的例子。

    实例要求:以字符序列的形式从终端输入语法正确的、不含变量的整数表达式,利用给定的算符优先关系,实现对算术四则混合运算表达式的求值,并演示在求值过程中运算符栈、操作数栈、输入字符和主要操作的变化过程。

    分析人们在书写表达式时通常采用的是一种“中缀”表示形式,也就是将运算符放在两个操作数中间,用这种“中缀”形式表示的表达式称为中缀表达式。但是,这种表达式表示形式对计算机处理来说是不大合适的。对于表达式的表示还有一种形式,称之为“后缀”表达式,即将运算符紧跟在两个操作数的后面。例如,前面的中缀表达式8+5*(7-3)可以写成8573-*+的后缀表达式。要计算该表达式,可以从左向右扫描,直到遇到一个运算符后即运算,在这个后缀表达式中所遇到的是减号“一”。由于每个运算符对它前面最近的两个操作数进行运算,于是就执行7-3的运算,并用此运算结果4取代原表达式的7、3、一,这样原表达式就变成了854*+;接着进行的是5*4的计算,得到新表达式820 +,再计算得结果28,这种计算方法既简单又方便,特别是适合计算机的处理方式。因此,要用计算机来处理计算算术表达式问题,首先要解决的问题是如何将人们习惯书写的中缀表达式转换成计算机容易处理的后缀表达式。

3.4.1.中缀表达式到后缀表达式的转换(算法见3.2.5)

    假定在中缀表示的算术表达式中只含有四种基本运算符,操作数是10以内的整数没有括号。例如,有一个中缀表达式4+2*3,它的后缀表达式为423*+。那么,如何将一个中缀表达式转换成后缀表达式呢?让我们来分析后缀表达式的生成过程:当从左至右扫描例子所给的中缀表达式时,首先遇到的是数字4,直接输出,当遇到运算符+号时先保存至栈中,不能立即输出,是因为紧跟其后的运算符有可能具有较高的优先级,必须先运算;紧接着扫描遇数字2,也直接输出;再接着扫描遇运算符*号,新扫描到的运算符优先级必须与前一个运算符的优先级进行比较,如果新的运算符优先级高,就要像前一个运算符那样保存它,直到扫描到第二个操作数3,输出3后再将该运算符从栈中取出后输出,即输出*,然后再输出+,最后得到后缀表达式423*+。因此,在转化中必须保存两个运算符,后保存的运算符先输出。用计算机来实现这一转化过程,就需要用到后进先出(即栈)的概念。

    如果在中缀表达式中含有小括号,那么由于括号隔离了优先级规则,它在整个表达式的内部产生了完全独立的子表达式,因此就需要修改前面的算法。当扫描到一个左括号时,需要将其压入栈中,使其在栈中产生一个“伪栈底”,这样算法就可以像前面一样进行。但当扫描到一个右括号时,就需要将从栈顶到这个“伪栈底”中的所有运算符全部弹出,然后再将这个“伪栈底”删除。

    综上分析,可得到通过栈将中缀表达式转换为后缀表达式的算法思想如下:顺序扫描中缀算术表达式,当读到数字时,直接将其送至输出队列中;当读到运算符时,将栈中所有优先级高于或等于该运算符的运算符弹出,送至输出队列中,再将当前运算符入栈;当读入左括号时,即入栈;当读到右括号时,将靠近栈顶的第一个左括号上面的运算符全部依次弹出,送至输出队列中,再删除栈中的左括号。

    在有了上述分析之后,实现其转换的算法就不难给出。为了简化算法,我们把括号也作为运算符看待,并规定它的优先级为最低,另外将表达式中的操作数规定为1位数字字符,运算符也只包括+-*/四种。将优先级别判断的函数定义如下:

int Priority(DataType op)

{  switch(op){

     case ‘(’:

     case ‘#’: retuen0;

     case ‘-’:

     case ‘+’: return 1;

     case ‘*’:

     case ‘/’: return 2;

}

return -1;

}

    为了方便边界条件(栈空)的判断,提高算法的运行效率,在扫描读入中缀表达式之前,在空栈中预先压入一个“#”字符作为栈底元素,另外在表达式的最后增加一个“#”字字符,作为中缀表达式的结束标志,该结束符与栈底元素“#”配对。本算法假设输入的表达式没有语法错误,但可以过滤掉输入符号之间的空格。具体算法如下:

void CTPostExp(CirQueue * Q)

{   SeqStack S;                                           //运算符栈

   char c,t;

   InitStack(&S);                                          //初始化栈

   Push(&S.’#’);                                           //压入栈低元素‘#’

   do{                                                  //扫描中缀表达式

    c=getchar();

    switch(c){

  case ‘ ‘:  break;                                     //去除空格符

  case ‘0’:

  case ‘1’:

  case ‘2’:

  case ‘3’:

  case ‘4’:

  case ‘5’:

  case ‘6’:

  case ‘7’:

  case ‘8’:

  case ‘9’:  EnQueue(Q,c); break;

      case ’(’:  Push(&S,c);break;

  case ‘)’:

  case ‘#’:

     do {

  t=Pop(&S);

  if(t!=’(’ && t!=’#’)  EnQueue(Q,t);

} while(t!=’(’ && S.top!=-1); break;

  case ‘+’:

  case ‘-’:

  case ‘*:

  case ‘/’:

         while()<=Priority(){

     t=Pop(&S);EnQueue(Q,t);

}

Push(&S,c); break;

}                                                   //EndSwitch

}while(c!=’#’);                                          //以’#’号结束表达式扫描

}

    执行上述程序,输入中缀表达式字符串: 9-(2+4*7) /5+3#,输出结果为: 9247*+5/-3+。其中,运算符栈和存放后缀表达式的队列的变化过程如表3.1所示。

3.4.2.后缀表达式的计算(算法见3.2.5)

    在后缀表达式中,不仅不需要括号,而且还能完全免除算符优先规则。对于后缀表达式来说,仅仅使用一个自然规则,即从左到右顺序完成计算,这个规则对计算机而言是很容易实现的。下面将讨论如何用计算机来实现计算后缀表达式的算法。如果在表达式中仅仅只有一个运算符,如像53*这样的表达式,显然计算过程非常简单,可立即进行。但后缀表达式在多数情况下都多于一个运算符,因此必须要像保存输入数字一样保存其中间结果。我们知道,在计算后缀表达式时,最后保存的值最先取出参与运算,所以要用到栈。利用前面生成的后缀表达式队列,很容易写出计算后缀表达式的算法。在算法中使用了整型栈S来存储读入的操作数和运算结果,因为在生成的后缀表达式队列中存放的是字符序列,因此在算法中要有一个数字字符到数值的转换。

具体实现算法如下:

int CPostExp(SeqQueue Q)

{  SeqStack1 S;

  char ch;  int x,y;

  InitSrack1(&S);

  while(!QueueEmpty(Q)){

  ch=DeQueue(&Q);

  if(ch>=’0’ && ch<=’9’)

    Puch1(&S,ch-’0’);

}else {

   y=Pop(&S);

   x=Pop1(&S);

   switch(ch){

   csae ‘+’: Push1(&S,x+y); break;

   csae ‘-’: Push1(&S,x-y); break;

   csae ‘*’: Push1(&S,x*y); break;

   csae ‘/’: Push1(&S,x/y); break;

}

} return GetTop1(S);

}

    这个算法非常简单,在这里就不再详细解析,只要将此算法与前一个中缀表达式到后缀表达式转换算法连在一起,稍作修改即可运行,这部分工作留给读者自己去完成。下面以后缀表达式9247*+5/-3+为例,使用上述算法计算该表达式的计算过程如表3.2所示。

小  结

栈和队列是两种十分重要的线性结构,它们的逻辑结构和前面介绍的线性表完全相同,只是对其操作运算有一定的限制,故又称它们为操作受限线性表。栈和队列结构在各种程序设计中被广泛应用。

本章重点介绍了顺序栈链栈循环队列链队列的存储结构及其基本运算,需要读者要能够熟练地掌握。本章的难点是理解递归算法执行过程中栈的状态变化过程以及循环队列对边界条件的处理问题。在本章中介绍了多个利用队列或栈设计算法解决简单应用问题的实例,特别是表达式求值问题的例子,它是一个综合的应用实例,几乎用到栈和队列的各种运算。通过这些例子,读者可以更好地理解栈和队列的特点和应用,增强分析问题解决问题的能力。

猜你喜欢

转载自blog.csdn.net/qq_43460743/article/details/130165822