3.4、栈与递归
3.4.1、采用递归算法解决的问题
1、定义是递归的:
- 若一个对象部分地包含自己,或用它自己给自己定义,则称这个对象是递归的;
- 若一个过程直接或者间接的调用自己,则称这个过程是递归的过程。
递归问题——用分治法求解
分治法:对于一个较为复杂的问题,能够分解成几个相对简单且解法相同或类似的子问题来求解
必备的三个条件
- 1、能将一个问题转变成一个新问题,而且新问题与原问题的解法相同或类同,不同的仅是处理的对象,且这些处理对象是变化有规律的
- 2、可以通过上述转化而使问题简化
- 3、必须有一个明确的递归出口,或者递归的边界
”‘分治法“求解递归问题算法的一般形式为:
voidp(参数) { if (递归结束条件成立) 可直接求解; // 递归终止的条件elsep(较小的参数); // 递归步骤} // 例如longFact (longn) { if (n==0) return1; // 基本项elsereturnn*Fact(n-1); // 归纳项}
函数调用过程:
调用前,系统完成:
- 将实参,返回地址等传递给被调用函数
- 为被调用函数的局部变量分配存储区
- 将控制转移到被调用函数的入口
调用后,系统完成:
- 保存被调用函数的计算结果
- 释放被调用的函数的数据区
- 依照被调用函数保存的返回地址将控制转移到调用函数
多个函数构成嵌套调用时:
第四节记录不全:详细内容请观看第05周12--3.5队列的表示和实现1--3.5.1队列的类型定义哔哩哔哩
3.5、队列的表示和操作的实现
为了防止大家忘记队列的相关概念
3.5.1、相关术语:
- 队列(Queue)是仅在表尾进行插入操作,在表头进行删除操作的线性表
- 表尾即an端,称为队尾;表头即a1端,称为队头。
- 它是一种先进先出(FIFO)的线性表
- 插入元素称为入队;删除元素称为出队
- 队列的存储结构为链队或顺序队(常用循环顺序队)
3.5.2、队列的相关概念
- 定义:只能在表的一端进行插入运算,在表的另一端进行删除运算的线性表
- 逻辑结构: 与线性表相同,仍为一对一关系
- 存储结构:顺序队或链队,以循环顺序队列更常见。
- 运算规则:只能在队首或队尾运算,且访问结点时依照先进先出(FIFO)的原则
- 实现方式:关键是掌握入队和出队操作,具体实现依顺序队或链队的不同而不同
3.5.3、队列的类型定义
ADTQueue { 数据对象: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); 操作结果:构造空队列QDestroyQueue(&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]
// 最大队列长度Typedefstruct { QElemType*base; // 初始化的动态分配存储空间intfront; // 头指针intrear; // 尾指针}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,表示队列为空
【算法描述】
StatusInitQueue(SqQueue&Q) { Q.base=newQElemType[MAXQSIZE]; // 分配数组空间// Q.base = (QElemType*)malloc(MAXQSIZE * sizeof(QElemType));if (!Q.base) exit(OVERFLOW); // 存储分配失败Q.front=Q.rear=0; // 头指针尾指针置为0,队列为空returnOK; }
4、求队列的长度
- 对于非循环队列,尾指针和头指针的差值就是队列的长度,而循环队列,差值可能为负数,所以需要加上MAXQSIZE,再与MAXQSIZE求余
【算法描述】
intQueueLength(SqQueueQ) { return (Q.rear-Q.front+MAXQSIZE) %MAXQSIZE; }
5、循环队列入队
【算法步骤】
- 判断队列是否满,若满则返回ERROR
- 将新元素插入队尾
- 队尾指针加1
【算法描述】
StatusEnQueue(SqQueue&Q, QElemTypee) { // 插入元素e为Q的新的队尾元素if ((Q.rear+1) %MAXQSIZE==Q.front) // 尾指针在循环意义上加1后等于头指针,表明队满returnERROR; Q.base[Q.rear] =e; // 新元素插入队尾Q.rear= (Q.rear+1) %MAXQSIZE; // 队尾指针加1returnOK; }
6、循环队列的出队
【算法步骤】
- 判断队列是否为空,若空则返回ERROR
- 保存队头元素
- 队头指针加1
【算法描述】
StatusDeQueue(SqQueue&Q, QElemType&e) { // 删除Q的队头元素if (Q.front==Q.rear) returnERROR; // 队空e=Q.base[Q.front]; // 保存队头元素Q.front= (Q.front+1) %MAXQSIZE; // 队头指针加1returnOK; }
7、循环队列取队头元素
- 当队列非空时,此操作返回当前队头元素的值,队头指针保持不变
【算法描述】
SElemTypeGetHead(SqQueueQ) { // 返回Q的队头元素,不修改队头指针if (Q.front!=Q.rear) // 队列非空returnQ.base[Q.front]; // 返回队头元素的值,队头指针不变}
3.5.5、队列的链式表示和实现
链队列运算指针变化状况
1、链式队列的定义
// 最大队列长度typedefstructQnode { QElemTypedata; structQNode*next; }QNode, *QueuePtr; typedefstruct { QueuePtrfront; // 队头指针QueuePtrrear; // 队尾指针}LinkQueue;
2、链队列初始化
【算法步骤】
- 生成新结点作为头结点,队头和队尾指针指向此结点
- 头结点的指针域为空
【算法描述】
StatusInitQueue(LinkQueue&Q) { // 构造一个空队列Q.front=Q.rear=newQNode; // 生成新结点作为头结点,队头和队尾指针指向该结点Q.front->next=NULL; // 头结点的指针域为空returnOK; }
3、链队的销毁
【算法步骤】
- 从头结点开始,依次释放所有结点
【算法描述】
StatusDestroyQueue (LinkQueue&Q) { while(Q.front) { Q.rear=Q.front->next; free(Q.front); Q.front=Q.rear; }+returnOK; }
4、链队列的入队
【算法描述】
- 为入队元素分配结点空间,用指针p指向
- 将新结点数据域置为e
- 将新结点插入到队尾
- 修改队尾指针为p
【算法描述】
StatusEnQueue(LinkQueue&Q, QElemTypee) { // 插入元素e为Q的新的队尾元素p=newQNode; // 为入队元素分配结点空间,用指针p指向p->data=e; // 将新结点数据域置为ep->next=NULL; Q.rear->next=p; // 将新结点插入到队尾Q.rear=p; // 修改队尾指针returnOK; }
5、链队列的出队
【算法步骤】
- 判断队列是否为空,若为空则返回ERROR
- 临时保存队头元素的空间,以备释放
- 修改头结点的指针域, 指向下一个结点
- 判断出队元素是否为最后一个元素,若是,则将队尾指针重新赋值,指向头结点
- 释放原队头元素的空间
【算法描述】
StatusDeQueue(LinkQueue&Q, QElemType&e){ // 删除Q的队头元素,用e返回其值if (Q.front==Q.rear) returnERROR; // 若队列为空,则返回ERRORp=Q.front->next; // p指向队头元素e=p->data; // e保存队头元素的值Q.front->next=p->next; // 修改头结点的指针域if (Q.rear==p) Q.rear=Q.front; // 最后一个元素被删,队尾指针指向头结点deletep; // 释放原队头元素的空间returnOK; }
6、链队中求队头元素
【算法描述】
SElemTypeGetHead(LinkQueueQ) { // 返回Q的队头元素,不修改队头指针if (Q.front!=Q.rear) // 队列非空returnQ.front->next->data; // 返回队头元素的值,队头指针不变}