博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
数据结构-栈和队列
阅读量:4105 次
发布时间:2019-05-25

本文共 16156 字,大约阅读时间需要 53 分钟。

分享一下我老师大神的人工智能教程!零基础,通俗易懂!

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

               

1.栈

1.1 栈的定义

栈是一种特殊的线性表。其特殊性在于限定插入和删除数据元素的操作只能在线性表的一端进行。如下所示:

结论:后进先出(Last In First Out),简称为LIFO线性表。

栈的基本运算有六种:

构造空栈:InitStack(S)、

判栈空: StackEmpty(S)、

判栈满: StackFull(S)、

进栈: Push(S,x)、可形象地理解为压入,这时栈中会多一个元素

退栈: Pop(S) 、 可形象地理解为弹出,弹出后栈中就无此元素了。

取栈顶元素:StackTop(S),不同与弹出,只是使用栈顶元素的值,该元素仍在栈顶不会改变。

    由于栈也是线性表,因此线性表的存储结构对栈也适用,通常栈有顺序栈和链栈两种存储结构,这两种存储结构的不同,则使得实现栈的基本运算的算法也有所不同。

我们要了解的是,在顺序栈中有"上溢"和"下溢"的概念。顺序栈好比一个盒子,我们在里头放了一叠书,当我们要用书的话只能从第一本开始拿(你会把盒子翻过来吗?真聪明^^),那么当我们把书本放到这个栈中超过盒子的顶部时就放不下了(叠上去的不算,哼哼),这时就是"上溢","上溢"也就是栈顶指针指出栈的外面,显然是出错了。反之,当栈中已没有书时,我们再去拿,看看没书,把盒子拎起来看看盒底,还是没有,这就是"下溢"。"下溢"本身可以表示栈为空栈,因此可以用它来作为控制转移的条件。

链栈则没有上溢的限制,它就象是一条一头固定的链子,可以在活动的一头自由地增加链环(结点)而不会溢出,链栈不需要在头部附加头结点,因为栈都是在头部进行操作的,如果加了头结点,等于要在头结点之后的结点进行操作,反而使算法更复杂,所以只要有链表的头指针就可以了。

1.2 栈的顺序存储

使用c++的面向对象封装:

// Test.cpp : Defines the entry point for the console application.//#include "stdafx.h"  #include 
using namespace std;#define MAX 10 // MAXIMUM STACK CONTENTclass stack   {
   private:    int arr[MAX];  int top; public:    stack()  {     inItStack();  } /************************************************************************/ /* 初始化栈                                                                     */ /************************************************************************/ void inItStack()  {   top=-1;  }  /************************************************************************/ /* 入栈                                                                     */ /************************************************************************/ void push(int a)  {     top++;  if(top < MAX)  {      arr[top]=a;   }   else   {      cout<<"STACK FULL!!"<
结论:由于栈的插入和删除操作具有它的特殊性,所以用顺序存储结构表示的栈并不存在插入删除数据元素时需要移动的问题,但栈容量难以扩充的弱点仍就没有摆脱。

1.3 栈的链式存储

若是栈中元素的数目变化范围较大或不清楚栈元素的数目,就应该考虑使用链式存储结构。人们将用链式存储结构表示的栈称作"链栈"。链栈通常用一个无头结点的单链表表示。如图所示:

栈的操作是线性表操作的特例。

简单用c实现:

// Test.cpp : Defines the entry point for the console application.  //  #include "stdafx.h"  #include 
  #include "stdlib.h"#include
using namespace std;//宏定义  #define TRUE   1  #define FALSE   0  #define OK    1  #define ERROR   0  #define INFEASIBLE -1  #define OVERFLOW -2 #define STACKEMPTY -3  #define LT(a,b)   ((a)<(b))  #define N = 100         typedef int Status;  typedef int ElemType;  typedef struct LNode{
   ElemType  data;                struct LNode   *next;     }LNode, *LinkList; typedef struct stack{
 LinkList top;} STACK;/************************************************************************//*     接口:*//************************************************************************/void InitStack(STACK &S);void Push(STACK &S,ElemType e);void Pop(STACK &S, ElemType *e);ElemType GetTop(STACK S,ElemType *e);int StackEmpty(STACK S);/************************************************************************//* *//************************************************************************/void InitStack(STACK &S){ S.top=NULL;}/************************************************************************//* 入栈 *//************************************************************************/void Push(STACK &S,ElemType e){ LinkList p; p = (LinkList )malloc(sizeof(LNode)); if (!p) exit(OVERFLOW); p->data = e; p->next = S.top; S.top = p;}/************************************************************************//* 出栈*//************************************************************************/void Pop(STACK &S, ElemType *e){ LinkList p; if(StackEmpty(S)) exit(STACKEMPTY); *e = S.top->data; p = S.top; S.top = p->next;  free(p);}/************************************************************************//* 获取栈顶元素内容*//************************************************************************/ElemType GetTop(STACK S, ElemType *e){ if(StackEmpty(S)) exit(STACKEMPTY); *e = S.top->data;}/************************************************************************//* 判断栈S是否空 *//************************************************************************/int StackEmpty(STACK S) { if(S.top==NULL) return TRUE; return   FALSE;}void main()  {   STACK S; InitStack( S); Push(S, 3); Push(S, 4); ElemType e; Pop(S,&e); cout<<"Pop elem:"<

1.4 栈的应用

1)  数制转换

2)语法词法分析

3)表达式求值等

1.5 栈的递归和实现

汉诺塔的问题:

解决:

1)如果有一个盘子,直接从X移到Z即可。

2)如果有n个盘子要从X移到Z,Y作为辅助。问题可以转化为,先将上面n-1个从X移动到Y,Z作为辅助,然后将第n个从X移动到Z,最后将剩余的n-1个从Y移动到Z,X作为辅助。

完整实现代码,包括栈的实现:

// Test.cpp : Defines the entry point for the console application.  //  #include "stdafx.h"  #include 
  #include "stdlib.h"#include
using namespace std;//宏定义  #define TRUE   1  #define FALSE   0  #define OK    1  #define ERROR   0  #define INFEASIBLE -1  #define OVERFLOW -2 #define STACKEMPTY -3  #define LT(a,b)   ((a)<(b))  #define N = 100         typedef int Status;  typedef int ElemType;  typedef struct LNode{
   ElemType  data;                struct LNode   *next;     }LNode, *LinkList; typedef struct stack{
 LinkList top;} STACK;/************************************************************************//*     接口:*//************************************************************************/void InitStack(STACK &S);void Push(STACK &S,ElemType e);void Pop(STACK &S, ElemType *e);ElemType GetTop(STACK S,ElemType *e);int StackEmpty(STACK S);/************************************************************************//* *//************************************************************************/void InitStack(STACK &S){ S.top=NULL;}/************************************************************************//* 入栈 *//************************************************************************/void Push(STACK &S,ElemType e){ LinkList p; p = (LinkList )malloc(sizeof(LNode)); if (!p) exit(OVERFLOW); p->data = e; p->next = S.top; S.top = p;}/************************************************************************//* 出栈*//************************************************************************/void Pop(STACK &S, ElemType *e){ LinkList p; if(StackEmpty(S)) exit(STACKEMPTY); *e = S.top->data; p = S.top; S.top = p->next;  free(p);}/************************************************************************//* 获取栈顶元素内容 *//************************************************************************/ElemType GetTop(STACK S, ElemType *e){ if(StackEmpty(S)) exit(STACKEMPTY); *e = S.top->data;}void printStack(STACK S){ LinkList p; p = S.top; printf("栈: "); while (p) {  printf("%d ", p->data);  p = p->next; }}/************************************************************************//* 如果有一个盘子,直接从X移到Z即可。如果有n个盘子要从X移到Z,Y作为辅助。问题可以转化为,先将上面n-1个从X移动到Y,Z作为辅助,然后将第n个从X移动到Z,最后将剩余的n-1个从Y移动到Z,X作为辅助。*//************************************************************************/void move(STACK &Sa,STACK &Sb){  ElemType e; Pop(Sa,&e); Push(Sb, e);}void hanoi(int n,STACK  &X,STACK &Y,STACK &Z){ if(n==1) return move(X, Z);  //将圆盘1号直接移到z hanoi(n-1,X,Z,Y);    //将x上的1大n-1圆盘移到y,z做辅助塔 move(X, Z);      //将编号为n的圆盘移z hanoi(n-1,Y,X,Z);       //将y上的1大n-1圆盘移到z,x做辅助塔}/************************************************************************//* 判断栈S是否空 *//************************************************************************/int StackEmpty(STACK S) { if(S.top==NULL) return TRUE; return   FALSE;}void main()  {   STACK Sx, Sy,Sz; InitStack( Sx); InitStack( Sy); InitStack( Sz); int i, n = 10; for (i = 10 ; i>=1 ;i--) {  Push(Sx, i); } printStack(Sx); hanoi(n,  Sx,Sy,Sz); printStack(Sz);} 

1.队列

1.1 队列定义 

队列(Queue)也是一种运算受限的线性表,它的运算限制与栈不同,是两头都有限制,插入只能在表的一端进行(只进不出),而删除只能在表的另一端进行(只出不进),允许删除的一端称为队尾(rear),允许插入的一端称为队头 (Front)

,队列的操作原则是先进先出的,所以队列又称作FIFO表(First In First Out)

队列的基本运算也有六种:

置空队 :InitQueue(Q)

判队空: QueueEmpty(Q)

判队满: QueueFull(Q)

入队 : EnQueue(Q,x)

出队 : DeQueue(Q)

取队头元素: QueueFront(Q),不同与出队,队头元素仍然保留。

队列也有顺序存储和链式存储两种存储结构,前者称顺序队列,后者为链队。

对于顺序队列,我们要理解"假上溢"的现象。

我们现实中的队列比如人群排队买票,队伍中的人是可以一边进去从另一头出来的,除非地方不够,总不会有"溢出"的现象,相似地,当队列中元素完全充满这个向量空间时,再入队自然就会上溢,如果队列中已没有元素,那么再要出队也会下溢。

那么"假上溢"就是怎么回事呢?

因为在这里,我们的队列是存储在一个向量空间里,在这一段连续的存储空间中,由一个队列头指针和一个尾指针表示这个队列,当头指针和尾指针指向同一个位置时,队列为空,也就是说,队列是由两个指针中间的元素构成的。在队列中,入队和出队并不是象现实中,元素一个个地向前移动,走完了就没有了,而是指针在移动,当出队操作时,头指针向前(即向量空间的尾部)增加一个位置,入队时,尾指针向前增加一个位置,在某种情况下,比如说进一个出一个,两个指针就不停地向前移动,直到队列所在向量空间的尾部,这时再入队的话,尾指针就要跑到向量空间外面去了,仅管这时整个向量空间是空的,队列也是空的,却产生了"上溢"现象,这就是假上溢。

为了克服这种现象造成的空间浪费,我们引入循环向量的概念,就好比是把向量空间弯起来,形成一个头尾相接的环形,这样,当存于其中的队列头尾指针移到向量空间的上界(尾部)时,再加1的操作(入队或出队)就使指针指向向量的下界,也就是从头开始。这时的队列就称循环队列。

通常我们应用的大都是循环队列。由于循环的原因,光看头尾指针重叠在一起我们并不能判断队列是空的还是满的,这时就需要处理一些边界条件,以区别队列是空还是满。方法至少有三种,一种是另设一个布尔变量来判断(就是请别人看着,是空还是满由他说了算),第二种是少用一个元素空间,当入队时,先测试入队后尾指针是不是会等于头指针,如果相等就算队已满,不许入队。第三种就是用一个计数器记录队列中的元素的总数,这样就可以随时知道队列的长度了,只要队列中的元素个数等于向量空间的长度,就是队满。

2.2 队列的顺序存储

顺序存储如图:

由于是顺序存储结构的存储空间是静态分配的,所以在添加数据的时,有可能没有剩余空间的情况。

解决这种“假溢出”情况,使用循环队列在c语言中,不能用动态分配的一维数组来实现循环队列。若使用循环队列,必须设置最大队列长度,若无法估计最大长度,就使用链式队列。

c实现:

// Test.cpp : Defines the entry point for the console application.  //  #include "stdafx.h"  #include 
  #include "stdlib.h"#include
using namespace std;//宏定义  #define TRUE   1  #define FALSE   0  #define OK    1  #define ERROR   0  #define INFEASIBLE -1  #define OVERFLOW -2 #define QUEUEEMPTY  -3        #define MAX_QUEUE 10 //队列的最大数据元素数目typedef int Status;  typedef int ElemType;  typedef struct queue{
   ElemType  elem[MAX_QUEUE] ;     ///假设当数组只剩下一个单元时认为队满           int front;  //队头指针 int rear;  //队尾指针   }QUEUE; /************************************************************************//*     各项基本操作算法。*//************************************************************************/void InitQueue(QUEUE *&Q);void EnQueue(QUEUE *Q,ElemType elem);void DeQueue(QUEUE *Q,ElemType *elem);int QueueEmpty(QUEUE Q);/************************************************************************//*   初始化  直接使用结构体指针变量,必须先分配内存地址,即地址的指针*//************************************************************************/void InitQueue(QUEUE *&Q) { Q = (QUEUE *) malloc (sizeof(QUEUE)); Q->front = Q->rear = -1;}/************************************************************************//*     入队                                                              *//************************************************************************/ void EnQueue(QUEUE *Q, ElemType elem){ if((Q->rear+1)% MAX_QUEUE == Q->front) exit(OVERFLOW); Q->rear = (Q->rear + 1)%MAX_QUEUE; Q->elem[Q->rear] = elem; }/************************************************************************//*     出队                                                               *//************************************************************************/void DeQueue(QUEUE *Q,ElemType *elem){ if (QueueEmpty(*Q))  exit(QUEUEEMPTY); Q->front =  (Q->front+1) % MAX_QUEUE; *elem=Q->elem[Q->front];}/************************************************************************//*    获取队头元素内容                                                            *//************************************************************************/void GetFront(QUEUE Q,ElemType *elem) { if ( QueueEmpty(Q) )  exit(QUEUEEMPTY); *elem = Q.elem[ (Q.front+1) % MAX_QUEUE ];}/************************************************************************//*    判断队列Q是否为空                                                             *//************************************************************************/int QueueEmpty(QUEUE Q){ if(Q.front==Q.rear) return TRUE; else return FALSE;}void main()  {   QUEUE *Q; InitQueue( Q); EnQueue( Q, 1); EnQueue( Q, 2); ElemType e; DeQueue( Q,&e); cout<<"De queue:"<
注意:InitQueue(QUEUE *&Q) 传的是指针的地址。

2.3 链式队列:

// Test.cpp : Defines the entry point for the console application.  //  #include "stdafx.h"  #include 
  #include "stdlib.h"#include
using namespace std;//宏定义  #define TRUE   1  #define FALSE   0  #define OK    1  #define ERROR   0  #define INFEASIBLE -1  #define OVERFLOW -2 #define QUEUEEMPTY  -3        typedef int Status;  typedef int ElemType;  typedef struct LNode {
   //链式队列的结点结构 ElemType elem;   //队列的数据元素类型 struct LNode *next;   //指向后继结点的指针}LNode, *LinkList;typedef struct queue{
 //链式队列 LinkList front;  //队头指针 LinkList rear;  //队尾指针}QUEUE; /************************************************************************//*     各项基本操作算法。*//************************************************************************/void InitQueue(QUEUE *Q);void EnQueue(QUEUE *Q,ElemType elem);void DeQueue(QUEUE *Q,ElemType *elem);void GetFront(QUEUE Q,ElemType *elem) ;bool QueueEmpty(QUEUE Q);/************************************************************************//*初始化队列Q  */void InitQueue(QUEUE *Q){ Q->front = (LinkList)malloc(sizeof(LNode)); if (Q->front==NULL) exit(ERROR); Q->rear= Q->front;}/*入队 */ void EnQueue(QUEUE *Q,ElemType elem){ LinkList s; s = (LinkList)malloc(sizeof(LNode)); if(!s) exit(ERROR); s->elem = elem; s->next = NULL; Q->rear->next = s; Q->rear = s;}/*出队  */ void DeQueue(QUEUE *Q,ElemType *elem){ LinkList s; if(QueueEmpty(*Q)) exit(ERROR); *elem = Q->front->next->elem; s = Q->front->next; Q->front->next = s->next; free(s);}/*获取队头元素内容  */ void GetFront(QUEUE Q,ElemType *elem) { if(QueueEmpty(Q)) exit(ERROR); *elem = Q.front->next->elem;}/*判断队列Q是否为空   */ bool QueueEmpty(QUEUE Q){ if(Q.front == Q.rear) return TRUE;    return FALSE;}void main()  {   QUEUE Q; InitQueue( &Q); EnQueue( &Q, 1); EnQueue( &Q, 2); ElemType e; DeQueue( &Q,&e); cout<<"De queue:"<

2. 4
.队列的应用
【举例 1 】银行排队
【举例 2 】模拟打印机缓冲区。
在主机将数据输出到打印机时,会出现主机速度与打印机的打印速度不匹配的问题。这时主机就要停下来等待打印机。显然,这样会降低主机的使用效率。为此人们设想了一种办法:为打印机设置一个打印数据缓冲区,当主机需要打印数据时,先将数据依次写入这个缓冲区,写满后主机转去做其他的事情,而打印机就从缓冲区中按照先进先出的原则依次读取数据并打印,这样做即保证了打印数据的正确性,又提高了主机的使用效率。由此可见,打印机缓冲区实际上就是一个队列结构。
【举例 3 】 CPU 分时系统
在一个带有多个终端的计算机系统中,同时有多个用户需要使用 CPU 运行各自的应用程序,它们分别通过各自的终端向操作系统提出使用 CPU 的请求,操作系统通常按照每个请求在时间上的先后顺序,将它们排成一个队列,每次把 CPU 分配给当前队首的请求用户,即将该用户的应用程序投入运行,当该程序运行完毕或用完规定的时间片后,操作系统再将 CPU 分配给新的队首请求用户,这样即可以满足每个用户的请求,又可以使 CPU 正常工作。
           

给我老师的人工智能教程打call!

这里写图片描述
你好! 这是你第一次使用 **Markdown编辑器** 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

新的改变

我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:

  1. 全新的界面设计 ,将会带来全新的写作体验;
  2. 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
  3. 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
  4. 全新的 KaTeX数学公式 语法;
  5. 增加了支持甘特图的mermaid语法 功能;
  6. 增加了 多屏幕编辑 Markdown文章功能;
  7. 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
  8. 增加了 检查列表 功能。

功能快捷键

撤销:Ctrl/Command + Z

重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G

合理的创建标题,有助于目录的生成

直接输入1次#,并按下space后,将生成1级标题。

输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

如何改变文本的样式

强调文本 强调文本

加粗文本 加粗文本

标记文本

删除文本

引用文本

H2O is是液体。

210 运算结果是 1024.

插入链接与图片

链接: .

图片: Alt

带尺寸的图片: Alt

当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

如何插入一段漂亮的代码片

去页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.

// An highlighted block var foo = 'bar';

生成一个适合你的列表

  • 项目
    • 项目
      • 项目
  1. 项目1
  2. 项目2
  3. 项目3
  • 计划任务
  • 完成任务

创建一个表格

一个简单的表格是这么创建的:

项目 Value
电脑 $1600
手机 $12
导管 $1

设定内容居中、居左、居右

使用:---------:居中

使用:----------居左
使用----------:居右

第一列 第二列 第三列
第一列文本居中 第二列文本居右 第三列文本居左

SmartyPants

SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:

TYPE ASCII HTML
Single backticks 'Isn't this fun?' ‘Isn’t this fun?’
Quotes "Isn't this fun?" “Isn’t this fun?”
Dashes -- is en-dash, --- is em-dash – is en-dash, — is em-dash

创建一个自定义列表

Markdown
Text-to-
HTML conversion tool
Authors
John
Luke

如何创建一个注脚

一个具有注脚的文本。

注释也是必不可少的

Markdown将文本转换为 HTML

KaTeX数学公式

您可以使用渲染LaTeX数学表达式 :

Gamma公式展示 Γ ( n ) = ( n − 1 ) ! ∀ n ∈ N \Gamma(n) = (n-1)!\quad\forall n\in\mathbb N Γ(n)=(n1)!nN 是通过欧拉积分

Γ ( z ) = ∫ 0 ∞ t z − 1 e − t d t &ThinSpace; . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,. Γ(z)=0tz1etdt.

你可以找到更多关于的信息 LaTeX 数学表达式.

新的甘特图功能,丰富你的文章

gantt        dateFormat  YYYY-MM-DD        title Adding GANTT diagram functionality to mermaid        section 现有任务        已完成               :done,    des1, 2014-01-06,2014-01-08        进行中               :active,  des2, 2014-01-09, 3d        计划一               :         des3, after des2, 5d        计划二               :         des4, after des3, 5d
  • 关于 甘特图 语法,参考 ,

UML 图表

可以使用UML图表进行渲染。 . 例如下面产生的一个序列图::

这将产生一个流程图。:

  • 关于 Mermaid 语法,参考 ,

FLowchart流程图

我们依旧会支持flowchart的流程图:

  • 关于 Flowchart流程图 语法,参考 .

导出与导入

导出

如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

导入

如果你想加载一篇你写过的.md文件或者.html文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,

继续你的创作。


  1. 注脚的解释

你可能感兴趣的文章
matplotlib.pyplot.plot()参数详解
查看>>
拉格朗日对偶问题详解
查看>>
MFC矩阵运算
查看>>
最小二乘法拟合:原理,python源码,C++源码
查看>>
ubuntu 安装mysql
查看>>
c# 计算器
查看>>
C# 简单的矩阵运算
查看>>
gcc 常用选项详解
查看>>
c++输入文件流ifstream用法详解
查看>>
c++输出文件流ofstream用法详解
查看>>
字符编码:ASCII,Unicode 和 UTF-8
查看>>
QT跨MinGW和MSVC两种编译器的解决办法
查看>>
firewalld的基本使用
查看>>
Linux下SVN客户端使用教程
查看>>
i2c-tools
查看>>
Linux分区方案
查看>>
nc 命令详解
查看>>
如何使用 systemd 中的定时器
查看>>
git命令速查表
查看>>
linux进程监控和自动重启的简单实现
查看>>