栈和队列

栈和队列

知识框架

  • 栈和队列是线性表的子集(是插入和删除位置受限的线性表)

No.1栈的定义

  • (Stack):是只允许在一端进行插入或删除的线性表。首先栈是一种线性表,但限定这种线性表只能在某一端进行插入和删除操作。
  • 栈顶(Top):线性表允许进行插入删除的那一端。
  • 栈底(Bottom):固定的,不允许进行插入和删除的另一端。
  • 空栈:不含任何元素的空表。
  • 栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构
  • 逻辑结构:与普通线性表相同(一对一关系)。
  • 存储结构:顺序栈或链栈存储均可。(顺序栈更常用)
  • 运算规则:只能在栈顶运算,且访问结点时依照后进先出的原则。(栈与普通线性表的唯一区别

  • top(栈顶)指针:指示栈顶元素在顺序栈中的位置。 --> top指针永远指向空,即指向栈顶元素的下一个位置或者始终指向栈顶元素(看情况而别)

  • base(栈底)指针:指示栈底元素在顺序栈中的位置。--> base指针永远指向首地址或基地址

  • stacksize:表示栈可使用的最大容量

  • 栈空标志:base == top或者top == -1

  • 栈满标志:top - base == stacksize或者top == MAXSIZE - 1

  • 栈满时的处理方法:

    1. 报错,返回操作系统。
    2. 分配更大的空间,作为栈的存储空间,将原栈的内容移入新栈。
  • 使用数组作为顺序栈存储方式的特点:

    1. 简单方便,但易产生溢出。(因为数组大小固定
    2. 上溢(overflow):栈已经满,又要压入元素
    3. 下溢(underflow):栈已经空,还要弹出元素
    4. 注意点:上溢是一种错误,使问题的处理无法进行;而下溢一般认为是一种结束条件,即问题的处理结束。

No.2栈的顺序存储结构

一、静态分配顺序栈

  • top始终指向栈顶元素
  • top不能超过MAXSIZE
  • 空栈的判断条件top == -1,初始值也是这个值,满栈的判断条件top == MAXSIZE-1
  • 采用顺序存储的栈称为顺序栈,它利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时附设一个指针(top)指示当前栈顶元素的位置。
  • 若存储栈的长度为stacksize,则栈顶位置top必须小于stacksize。当栈存在一个元素时,top等于0,因此通常把空栈的判断条件定位top等于-1。
  • 若现在有一个栈,stacksize是5,则栈的普通情况、空栈、满栈的情况分别如下图所示:

//头文件
#include<stdio.h>
#include<stdlib.h>
#define OK 1    //成功标识
#define ERROR 0 //失败标识

typedef int Status;	//Status是函数的类型,其值是函数结果状态代码,如OK等

// 定义顺序栈
// 数组形式
# define MAXSIZE 100   // 数组的大小
typedef int ElemType;
typedef struct {
	ElemType data[MAXSIZE];   // 存放栈中元素
	int top;		// 栈顶指针(元素个数)
}SqStack;

1.初始化

void InitStack(SqStack *S){
    S->top = -1;    //初始化栈顶指针
}

2.入栈

/*插入元素e为新的栈顶元素*/
Status Push(SqStack *S, ElemType e){
    //满栈
    if(S->top == MAXSIZE-1){
        return ERROR;
    }
    S->top++;   //栈顶指针增加一
    S->data[S->top] = e;    //将新插入元素赋值给栈顶空间
    return OK;
}

3.出栈

/*若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
Status Pop(SqStack *S, ElemType *e){
    if(S->top == -1){
        return ERROR;
    }
    *e = S->data[S->top];   //将要删除的栈顶元素赋值给e
    S->top--;   //栈顶指针减一
    return OK;
}

4.取栈顶元素

/*读栈顶元素*/
Status GetTop(SqStack S, ElemType* e) {
    if (S.top == -1) {   //栈空
        return ERROR;
    }
    *e = S.elem[S.top];   //记录栈顶元素
    return OK;
}

5.完整代码

#include <stdio.h>
#include <stdlib.h>
#define OK 1    //成功标识
#define ERROR 0 //失败标识

typedef int Status; //Status是函数的类型,其值是函数结果状态代码,如OK等

// 定义顺序栈
// 数组形式
#define MAXSIZE 100   // 数组的大小
typedef int ElemType;
typedef struct {
    ElemType elem[MAXSIZE];   // 存放栈中元素
    int top;        // 栈顶指针(元素个数)
} SqStack;

void InitStack(SqStack* S) {
    S->top = -1;    //初始化栈顶指针
}

/*插入元素e为新的栈顶元素*/
Status Push(SqStack* S, ElemType e) {
    //满栈
    if (S->top == MAXSIZE - 1) {
        return ERROR;
    }
    S->top++;   //栈顶指针增加一
    S->elem[S->top] = e;    //将新插入元素赋值给栈顶空间
    return OK;
}

/*若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
Status Pop(SqStack* S, ElemType* e) {
    if (S->top == -1) {
        return ERROR;
    }
    *e = S->elem[S->top];   //将要删除的栈顶元素赋值给e
    S->top--;   //栈顶指针减一

    return OK;
}

/*读栈顶元素*/
Status GetTop(SqStack S, ElemType* e) {
    if (S.top == -1) {   //栈空
        return ERROR;
    }
    *e = S.elem[S.top];   //记录栈顶元素
    return OK;
}

int main()
{
    SqStack S;
    InitStack(&S);
    Push(&S, 1);
    Push(&S, 2);
    Push(&S, 3);

    ElemType e1, e2, e3;
    Pop(&S, &e1);
    Pop(&S, &e2);
    Pop(&S, &e3);
    printf("e1: %d\n", e1); // 正确地输出了 e1: 3
    printf("e2: %d\n", e2); // 正确地输出了 e2: 2
    printf("e3: %d\n", e3); // 正确地输出了 e3: 1

    ElemType n;
    GetTop(S, &n);

    return 0;
}

二、动态分配顺序栈

//头文件
#include<stdio.h>
#include<stdlib.h>
#define OK 1    //成功标识
#define ERROR 0 //失败标识

typedef int Status;	//Status是函数的类型,其值是函数结果状态代码,如OK等

// 定义顺序栈
// 指针形式
# define MAXSIZE 100   // 数组的大小
typedef int ElemType;
typedef struct {
    ElemType* base;   // 栈底指针
    ElemType* top;    // 栈顶指针
    int stacksize;     // 栈可用最大容量
}SqStack;

1.初始化

Status InitStack(SqStack* S) {
    S->base = (ElemType*)malloc(sizeof(ElemType) * MAXSIZE);
    if (!S->base)
        return ERROR;
    S->top = S->base;
    S->stacksize = MAXSIZE;
    return OK;
}

2.入栈

/*插入元素e为新的栈顶元素*/
Status Push(SqStack* S, ElemType e) {
    if (S->top - S->base == S->stacksize)
        return ERROR;
    *S->top++ = e;
    return OK;
}

3.出栈

/*若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
Status Pop(SqStack* S, ElemType* e) {
    if (S->top == S->base)
        return ERROR;
    *e = *--S->top;
    return OK;
}

4.取栈顶元素

/*读栈顶元素*/
ElemType GetTop(SqStack S) {
    if (S.top != S.base)
        return *(S.top - 1);
}

5.完整代码

#include <stdio.h>
#include <stdlib.h>
#define OK 1    //成功标识
#define ERROR 0 //失败标识

typedef int Status; //Status是函数的类型,其值是函数结果状态代码,如OK等

// 定义顺序栈
// 指针形式
#define MAXSIZE 100   // 数组的大小
typedef int ElemType;
typedef struct {
    ElemType* base;   // 栈底指针
    ElemType* top;    // 栈顶指针
    int stacksize;    // 栈可用最大容量
} SqStack;

Status InitStack(SqStack* S) {
    S->base = (ElemType*)malloc(sizeof(ElemType) * MAXSIZE);
    if (!S->base)
        return ERROR;
    S->top = S->base;
    S->stacksize = MAXSIZE;
    return OK;
}

Status Push(SqStack* S, ElemType e) {
    if (S->top - S->base == S->stacksize)
        return ERROR;
    *S->top++ = e;
    return OK;
}

Status Pop(SqStack* S, ElemType* e) {
    if (S->top == S->base)
        return ERROR;
    *e = *--S->top;
    return OK;
}

ElemType GetTop(SqStack S) {
    if (S.top != S.base)
        return *(S.top - 1);
}

int main() {
    SqStack S;
    InitStack(&S);
    Push(&S, 1);
    Push(&S, 2);
    Push(&S, 3);

    ElemType e;
    Pop(&S, &e);
    printf("Popped element: %d\n", e);

    ElemType n = GetTop(S);
    printf("Top element: %d\n", n);

    return 0;
}

三、共享栈

1.共享栈基本概念

  • 两个栈的栈顶指针都指向栈顶元素,top0=-1时0号栈为空,top1=MaxSize时1号栈为空;仅当两个栈顶指针相邻(top0+1=top1)时,判断为栈满。当0号栈进栈时top0先加1再赋值,1号栈进栈时top1先减一再赋值出栈时则刚好相反。
  • 利用栈底位置相对不变的特征,可让两个顺序栈共享一个一维数组空间,将两个栈的栈底分别设置在共享空间的两端,两个栈顶向共享空间的中间延伸,如下图所示:

2.共享栈空间结构

/*两栈共享空间结构*/
#define MAXSIZE 50  //定义栈中元素的最大个数
typedef int ElemType;   //ElemType的类型根据实际情况而定,这里假定为int
/*两栈共享空间结构*/
typedef struct{
	ElemType elem[MAXSIZE];
	int top0;	//栈0栈顶指针
	int top1;	//栈1栈顶指针
}SqDoubleStack;

3.共享栈进栈

/*插入元素e为新的栈顶元素*/
Status Push(SqDoubleStack* S, ElemType e, int stackNumber) {
    if (S->top0 + 1 == S->top1) {   //栈满
        return ERROR;
    }
    if (stackNumber == 0) {   //栈0有元素进栈
        S->elem[++S->top0] = e; //若栈0则先top0+1后给数组元素赋值
    }
    else if (stackNumber == 1) { //栈1有元素进栈
        S->elem[--S->top1] = e; //若栈1则先top1-1后给数组元素赋值
    }
    return OK;
}

4.共享栈出栈

/*若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR*/
Status Pop(SqDoubleStack* S, ElemType* e, int stackNumber) {
    if (stackNumber == 0) {
        if (S->top0 == -1) {
            return ERROR;   //说明栈0已经是空栈,溢出
        }
        *e = S->elem[S->top0--]; //将栈0的栈顶元素出栈,随后栈顶指针减1
    }
    else if (stackNumber == 1) {
        if (S->top1 == MAXSIZE) {
            return ERROR;   //说明栈1是空栈,溢出
        }
        *e = S->elem[S->top1++];    //将栈1的栈顶元素出栈,随后栈顶指针加1
    }
    return OK;
}

No.3栈的链式存储结构

一、链栈

  • 链栈是运算受限的单链表,只能在链表头部进行操作
  • 链栈中指针的方向与普通单链表指针的方向正好相反
  • 链栈的头指针就是栈顶(不需要头结点)。
  • 基本不存在栈满的情况,且空栈相当于头指针指向空。
  • 插入和删除操作仅在栈顶处进行。
  • 采用链式存储的栈称为链栈,链栈的优点是便于多个栈共享存储空间和提高其效率,且不存在栈满上溢的情况。通常采用单链表实现,并规定所有操作都是在单链表的表头进行的。这里规定链栈没有头节点,Lhead指向栈顶元素
  • 对于空栈来说,链表原定义是头指针指向空,那么链栈的空其实就是top=NULL的时候。

//头文件
#include <stdio.h>
#include <stdlib.h>
#define OK 1    //成功标识
#define ERROR 0 //失败标识

typedef int Status; //Status是函数的类型,其值是函数结果状态代码,如OK等

// 定义链栈
typedef int ElemType;
typedef struct StackNode {
    ElemType elem;
    struct StackNode* next;
}StackNode, * LinkStack;

1.初始化

Status InitStack(LinkStack* S)
{
    *S = NULL;
    return OK;
}

2.入栈

对于链栈的进栈push操作,假设元素值为e的新节点是s,top为栈顶指针。

Status Push(LinkStack* S, ElemType e)
{
    StackNode* p = (StackNode*)malloc(sizeof(StackNode));
    if (!p)
        return ERROR;
    p->elem = e;
    p->next = *S;
    *S = p;
    return OK;
}

3.出栈

链栈的出栈pop操作,也是很简单的三句操作。假设变量p用来存储要删除的栈顶结点,将栈顶指针下移以为,最后释放p即可。

Status Pop(LinkStack* S, ElemType *e)
{
    if (*S == NULL)
        return ERROR;
    *e = (*S)->elem;
    StackNode* p = *S;
    *S = (*S)->next;
    free(p);
    return OK;
}

4.取栈顶元素

ElemType GetTop(LinkStack S)
{
    if (S != NULL)
        return S->elem;
}

5.完整代码

//头文件
#include <stdio.h>
#include <stdlib.h>
#define OK 1    //成功标识
#define ERROR 0 //失败标识

typedef int Status; //Status是函数的类型,其值是函数结果状态代码,如OK等

// 定义链栈
typedef int ElemType;
typedef struct StackNode {
    ElemType elem;
    struct StackNode* next;
}StackNode, * LinkStack;

Status InitStack(LinkStack* S)
{
    *S = NULL;
    return OK;
}

Status Push(LinkStack* S, ElemType e)
{
    StackNode* p = (StackNode*)malloc(sizeof(StackNode));
    if (!p)
        return ERROR;
    p->elem = e;
    p->next = S;
    S = p;
    return OK;
}

Status Pop(LinkStack* S, ElemType *e)
{
    if (*S == NULL)
        return ERROR;
    *e = (*S)->elem;
    StackNode* p = *S;
    *S = (*S)->next;
    free(p);
    return OK;
}

ElemType GetTop(LinkStack S)
{
    if (S != NULL)
        return S->elem;
}

int main() {
    LinkStack S;
    InitStack(&S);
    Push(&S, 1);
    Push(&S, 2);
    Push(&S, 3);

    ElemType e;
    Pop(&S, &e);
    printf("Popped element: %d\n", e);

    ElemType n = GetTop(S);
    printf("Top element: %d\n", n);

    return 0;
}

二、性能分析

  • 链栈的进栈push和出栈pop操作都很简单,时间复杂度均为O(1)。
  • 对比一下顺序栈与链栈,它们在时间复杂度上是一样的,均为O(1)
  • 对于空间性能,顺序栈需要事先确定一个固定的长度,可能会存在内存空间浪费的问题,但它的优势是存取时定位很方便,而链栈则要求每个元素都有指针域,这同时也增加了一些内存开销,但对于栈的长度无限制。所以它们的区别和线性表中讨论的一样,如果栈的使用过程中元素变化不可预料,有时很小,有时非常大,那么最好是用链栈,反之,如果它的变化在可控范围内,建议使用顺序栈会更好一些。

No.4栈的应用——递归

一、递归的定义

  • 递归是一种重要的程序设计方法。简单地说,若在一个函数、过程或数据结构的定义中又应用了它自身,则这个函数、过程或数据结构称为是递归定义的,简称递归。

  • 它通常把一个大型的复杂问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的代码就可以描述岀解题过程所需要的多次重复计算,大大减少了程序的代码量但在通常情况下,它的效率并不是太高。

  • 若一个对象部分地包含它自己,或用它自己给自己进行定义,则称这个对象是递归的。

  • 若一个过程直接地或间接地调用自己,则称这个过程是递归的过程。

  • 以下三种情况常用到递归方法:

    1. 递归定义的数字函数。 ---> 斐波那契数列
    2. 具有递归特性的数据结构。 ---> 链式结构
    3. 可递归求解的问题。 ---> 分治法
  • 递归问题使用分治法求解:

    1. 分治法:对于一个较为复杂的问题,能够分解成几个相对简单的且解法相同或类似的子问题来求解。
    2. 必备的三大条件:
      1. 能将一个问题转变成一个新问题,而新问题与原问题的解法相同或类似,不同的仅是处理的对象,且这些处理对象是变化有规律的。
      2. 可以通过上述转化而使问题简化。
      3. 必须有一个明确的递归出口,或称递归的边界。
// 分治法求解递归问题算法的一般形式
void p (参数表) {
    if (递归结束条件) 可直接求解步骤;    -->  基本项
    else p (较小的参数);                -->  归纳项
}

// 阶乘函数
long Fact(long n){
    if (n==0)
        return 1;                      -->  基本项(递归终止的条件)
    else 
        return n * Fact(n-1);          -->  归纳项(递归步骤)
}

// 斐波那契数列
long Fib(long n){
    if (n==1 || n==2)
        return 1;                      -->  基本项(递归终止的条件)
    else 
        return Fib(n-1) + Fib(n-2);    -->  归纳项(递归步骤)
}

二、函数调用过程

1.调用前

  • 实参,返回地址等传递给被调用函数。
  • 为被调用函数的局部变量分配存储区。
  • 将控制权转移到被调用函数的入口

2.调用后

  • 保存被调用函数的计算结果
  • 释放被调用函数的数据区域
  • 依照被调用函数保存的返回地址将控制权转移到调用函数。

3.嵌套调用

  • 多个函数构成嵌套调用时,遵循后调用的先返回的原则。

三、递归优缺点

1.优点

  • 结构清晰,程序易读。

2.缺点

  • 每次调用要生成工作记录,保存状态信息,入栈;返回时要出栈,恢复状态信息,时间开销大。

3.注意点

  • 当追求高时间效率时,就要避免使用递归。
  • 递归 --> 非递归
    1. 方法1:尾递归、单向递归 --> 循环结构
    2. 方法2:手动设计栈来模拟系统的运行时栈。
// 阶乘函数
// 尾递归 --> 循环结构
long Fact(long n){
    int t = 1;
    for (int i = 1; i <= n; i++)
        t *= i;
    return t;
}

// 斐波那契数列
// 单向递归 --> 循环结构
long Fib(long n) {
    if (n == 1 || n == 2)
        return 1;
    else {
        int t1 = 1;
        int t2 = 1;
        int t3;
        for (int i = 3; i <= n; i++) {
            t3 = t1 + t2;
            t1 = t2;
            t2 = t3;
        }
        return t3;
    }
}

队列

No.1队列的定义

  • 队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
  • 队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。
  • 只允许在一端进行插入(入队),在另一端进行删除(出队)。 --> 头删尾插
  • 队头:允许删除的一端,也就是 a 1端。
  • 队尾:允许插入的一端,也就是 a n端。
  • 空队列:不含任何元素的队列。
  • 入队:插入元素到队尾的操作。
  • 出队:从队头中删除元素的操作。
  • 逻辑结构:与普通线性表相同(一对一关系)。
  • 存储结构:顺序队列或链队列存储均可。(循环顺序队列更常用)
  • 运算规则:只能在队头和队尾进行运算,且访问结点时依照先进先出的原则。(队列与普通线性表的唯一区别


No2.区分队空还是队满的情况

方案一:牺牲一个单元来区分队空和队满

  • 队满:队尾指针的再下一个位置就是队头,即 (Q.rear+1) % MaxSize == Q.front
  • 队空:Q.front == Q.rear
  • 队列中元素的个数: (Q->rear - Q ->front + Maxsize) % Maxsize
  • 取模%的目的就是为了避免假溢出(假溢出是因为里面还有空位置,却无法再继续操作)

方案二:不牺牲存储空间,设置size

  • 定义一个变量 size用于记录队列此时记录了几个数据元素,初始化 size = 0,进队成功 size++,出队成功size--,根据size的值判断队满与队空
  • 队满条件:Q->size == MAXSIZE
  • 队空条件:Q->size == 0
//头文件
#include <stdio.h>
#include <stdlib.h>
#define OK 1    //成功标识
#define ERROR 0 //失败标识

typedef int Status; //Status是函数的类型,其值是函数结果状态代码,如OK等

# define MAXSIZE 100      // 最大队列大小
typedef int ElemType;
typedef struct {
    ElemType* base;       // 初始化的动态分配存储空间
    int front;             // 队头指针
    int rear;              // 队尾指针
    int size;              // 队列当前长度
}SqQueue;

//初始化队列
void InitQueue(SqQueue *Q) {
    Q->rear = Q->front = 0;
    Q->size = 0;
}

方案三:不牺牲存储空间,设置tag

  • 定义一个变量 tag;tag = 0(最近进行的是删除操作);tag = 1(最近进行的是插入操作);
  • 每次删除操作成功时,都令tag = 0;只有删除操作,才可能导致队空;
  • 每次插入操作成功时,都令tag = 1;只有插入操作,才可能导致队满;
  • 队满条件:Q.front == Q.rear && tag == 1
  • 队空条件:Q.front == Q.rear && tag == 0
//头文件
#include <stdio.h>
#include <stdlib.h>
#define OK 1    //成功标识
#define ERROR 0 //失败标识

typedef int Status; //Status是函数的类型,其值是函数结果状态代码,如OK等

# define MAXSIZE 100      // 最大队列大小
typedef int ElemType;
typedef struct {
    ElemType* base;       // 队列中的数据元素类型是QElemType
    int front;             // 队头指针
    int rear;              // 队尾指针
    int tag;               //最近进行的是删除or插入
}SqQueue;

No.3队列的顺序存储结构

一、顺序队列

  • 初始状态(队空条件):Q->front == Q->rear == 0
  • 进队操作:队不满时,先送值到队尾元素,再将队尾指针加1。
  • 出队操作:队不空时,先取队头元素值,再将队头指针加1。
#define MAXSIZE 50	//定义队列中元素的最大个数
typedef struct{
	ElemType data[MAXSIZE];	//存放队列元素
	int front,rear;
}SqQueue;

  • 如图d,队列出现“上溢出”,然而却又不是真正的溢出,所以是一种“假溢出”。

二、循环队列

  • 解决假溢出的方法就是后面满了,就再从头开始,也就是头尾相接的循环。我们把队列的这种头尾相接的顺序存储结构称为循环队列。
  • 当队首指针Q->front = MAXSIZE-1后,再前进一个位置就自动到0,这可以利用除法取余运算(%)来实现。
  • a%b == a除以b的余数
  • 初始:Q.front = Q.rear = 0
  • 队首指针进1:Q.front = (Q.front + 1) % MAXSIZE
  • 队尾指针进1:Q.rear = (Q.rear + 1) % MAXSIZE --> 队尾指针后移,当移到最后一个后,下次移动会到第一个位置
  • 队列长度:(Q.rear - Q.front + MAXSIZE) % MAXSIZE
  • 队列的顺序实现是指分配一块连续的存储单元存放队列中的元素,并附设两个指针:队头指针 front指向队头元素,队尾指针 rear 指向队尾元素的下一个位置。
  • 将循环顺序队列臆造为一个环状的空间,即把存储队列元素的表从逻辑上视为一个环。
//头文件
#include <stdio.h>
#include <stdlib.h>
#define OK 1    //成功标识
#define ERROR 0 //失败标识

typedef int Status; //Status是函数的类型,其值是函数结果状态代码,如OK等

# define MAXSIZE 100      // 最大队列大小
typedef int ElemType;
typedef struct {
    ElemType* base;       // 队列中的数据元素类型是ElemType
    int front;             // 队头指针
    int rear;              // 队尾指针
}SqQueue;

1.初始化
Status InitQueue(SqQueue* Q)
{
    Q->base = (ElemType*)malloc(sizeof(ElemType) * MAXSIZE);
    if (!Q->base)
        return ERROR;
    Q->front = Q->rear = 0;
    return OK;
}
2.求队长

队尾指针减去队头指针再加上队列大小,队其队列大小进行取模。 (避免负数的出现)

int QueueLength(SqQueue Q)
{
    return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}
3.入队

在队尾插入一个新元素

Status EnQueue(SqQueue* Q, ElemType e)
{
    if ((Q->rear + 1) % MAXSIZE == Q->front)
        return ERROR;
    Q->base[Q->rear] = e;
    Q->rear = (Q->rear + 1) % MAXSIZE;
    return OK;
}
4.出队

将队头元素删除

Status DeQueue(SqQueue* Q, ElemType* e)
{
    if (Q->front == Q->rear)
        return ERROR;
    *e = Q->base[Q->front];
    Q->front = (Q->front + 1) % MAXSIZE;
    return OK;
}
5.取队头元素

当队列非空时,返回当前队头元素值,队头指针保持不变。

ElemType GetHead(SqQueue Q)
{
    if (Q.front != Q.rear)
        return Q.base[Q.front];
}
6.完整代码
//头文件
#include <stdio.h>
#include <stdlib.h>
#define OK 1    //成功标识
#define ERROR 0 //失败标识

typedef int Status; //Status是函数的类型,其值是函数结果状态代码,如OK等

# define MAXSIZE 100      // 最大队列大小
typedef int ElemType;
typedef struct {
    ElemType* base;       // 队列中的数据元素类型是QElemType
    int front;             // 队头指针
    int rear;              // 队尾指针
}SqQueue;

Status InitQueue(SqQueue* Q)
{
    Q->base = (ElemType*)malloc(sizeof(ElemType) * MAXSIZE);
    if (!Q->base)
        return ERROR;
    Q->front = Q->rear = 0;
    return OK;
}

int QueueLength(SqQueue Q)
{
    return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}

Status EnQueue(SqQueue* Q, ElemType e)
{
    if ((Q->rear + 1) % MAXSIZE == Q->front)
        return ERROR;
    Q->base[Q->rear] = e;
    Q->rear = (Q->rear + 1) % MAXSIZE;
    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) % MAXSIZE;
    return OK;
}

ElemType GetHead(SqQueue Q)
{
    if (Q.front != Q.rear)
        return Q.base[Q.front];
}

int main()
{
    SqQueue Q;
    InitQueue(&Q);
    QueueLength(Q);

    EnQueue(&Q, 1);
    EnQueue(&Q, 2);
    EnQueue(&Q, 3);
    QueueLength(Q);

    ElemType e;
    DeQueue(&Q, &e);
    QueueLength(Q);

    GetHead(Q);

}

No.4队列的链式存储结构

一、链式队列

  • 队列的链式表示称为链队列,它实际上是一个同时带有队头指针和队尾指针的单链表。
  • 用链表表示的队列,是限制仅在表头删除和表尾插入的单链表。
  • 若无法估计所用队列的长度,则采用链队列。
  • 空队列时,front和rear都指向头结点。

  • 队列的链式存储结构表示为链队列,它实际上是一个同时带有队头指针和队尾指针的单链表,只不过它只能尾进头出而已

//头文件
#include <stdio.h>
#include <stdlib.h>
#define OK 1    //成功标识
#define ERROR 0 //失败标识

typedef int Status; //Status是函数的类型,其值是函数结果状态代码,如OK等

# define MAXSIZE 100      // 最大队列大小
typedef int ElemType;
typedef struct QNode {
    ElemType elem;         // 数据域
    struct QNode* next;     // 指针域
}QNode, * QueuePtr;

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

1.初始化

Status InitQueue(LinkQueue* Q)
{
    Q->front = Q->rear = (QNode*)malloc(sizeof(QNode));
    Q->front->next = NULL;
    return OK;
}

2.入队

Status EnQueue(LinkQueue* Q, ElemType e)
{
    QNode* p = (QNode*)malloc(sizeof(QNode));
    p->elem = e;
    p->next = NULL;
    Q->rear->next = p;
    Q->rear = p;
    return OK;
}

3.出队

Status DeQueue(LinkQueue* Q, ElemType* e)
{
    if (Q->front == Q->rear)
        return ERROR;
    QNode *p = Q->front->next;
    e = p->elem;
    Q->front->next = p->next;
    if (Q->rear == p)
        Q->rear = Q->front;
    free(p);
    return OK;
}

4.取队头元素

ElemType GetHead(LinkQueue Q)
{
    if (Q.front != Q.rear)
        return Q.front->next->elem;
}

5.完整代码

//头文件
#include <stdio.h>
#include <stdlib.h>
#define OK 1    //成功标识
#define ERROR 0 //失败标识

typedef int Status; //Status是函数的类型,其值是函数结果状态代码,如OK等
typedef int ElemType;

typedef struct QNode {
    ElemType elem;         // 数据域
    struct QNode* next;     // 指针域
}QNode, * QueuePtr;

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

Status InitQueue(LinkQueue* Q)
{
    Q->front = Q->rear = (QNode*)malloc(sizeof(QNode));
    Q->front->next = NULL;
    return OK;
}

Status EnQueue(LinkQueue* Q, ElemType e)
{
    QNode* p = (QNode*)malloc(sizeof(QNode));
    p->elem = e;
    p->next = NULL;
    Q->rear->next = p;
    Q->rear = p;
    return OK;
}

Status DeQueue(LinkQueue* Q, ElemType* e)
{
    if (Q->front == Q->rear)
        return ERROR;
    QNode *p = Q->front->next;
    e = p->elem;
    Q->front->next = p->next;
    if (Q->rear == p)
        Q->rear = Q->front;
    free(p);
    return OK;

}

ElemType GetHead(LinkQueue Q)
{
    if (Q.front != Q.rear)
        return Q.front->next->elem;
}

int main()
{
    LinkQueue Q;
    InitQueue(&Q);

    EnQueue(&Q, 1);
    EnQueue(&Q, 2);
    EnQueue(&Q, 3);

    ElemType e;
    DeQueue(&Q, &e);

    ElemType n;
    GetHead(Q, &n);
}

No.5案例与分析

一、数制转换

# include<stdio.h>
# include<stdbool.h>
# include<stdlib.h>
# define MAXSIZE 100

typedef int SElemType;
typedef struct {
	SElemType* top;
	SElemType* base;
	int stacksize;
}SqStack;

bool InitStack(SqStack& S) {
	S.base = (SElemType*)malloc(MAXSIZE * sizeof(SElemType));
	if (!S.base)
		return false;
	S.top = S.base;
	S.stacksize = MAXSIZE;
	return true;
}

bool StackEmpty(SqStack S) {
	if (S.top == S.base)
		return true;
	else
		return false;
}

bool Push(SqStack& S, SElemType e) {
	if (S.top - S.base == S.stacksize)
		return false;
	*S.top++ = e;

	return true;
}

bool Pop(SqStack& S, SElemType& e) {
	if (S.top == S.base)
		return false;
	e = *--S.top;

	return true;
}

bool Conversion(int N) {
	SqStack S;
	int n;
	printf("请输入进制数:");
	scanf("%d", &n);
	InitStack(S);     // 初始化空栈
	while (N)         // 当N非零时,循环
	{
		Push(S, N % n);  // 将N与进制数求余数得到的数压入栈S
		N /= n;          // N更新为N与进制数的商
	}
	while (!StackEmpty(S))   // 当栈S非空时,循环
	{
		SElemType e;
		Pop(S, e);           // 弹出栈顶元素e
		printf("%d", e);     // 输出e
	}

	return true;
}

int main() {
	Conversion(159);
}

二、括号匹配的检验

# include<stdio.h>
# include<stdbool.h>
# include<stdlib.h>
# define MAXSIZE 100

typedef int SElemType;
typedef struct {
	SElemType* top;
	SElemType* base;
	int stacksize;
}SqStack;

bool InitStack(SqStack& S) {
	S.base = (SElemType*)malloc(MAXSIZE * sizeof(SElemType));
	if (!S.base)
		return false;
	S.top = S.base;
	S.stacksize = MAXSIZE;
	return true;
}

bool StackEmpty(SqStack S) {
	if (S.top == S.base)
		return true;
	else
		return false;
}

bool Push(SqStack& S, SElemType e) {
	if (S.top - S.base == S.stacksize)
		return false;
	*S.top++ = e;

	return true;
}

bool Pop(SqStack& S, SElemType& e) {
	if (S.top == S.base)
		return false;
	e = *--S.top;

	return true;
}

SElemType GetTop(SqStack S) {
	if (S.top != S.base)
		return *(S.top - 1);
}

bool Matching() {
	SqStack S;
	InitStack(S);       // 初始化空栈
	int flag = 1;       // 标记匹配结果以控制循环及返回结果
	int e;
	char ch;
	scanf("%c", &ch);   // 读入第一个字符
	while (ch != '#' && flag)      // 通过表达式'#'或者 flag = 0来结束循环
	{
		switch (ch) {
		// 若是左括号,将其压入栈
		case'[':
			Push(S, ch);
			break;
		case'(':
			Push(S, ch);
			break;

		// 若是右括号,分情况考虑
		// 若栈非空且栈顶元素为左括号,则正确匹配
		// 若栈为空或栈顶元素不是左括号,则错误匹配
		case')':
			if (!StackEmpty(S) && GetTop(S) == '(')
				Pop(S, e);
			else
				flag = 0;
			break;
		case']':
			if (!StackEmpty(S) && GetTop(S) == '[')
				Pop(S, e);
			else
				flag = 0;
			break;
		}
		scanf("%c", &ch);     // 继续读入下一个字符
	}
	if (StackEmpty(S) && flag)
		return true;       // 匹配成功
	else 
		return false;      // 匹配失败
}

int main() {
	Matching();
	/*
	正确匹配:(()[])
	错误匹配:[([])
	*/
}

三、舞伴问题

# include<stdio.h>
# include<stdbool.h>
# include<stdlib.h>
# define MAXQSIZE 13

typedef struct {
	char name[20]; //姓名
	char sex; //性别,'F'表示女性,'M'表示男性
}Person;

typedef struct {
	Person* base; //队列中数据元素类型为Person
	int front; //头指针
	int rear;  //尾指针
}SqQueue;

bool InitQueue(SqQueue& Q) {
	Q.base = (Person*)malloc(MAXQSIZE * sizeof(Person));
	if (!Q.base)
		return false;
	Q.front = Q.rear = 0;
	return true;
}

bool QueueEmpty(SqQueue Q) {
	if (Q.front == Q.rear)
		return true;
	else
		return false;
}

bool EnQueue(SqQueue& Q, Person p) {
	if ((Q.rear + 1) % MAXQSIZE == Q.front)   // 队满情况
		return false;
	Q.base[Q.rear] = p;       // 将新的元素值赋值给队尾
	Q.rear = (Q.rear + 1) % MAXQSIZE;  // 队尾指针向后移一位,取模的目的是为了避免假溢出的情况,能够回到最初位置0
	return true;
}

bool DeQueue(SqQueue& Q, Person& p) {
	if (Q.front == Q.rear)    // 队空情况
		return false;
	p = Q.base[Q.front];      // 将对头的元素值赋值给e
	Q.front = (Q.front + 1) % MAXQSIZE;  // 队头指针向后移一位,取模的目的是为了避免假溢出的情况,能够回到最初位置0
	return true;
}

Person GetHead(SqQueue Q) {
	if (Q.front != Q.rear)     // 判断队列是否为空
		return Q.base[Q.front];
}

void DancePartner(Person dancer[], int num)
{//结构数组dancer中存放跳舞的男女,num是跳舞的人数。
	SqQueue Mdancers, Fdancers;
	Person p;
	int i;
	InitQueue(Mdancers); //男士队列初始化
	InitQueue(Fdancers); //女士队列初始化
	for (i = 0; i < num; i++) //依次将跳舞者根据其性别人队
	{
		p = dancer[i];
		if (p.sex == 'F') EnQueue(Fdancers, p); //插入女队
		else EnQueue(Mdancers, p); //插人男队
	}
	printf("The dancing partners are:\n");
	while (!QueueEmpty(Fdancers) && !QueueEmpty(Mdancers))
	{//依次输出男女舞伴的姓名
		DeQueue(Fdancers, p); //女士出队
		printf("%s\n", p.name); //输出出队女士姓名
		DeQueue(Mdancers, p); //男士出队
		printf("%s\n", p.name); //输出出队男士姓名
	}
	if (!QueueEmpty(Fdancers)) //女士队列非空,输出队头女士的姓名
	{
		p = GetHead(Fdancers); //取女士队头
		printf("The first woman to get a partner is: %s\n", p.name);
	}
	else if (!QueueEmpty(Mdancers)) //男士队列非空,输出队头男士的姓名
	{
		p = GetHead(Mdancers); //取男士队头
		printf("The first man to get a partner is: %s\n", p.name);
	}
}

int main() {
	Person dancer[MAXQSIZE];
	int i, num;
	printf("请输入跳舞总人数:");
	scanf("%d", &num);
	printf("请输入各个跳舞人的姓名和性别('F'表示女性,'M'表示男性):\n");
	for (i = 0; i < num; i++)
	{
		printf("请输入第%d个跳舞人的姓名和性别(用空格隔开):", i + 1);
		scanf("%s %c", &dancer[i].name, &dancer[i].sex);
	}
	DancePartner(dancer, num);
	return 0;

}
posted @ 2023-08-05 15:10  Ghost3  阅读(60)  评论(0编辑  收藏  举报