数据结构和算法设计4 栈,队列和递归
2021/10/3 20:14:05
本文主要是介绍数据结构和算法设计4 栈,队列和递归,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
目录
一.栈
1.栈的定义和特点
2.栈的基本操作与类模板的定义
3.共享栈
1. 设计思路
2.共享栈的类模板定义与实现
二.队列
1.队列的定义和特点
2.顺序队列
1.顺序队列的三种正常状态
2.顺序队列的上溢和下溢
3.循环队列
1.循环队列基本思想
2.队满、队空判定条件
3.循环队列的类模板定义和实现
三.递归
1 .递归的概念
2.递归的应用 :
3.递归过程与递归工作栈
1.非递归函数的调用
2.递归函数的调用
一.栈
1.栈的定义和特点
栈(stack)是一种常用的重要的数据结构,其应用十分广泛。栈定义为只允许在表的末端进行插入和删除的线性表。允许插入和删除的一端叫做栈顶(top),另一端叫做栈底(bottom)。无元素的栈称为空栈。
栈的特点:先进后出。
2.栈的基本操作与类模板的定义
由于栈本身就是一个操作受限的线性表,所以顺序栈和链式栈的基本操作与线性表的操作基本一样,甚至还要简单,所以这里就不给出普通栈的定义了,有需要的同学可以看一下
数据结构与算法设计3 顺序表和链表_m0_54024106的博客-CSDN博客
3.共享栈
栈的顺序存储结构是一种静态的存储结构,栈中元素的多少受到maxSize的限制,空间太大会造成存储空间的浪费,为解决这个问题,同时建立多个顺序栈,以实现存储空间的共享,这样就可以相互调节余缺,既能高效地节约存储空间,又能降低上溢的发生概率。 为两个栈申请一个共享的一维数组空间,将两个栈的栈底分别放在一维数组的两端,分别是-1和m。 由于两个栈顶动态变化,这样可以形成互补,使得每个栈可用的最大空间与实际使用的需求有关。由此可见,两栈共享比两个栈分别申请M/2的空间利用率高。
栈空的条件:top1 == -1,top2==m
栈满的条件:top1 + 1 == top2
1. 设计思路
(1)定义两个栈顶指针top1,top2分别管理共享栈中的前后两个栈。
(2)定义函数成员时,通过参数指明相关操作实施的栈号,例如Status Push(int n,const ElemType e);//入栈函数,其中n为1或2,表示入栈的序号。
(3)判断栈空函数的实现:当top1等于-1,top2等于maxSize时栈空。
(4)入栈功能实现:先判断是否栈满,再判断n的值
n=1时,将top1+1,再将top1位置的元素赋为e;
n=2时,将top2-1,再将top2位置的元素赋为e。
(5)出栈功能实现:先判断是否栈空,再判断n的值
n=1时,将top1位置的元素赋给e,再将top1-1;
n=2时,将top2位置的元素赋给e,再将top2+1。
枚举类型Status定义
#pragma once typedef enum{ NOT_PRESENT, ENTRY_FOUND, RANGE_ERROR, SUCCESS, OVER_FLOW, DEFAULT_SIZE }Status;
2.共享栈的类模板定义与实现
#pragma once #include"status.h" template<class ElemType> class SeqStack { protected: int top1, top2; // 栈顶指针 int maxSize; // 栈最大容量 ElemType* elems; // 元素存储空间 public: SeqStack(int size = DEFAULT_SIZE);//构造函数 virtual ~SeqStack();//析构函数 int GetLength(int n);//取得栈的长度 bool IsEmpty(int n) const;//判断栈是否为空 Status Push(int n,const ElemType e);//入栈函数 Status Pop(int n,ElemType& e);//出栈函数 void Traverse(int n,void (*Visit)(const ElemType&)) const;//遍历函数 }; //构造函数 template<class ElemType> SeqStack<ElemType>::SeqStack(int size) // 操作结果:构造一个最大容量为size的空栈 { maxSize = size; elems = new ElemType[maxSize]; top1 = -1; top2 = size; } //析构函数 template<class ElemType> SeqStack<ElemType>:: ~SeqStack() { delete [] elems; } //取得栈的长度 template<class ElemType> int SeqStack<ElemType>::GetLength(int n) { //判断n的值 if (n == 1) //共享栈前面的栈 return top1+1; if (n == 2) //共享栈后面的栈 return maxSize-top2; } //判断栈是否为空 template<class ElemType> bool SeqStack<ElemType>:: IsEmpty(int n) const { //前面那个栈top1=-1的时候为空,后面那个栈top2=maxSize的时候为空; if (n == 1) return (top1 == -1) ; if (n == 2) return (top2 == maxSize) ; } //入栈函数 template<class ElemType> Status SeqStack<ElemType>::Push(int n, const ElemType e) { if (top1 + 1 == top2)//判断是否栈满 return OVER_FLOW; else{ if (n == 1) elems[++top1] = e;//先将top1+1,再将top1位置的元素赋为e if (n == 2) elems[--top2] = e;//先将top2-1,再将top2位置的元素赋为e return SUCCESS; } } //出栈函数 template<class ElemType> Status SeqStack<ElemType>::Pop(int n, ElemType& e) { if (IsEmpty(1)&& IsEmpty(2) )//判断两个栈是否都为空,如果是返回下溢信息 return UNDER_FLOW; else { if (n == 1) e=elems[top1--] ;//先将top1位置的元素赋给e,再将top1-1; if (n == 2) e=elems[top2++];//先将top2位置的元素赋给e,再将top2+1; return SUCCESS; } } //遍历函数 template<class ElemType> void SeqStack<ElemType>::Traverse(int n,void (*Visit)(const ElemType&)) const { if (n == 1) { for (int i = top1; i >= 0; i--) { (*Visit)(elems[i]); } } if (n == 2) { for (int i = top2; i <= maxSize - 1; i++) { (*Visit)(elems[i]); } } }
二.队列
1.队列的定义和特点
队列 (Queue)是另一种限定性的线性表,它只允许在表的一端插入元素,而在另一端删除元素。 在队列中,允许插入的一端叫做队尾(rear),允许删除的一端则称为队头(front)。 在队列中插入一个新元素的操作简称为进队或入队,新元素进队后就成为新的队尾元素; 从队列中删除一个元素的操作简称为出队或离队,当元素出队后,其后继元素就成为新的队头元素。
假设队列为q = (a1, a2 , … , an ),那么a1是队头元素,an是队尾元素。队列中的元素是按照a1, a2 , … , an的顺序进入的,退出队列也只能按照这个顺序退出。
队列的特点:先进先出。
2.顺序队列
1.顺序队列的三种正常状态
(1)若顺序队列为空,则front==rear,队列的初始状态可设置为front=rear=0;
(2)若顺序队列为满,则front==0且rear==MAXSIZE;
(3)若顺序队列非空非满,则MAXSIZE>rear>front;
2.顺序队列的上溢和下溢
(1)当队空时,再进行出队就会产生“下溢”;
(2)当队满时,再进行入队就会产生“上溢”;
(3)此外,顺序队列还存在“假上溢”现象,即rear==MAXSIZE且front>0。
如何解决,最容易想到的就是将front置为0,将元素一个一个往前移,但效率太低。
所以由于存在假溢出现象,实际软件系统中不使用上述顺序队列,而是使用接下来的循环队列。
3.循环队列
1.循环队列基本思想
把队列设想成环形,即若rear+1==maxsize,则令rear=0;若front+1==maxsize,亦令front=0 。
实现:利用“模”运算(以前例说明)
入队: elems[rear]=e; rear=(rear+1)%maxsize;
出队: e=elems[front]; front=(front+1)%maxsize;
2.队满、队空判定条件
假如将循环队列的所有空间都用上的话,队空与队满的判断条件相同,都为front=rear;
这样就区分不了队空还是队满,所以我们要
1.另外设一个标志以区别队空、队满
2.少用一个元素空间:
队空:front==rear
队满:(rear+1)%maxsize==front
当maxsize比较大时,一个存储空间的浪费是微乎其微的。
枚举类型Status定义
#pragma once typedef enum{ NOT_PRESENT, ENTRY_FOUND, RANGE_ERROR, SUCCESS, OVER_FLOW, DEFAULT_SIZE }Status;
3.循环队列的类模板定义和实现
template<class ElemType> class SeqQueue { protected: int front, rear; // 队头队尾指针 int maxSize; // 队列容量 ElemType *elems; // 元素存储空间 public: SeqQueue(int size = DEFAULT_SIZE); virtual ~SeqQueue(); int GetLength() const; bool IsEmpty() const; void Clear(); Status DelQueue(ElemType &e); Status GetHead(ElemType &e) const; Status EnQueue(const ElemType e); void Traverse(void (*Visit)(const ElemType &)) const; }; template<class ElemType> SeqQueue<ElemType>::SeqQueue(int size){ maxSize = size; elems = new ElemType[maxSize]; rear = front = 0; } template<class ElemType> int SeqQueue<ElemType>::GetLength() const { return (rear - front + maxSize) % maxSize; } template<class ElemType> Status SeqQueue<ElemType>::EnQueue(const ElemType e) { if ((rear + 1) % maxSize == front) return OVER_FLOW; else{ elems[rear] = e; rear = (rear + 1) % maxSize; return SUCCESS; } } template<class ElemType> Status SeqQueue<ElemType>::GetHead(ElemType &e) const { if (!IsEmpty()) { e = elems[front]; return SUCCESS; } else return UNDER_FLOW; } template<class ElemType> Status SeqQueue<ElemType>::DelQueue(ElemType &e) { if (!IsEmpty()) { e = elems[front]; front = (front + 1) % maxSize; return SUCCESS; } else return UNDER_FLOW; } //判断循环队列是否为空 template<class ElemType> bool SeqQueue<ElemType>::IsEmpty() const { return rear == front; } //循环队列的析构函数 template <class ElemType> SeqQueue<ElemType>::~SeqQueue() { delete []elems; } //清空循环队列 template<class ElemType> void SeqQueue<ElemType>::Clear() { rear = front = 0; } //遍历循环队列 template <class ElemType> void SeqQueue<ElemType>::Traverse(void (*Visit)(const ElemType &)) const { for (int i = front; i != rear; i = (i + 1) % maxSize) (*Visit)(elems[i]); }
三.递归
1 .递归的概念
递归的定义:一个对象部分地包含自己,或用自己给自己定义,则该对象是递归的。
一个过程直接或间接地调用自己,则该过程是递归的。
2.递归的应用 :
定义是递归的
问题解法是递归的 汉诺塔问题
3.递归过程与递归工作栈
1.非递归函数的调用
在函数调用前保存如下信息:(1)返回地址;(2)本函数调用时,与形参结合的实参值,包括函数名和函数参数;(3)本函数的局部变量值。
2.递归函数的调用
使用递归工作栈保存如下信息:(1)返回地址:上一层中本次调用语句的后继语句地址;(2)本次函数调用时,与形参结合的实参值;(3)本层的局部变量值。
long fact(int n) { if(n == 0) return 1; else return n * fact(n-1); }
void main() { int n; long f; cout << input a integer number:”; cin >> n; f = fact(n); cout << n << “!=“ << f; }
这篇关于数据结构和算法设计4 栈,队列和递归的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解
- 2024-11-23Java对接阿里云智能语音服务入门教程
- 2024-11-23JAVA对接阿里云智能语音服务入门教程
- 2024-11-23Java副业入门:初学者的简单教程
- 2024-11-23JAVA副业入门:初学者的实战指南