一、概念

  • 栈(stack)又名堆栈,它是一种运算受限的线性表:
    • 限定仅在表尾进行插入和删除操作的线性表。允许插入和删除的一端为栈顶,另一端是栈底。
    • 向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素。
    • 从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。
  • 举个例子,向 AK-47 的弹夹那样,最后压(存)入的子弹最先被打出去,最开始压入的子弹,最后才能被射出。

  • 再举一个例子,一个空盘子,每烙好一张饼都放在盘子最上面(这是 push 压入栈中),烙完后,盘子里堆了一叠饼,最下面的是最先烙好的。最上面的是刚烙好的,每一次吃只能从上面一张张拿,吃完一张拿下一张饼(这是 pop 出栈),直到盘子为空。

  • 栈的性质:栈是一种 LIFO(Last In First Out) 的线性表,也就是数据元素遵循后进先出的原则。
  • 栈的抽象数据类型:
ADT 栈(Stack)
Data具有线性表一样的性质。
Operationtop:获取栈顶元素count:栈的长度isEmpty:栈内元素是否为空push(data):元素进栈pop():元素出栈removeAll():清空栈内元素
EndADT
  • 栈的分类(存储结构):
    ① 栈的顺序存储结构:单栈、共享栈;
    ② 栈的链式存储结构;
  • 共享栈:两个顺序栈共享存储空间,栈1的栈顶在下标0处,栈2的栈顶在下标n-1处。
  • 栈的顺序结构和链式结构区别:
    ① 顺序结构需要预先估算存储空间大小,适合数据元素变化不大的情况;
    ② 链式结构不需要预先估算存储空间,适合数据元素变化较大的情况;
  • 栈和线性表的不同之处在于,栈只有进栈和出栈操作,并且遵循后进先出的规则,也就是说数据元素顺序进栈,逆序出栈。栈可以实现回退操作,递归和四则运算等。

二、栈的操作

  • s.push(item) // 在栈顶压入新元素
  • s.pop() // 删除栈顶元素但不返回其值
  • s.empty() // 如果栈为空返回true,否则返回false
  • s.size() // 返回栈中元素的个数
  • s.top() // 返回栈顶的元素,但不删除该元素

三、顺序存储栈

  • 概念
    • 顺序存储栈即物理结构是顺序存储,先开辟一块内存空间(和顺序存储链表一样有没有),每 push 一个新元素,栈顶标记 top+1。
    • 直到开辟的空间被存满,每 pop 一个栈顶元素,top-1,也就是下一个元素变成栈顶元素。
  • 定义数据结构:
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OK 1
#define MAXSIZE 20 /* 存储空间初始分配量 */typedef int Status;/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType;/* ElemType类型根据实际情况而定,这里假设为int *//* 顺序栈结构 */
typedef struct {ElemType data[MAXSIZE];int top; /* 用于栈顶指针 */
}Stack;

  • 初始化:
Status InitStack(Stack *S) {S->top = -1;return OK;
}
  • 清空:
Status ClearStack(Stack *S) {S->top = -1;return OK;
}
  • 获取栈顶元素:
Status GetTop(Stack S, ElemType *e) {if (S.top == -1) return ERROR;*e = S.data[S.top];return OK;
}
  • 获取栈长度:
int StackLength(Stack S) {return S.top+1;
}
  • Push:
Status PushStack(Stack *S, ElemType e) {    if (S->top == MAXSIZE -1) return ERROR;    S->top++;    S->data[S->top] = e;    return OK;
}
  • Pop:
Status PopStack(Stack *S, ElemType *e) {if (S->top == -1) {return ERROR;}*e = S->data[S->top];S->top--;return OK;
}
  • 输出测试:
int main(int argc, const char * argv[]) {// 创建栈Stack S;InitStack(&S);for (int i = 0; i < 10; i++) {PushStack(&S, i);}StackPrint(S);// 出栈ElemType e;PopStack(&S, &e);printf("出栈:%d",e);StackPrint(S);// 获取栈顶元素GetTop(S, &e);printf("栈顶:%d\n",e);// 输出长度printf("栈长度:%d\n",StackLength(S));return 0;
}
栈
0 1 2 3 4 5 6 7 8 9
出栈:9
栈
0 1 2 3 4 5 6 7 8
栈顶:8
栈长度:9

四、链式存储栈

  • 概念
    • 链式存储栈以链表的形式,新入栈的节点,next 指向原来的栈顶节点,插在链表的最前端,成为新的栈顶(和链表的头插法像不像?)。
    • 用 top 标记栈顶节点,而不是上面顺序存储的位置,每一次入栈新节点,top 指向新栈顶节点,count 也随之 +1。出栈时,top 指向栈顶节点的 next 节点,count-1。


  • 定义:
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OK 1typedef int Status;/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType;/* ElemType类型根据实际情况而定,这里假设为int *//* 链栈的每一个节点,和单链表很像有没有 */
typedef struct StackNode {ElemType data;struct StackNode *next;
}StackNode;typedef struct StackNode * StackNodePtr;/* 栈结构 */
typedef struct {StackNodePtr top;int count;
}LinkStack;
  • 初始化:
Status InitStack(LinkStack *S) {S->top = NULL;S->count = 0;return OK;
}
  • 清空:
Status ClearStack(LinkStack *S) {    if (S->top == NULL) return ERROR;    StackNodePtr p;    while (S->count != 0) {        p = S->top;        S->top = S->top->next;        free(p);        S->count--;    }    return OK;
}
  • 获取栈顶元素:
Status GetTop(LinkStack S, ElemType *e) {if (S.top == NULL) return ERROR;// if (S->count == 0) return ERROR; // 也可以*e = S.top->data;return OK;
}
  • 获取栈长度:
int StackLength(LinkStack S) {return S.count;
}
  • Push:
Status PushStack(LinkStack *S, ElemType e)
{// 创建新元素StackNodePtr p = (StackNodePtr)malloc(sizeof(StackNode));if (p == NULL) return ERROR;p->data = e;p->next = S->top;S->top = p;S->count++;return OK;
}
  • Pop:
Status PopStack(LinkStack *S, ElemType *e) {    if (S->top == NULL) return ERROR;    // if (S->count == 0) return ERROR; // 也可以    /* 将栈顶指针指向新的栈顶 */    StackNodePtr temp = S->top;    S->top = S->top->next;    *e = temp->data;    free(temp);    S->count--;    return OK;
}
  • 输出测试:
nt main(int argc, const char * argv[]) {// 创建栈LinkStack S;InitStack(&S);for (int i = 0; i < 10; i++) {PushStack(&S, i);}StackPrint(S);ElemType e;PopStack(&S, &e);printf("出栈:%d\n",e);StackPrint(S);// 获取栈顶元素GetTop(S, &e);printf("栈顶:%d\n",e);// 输出长度printf("栈长度:%d\n",StackLength(S));return 0;
}
9 8 7 6 5 4 3 2 1 0
出栈:9
8 7 6 5 4 3 2 1 0
栈顶:8
栈长度:9

队列

一、概念

  • 只允许在一段进行插入操作,而在另一端进行删除操作的线性表;
  • 性质:先进先出
  • 队列的抽象数据类型:
ADT 队列(Queue)
Data具有线性表一样的性质。
Operationfront:队列第一个数据count:队列的长度isEmpty():队列是否为空enQueue(data):数据进队列deQueue():数据出队列removeAll():清空队列内的所有数据
EndADT
  • 顺序队列:就是使用数组来模拟队列,但是数组的插入和删除需要移动数据,比较繁琐。
  • 循环顺序队列:在顺序队列的基础上改造,使队列的队头和队尾可以在数组中循环变化,在数据插入和删除就不需要频繁移动数据了。但是顺序队列,都需要提前申请存储空间,还有溢出的可能。
  • 链式队列:为了解决顺序队列的不足,引用链式队列。不需要提前申请空间,只不过会引入频繁申请和释放的操作。
  • 队列有什么作用?在开发过程中,接触最多的应该是全局并发队列。为什么要用队列实现呢?在线程的优先级一样的情况下,不应该先申请系统资源的先被满足吗?这和在银行排队取钱是一个道理。

二、队列的操作

  • push(item)
  • q.pop()
  • q.front()
  • q.back()
  • q.size()
  • q.empty()

三、链队列

  • 链式队列的表示:

  • 是不是似曾相识的结构?链栈,再看看链栈的表示:

  • 区别:栈,新的元素添加在栈顶,而且栈顶先出;队列,队尾进,队首出。
  • 二者相反,所以,链队列的操作简单来说就是:
    • 进入队列:Q.rear 尾节点后追加新节点,将 Q.rear 指向新节点,新节点成队尾;
    • 出队列:Q.front 指向的首元节点出队列,Q.front 指向首元节点的下一个节点。
  • 先定义数据结构:
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define OK 1typedef int Status;/* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int ElemType;/* ElemType类型根据实际情况而定,这里假设为int */typedef struct QueueNode {ElemType data;struct QueueNode *next;
} QueueNode, *QueueNodePtr;typedef struct {QueueNodePtr front;QueueNodePtr rear;
} LinkQueue;
  • 初始化:先创建一个头节点,让 Q.front 和 Q.rear 指向头节点,头节点的 next 为 NULL:

// 初始化Status
InitQueue(LinkQueue *Q) {    // 初始队列为空,只有头节点,不是有效数据的节点    *Q->front = *Q->rear = (QueueNodePtr)malloc(sizeof(QueueNode));    if (*Q->front == NULL) return ERROR;    // 头节点的后面为空    *Q->front->next = NULL;    return OK;
}
  • 判断队列为空:当队列为空时,恰入上图初始化的状态,只剩一个头节点,此时 Q.front == Q.rear;
// 判断是否为空Status
QueueEmpty(LinkQueue Q) {    if (Q.front == Q.rear) return TRUE;    return FALSE;
}
  • 进入队列:
    • 进入队列的操作,是将新元素,追加到 rear 指向的队尾之后,rear->next = 新元素,再将 rear 指向新元素,此时,新元素成为队尾。因为不受存储空间限制(内存占满另说),所以不需要一开始就判断是否队满,也没有队满的操作。
    • 创建新节点 p;
    • 追加到队尾;
    • 队列的 rear 指向 p,标记成新队尾。

Status EnQueue(LinkQueue *Q, ElemType e) {QueueNodePtr p = (QueueNodePtr)malloc(sizeof(QueueNode));if (p == NULL) return ERROR;p->data = e;p->next = NULL;// 追加到队尾*Q->rear->next = p;// 标记成队尾*Q->rear = p;return OK;
}
  • 出队列:出队列操作,是将首元节点从链队删除。
    • 判断队列是否为空;
    • 找到队首节点 head(此时已拿到节点,将该节点的 data 回调出去),等待删除;
    • 更改标记 head 后面的节点 s 为首元节点,即 head->next;
    • 判断是否是最后一个节点,是的话,rear 也指向头节点;
    • 释放原首节点 head。

Status DeQueue(LinkQueue *Q, ElemType *e) {if (QueueEmpty(*Q)) return ERROR;QueueNodePtr head;// 找到要删除的节点head = *Q->front->next;// 回调到函数外*e = head->data;// 更改头节点*Q->front->next = head->next;// 如果删到了队尾最后一个元素if (*Q->rear == head)*Q->rear = *Q->front;// 删除临时指针指向的头节点free(head);return OK;
}
  • 清空:仅清空头节点以外的全部节点,有头节点在,清空完,还能继续 EnQueue() 操作,又回到初始化后的状态;

// 清空队列
Status ClearQueue(LinkQueue *Q) {if (*Q->front->next == NULL) return ERROR;QueueNodePtr temp, p; // 首元节点*Q->rear = *Q->front;p = *Q->front->next;*Q->front->next = NULL;// 此时只有temp指向首元节点while (p) {temp = p;p = p->next;free(temp);}return OK;
}
  • 销毁:销毁操作,和清空不一样,清空仅仅删除除头节点以外的所有节点,清空后还可以再入队。但是销毁已经彻底不使用,需要连头节点也一并 free 掉,所以代码中是从 front 开始,而非 front->next,已 free 所有的节点;

// 销毁队列
Status DestoryQueue(LinkQueue *Q) {if (*Q->front->next == NULL) return ERROR;/*说明,头节点也是个malloc开辟的,也需要释放*/while (*Q->front) {*Q->rear = *Q->front->next;free(*Q->front);*Q->front = *Q->rear;}return OK;
}
  • 获取队首:不更改队列,不破坏队列现有结构,仅查找,所以首元节的数据直接读取 Q.front->next->data;
Status GetHead(LinkQueue Q, ElemType *e) {if (QueueEmpty(*Q)) return ERROR;*e = Q.front->next->data;return OK;
}

【数据结构与算法】之栈与队列的应用和操作相关推荐

  1. 数据结构与算法--利用栈实现队列

    利用栈实现队列 上一节中说明了栈的特点 后进先出,我们用数组的方式实现了栈的基本操作api,因此我们对栈的操作是不考虑排序的,每个api的操作基本都是O(1)的世界,因为不考虑顺序,所以找最大,最小值 ...

  2. 数据结构与算法(2)——栈和队列

    前言:题图无关,只是好看,接下来就来复习一下栈和队列的相关知识 前序文章: 数据结构与算法(1)--数组与链表(https://www.jianshu.com/p/7b93b3570875) 栈 什么 ...

  3. 【数据结构与算法】栈与队列

    栈 一.什么是栈? 1.后进者先出,先进者后出,这就是典型的"栈"结构. 2.从栈的操作特性来看,是一种"操作受限"的线性表,只允许在端插入和删除数据. 二.为 ...

  4. 数据结构与算法之栈与队列:java实现

    闻理似悟,遇境则迷!!! 栈与队列来说也算是一种特殊的线性表,栈的特点是后进先出,队列的特点是先进先出. 栈 栈的特点是后进先出,栈的操作只有出栈和入栈(也叫压栈),除此之外,还包含栈顶与栈底的指向以 ...

  5. 【数据结构与算法】栈与队列【C语言版】

    目录 3.1 栈和队列的定义和特点 3.2 栈.队列与一般线性表的区别 3.3 栈的表示和操作的实现 顺序栈与顺序表 ================= 顺序栈的表示 顺序栈初始化 判断顺序栈是否为空 ...

  6. 用JS描述的数据结构及算法表示——栈和队列(基础版)

    前言:找了上课时数据结构的教程来看,但是用的语言是c++,所以具体实现在网上搜大神的博客来看,我看到的大神们的博客都写得特别好,不止讲了最基本的思想和算法实现,更多的是侧重于实例运用,一边看一边在心里 ...

  7. 数据结构与算法(二) 栈与队列(代码示例)

    数据结构与算法 栈与队列 1. 数组和链表实现栈 2. 用O(1)的时间复杂度求栈中的最小元素 3. 链表和数组实现队列 4. 用两个栈模拟队列操作 1. 数组和链表实现栈 链表的方式: /*** 描 ...

  8. 数据结构与算法 | 用栈实现队列

    之前的几章我讲解了栈和队列的基本特性和一些基本的操作方法,那么如何能利用栈来实现队列呢? 下面我来讲解下具体思路,栈的特性先进后出,队列是先进先出,如果要模拟队列的这个特性,我们就必须用到两个栈. 一 ...

  9. Python数据结构与算法(六)--栈和队列

    栈和队列 栈(stack),有些地方称为堆栈,是一种容器,可存入数据元素.访问元素.删除元素,它的特点在于只能允许在容器的一端(称为栈顶端指标,英语:top)进行加入数据(英语:push)和输出数据( ...

  10. python数据结构和算法3 栈、队列和排序

    顺序表和链表都是线性表,线性数据 栈 stack,也叫堆栈,是一种容器,可存入元素.访问元素.删除元素,特点是只允许在容器的一端(栈顶,top)进行加入数据(压栈,push)和输出数据(pop),按照 ...

最新文章

  1. React router 的 Route 中 component 和 render 属性理解
  2. 为什么程序员都不喜欢使用 switch ,而是大量的 if……else if ?
  3. cmos 和 bios中断大全
  4. 帝国cms上传大图片分片大视频大文件webuploader插件
  5. OpenCV学习笔记(六)(七)(八)(九)(十)
  6. 离散数学复习命题公式的范式
  7. 程序员转实施工程师_只有程序员才能看得懂?程序员:算了,不看了,我得写代码了...
  8. sql 删除最低分数_软件测试从业者:必备SQL语句21天打卡,前10天
  9. 多个ai文件合并成pdf_如何将多个文档合并成PDF?
  10. Linux tshark发送抓取的数据到kafka
  11. AI产品开发指南:5大核心环节搞定机器学习工作流
  12. 百度每周更新时间列表
  13. 在java中产生随机数_在Java中产生随机数的两个方法
  14. 按中文排序List集合
  15. H3C交换机配置VLAN
  16. js 浏览器下载显示进度
  17. 什么是等保(信息安全等级保护)?
  18. 有赞BI平台实现原理
  19. 7. ShowWindow
  20. 波浪动力滑翔机的综述

热门文章

  1. 中国移动基于ARM/x86服务器的Ceph性能对比
  2. C# 强制关闭当前程序进程(完全Kill掉不留痕迹)
  3. python学习笔记(字典)
  4. HDU 1159 Common Subsequence
  5. 免费的SEO工具软件大全
  6. C#嵌套任务和子任务
  7. [**奇文共赏**补充问题] 据说看五遍能懂的人智商 200
  8. linux下c语言tcp文件传输,C语言实现TCP通信
  9. windows下使用docker(一)—— 安装
  10. 《结对-结对编项目作业名称-测试过程》