diff --git a/manuscripts/back/栈和队列/1.栈的基本概念和基本操作.md b/manuscripts/back/栈和队列/1.栈的基本概念和基本操作.md deleted file mode 100644 index 9f05c10..0000000 --- a/manuscripts/back/栈和队列/1.栈的基本概念和基本操作.md +++ /dev/null @@ -1,52 +0,0 @@ - -## 基本概念和基本操作 - -`栈`: 只允许在一端进行插入或者删除操作的**线性表**,`后进先出的线性表`。 - - -- 明确栈是一种线性表 -- 限定栈只能在某一端进行插入或者删除操作 - -![栈的顺序结构](./images/栈的基本结构.png) - -`栈顶`:线性表允许进行插入和删除的一端。 - -`栈底`:不允许进行插入和删除的另外一端,是**固定的**。类似杯底这中概念 - -`空栈`:不含任何元素的空表,也叫**栈空** - - - -基本结构如下: - - - - -在上面的基本结构中,可以假设存在栈S=(a1,a2,a3,a4,a5,a6,a7,a8),很明显 - -- 栈顶元素:a1 -- 栈底元素:a8 - - -栈只能在栈顶进行插入和删除操作 - -- 进栈顺序:a1->a2->a3->a4->a5->a6->a7->a8 -- 出栈顺序:a8->a7->a6->a5->a4->a3->a2->a1 - - -可以得出结论:栈是后进先出(先进后出),即:LIFO(Last In First Out),也可以叫**后进先出的线性表** - - -## 栈的基本操作 - - -- `InitStack(&S)`: 初始化一个空栈`S`,栈顶指针初始化为-1 -- `StackEmpty(S)`: 判断一个栈是否为空,如果栈空则返回`true`,否则返回`false` -- `Push(&S,x)`: 进栈,若栈未满,`x`进栈操作,插入到栈内成为`新的栈顶元素`。 -- `Pop(&S,&x)`: 出栈,若栈非空,出栈操作,**弹出栈顶元素**,用指针`x`进行返回。 -- `GetTop(S,&x)`: 读栈顶元素,若栈`S`非空,用x返回栈顶元素。 -- `ClearStack(&S)`: 销毁栈,释放栈`S`占用的存储空间。 - - - -> Tips: `&`是C++特有的,可以用来表示引用调用,类似`传址目的`,可以类比指针。 当然,在C语言中*代表指针,指向存储地址,也是具有`传址目的` \ No newline at end of file diff --git a/manuscripts/back/栈和队列/2.栈的顺序存储结构.md b/manuscripts/back/栈和队列/2.栈的顺序存储结构.md deleted file mode 100644 index 19f05d1..0000000 --- a/manuscripts/back/栈和队列/2.栈的顺序存储结构.md +++ /dev/null @@ -1,221 +0,0 @@ - -## 栈的顺序存储结构 - - -`顺序栈`:栈的顺序存储,利用一组地址连续的存储单元存放自栈底到栈顶的所有元素,同时**附加一个用来指向当前栈顶位置的指针** - -> 指针指向栈顶(top)主要是因为栈在线性表的一端操作,操作的那端就是栈顶,方便操作。 - - -### 顺序栈的存储类型 - -```cpp -// 定义栈中元素的最大个数 -# define MaxSize 50 - -// 结构体定义 -typedef struct{ - ElemType data[MaxSize]; // 存放栈中元素 - int top; // 栈顶指针 -}SqStack; - -``` - -假设存在顺序栈S,则: - -- 栈顶指针:S.top 初始化时设置S.top=-1 -- 栈顶元素:S.data[S.top],其中S.top为栈顶指针 -- 进栈操作:栈不满时,栈顶指针+1,再送值到栈顶元素 -- 出栈操作:栈非空时,先取栈顶元素值,再将栈顶指针-1 -- 栈空条件:S.top=-1 -- 栈满条件:S.top=MaxSize-1 -- 栈长:S.top+1 - - -> Tips: 进栈先移动指针,避免占满,元素无法添加,出现外溢;出栈先取栈顶元素,保证指针永远指向栈顶。 - - - -顺序栈的存储结构体定义可以很明显看出,顺序栈的入栈操作会受到数组上界(MaxSize)的约束。 - -**当对栈的最大使用空间估计不足时,容易出现栈上溢(外溢),需要主动向用户报告反馈,避免出现错误;** - - - -### 顺序栈的基本运算 - - -- `InitStack(&S)`: 初始化一个空栈`S`,栈顶指针初始化为-1 -- `StackEmpty(S)`: 判断一个栈是否为空,如果栈空则返回`true`,否则返回`false` -- `Push(&S,x)`: 进栈,若栈未满,`x`进栈操作,插入到栈内成为`新的栈顶元素`。 -- `Pop(&S,&x)`: 出栈,若栈非空,出栈操作,**弹出栈顶元素**,用指针`x`进行返回。 -- `GetTop(S,&x)`: 读栈顶元素,若栈`S`非空,用x返回栈顶元素。 -- `ClearStack(&S)`: 销毁栈,释放栈`S`占用的存储空间。 - - -#### 初始化 - - -`InitStack(&S)`: 初始化一个空栈`S`,栈顶指针初始化为-1 - -```cpp - -void InitStack(&S){ - // 栈顶指针-1 - s.top=-1; -} - -``` - - -#### 栈空判断 - -`StackEmpty(S)`: 判断一个栈是否为空,即:栈顶指针是否为-1,如果栈空则返回`true`,否则返回`false` - -```cpp - -bool StackEmpty(S){ - if(S.top==-1){ - // 栈空 - return true; - }else{ - // 栈非空 - return false; - } -} - -``` - - -#### 进栈 - - -`Push(&S,x)`: 进栈,若栈未满,`x`进栈操作,插入到栈内成为`新的栈顶元素`。 - -```cpp - -bool Push(SqStack &S,ElemType x){ - if(S.top==MaxSize-1){ - // 栈满,返回false,元素无法进行进栈操作 - return false; - }else{ - // 可进栈,栈顶指针+1,再元素入栈 - S.data[++S.top]=x; - - // 入栈成功 - return true; - } -} - -``` -注意: - -- 进栈先移动栈顶指针+1,再操作入栈元素 -- `++i`是简写,先对变量i进行递加操作,再进行使用,即:先加后用 - -#### 出栈 - -`Pop(&S,&x)`: 出栈,若栈非空,出栈操作,**弹出栈顶元素**,用指针`x`进行返回。 - -```cpp - -bool Pop(SqStack &S,ElemType &x){ - if(S.top==-1){ - // 栈空,无栈顶元素可出栈,返回false - return false; - }else{ - // 栈非空,先元素出栈,再进行指针-1 - x=S.data[S.top--]; - - // 出栈成功,返回true - return true; - } -} - -``` - -注意: - -- 出栈操作,先让元素出栈,获取栈顶元素,再移动指针-1 -- `i--`是先使用变量i,再对变量做递减操作,即:先用后加(减) - - - -#### 读(获取)栈顶元素 - -`GetTop(S,&x)`: 读栈顶元素,若栈`S`非空,用x返回栈顶元素。 - -```cpp - -bool GetTop(SqStack S,ElemType &x){ - - if(S.top==-1){ - // 栈空,无栈顶元素,返回false - return false; - }else{ - - // 通过栈顶指针,获取栈顶元素,赋值给变量x - x=S.data[S.top]; - - // 读取栈顶元素成功,返回true - return true; - } -} - -``` - - - -**上面的这些操作都是基于栈顶指针初始化为`-1`的情况** - -当栈顶指针初始化为`S.top=0`,相关操作操作会有区别: - -- 入栈: `S.data[S.top++]=x` -- 出栈: `x=S.data[--S.top]` - - -**同时, 栈空、栈满条件也会有变化,要仔细对比揣摩** - - - -### 共享栈 - - -`共享栈`:利用栈底位置相对不变的特性,可以让两个顺序栈共享一个`一维存储空间`,将两个栈的栈底分别设置在共享空间的两端,两个栈顶则向共享空间的中间延伸 - ->Tips: 类似头对头,一致对外这种感觉,噗呲哈哈 - -![顺序栈共享存储空间](./images/顺序栈共享存储空间.png) - - -在上面的共享栈结构图中,两个栈(0、1号顺序栈)的栈顶指针都指向栈顶元素 - -- 0号栈栈顶指针`top=-1`时,0号栈为空 -- 1号栈栈顶指针`top=MaxSize`时,1号栈为空 - -当且仅当两个栈的栈顶指针相邻(`top1-top0=1`),可以判断共享栈栈满 - - -#### 进栈 - - -> 进栈:先移动指针,后进行赋值 - - -- 当0号栈进栈时,0号栈栈顶指针top0`先加1后赋值` -- 当1号栈进栈时,0号栈栈顶指针top1`先减1后赋值` - -#### 出栈 - -> 出栈:先进行赋值,后移动指针 - - -- 当0号栈进栈时,0号栈栈顶指针top0`先赋值后减1` -- 当1号栈进栈时,0号栈栈顶指针top1`先赋值后加1` - - - -共享栈能够更有效的利用存储空间,两个栈空间进行相互调节。只有当这个存储空间(即:共享空间)被占满时才会发生上溢。存取数据的时间复杂度都为O(1),在栈顶操作。 - - -**共享栈对存取效率没有什么影响** \ No newline at end of file diff --git a/manuscripts/back/栈和队列/3.栈的链式存储结构.md b/manuscripts/back/栈和队列/3.栈的链式存储结构.md deleted file mode 100644 index c876cb9..0000000 --- a/manuscripts/back/栈和队列/3.栈的链式存储结构.md +++ /dev/null @@ -1,131 +0,0 @@ - -## 栈的链式存储结构 - -`链栈`: 采用链式存储的栈 - -`栈满`:对于链栈来说,是基于链式存储的,基本不存在栈满的情况,除非内存已经没有使用空间了。 - -`栈空`:对于空栈来说,链表原来的定义是头指针指向空,那么链栈的空其实就是`top=NULL`,链栈元素总数为0 - - -栈只是栈顶在做插入和删除操作,栈顶应该放在单链表的头部,所以单链表中的头结点也就失去了意义。 - -**通常对于链栈来说,是不需要头结点的,当然也存在带头结点的链栈** - - -![](./images/栈的链式存储结构.png) - - -栈的链式存储类型: - -```cpp - -// 链栈类型定义【基础】 -typedef struct LinkNode{ - ElemType data; // 栈元素结点数据域 - struct LinkNode *next; // 栈元素结点指针域 -} *LinkStack; - -// 更为详细的定义 - -typedef struct StackNode -{ - int data;//结点数据域 - struct StackNode* next;//结点指针域 -}StackNode,* Linktop; - -//链栈的数据结构 -typedef struct LinkStack -{ - Linktop top; //栈顶结点,定义了一个指向上个结构体的指针 - int count;//元素个数 -}LinkStack; - -``` - -### 优点 - -- 便于多个栈共享存储空间 -- 不存在栈满上溢的情况,避免程序因溢出导致出错 -- 有效的提高存取效率 - - -### 进栈 - -- 如果链栈不存在,则栈满,入栈操作失败,返回false; -- 如果链栈存在,进行单链表的结点插入操作,移动指针,结点元素赋值,再将结点压入链栈中,移动链栈栈顶指针,最后链栈元素总数+1,返回true - - -```cpp - -/* - * @Description: 基于单链表链栈的进栈操作 - * @Version: Beta1.0 - * @Author: 【B站&公众号】Rong姐姐好可爱 - * @Date: 2020-03-04 07:36:04 - * @LastEditors: 【B站&公众号】Rong姐姐好可爱 - * @LastEditTime: 2020-03-04 11:39:16 - */ -bool linkStackPushNode(LinkStack* linkStack,int e){ - - // 判断链栈是否存在 - if (!linkStack){ - //链栈不存在,无法进栈操作,返回false - return false; - } - // 开辟栈结点元素内存控件 - StackNode* node = (StackNode*)malloc(sizeof(StackNode)); - // 新结点指针域指向链表,即栈顶指针位置,元素加入链表 - node->next = linkStack->top; - // 新结点数据域赋值 - node->data = e; - // 元素进栈,移动栈顶指针,指向新入栈的元素 - linkStack->top = node; - // 链栈元素总数+1 - linkStack->count++; - //链栈入栈成功,返回true - return true; -} -``` - -### 出栈 - -- 如果链栈不存在,或者为空栈,则无法进行出栈操作,返回false -- 如果链栈满足出栈条件,则通过栈顶指针获取到链栈栈底结点,将其数据域赋值给变量e,移动栈顶指针指向待出栈元素的后继结点,同时释放待出栈元素的内存空间,链栈元素总数-1 ,出栈成功,返回true. - - -```cpp - -/* - * @Description: 基于单链表链栈的出栈操作 - * @Version: Beta1.0 - * @Author: 【B站&公众号】Rong姐姐好可爱 - * @Date: 2020-03-04 23:38:04 - * @LastEditors: 【B站&公众号】Rong姐姐好可爱 - * @LastEditTime: 2020-03-04 23:39:16 - */ -bool linkStackPopNode(LinkStack* linkStack,int *e){ - // 判断链栈是否存在及是否为空 - if (!linkStack || linkStack->count==0){ - //出栈失败,返回false - return false; - } - // 获取栈顶元素结点 - StackNode* node = stack->top; - - // 结点元素数据域赋值给变量e - *e = linkStack->data; - // 移动栈顶指向,栈顶指针指向待出栈结点的后继结点 - linkStack->top = node->next; - // 变量e已被赋值,释放链栈出栈元素的内存控件 - free(node); - // 链栈元素个数-1 - linkStack->count--; - // 出栈成功,返回true. - return true; -} - -``` - - -以上是基于单链表的链栈入栈、出栈操作,很明显**时间复杂度都为O(1)**,重点`注意移动指针,保持不断链` \ No newline at end of file diff --git a/manuscripts/back/栈和队列/4.队列的基本概念和基础操作.md b/manuscripts/back/栈和队列/4.队列的基本概念和基础操作.md deleted file mode 100644 index b4e13bc..0000000 --- a/manuscripts/back/栈和队列/4.队列的基本概念和基础操作.md +++ /dev/null @@ -1,42 +0,0 @@ -## 队列的基本概念和基础操作 - - -### 基本概念 - -`队列`:和栈一样,是一种操作受限制的线性表,只允许在表的一端进行插入,在表的另外一端进行删除,简称为`队`,常记作:`Queue` - -`入队`: 向队列中插入元素,也叫做`进队` - -`出队`: 删除队列元素,也叫做`离队` - - -![](./images/队列的基本结构.png) - -结合生活中排队的经验,在群体素质高、无人插队的情况下(`薛定谔排队`,噗呲,哈哈哈),**一般最早排队的也是最早离队的**,和栈的`后进先出`不一样的是,队列是`先进先出`的,即:First In Frist Out - -> Tips -> - 栈:又叫做后进先出的线性表 -> - 队列:又叫做先进先出的线性表 - - - -`队头`:允许进行删除操作的一端,也叫做`队首`,常记作:`Front` - -`队尾`:允许进行插入操作的一端,常记作:`Rear` - -`空队列`:不含任何元素的空表,注意这个表是指`线性表` - - - - -### 基础操作 - - -> 和栈一样,队列是操作受限的线性表,具有`先进先出`的特性,不是任何对线性表的操作都可以作为队列的操作。例如:无法随便读取到队列中间的某个数据,需要将前面的元素先出队 - - -- `InitQueue(&Q)`: 初始化一个队列,构造空队列Q -- `QueueEmpty(Q)`: 判断队列是否为空,队空返回true,否则返回false -- `EnEmpty(&Q,x)`: 入队,如果队列Q未满,将x入队,成为新的队尾元素 -- `DeEmpty(&Q,&x)`: 出队,如果队列Q非空,删除队头元素,复制给x返回 -- `GetHead(Q,&x)`: 读取队头元素,如果队列Q非空,则将队头元素赋值给x diff --git a/manuscripts/back/栈和队列/5.队列的顺序存储结构.md b/manuscripts/back/栈和队列/5.队列的顺序存储结构.md deleted file mode 100644 index 871172a..0000000 --- a/manuscripts/back/栈和队列/5.队列的顺序存储结构.md +++ /dev/null @@ -1,336 +0,0 @@ -## 队列的顺序存储结构 - - -> 队列的顺序实现是指分配一块连续的存储单元用来存放队列中的元素,并且附加两个指针。 -> - `front指针`: 指向队头元素的位置 -> - `rear指针`: 指向队尾元素的位置 - -队列顺序存储类型: - -```cpp -// 队列最大存储元素个数 -#define MaxSize 50 - -// 结构体定义 -typedef struct { - // 存放队列元素 - ElemType data[MaxSize]; - // 队头指针和队尾指针 - int front,rear; -} SqQueue; -``` - - -假定: - -- 队头指针指向队头元素 -- 队尾指针指向队尾元素的下一个位置 - -则: - -- 初始状态(**队空条件**):`Q.front`===`Q.rear`===0 -- 入队操作:队不满时,先赋值给队尾元素,再移动队尾指针+1 -- 出队操作: 队不空时,先取队头元素值,再移动队头指针+1 - - - -![](./images/入队.png) - -在空队列中,初始状态为`Q.front===Q.rear==0`,当元素a入队时,队尾指针rear后移+1,入队成功后,`Q.front==0`、`Q.rear==1`,在队不满的情况下进队,都是`先赋值给队尾元素,再移动队尾指针rear+1`,通过上面的图宝贝可以看到,队列被元素打满的时: - -- 在这个进队的过程中,没有元素出队,队头指针并没有做改变,`Q.front==0` -- 进队操作直接影响队尾指针的变化,队列满的时候`Q.rear==Maxsize` - -> Tips: MaxSize为队列结构体定义中,最大存储元素个数哦~ - - -![](./images/出队.png) - -进队说完了,那给宝贝来说说出队吧。以上图为例,队列中`Q.rear==Maxsize`、`Q.front==0`;当出现元素在队首出队,就会直接影响队首指针,从上面的流程上看: - -- 元素出队,front指针后移+1,在队不空的情况下,操作为:`先取队头元素值,再移动队头指针+1` - -- 当队列中的元素都陆续出队,抛弃了宝贝(都是渣男,噗呲,哈哈哈),指针会是:`Q.rear==Q.front==MaxSize` - - - -从上面两张图中,我们来思考: - -> 前面队空条件为:`Q.front===Q.rear===0`,那能用`Q.rear==MaxSize`来表示队满嘛? - - -傻瓜,你在瞅瞅前面的图,明显存在`Q.rear==MaxSize`,但队列确实空的情况呀。队满要灵活判断,可不要死记书上总结的。书上说的很多结论都是有前提的,老师记结论不记前提容易张冠李戴、含糊不清的呀~ - - - - - - -很显然,也存在下面这种情况: - -- 队头指针指向队头元素的前一个位置 -- 队尾指针指向队尾元素 - -此时的入队、出队过程就宝贝自己去画流程图咯 - - - -### 循环队列 - -在上面的顺序队列中,当队满后进行出队列,由于顺序队列出队只在队首进行操作,并且只会修改队首指针,这时候就会出现队尾指针一直`Q.rear===MaxSize`情况,就如下: - - -![队列队满情况分析](./images/队列队满情况分析.png) - -可以很明显的看到,明明队列不满,但是由于进队列只能在队尾操作,因此不能进行进队操作;通常在这种情况下入队就会出现“上溢出”。 - -> 需要明确的是:上溢出并不是真正的溢出,只是表明在顺序队列中队不满却无法入队的情况,是一种假的”溢出“ - - - - -这种情况在顺序队列中是非常常见的,也是顺序队列的一大缺点。为了克服这个缺点,计算机先贤们总是能够有很多很好的办法,这里不得不佩服!!,所以就有了循环队列,**一个将顺序队列臆想为一个环状的空间** - - -> 很多时候就是这样,为了解决一个问题,从而衍生出一个新的知识 - - -`循环队列`:把顺序队列臆想为一个环状的空间,将存储队列元素的表从逻辑上看做为一个环 - - - -![](./images/循环队列初始化.png) - - -当队首指针`Q.front=MaxSize-1`后,再有元素`出队`就前进一个位置自动到位置0了【注意:可以结合时钟来理解,一圈转完了】 - - -- 初始时:`Q.front=Q.rear=0` -- 队首指针进1: `Q.front=(Q.front+1)%MaxSize` -- 队尾指针进1: `Q.rear=(Q.rear+1)%MaxSize` -- 队列长度: `(Q.rear+MaxSize-Q.front)%MaxSize` - - -> 是不是理解起来有点抽象,其实我最开始学到这里的时候,也不明白为什么要用`除法取余运算(%)`来实现。后来我看看了手机上的时钟指针,一圈两圈三圈的转,好像就开始悟了...其实这种取余操作在计算机知识体系中还是非常常见的,例如:组成原理中将会学到的补码,据说idea就是来源于时钟.. - - -**和时钟一样,顺时钟进行时间变换,在出队、入队时,队首、队尾指针都是按顺时针方向进1** - - -![](./images/循环队列入队.png) - -如上图,循环队列从最开始初始化为空队列时:`Q.front==Q.rear==0`,经过元素a入队,队尾指针顺时针前移`Q.rear+1`,到元素a、b、c、d陆续入队,就好像时钟转完了一圈,循环队列已满,此时发现:`Q.front==Q.rear==0`在队满时候依然成立,所以结合前面提到的初始化对空条件:`Q.front==Q.rear==0`,用`Q.front==Q.rear`来区分`队空`和`队满`是非常不合适的。 - - - - -#### 如何区别队空还是队满 - -> 为了很好的区别循环队列的`队空`还是`队满`的情况,一般有三种处理方式. -##### 牺牲一个单元来区分队空和队满 - -这种方式**要求在入队时少用一个队列单元**,是一种比较普遍的做法。约定: - -**队头指针在队尾指针在队尾指针的下一个位置作为队满标志【重要】** - -- 队满条件:`(Q.rear+1)%MaxSize==Q.front` -- 队空条件:`Q.front==Q.rear` -- 队列中元素个数:`(Q.rear+MaxSize-Q.front)%MaxSize` - - -##### 类型中增设表示元素个数的数据成员 - -这种就很直接了,直接和MaxSize去比较,就可以有: - -- 队空条件: `Q.count=0` -- 队满条件: `Q.count=MaxSize` - - -值的注意的是:在这个前提下,不论是`队空`还是`队满`,对会存在`Q.front=Q.rear`,这个可以通过前面方案解决。 - -##### 类型中增设tag数据成员标记 - -通过添加tag标记的方式,区分`队空`还是`队满` - -- `tag==0`的情况下,如果因为删除导致`Q.front==Q.rear`,则队空; -- `tag==1`的情况下,如果因为插入导致`Q.front==Q.rear`,则队满; - -可能你会对上面的这两种情况有迷惑,说实话我第一次看的时候,也挺迷惑的,这里我按照我的理解来解释一下: - -> 在循环队列中增加tag数据成员标记,tag的主要作用: -> - 在有元素入队的时候,设置tag=1 -> - 在有元素出队的时候,设置tag=0 - - -对应的算法实现: -```cpp -// 入队算法 -// 尾插法:Q.data[Q.rear]=x;Q.rear=(Q.rear+1)%Maxsize;Q.tag=1 -// 队空条件:Q.front== Q.rear且Q.tag==0 -int EnLoopQueue(SqQueue &Q, ElemType x){ - if(Q.front==Q.rear&&Q.tag==1){ - return 0; - } - Q.data[Q.rear]=x; - Q.rear=(Q.rear+1)%MaxSize; - Q.tag=1; - return 1; -} - - - -// 出队算法 -// 头结点删除:x=Q.data[Q.front];Q.front=(Q.front +1)%Maxsize;Q.tag=0 -// 队满条件:Q.front == Q.rear且Q.tag=1 -// 注意:当删除之后链表为空时,还需增加一步,将尾指针指向头结点 -int DeLoopQueue(SqQueue &Q, ElemType &x){ - if (Q.front==Q.rear&&Q.tag==0){ - return 0; - } - x=Q.data[Q.front]; - Q.front=(Q.front+1)%MaxSize; - Q.tag=0; - return 1; -} - -``` -##### - - -### 代码实现 - - -#### 初始化空队列 - - -```cpp -/* - * @Description: 循环队列初始化,队列为空 - * @Version: Beta1.0 - * @Author: 【B站&公众号】Rong姐姐好可爱 - * @Date: 2019-09-27 14:17:28 - * @LastEditors: 【B站&公众号】Rong姐姐好可爱 - * @LastEditTime: 2021-03-18 22:15:06 - */ -void InitLoopQueque(&Q){ - Q.front=Q.rear=0; -} -``` - -#### 队列是否为空 - -```cpp -/* - * @Description: 判断循环队列是否为空 - * @Version: Beta1.0 - * @Author: 【B站&公众号】Rong姐姐好可爱 - * @Date: 2019-09-27 14:17:28 - * @LastEditors: 【B站&公众号】Rong姐姐好可爱 - * @LastEditTime: 2021-03-18 22:15:06 - */ -bool isEmpatyLoopQueue(Q){ - // 注意循环队列对空条件:Q.rear=Q.front - if(Q.rear=Q.front){ - // 队空 - return true; - }else{ - // 非空 - return false; - } -} - -``` - - -#### 入队操作 - -```cpp -/* - * @Description: 循环队列元素入队 - * @Version: Beta1.0 - * @Author: 【B站&公众号】Rong姐姐好可爱 - * @Date: 2019-09-27 14:17:28 - * @LastEditors: 【B站&公众号】Rong姐姐好可爱 - * @LastEditTime: 2021-03-18 22:15:06 - */ -bool EnLoopQueue(SqQueue &Q, ElemType x){ - // 判断循环队列是否已满 注意判断条件:(Q.rear+1)%MaxSize===Q.front - if((Q.rear+1)%MaxSize===Q.front){ - // 循环队列满 - return false; - } - // 队列未满,可进行入队操作【队尾进行】 - - // 队尾指针指向的数据域进行赋值 - Q.data[Q.rear]=x; - - //队尾指针后移+1【类似时钟的顺时针方向】 - Q.rear=((Q.rear+1)%MaxSize); - - // 入队成功,返回true - return true; -} - -``` - -#### 出队操作 - - -```cpp - -/* - * @Description: 循环队列元素出队 - * @Version: Beta1.0 - * @Author: 【B站&公众号】Rong姐姐好可爱 - * @Date: 2019-09-27 14:17:28 - * @LastEditors: 【B站&公众号】Rong姐姐好可爱 - * @LastEditTime: 2021-03-18 20:32:18 - */ -bool DeLoopQueue(SqQueue &Q, ElemType &x){ - - // 判断循环队列是否为空队列 - if(Q.rear==Q.front){ - - // 队列为空,无法进行出队操作,返回false - return false; - } - - // 循环队列非空,元素可出队【队首操作】 - - // 将循环队列队首指针指向的元素的数据域赋值给变量x - x=Q.data[Q.front]; - - // 移动队首指针,顺时针后移+1 - Q.front=(Q.front+1)%MaxSize; - - // 出队成功,返回true - return true; -} - -``` - -#### 获取队头元素 - -```cpp -/* - * @Description: 获取循环队列队头元素 - * @Version: Beta1.0 - * @Author: 【B站&公众号】Rong姐姐好可爱 - * @Date: 2019-09-27 14:17:28 - * @LastEditors: 【B站&公众号】Rong姐姐好可爱 - * @LastEditTime: 2021-03-18 20:15:33 - */ -bool GetLoopQueueHead(SqQueue &Q, ElemType &x){ - // 判断循环队列是否为空队列 - if(Q.front==Q.rear){ - // 队列为空,没有队头元素,返回false - return false; - }else{ - // 获取队头指针指向元素的数据域,赋值给x - x=Q.data[Q.front]; - - // 获取队头元素成功,返回true - return true; - } -} -``` \ No newline at end of file diff --git a/manuscripts/back/栈和队列/6.队列的链式存储结构.md b/manuscripts/back/栈和队列/6.队列的链式存储结构.md deleted file mode 100644 index 23f2797..0000000 --- a/manuscripts/back/栈和队列/6.队列的链式存储结构.md +++ /dev/null @@ -1,231 +0,0 @@ - -## 队列的链式存储结构 - -`链队列`:和顺序队列一样,基于队列的链式表示叫做`链队列`,实际上为:**一个同时带有队头指针和队尾指针的单链表** - -- 头指针指向队头结点 -- 尾指针指向队尾结点(单链表的最后一个结点) - - -这里复习下顺序队列的定义,进行比较学习 - -> 队列的顺序实现是指分配一块连续的存储单元用来存放队列中的元素,并且附加两个指针。 -> - 队头指针指向队头元素 -> - 队尾指针指向队尾元素的下一个位置 - -![](./images/不带头结点的链式队列.png) - - -队列的链式存储结构: - - -```cpp -// 链式队列节点定义 -typedef struct{ - // 结果点数据域 - ElemType data; - // 结点指针域 - struct LinkNode *next; -}LinkNode; - -// 链式队列定义 -typedef struct{ - // front 队头指针,rear 队尾指针 - LinkNode *front,*rear; -}LinkQueue; - -``` - -结合上面的`不带头结点`链式队列结构图,假设存在链式队列 - -- 队空: `linkQueue.front==NULL`且`linkQueue.rear==NULL` -- 出队: 先判断队列是否为空,非空队列则取出队头元素,从链表中闪出去,`队头指针Q.front指向下一个结点`,如果出队的结此为尾结点,出队后队空,需要将Q.front和Q.rear都置为NULL -- 入队: 建立一个新的结点,将新的结点插入到链表的尾部,修改队尾指针Q.rear指向新插入的结点。如果原队列为空,需要将队首指针也指向该结点 - -仔细思考上面的入队、出队操作,都需要考虑队空的情况下的特殊处理,不带头结点的队列导致队空队首和队尾指针都为NULL,比较麻烦;结合之前整理、学习过的单链表,套用一下先贤的思路,也整上一个头结点,就会发现容易处理很多; - -![](./images/带头结点的链式队列.png) - -链式队列加上头结点后,之前较为复杂的入队、出队操作就统一起来了。 - -- 队空:`Q.front==Q.rear`,都指向头结点,一般数据域可以为空 -- 出队:判断队列是否为空,队列非空则在队首移动指针,将队首指针指向下一个元素。如果队列中就一个元素,则出队后将成为空队,`Q.rear==Q.front`,最后释放元素内存空间。 -- 入队:将元素插入队尾,移动队尾指针,即便为空队列入队,由于队列带有头结点,此时就很好的避免操作队首指针了。 - - -特别注意: - -- 用单链表表示的链式队列非常适合频繁出队、入队、元素变化大的场景 -- 不存在队满情况,也不会出现溢出情况; -- 链式队列不会出现存储分配不合理、“溢出”的情况,内存动态分配 - -### 基本操作 - -> Tips: 基于带头结点链式队列的基础操作 - -#### 队列初始化 - -```cpp - -/* - * @Description: 链式队列初始化 - * @Version: Beta1.0 - * @Author: 【B站&公众号】Rong姐姐好可爱 - * @Date: 2020-06-27 14:17:28 - * @LastEditors: 【B站&公众号】Rong姐姐好可爱 - * @LastEditTime: 2021-02-18 22:15:06 - */ -voide InitLinkQueue(LinkQueue &Q){ - - // 创建头结点 - Q.front=Q.rear=(LinkNode*)malloc(sizeof (LinkNode)); - - // 头结点的指针域指向的下一个结点为空 - Q.front->next=NULL; -} -``` - -注意这个初始化操作,我第一次看的时候,非常不理解为什么在队首指针和队尾指针都指向已经创建好的头结点后,突然写一行`Q.front->next=NULL;`,后来的的理解是: - -> 链式队列,本质是基于单链表的队列,那带头结点的链式队列,其实强调的也就是单链表要带头结点。队列的队首指针和队尾指针确定指向单表的队首和队尾就ok;初始化的时候,带头结点的单链表实质就只有一个头结点。而此时的链式队列,需要将队首指针和队尾指针指向单链表的头结点就行,好像到这里就完了。但是这样却忽视了单链表,只是注重的队列的front和rear指针。单链表的结点元素是分数据域和指针域的,即便是头结点的数据域可以不存什么(当然也常会存一些链表信息什么的),此处的`Q.front->next=NULL`就是用来处理链表的头结点的指针域的,让其指向下一个单链表元素为空,这里是非常需要处理的,非常细节! - -#### 判断队空 - -```cpp - -/* - * @Description: 判断链式队列是否为空 - * @Version: Beta1.0 - * @Author: 【B站&公众号】Rong姐姐好可爱 - * @Date: 2020-06-27 14:24:22 - * @LastEditors: 【B站&公众号】Rong姐姐好可爱 - * @LastEditTime: 2021-02-16 22:15:06 - */ -bool IsEmptyLinkQueue(LinkQueue Q){ - if(Q.front==Q.rear){ - // 队首、队尾指针指向同一个结点内存地址,队空 - return true; - }else{ - // 非空 - return false; - } -} -``` - -#### 入队 - - -```cpp -/* - * @Description: 链式队列入队操作 - * @Version: Beta1.0 - * @Author: 【B站&公众号】Rong姐姐好可爱 - * @Date: 2020-06-46 14:17:28 - * @LastEditors: 【B站&公众号】Rong姐姐好可爱 - * @LastEditTime: 2021-02-18 22:15:06 - */ -void EnLinkQueue(LinkQueue &Q, ElemType x){ - - //创建入队结点元素 - node=(LinkNode *)malloc(sizeof(LinkNode)); - - // 赋值给结点数据域 - node->data=x; - // 单链表中结点的指针指向下一个元素,为空 - node->next=NULL; - - // 队尾进队,将结点node和队尾结点链接起来,确保不断链 - Q.rear->next=node; - // 修改队尾指针,指向新入队的结点node - Q.rear=node; -} - -``` - - -#### 出队 - -```cpp - -/* - * @Description: 链式队列出队操作 - * @Version: Beta1.0 - * @Author: 【B站&公众号】Rong姐姐好可爱 - * @Date: 2020-05-18 11:25:28 - * @LastEditors: 【B站&公众号】Rong姐姐好可爱 - * @LastEditTime: 2021-02-22 06:15:06 - */ -bool DeLinkQueue(LinkQueue &Q, ElemType &x){ - - // 判断队列是否为空 - if(Q.front==Q.rear){ - // 队列为空,没有元素出队 - return false; - } - - // Q.front指向单链表的头结点,出队需要出单链表的除头结点外的第一个结点(头结点的下一个结点:Q.front->next) - temp=Q.front->next; - - // 变量x赋值 - x=temp->data; - - // 修改单例表的头指针,指向链表中待删除元素的下一个元素 - Q.front->next=temp->next; - - // 队尾指针和队首指针的下一个结点重合,表示当前链式队列只有一个元素,删除后,队列为空 - if(Q.rear==temp){ - // 链表中队首、队尾指针重合,队列为空 - Q.rear=Q.front; - } - - // 释放删除元素的内存空间,注意,这里释放的是头结点的下一个结点 - free(temp); - - // 出队成功 - return true; -} - -``` - -出队的时候明显有些绕,需要明确队列中头结点的存在,出队出的是单链表中头结点的后一个结点,同时要确保整个过程`不断链` - -![](./images/链式队列出队.png) - - - -### 双端队列 - -`双端队列`: 允许在两端都可以进行入队和出队操作的队列,元素的逻辑结构仍然是线性结构 - -![](./images/双端队列.png) - -**双端队列的两端分别称为`前端`和`后端`,两端都可以`入队`和`出队`** - -- 进队:前端进的元素排列在队列中后端进的元素的前面,后端进的元素排列在队列前端进的元素后面; -- 出队:无论是前端还是后端出队,先出的的元素排列在后出的元素的前面 - - -在双端队列的基础上,还衍生出: - -- `输入受限的双端队列`:允许在一端进行插入和删除操作,但在另外一端只允许进行删除的双端队列 -- `输出受限的双端队列`:允许在一端进行插入和删除曹组,但在另外一端只允许进行插入的双端队列 - - -当然,如果`限定双端队列从某个断点插入的元素只能从该端点删除`,那么此时的双端队列就演变为两个栈底相邻的栈了。 - - - - -### 队列的补充 - -- 最适合用来链队的链表是:`带队首指针和队尾指针的非循环单链表` - -- 栈和队列的逻辑结构都是线性表,存储结构可能是顺序的(顺序栈、顺序队列),也可能是链式的(链栈、链队) -- 不论是顺序存储还是链式存储,**栈和队列都只能进行顺序存取(本质是线性表)**。数组是可以做到随机存取(本质是顺序表) -- 队列先进先出的特性:`先进队列的元素先出队列,后进队列的元素后出队列` - - -特别注意: - -队列用链表形式存储时,删除元素,需要从队头删除,一般情况下,仅仅修改头指针,但是如果此时队列中 -仅有一个元素,则尾指针也是需要被修改的。**因为队列中只有一个元素时,删除后队列为空,需要修改尾指针为:`rear=front`** \ No newline at end of file diff --git a/manuscripts/back/栈和队列/7.栈和队列的应用.md b/manuscripts/back/栈和队列/7.栈和队列的应用.md deleted file mode 100644 index a47626f..0000000 --- a/manuscripts/back/栈和队列/7.栈和队列的应用.md +++ /dev/null @@ -1,127 +0,0 @@ - -## 栈和队列的应用 - -> Tips: 这里不会做过多文字介绍相关应用,具体需要自己看书、查资料揣摩 - -### 栈的应用 - -- 括号匹配 -- 表达式求值 -> 表达式求值是程序设计语言编译中一个最基本的问题,类似有前缀、中缀、后缀表达式转换等典型问题。 -- 递归 - -这里重点总结下递归,递归非常重要,是一种很经典的程序设计方法 - -> 递归的简单定义: 如果在一个函数、过程或数据结构的定义中又应用了自身,那么这个函数、过程或者数据结构称为递归定义的,简称递归。 - - - -递归通常把一个大型的复杂问题,层层转化为一个与原问题相似的规模较小的问题来求解。 - -**递归策略只需要少量的代码就可以描述出解题过程所需要的多次重复的计算,很大程度上减少了程序的代码量,当时通常情况下,递归的效率并不是很高** - - -经典的斐波拉切数列,可以用递归来实现: - - -```cpp - -// 定义递归函数,实现斐波拉切数列 - -int Fibonacci(n){ - if(n==0){ - return 0; - }else if(n==1){ - return 1; - }else{ - // 调用Fibonacci函数,进行递归 - return Fibonacci(n-1)+Fib(n-2); - } -} - - -``` - -上面很基础的代码,是分`n=0`和`n=1`的情况,先进行过滤,其他情况下则进行递归,其实在日常开发中,经常会有简化的函数封装 - - -```cpp - -// 定义递归函数,实现斐波拉切数列 - -int Fibonacci(n){ - if(n<2){ - return n; - }else{ - // 调用Fibonacci函数,进行递归 - return Fibonacci(n-1)+Fib(n-2); - } -} - -``` - -上面的简化,则需要程序考虑变量n是整数。 - -当然,前面有说道:**通常情况下,递归的效率并不搞**,主要原因就是递归通过返回调用自己本身,导致往往时间复杂度较高。 - -可以采用空间换时间的思路,来降低算法的时间复杂度 - -```cpp -// 使用非递归实现 -int Fibonacci(int n) { - if(n < 2) - return n; - int f = 0, g = 1; - int result = 0; - - // 迭代 - for(int i = 1; i < n; i++){ - result = f + g; - f = g; - g = result; - } - return result; -} - -// 利用数组,空间换时间 -int Fibonacci(n){ - // 注意溢出 - int arr[100]={0,1,1} - - // 叠加,结果存放在数组中 - for(let i=3;i<=n;i++){ - arr[i]=arr[i-1]+arr[i-2] - } - return arr[n] -} - -``` - - -必须注意递归模型不能是循环定义的,满足条件: - -- 递归表达式(递归体) -- 边界条件(递归出口),即:算法结束条件 - - -递归的精髓在于是否能够将原来的问题转化为属性相同但规模较小的问题。有点类似实现目标过程中,将大目标转化为小目标(1亿....)来解决,噗呲啊哈哈哈 - -**递归次数过多容易造成栈溢出,效率不高的主要原因是递归调用过程中包含很多重复的计算** - - -### 队列的应用 - -- 层次遍历 - -> 例如:二叉树的遍历 - -- 计算机系统 - - -队列在计算机系统中的应用非常广泛,作用: - -- 解决主机和外部设备之间速度不匹配的问题(例如:内存和打印机) -- 解决由多用户引起的资源竞争问题(例如:操作系统中的进程调度...) - - -其实,队列在计算机系统的中应用, 在看完操作系统那本书后,就会很好理解,建议学到这里,也去翻翻操作系统,汤晓丹那本很经典哟... \ No newline at end of file diff --git a/manuscripts/back/栈和队列/8.特殊矩阵的压缩存储.md b/manuscripts/back/栈和队列/8.特殊矩阵的压缩存储.md deleted file mode 100644 index 9cac6e6..0000000 --- a/manuscripts/back/栈和队列/8.特殊矩阵的压缩存储.md +++ /dev/null @@ -1,51 +0,0 @@ -## 特殊矩阵的压缩存储 - -> 这部分知识我个人觉得以了解为主,复习、学习的时候还是要以前面的部分为主! - -矩阵在`计算机图形学`、`工程计算`中占有举足轻重的地位。 - -### 数组的定义 - -`数组`: 由n(n≥1)个相同类型的数据元素构成的有限序列。 - -每个数据元素称为一个数组元素,同时每个元素受n个线性关系的约束,**每个元素在n个线性关系中的序号称为元素的`下标`** -,称为该数组为n的数组。 - -数组和线性表的关系: - -- 数组是线性表的推广。 -- 数组一旦被定义,维数和维界就不再改变。 -- 除了结构的初始化和销毁外,数组只会有存取元素和修改元素的操作。 - -一维数组可以看做是一个线性表 - -二维数组可以看做元素是线性表的线性表 - -.... - -### 矩阵的压缩存储 - -`压缩存储`:多个值相同的元素只分配一个存储空间,对零元素不分配存储空间---->节省存储空间。 - -`特殊矩阵`:具有很多相同矩阵元素或零元素,并且这些相同矩阵元素或零元素的分布有一定规律性的矩阵。 - -- 对称矩阵 -- 上、下三角矩阵 -- 对角矩阵(带状矩阵) -- .... - -这里如果学过线性代数这本书,其实也就很好理解(赶紧去把数学知识捡起来鸭,噗呲哈哈啊哈) - -`稀疏矩阵`:矩阵元素个数s相对于矩阵中非零元素的个数t来说非常多、差距非常大,即`s>>t的矩阵`可以叫`稀疏矩阵` - -注意: - -- 常规方法来存储稀疏矩阵,会想当浪费存储空间,所以稀疏矩阵只需要存储非零元素 -- 通常非零元素的分布是没有规律的,除了存储非零元素外,还需要存储元素所在位置的行和列 -- 寻相互存储三元组 `<行标,列表,值>` - -![](./稀疏矩阵的存储变换.png) - -三元组的结点存储了行标(row)、列表(col)、值(value)三种信息,是主要用来存储稀疏矩阵的一种数据结构。 - -**注意:矩阵压缩存储的目的就是为了节省空间,已经存过的就不存或者少存(经典想法)** \ No newline at end of file diff --git a/manuscripts/back/栈和队列/Readme.md b/manuscripts/back/栈和队列/Readme.md deleted file mode 100644 index 66c3643..0000000 --- a/manuscripts/back/栈和队列/Readme.md +++ /dev/null @@ -1,14 +0,0 @@ - - - - -![](./栈和队列_水印.png) \ No newline at end of file diff --git a/manuscripts/back/栈和队列/images/不带头结点的链式队列.png b/manuscripts/back/栈和队列/images/不带头结点的链式队列.png deleted file mode 100644 index 3004098..0000000 Binary files a/manuscripts/back/栈和队列/images/不带头结点的链式队列.png and /dev/null differ diff --git a/manuscripts/back/栈和队列/images/入队.png b/manuscripts/back/栈和队列/images/入队.png deleted file mode 100644 index 30e6ee8..0000000 Binary files a/manuscripts/back/栈和队列/images/入队.png and /dev/null differ diff --git a/manuscripts/back/栈和队列/images/出队.png b/manuscripts/back/栈和队列/images/出队.png deleted file mode 100644 index 970a7d5..0000000 Binary files a/manuscripts/back/栈和队列/images/出队.png and /dev/null differ diff --git a/manuscripts/back/栈和队列/images/双端队列.png b/manuscripts/back/栈和队列/images/双端队列.png deleted file mode 100644 index 90f52b9..0000000 Binary files a/manuscripts/back/栈和队列/images/双端队列.png and /dev/null differ diff --git a/manuscripts/back/栈和队列/images/带头结点的链式队列.png b/manuscripts/back/栈和队列/images/带头结点的链式队列.png deleted file mode 100644 index b96c1da..0000000 Binary files a/manuscripts/back/栈和队列/images/带头结点的链式队列.png and /dev/null differ diff --git a/manuscripts/back/栈和队列/images/循环队列入队.png b/manuscripts/back/栈和队列/images/循环队列入队.png deleted file mode 100644 index f6790ba..0000000 Binary files a/manuscripts/back/栈和队列/images/循环队列入队.png and /dev/null differ diff --git a/manuscripts/back/栈和队列/images/循环队列初始化.png b/manuscripts/back/栈和队列/images/循环队列初始化.png deleted file mode 100644 index ffb80e5..0000000 Binary files a/manuscripts/back/栈和队列/images/循环队列初始化.png and /dev/null differ diff --git a/manuscripts/back/栈和队列/images/栈的基本结构.png b/manuscripts/back/栈和队列/images/栈的基本结构.png deleted file mode 100644 index ac8e3d9..0000000 Binary files a/manuscripts/back/栈和队列/images/栈的基本结构.png and /dev/null differ diff --git a/manuscripts/back/栈和队列/images/栈的链式存储结构.png b/manuscripts/back/栈和队列/images/栈的链式存储结构.png deleted file mode 100644 index ac02722..0000000 Binary files a/manuscripts/back/栈和队列/images/栈的链式存储结构.png and /dev/null differ diff --git a/manuscripts/back/栈和队列/images/稀疏矩阵的存储变换.png b/manuscripts/back/栈和队列/images/稀疏矩阵的存储变换.png deleted file mode 100644 index 8956501..0000000 Binary files a/manuscripts/back/栈和队列/images/稀疏矩阵的存储变换.png and /dev/null differ diff --git a/manuscripts/back/栈和队列/images/链式队列出队.png b/manuscripts/back/栈和队列/images/链式队列出队.png deleted file mode 100644 index 528a0e5..0000000 Binary files a/manuscripts/back/栈和队列/images/链式队列出队.png and /dev/null differ diff --git a/manuscripts/back/栈和队列/images/队列的基本结构.png b/manuscripts/back/栈和队列/images/队列的基本结构.png deleted file mode 100644 index c1477da..0000000 Binary files a/manuscripts/back/栈和队列/images/队列的基本结构.png and /dev/null differ diff --git a/manuscripts/back/栈和队列/images/队列队满情况分析.png b/manuscripts/back/栈和队列/images/队列队满情况分析.png deleted file mode 100644 index 33db4d8..0000000 Binary files a/manuscripts/back/栈和队列/images/队列队满情况分析.png and /dev/null differ diff --git a/manuscripts/back/栈和队列/images/顺序栈共享存储空间.png b/manuscripts/back/栈和队列/images/顺序栈共享存储空间.png deleted file mode 100644 index 9b84f6e..0000000 Binary files a/manuscripts/back/栈和队列/images/顺序栈共享存储空间.png and /dev/null differ diff --git a/manuscripts/back/栈和队列/栈和队列_水印.png b/manuscripts/back/栈和队列/栈和队列_水印.png deleted file mode 100644 index 6b473be..0000000 Binary files a/manuscripts/back/栈和队列/栈和队列_水印.png and /dev/null differ