数据结构(严蔚敏版)第三章——栈和队列(三)【队列的表示和操作的实现】

3.4、栈与递归

3.4.1、采用递归算法解决的问题

1、定义是递归的:
  • 若一个对象部分地包含自己,或用它自己给自己定义,则称这个对象是递归的;
  • 若一个过程直接或者间接的调用自己,则称这个过程是递归的过程。

递归问题——用分治法求解

分治法:对于一个较为复杂的问题,能够分解成几个相对简单且解法相同或类似的子问题来求解

必备的三个条件

  • 1、能将一个问题转变成一个新问题,而且新问题与原问题的解法相同或类同,不同的仅是处理的对象,且这些处理对象是变化有规律的
  • 2、可以通过上述转化而使问题简化
  • 3、必须有一个明确的递归出口,或者递归的边界

”‘分治法“求解递归问题算法的一般形式为:

void p(参数) {
  if (递归结束条件成立) 可直接求解;			// 递归终止的条件
  else p(较小的参数);								 // 递归步骤
}

// 例如
long Fact (long n) {
  if (n == 0) return 1;							// 基本项
  else return n * Fact(n - 1);			// 归纳项
}

函数调用过程:

调用前,系统完成:

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

调用后,系统完成:

  1. 保存被调用函数的计算结果
  2. 释放被调用的函数的数据区
  3. 依照被调用函数保存的返回地址将控制转移到调用函数

多个函数构成嵌套调用时:

image-20221014150823749

第四节记录不全:详细内容请观看第05周12–3.5队列的表示和实现1–3.5.1队列的类型定义_哔哩哔哩_

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

为了防止大家忘记队列的相关概念

3.5.1、相关术语:
  • 队列(Queue)是仅在表尾进行插入操作,在表头进行删除操作的线性表
  • 表尾即an端,称为队尾;表头即a1端,称为队头。
  • 它是一种先进先出(FIFO)的线性表
  • 插入元素称为入队;删除元素称为出队
  • 队列的存储结构为链队或顺序队(常用循环顺序队)
3.5.2、队列的相关概念
  1. 定义:只能在表的一端进行插入运算,在表的另一端进行删除运算的线性表
  2. 逻辑结构: 与线性表相同,仍为一对一关系
  3. 存储结构:顺序队或链队,以循环顺序队列更常见。
  4. 运算规则:只能在队首或队尾运算,且访问结点时依照==先进先出(FIFO)==的原则
  5. 实现方式:关键是掌握入队和出队操作,具体实现依顺序队或链队的不同而不同
3.5.3、队列的类型定义
ADT Queue {
  数据对象:D = {ai | ai 属于 ElemSet, i = 1, 2, ... , n, n >= 0}
  数据关系:R = { <ai-1, ai> | ai-1, ai 属于D, i = 2, ... , n} // 约定其中a1端为队列头,an端为队列尾
  基本操作:
    InitQueue(&Q);
  	操作结果:构造空队列Q
    DestroyQueue(&Q);
  	条件:队列Q已存在
  	操作结果:队列Q被销毁
  	ClearQueue(&Q);
  	条件:队列Q已存在
  	操作结果:将Q清空
    QueueLength(&Q);
    条件:队列Q已存在
  	操作结果:返回Q的元素个数,即队长
    GetHead(Q, &e);
  	条件:Q为非空队列
  	操作结果:用e返回Q的队头元素
    EnQueue(&Q, e);
    条件:队列Q已存在
  	操作结果:插入元素e为Q的队尾元素
    DeQueue(&Q, &e);
    条件:Q为非空队列
  	操作结果:删除Q的队头元素,用e返回值
    QueueTraverse(Q);
  	条件:Q存在且非空
    操作结果:从队头到队尾,依次对Q的每个数据元素访问
}
  • 队列的物理存储可以分为顺序存储结构,也可用链式存储结构即:顺序队列链式队列
3.5.4、队列的顺序表示和实现
1、顺序存储结构
  • 队列的顺序表示—用一维数组base[MAXQSIZE]
#define MAXQSIZE 100			// 最大队列长度
Typedef struct {
  QElemType *base;				// 初始化的动态分配存储空间
  int front;							// 头指针
  int rear;								// 尾指针
}SqQueue;					
2、解决假上溢的方法——引入循环队列

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

实现方法:利用模(mod, C语言中:%)运算

插入元素:Q.base[Q.rear] = x;

​ Q.rear = (Q.rear + 1) % MAXQSIZE;

删除元素:x = Q.base[s.front]

​ Q.front = (Q.front + 1) % MAXQSIZE;

两个重要的判断条件:

队空的条件:Q.front == Q.rear

队满的条件:(Q.rear+1) % MAXQSIZE == Q.front

3、队列的初始化

【算法步骤】

  • 为队列分配一个最大容量为MAXQSIZE的数组空间,base指向数组空间的首地址。
  • 头指针和尾指针置为0,表示队列为空

【算法描述】

Status InitQueue(SqQueue &Q) {
  Q.base = new QElemType[MAXQSIZE];						// 分配数组空间
  // Q.base = (QElemType*)malloc(MAXQSIZE * sizeof(QElemType));
  if (!Q.base) exit(OVERFLOW);								// 存储分配失败
  Q.front = Q.rear = 0;												// 头指针尾指针置为0,队列为空
  return OK;
}
4、求队列的长度
  • 对于非循环队列,尾指针和头指针的差值就是队列的长度,而循环队列,差值可能为负数,所以需要加上MAXQSIZE,再与MAXQSIZE求余

【算法描述】

int QueueLength(SqQueue Q) {
  return (Q.rear - Q.front + MAXQSIZE) % MAXQSIZE;
}
5、循环队列入队

【算法步骤】

  • 判断队列是否满,若满则返回ERROR
  • 将新元素插入队尾
  • 队尾指针加1

【算法描述】

Status EnQueue(SqQueue &Q, QElemType e) {
  // 插入元素e为Q的新的队尾元素
  if ((Q.rear + 1) % MAXQSIZE == Q.front) 				// 尾指针在循环意义上加1后等于头指针,表明队满
    return ERROR;
  Q.base[Q.rear] = e;															// 新元素插入队尾
  Q.rear = (Q.rear + 1) % MAXQSIZE;								// 队尾指针加1
  return OK;
}
6、循环队列的出队

【算法步骤】

  • 判断队列是否为空,若空则返回ERROR
  • 保存队头元素
  • 队头指针加1

【算法描述】

Status DeQueue(SqQueue &Q, QElemType &e) {
  // 删除Q的队头元素
  if (Q.front == Q.rear) return ERROR;						// 队空
  e = Q.base[Q.front];														// 保存队头元素
  Q.front = (Q.front + 1) % MAXQSIZE;							// 队头指针加1
  return OK;
}
7、循环队列取队头元素
  • 当队列非空时,此操作返回当前队头元素的值,队头指针保持不变

【算法描述】

SElemType GetHead(SqQueue Q) {
  // 返回Q的队头元素,不修改队头指针
  if (Q.front != Q.rear)						// 队列非空
    return Q.base[Q.front];					// 返回队头元素的值,队头指针不变
}
3.5.5、队列的链式表示和实现

链队列运算指针变化状况

image-20221017182033498

1、链式队列的定义
#define MAXQSIZE 100				// 最大队列长度
typedef struct Qnode {
  QElemType data;
  struct QNode *next;
}QNode, *QueuePtr;

typedef struct {
  QueuePtr front;						// 队头指针
  QueuePtr rear;						// 队尾指针
}LinkQueue;
2、链队列初始化

【算法步骤】

  • 生成新结点作为头结点,队头和队尾指针指向此结点
  • 头结点的指针域为空

【算法描述】

Status InitQueue(LinkQueue &Q) {
  // 构造一个空队列
  Q.front = Q.rear = new QNode;							// 生成新结点作为头结点,队头和队尾指针指向该结点
  Q.front->next = NULL;											// 头结点的指针域为空
  return OK;
}
3、链队的销毁

【算法步骤】

  • 从头结点开始,依次释放所有结点

【算法描述】

Status DestroyQueue (LinkQueue &Q) {
  while(Q.front) {
    Q.rear = Q.front->next;
    free(Q.front);
    Q.front = Q.rear;
  }+
  return OK;
}
4、链队列的入队

【算法描述】

  • 为入队元素分配结点空间,用指针p指向
  • 将新结点数据域置为e
  • 将新结点插入到队尾
  • 修改队尾指针为p

【算法描述】

Status EnQueue(LinkQueue &Q, QElemType e) {
  // 插入元素e为Q的新的队尾元素
  p = new QNode;												// 为入队元素分配结点空间,用指针p指向
  p -> data = e;												// 将新结点数据域置为e
  p -> next = NULL; Q.rear -> next = p;	// 将新结点插入到队尾
  Q.rear = p;														// 修改队尾指针
  return OK;
}
5、链队列的出队

【算法步骤】

  • 判断队列是否为空,若为空则返回ERROR
  • 临时保存队头元素的空间,以备释放
  • 修改头结点的指针域, 指向下一个结点
  • 判断出队元素是否为最后一个元素,若是,则将队尾指针重新赋值,指向头结点
  • 释放原队头元素的空间

【算法描述】

Status DeQueue(LinkQueue &Q, QElemType &e){
  // 删除Q的队头元素,用e返回其值
  if (Q.front == Q.rear) return ERROR;					// 若队列为空,则返回ERROR
  p = Q.front -> next;												  // p指向队头元素
  e = p -> data;																// e保存队头元素的值
  Q.front -> next = p -> next;									// 修改头结点的指针域
  if (Q.rear == p) Q.rear = Q.front;						// 最后一个元素被删,队尾指针指向头结点
  delete p;																			// 释放原队头元素的空间
  return OK;
}
6、链队中求队头元素

【算法描述】

SElemType GetHead(LinkQueue Q) {
  // 返回Q的队头元素,不修改队头指针
  if (Q.front != Q.rear)													// 队列非空
    return Q.front -> next -> data;								// 返回队头元素的值,队头指针不变
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

java小豪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值