赞
踩
之前鸽了真的抱歉,肝了一段时间把栈和队列全部更完。
你有没有想过,为什么我们吃饭的时候,总是先吃掉最后放进嘴里的那一口?为什么我们排队的时候,总是先让最先来的人先走?这些生活中的现象,其实都和栈和队列的数据结构有关。
栈和队列是两种常见的数据结构,它们分别对应了后进先出(LIFO)和先进先出(FIFO)的原则。
栈就像是一叠盘子,你只能从最上面拿走或放上一个盘子,这个盘子就是栈顶。
队列就像是一条长龙,你只能从尾巴加入或从头部离开,这两个位置就是队尾和队首。
本文将向你大致介绍栈和队列的概念、特征和应用场景。
本博客为本人在学习数据结构路途上的知识整理,如觉得对有你有所帮助,还希望不要吝啬你的赞,整理知识点是真的很累|*´Å`)ノ 。由于博主只是一名大一新生,在文章难免会出现错误,还希望指正。如果想要转载,附上链接就行。
目录
例如:栈S=(a1,a2,a3...,an1,an)a1称为栈底元素,an称为栈顶元素
栈只能在栈顶进行插入和删除的操作,只能在an-1尾部进行插入,插入an之后,只能先删除an然后再删除an-1
与同线性表相同,仍为一对一关系。
用顺序栈或链栈存储均可,但以顺序栈更常见
只能在栈顶运算,且访问结点时依照后进先出(LIFO)的原则。
关键是编写入栈和出栈函数,具体实现依顺序栈或链栈的不同而不同
与同线性表相同,仍为一对一关系。
顺序队或链队,以循环顺序队列更常见。
只能在队首和队尾运算,直访问结点时依照先进先出(FIFO)的原则
关键是掌握入队和出队操作,具体实现依顺序队或链队的不同而不同
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
示例 1:
输入:s = "()" 输出:true
示例 2:
输入:s = "()[]{}" 输出:true
示例 3:
输入:s = "(]" 输出:false
解题步骤:
①将s依次栈入栈
②将s栈入的时候进行判段,为左括号直接栈入
③如果为右括号,判断是否与栈顶匹配,匹配则将栈顶栈出
④如果右括号与栈顶不匹配,则为非有效的括号,直接结束函数。
舞伴问题
假设在舞会上,男士和女士各自排成一队。舞会开始依次从男队和女队的队头各出一人配成舞伴。如果两队初始人数不相同,则较长的那一队中未配对者等待下一轮舞曲。现要求写一算法模拟上述舞伴配对问题。
显然,先入队的男士或女士先出队配成舞伴。因此该问题具有典型的先进先出特性,可以用队列作为算法的数据结构。
①首先构造两个队列
②依次将队头元素出队配成舞伴
③某队为空,则另外一队等待着则是下一舞曲第一个
可获得舞伴的人。
ADT Stack {
数据对象:
D= (ai /ai EElemSet, i=1,2..,n, n≥0)
数据关系:
R1={ <ai-1,ai> | ai-1,ai ∈D,i=2...,n}
约定an端为栈顶,a1端为栈底。
基本操作:初始化、进栈、出栈、取栈顶元素等
} ADT Stack
InitStack(&S)
初始化操作
操作结果:构造一个空栈S
DestroyStack(&S)
销毁栈操作
初始条件:栈S已存在。
操作结果:栈S被销毁。
StackEmpty(S)判定S是否为空栈
初始条件:栈S已存在。
操作结果:若栈S为空栈,则返回TRUE,否则返回FALSE
StackLength(S)
求栈的长度
初始条件:栈S
初始条件:栈S已存在且非空
操作结果:用e返回S的栈顶元素。
ClearStack(&S)
栈置空操作
初始条件:栈S已存在
操作结果:将S清为空栈
Push(&S,e)
入栈操作
初始条件:栈S已存在
操作结果:插入元素e为新的栈顶元素。
Pop(&S &e)
出栈操作
初始条件:栈S已存在且非空。
操作结果:删除S的栈顶元素an,并用e返回其值。
由于栈本身就是线性表,于是栈也有顺序存储和链式存储两种实现方式。
栈的顺序存储-顺序栈
栈的链式存储-链栈
简单方便、但易产生溢出(数组大小固定)
上溢(stack overflow):栈已经满,又要压入元素
下溢(stack underflow):栈已经空,还要弹出元素 注:上溢是一种错误,使问题的处理无法进行;而下溢一般认为是一种结束条件,即问题处理结束。
同一般线性表的顺序存储结构完全相同利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素。栈底一般在低地址端
设top指针,指示栈顶元素在顺序栈中的位置
设base指针,指示栈底元素在顺序栈中的位置
为了方便操作,通常top指示真正的栈顶元素之上的下标地址
用stacksize表示栈可使用的最大容量
- #include <stdio.h>
- #include <stdlib.h>
- #include <stdbool.h>
-
- typedef int ElemType; // 定义栈元素的数据类型
-
- typedef struct
- {
- ElemType data; // 数据域
- LinkStack next;
- }StackNode, *LinkStack; // 定义链栈栈的类型
- void InitStack(SqStack* S)
- {
- S->base = (ElemType*)malloc(MAXSIZE * sizeof(ElemType)); // 分配存储空间
- if (!S->base) // 分配失败
- {
- exit(1);
- }
- S->top = S->base; // 栈顶和栈底指针初始指向同一位置
- S->stacksize = MAXSIZE; // 栈的最大容量
- }
- void DestroyStack(SqStack* S)
- {
- free(S->base); // 释放存储空间
- S->base = S->top = NULL; // 将栈底和栈顶指针置为NULL
- S->stacksize = 0; // 当前栈的容量为0
- }
- bool StackEmpty(SqStack S)
- {
- if (S.top == S.base) // 栈顶和栈底指针相同时,即栈为空
- return true; // 返回TRUE
- else
- return false; // 返回FALSE
- }
- int StackLength(SqStack S)
- {
- return (S.top - S.base); // 栈顶指针减去栈底指针就是栈的长度
- }
- oid ClearStack(SqStack* S)
- {
- S->top = S->base; // 将栈顶和栈底指针指向同一位置
- }
- bool GetTop(SqStack S, ElemType* e)
- {
- if (S.top == S.base) // 栈为空时,返回FALSE
- return false;
- *e = *(S.top - 1); // 栈顶元素的值赋给e
- return true;
- }
- void Push(SqStack* S, ElemType e)
- {
- if (S->top - S->base == S->stacksize) // 栈满时,扩充存储空间
- {
- S->base = (ElemType*)realloc(S->base, (S->stacksize + MAXSIZE) * sizeof(ElemType));
- if (!S->base)
- {
- exit(1);
- }
- S->top = S->base + S->stacksize; // 栈底指针不变,栈顶指针指向新的位置
- S->stacksize += MAXSIZE; // 栈的最大容量增加 MAXSIZE
- }
- *(S->top++) = e; // 元素 e 入栈,栈顶指针加1
- }
- bool Pop(SqStack* S)
- {
- if (S->top == S->base) // 栈为空时,返回FALSE
- {
- return false;
- }
- S--;
- return true;
- }
- #include <stdio.h>
- #include <stdlib.h>
- #include <stdbool.h>
-
- typedef int ElemType; // 定义栈元素的数据类型
-
- typedef struct StackNode
- {
- ElemType data; // 数据域
- struct StackNode* next; // 指向下一个结点的指针
- } StackNode, * LinkStack; // 定义链栈栈的类型
- void InitStack(LinkStack* S)
- {
- *S = NULL; // 初始化栈顶指针为空
- }
- void ClearStack(LinkStack* S)
- {
- StackNode* p;
- while (*S != NULL)
- {
- p = *S; // 指向栈顶结点
- *S = (*S)->next; // 栈顶指针指向下一个结点
- free(p); // 释放当前结点的内存空间
- }
- }
- bool StackEmpty(LinkStack S)
- {
- if (S == NULL) // 栈顶指针为空时,即栈为空
- return true;
- else
- return false;
- }
- int StackLength(LinkStack S)
- {
- int len = 0; // 栈的长度
- while (S != NULL)
- {
- S = S->next; // 栈顶指针向下移动
- len++; // 栈的长度加1
- }
- return len;
- }
- bool GetTop(LinkStack S, ElemType* e)
- {
- if (S == NULL) // 栈为空时,返回FALSE
- return false;
- *e = S->data; // 栈顶元素的值赋给e
- return true;
- }
- void Push(LinkStack* S, ElemType e)
- {
- StackNode* p;
- p = (StackNode*)malloc(sizeof(StackNode)); // 创建新结点
- if (!p) // 分配内存失败
- {
- exit(1);
- }
- p->data = e; // 将元素e赋值给新结点的数据域
- p->next = *S; // 新结点的next指针指向原栈顶指针指向的结点
- *S = p; // 修改栈顶指针指向新的结点
- }
- bool Pop(LinkStack* S)
- {
- StackNode* p;
- if (*S == NULL) // 栈为空时,返回FALSE
- return false;
- p = *S; // p指向栈顶结点
- *S = p->next; // 栈顶指针指向下一个结点
- free(p); // 释放当前结点的内存空间
- return true;
- }
ADT Queuel {
数据对象:D={ai | ai∈ElemSet,=1,2,...,n≥0}
数据关系:R={<ai-1,ai> | ai-1,aI∈D,=2..,n) 约定a1端为队列头,an端为队列尾。
基本操作:初始化、入队、出队、将队列置空、遍历队列等操作...
} ADT Queue
InitQueue(&Q)
操作结果:构造空队列Q
DestroyQueue(&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返回值
同一般线性表的顺序存储结构完全相同利用一组地址连续的存储单元依次存放自队列头到队尾的数据元素。队尾一般在低地址端
设front指针,指示队头元素在顺序栈中的位置
设rear指针,指示队尾元素在顺序栈中的位置
- #include <stdio.h>
- #include <stdlib.h>
- #include <stdbool.h>
-
- #define MAX_SIZE 100 // 队列的最大长度
-
- typedef int ElemType; // 定义队列元素的数据类型
-
- typedef struct
- {
- ElemType* data; // 存储队列元素的数组
- int front; // 队头指针,指向队头元素
- int rear; // 队尾指针,指向队尾元素的下一个位置
- } SqQueue;
-
- // 构造空队列Q
- void InitQueue(SqQueue* Q)
- {
- Q->data = (ElemType*)malloc(MAX_SIZE * sizeof(ElemType)); // 动态分配数组空间
- Q->front = Q->rear = 0; // 初始化队头和队尾指针
- }
-
- // 队列Q销毁
- void DestroyQueue(SqQueue* Q)
- {
- free(Q->data); // 释放队列的数组空间
- Q->data = NULL; //将指针置空
- Q->front = Q->rear = 0; // 初始化队头和队尾指针
- }
-
- // 将队列Q清空
- void ClearQueue(SqQueue* Q)
- {
- Q->front = Q->rear = 0; // 将队头和队尾指针置为0,表示队列为空
- }
-
- // 返回队列Q的元素个数,即队列的长度
- int QueueLength(SqQueue Q)
- {
- return Q.rear - Q.front;
- }
-
- // 用e返回队列Q的队头元素
- bool GetHead(SqQueue Q, ElemType* e)
- {
- if (Q.front == Q.rear) // 队列为空,无法取出队头元素
- {
- return false;
- }
- *e = Q.data[Q.front]; // 将队头元素赋值给e
- return true;
- }
-
- // 插入元素e为队列Q的队尾元素
- bool EnQueue(SqQueue* Q, ElemType e)
- {
- if ((Q->rear + 1) % MAX_SIZE == Q->front) // 队列已满,插入失败
- {
- return false;
- }
- Q->data[Q->rear] = e; // 将元素e插入队尾
- Q->rear++; // 更新队尾指针
- return true;
- }
-
- // 删除队列Q的队头元素,并用e返回值
- bool DeQueue(SqQueue* Q, ElemType* e)
- {
- if (Q->front == Q->rear) // 队列为空,删除失败
- {
- return false;
- }
- *e = Q->data[Q->front]; // 将队头元素赋值给e
- Q->front++; // 更新队头指针
- return true;
- }
如果仔细观察上面的代码,不难发一个问题:
当我门进行出队操作时,front+1后之前的空间便无法利用,如果此时队满了,出现上溢情况,但实际数组中却还有空间无法被我们使用,我们把这种情况称为假上溢。
假上溢会极大地浪费我们的内存空间,我们该如何解决这个问题呢?
解决假上溢的方法
实现方法:利用模(%)运算。
不难发现,循环队列中队空为:rear==front,队满也为rear==front,该如判断队空队满?
代码实现:
- #include <stdio.h>
- #include <stdlib.h>
- #include <stdbool.h>
-
- #define MAX_SIZE 100 // 循环队列的最大长度
-
- typedef int ElemType; // 定义队列元素的数据类型
-
- typedef struct
- {
- ElemType* data; // 存储队列元素的数组
- int front; // 队头指针,指向队头元素
- int rear; // 队尾指针,指向队尾元素的下一个位置
- int size; // 队列元素个数
- } SqQueue;
-
- // 构造空队列Q
- void InitQueue(SqQueue* Q)
- {
- Q->data = (ElemType*)malloc(MAX_SIZE * sizeof(ElemType)); // 动态分配数组空间
- Q->front = Q->rear = 0; // 初始化队头和队尾指针
- Q->size = 0; // 初始化队列元素个数
- }
-
- // 队列Q销毁
- void DestroyQueue(SqQueue* Q)
- {
- free(Q->data); // 释放队列的数组空间
- Q->data = NULL; //将指针置空
- Q->front = Q->rear = 0; // 初始化队头和队尾指针
- Q->size = 0; // 将队列元素个数置为0,表示队列为空
- }
-
- // 将队列Q清空
- void ClearQueue(SqQueue* Q)
- {
- Q->front = Q->rear = 0; // 将队头和队尾指针置为0,表示队列为空
- Q->size = 0; // 将队列元素个数置为0,表示队列为空
- }
-
- // 返回队列Q的元素个数,即队列的长度
- int QueueLength(SqQueue Q)
- {
- return Q.size;
- }
-
- // 用e返回队列Q的队头元素
- bool GetHead(SqQueue Q, ElemType* e)
- {
- if (Q.front == Q.rear) // 队列为空,无法取出队头元素
- return false;
- *e = Q.data[Q.front]; // 将队头元素赋值给e
- return true;
- }
-
- // 插入元素e为队列Q的队尾元素
- bool EnQueue(SqQueue* Q, ElemType e)
- {
- if ((Q->rear + 1) % MAX_SIZE == Q->front) // 队列已满,插入失败
- return false;
- Q->data[Q->rear] = e; // 将元素e插入队尾
- Q->rear = (Q->rear + 1) % MAX_SIZE; // 更新队尾指针
- Q->size++; // 队列元素个数加1
- return true;
- }
-
- // 入队,插入元素e为队列Q的队尾元素
- bool EnQueue(SqQueue* Q, ElemType e)
- {
- if ((Q->rear + 1) % MAX_SIZE == Q->front) // 队列已满,插入失败
- return false;
- Q->data[Q->rear] = e; // 将元素e插入队尾
- Q->rear = (Q->rear + 1) % MAX_SIZE; // 更新队尾指针
- Q->size++; // 队列元素个数加1
- return true;
- }
-
- // 出队,删除队列Q的队头元素,并用e返回值
- bool DeQueue(SqQueue* Q, ElemType* e)
- {
- if (Q->front == Q->rear) // 队列为空,删除失败
- return false;
- *e = Q->data[Q->front]; // 将队头元素赋值给e
- Q->front = (Q->front + 1) % MAX_SIZE; // 更新队头指针
- Q->size--; // 队列元素个数减1
- return true;
- }
- #include <stdio.h>
- #include <stdlib.h>
- #include <stdbool.h>
-
- typedef int ElemType; // 定义队列元素的数据类型
-
- // 队列结点类型
- typedef struct QNode
- {
- ElemType data; // 结点的数据域
- struct QNode* next; // 结点的指针域
- }QNode, * QuenePtr;
-
- // 链式队列类型
- typedef struct
- {
- QuenePtr front; // 队头指针,指向队头结点
- QuenePtr rear; // 队尾指针,指向队尾结点
- }LinkQuene;
-
- // 初始化队列Q
- void InitQuene(LinkQuene* Q)
- {
- Q->front = Q->rear = (QuenePtr)malloc(sizeof(QNode)); // 创建头结点
- if (!Q->front) // 内存分配失败
- exit(1);
- Q->front->next = NULL; // 头结点的指针域置空
- }
-
- // 销毁队列Q
- void DestroyQuene(LinkQuene* Q)
- {
- QuenePtr p = Q->front;
- while (p) // 逐个释放结点内存
- {
- Q->front = p->next;
- free(p);
- p = Q->front;
- }
- Q->front = Q->rear = NULL; // 将队列指针置空
- }
-
-
- // 返回队列Q的元素个数,即队列的长度
- int QueneLength(LinkQuene Q)
- {
- int count = 0;
- QuenePtr p = Q.front->next;
- while (p) // 遍历队列计数
- {
- count++;
- p = p->next;
- }
- return count;
- }
-
- // 用e返回队列Q的队头元素
- bool GetHead(LinkQuene Q, ElemType* e)
- {
- if (Q.front == Q.rear) // 队列为空,无法取出队头元素
- return false;
- *e = Q.front->next->data; // 将队头元素赋值给e
- return true;
- }
-
- // 入队操作
- bool EnQuene(LinkQuene* Q, ElemType e)
- {
- QuenePtr p = (QuenePtr)malloc(sizeof(QNode)); // 创建新结点
- if (!p) // 内存分配失败
- return false;
- p->data = e; // 将元素e存储到新结点中
- p->next = NULL; // 将新结点的next指针置为NULL
-
- Q->rear->next = p; // 将新结点链接到队尾结点的后面
- Q->rear = p; // 将队尾指针指向新结点
-
- return true;
- }
-
- // 出队操作
- bool DeQuene(LinkQuene* Q, ElemType* e)
- {
- if (Q->front == Q->rear) // 队列为空,无法出队
- return false;
- QuenePtr p = Q->front->next; // 指向队头结点
- *e = p->data; // 将队头元素赋值给e
- Q->front->next = p->next; // 将队头结点删除
- if (Q->rear == p) // 如果队列只有一个元素,出队后队列为空,需要将队尾指针指向头结点
- Q->rear = Q->front;
- free(p); // 释放出队的结点的空间
- return true;
- }
栈和队列是两种常用的数据结构,都是操作方式受限的线性表,都支持快速插入和删除元素,但它们的特点和应用场景略有不同。
栈(Stack)是一种后进先出(Last In First Out, LIFO)的数据结构,通常用数组或链表实现。栈有两个基本操作:入栈(push)和出栈(pop)。在栈中,只能访问最后一个入栈的元素,不能访问其他元素。栈的典型应用包括:表达式求值、函数调用、括号匹配等。
队列(Queue)是一种先进先出(First In First Out, FIFO)的数据结构,通常用数组或链表实现。队列有两个基本操作:入队(enQueue)和出队(deQueue)。在队列中,只能访问最先入队的元素,不能访问其他元素。队列的典型应用包括:进程调度、广度优先搜索、缓存等。
在实际应用中,栈和队列的应用十分广泛。例如,在计算机底层,操作系统利用栈来保存程序调用信息和临时变量;在图形学中,使用栈来实现画图的撤销和重做操作;在网络通信中,使用队列来实现数据包的存储和转发。掌握栈和队列的基本操作及其应用,是编程中必不可少的基本功。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。