之前,我们学习了顺序表和链表的相关知识,也完成了相应的练习,接下来我们要学习的是栈和队列!
他们也可以说是顺序表和链表的延续,主要思想还是和顺序表+链表的思想相关的,也需要借助顺序表和链表来实现栈和队列。
栈:一种特殊的线性表(逻辑上一定连续,物理上不一定连续),其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
后进先出:可以理解为吃饼干---按照顺序吃饼干,最先吃到的饼干是厂家最后装进去的
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
栈是一端出、入,是为栈顶;一端自动划分为栈尾
那么,我们是通过顺序表还是链表来实现呢?
下面,我们来简单的权衡利弊一下:
#pragma once
#include
#include
#include
#include
// 支持动态增长的栈
typedef int STDataType;
typedef struct Stack
{
STDataType* _a;
int _top; // 栈顶
int _capacity; // 容量
}Stack;
// 初始化栈
void StackInit(Stack* ps);
// 入栈
void StackPush(Stack* ps, STDataType data);
// 出栈
void StackPop(Stack* ps);
// 获取栈顶元素
STDataType StackTop(Stack* ps);
// 获取栈中有效元素个数
int StackSize(Stack* ps);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* ps);
// 销毁栈
void StackDestroy(Stack* ps);
我们先来实现初始化:
void StackInit(Stack* ps) { assert(ps); ps->_a = NULL; ps->_capacity = 0; ps->_top = 0;//top类比于顺序表的size,用来计数的 }
按照之前的顺序表的思路,我们很容易就想到了将top(类比于size--->计数)赋值为0
但是,请注意⚠️
这里潜藏着一个问题:
#include"stack.h"
// 初始化栈
void StackInit(Stack* ps)
{
assert(ps);
ps->_a = NULL;
ps->_capacity = 0;
ps->_top = 0;//top类比于顺序表的size,用来计数的
}
// 入栈
void StackPush(Stack* ps, STDataType data)
{
assert(ps);
if (ps->_top == ps->_capacity)
{
int newcapacity = ps->_capacity == 0 ? 4 : 2 * ps->_capacity;
STDataType* tmp = (STDataType*)realloc(ps->_a, sizeof(STDataType) * newcapacity);
if (tmp == NULL)
{
perror("realloc error!\n");
return;
}
ps->_a = tmp;
ps->_capacity = newcapacity;
}
ps->_a[ps->_top] = data;
ps->_top++;
}
// 出栈
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->_top > 0);//栈不为空
ps->_top--;
}
// 获取栈顶元素
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(ps->_top > 0);//栈不为空
return ps->_a[ps->_top-1];
}
// 获取栈中有效元素个数
int StackSize(Stack* ps)
{
assert(ps);
return ps->_top;
}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* ps)
{
assert(ps);
if (ps->_top == 0)
return 1;
return 0;
}
// 销毁栈
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->_a);
ps->_a = NULL;
ps->_capacity = ps->_top = 0;
}
#include"stack.h"
// 初始化栈
void StackInit(Stack* ps)
{
assert(ps);
ps->_a = NULL;
ps->_capacity = 0;
ps->_top = -1;//top类比于顺序表的size,用来计数的
}
// 入栈
void StackPush(Stack* ps, STDataType data)
{
assert(ps);
if (ps->_top+1 == ps->_capacity)
{
int newcapacity = ps->_capacity == 0 ? 4 : 2 * ps->_capacity;
STDataType* tmp = (STDataType*)realloc(ps->_a, sizeof(STDataType) * newcapacity);
if (tmp == NULL)
{
perror("realloc error!\n");
return;
}
ps->_a = tmp;
ps->_capacity = newcapacity;
}
ps->_top++;
ps->_a[ps->_top] = data;
}
// 出栈
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->_top >= 0);//栈不为空
ps->_top--;
}
// 获取栈顶元素
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(ps->_top >= 0);//栈不为空
return ps->_a[ps->_top];
}
// 获取栈中有效元素个数
int StackSize(Stack* ps)
{
assert(ps);
return ps->_top+1;
}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* ps)
{
assert(ps);
if (ps->_top == -1)
return 1;
return 0;
}
// 销毁栈
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->_a);
ps->_a = NULL;
ps->_capacity = 0;
ps->_top = -1;
}
#include"stack.h"
int main()
{
Stack st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
StackPush(&st, 5);
while (!StackEmpty(&st))
{
printf("%d ", StackTop(&st));
StackPop(&st);
}
printf("\n");//访问一次之后栈就为空了
StackDestroy(&st);
return 0;
}
思考一下,后进先出是绝对的吗?
进去顺序:1 2 3 4 5
出来顺序:5 4 3 2 1
这是一定的吗?还有别的出栈顺序吗?
我们不妨来看看代码的实现效果:
我们发现,出栈的顺序竟然可以不是54321
我们可以进一个立马出一个‼️然后再进‼️
所以,后进先出是相对的,不是绝对的!是相对于栈里面现有的数据而言的后进先出‼️
故,入栈顺序和出栈顺序是一对多的关系‼️
1.一个栈的初始状态为空。现将元素1、2、3、4、5、A、B、C、D、E依次入栈,然后再依次出栈,则元素出
栈的顺序是( )。
A 12345ABCDE
B EDCBA54321
C ABCDE12345
D 54321EDCBA
很容易地,我们根据后进先出的原则,知道选B
2.若进栈序列为 1,2,3,4 ,进栈过程中可以出栈,则下列不可能的一个出栈序列是()
A 1,4,3,2
B 2,3,4,1
C 3,1,4,2
D 3,4,2,1
我们逐一击破:
A.入1--->出1--->入2 3 4--->出4 3 2
B.入1 2--->出2--->入3 4--->出4 3 1
C.入1 2 3--->出3--->出1不可能!!!
D.入1 2 3--->出3--->入4--->出4--->出2 1
综上,我们选择C
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
typedef int STDataType;
typedef struct Stack
{
STDataType* _a;
int _top; // 栈顶
int _capacity; // 容量
}Stack;
void StackInit(Stack* ps)
{
assert(ps);
ps->_a=NULL;
ps->_capacity=ps->_top=0;
}
void StackPush(Stack* ps, STDataType data)
{
assert(ps);
if(ps->_top==ps->_capacity)
{
int newcapacity=ps->_capacity==0?4:2*ps->_capacity;
STDataType* tmp=(STDataType*)realloc(ps->_a,sizeof(STDataType)*newcapacity);
if(tmp==NULL)
{
perror("realloc error!\n");
return ;
}
ps->_a=tmp;
ps->_capacity=newcapacity;
}
ps->_a[ps->_top]=data;
ps->_top++;
}
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->_top>0);
ps->_top--;
}
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(ps->_top>0);
return ps->_a[ps->_top-1];
}
int StackSize(Stack* ps)
{
assert(ps);
return ps->_top;
}
bool StackEmpty(Stack* ps)
{
assert(ps);
return ps->_top==0;
}
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->_a);
ps->_a=NULL;
ps->_capacity=ps->_top=0;
}
bool isValid(char* s) {
Stack st;
StackInit(&st);
while(*s)
{
//左括号入栈
if((*s=='(')||(*s=='[')||(*s=='{'))
{
StackPush(&st,*s);
}
else
{
//没有左括号的情况
if(StackEmpty(&st))
{
StackDestroy(&st);
return false;
}
//左括号出栈
char top=StackTop(&st);
StackPop(&st);
if((*s==')'&& top!='(')
||(*s==']'&& top!='[')
||(*s=='}'&& top!='{'))
{
StackDestroy(&st);
return false;
}
}
s++;
}
//可能没有右括号,此时栈不为空
bool ret=StackEmpty(&st);
StackDestroy(&st);
return ret;
}
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头
这个方式可以类比于我们日常排队结账,只能排到队尾,不能直接站到队头,收银台也只在队头,结完账就离开了队伍
先进先出:先来的先结账,后来的后结账,公平公正
其实,有了上面的类比之后,我们就可以很好的理解到先进先出是绝对的,毕竟你也不想排队的时候还有人插队吧!
先进先出是绝对的(公平公正),入队和出队的顺序是一一对应的
队列一端入数据,一端出数据,那么是顺序表实现还是链表实现?是单链表还是双链表?
下面我们来看看其中的优缺点:
改进之后:
#pragma once
#include
#include
#include
#include
// 链式结构:表示队列
typedef int QDataType;
typedef struct QListNode
{
struct QListNode* _next;
QDataType _data;
}QNode;
// 队列的结构
typedef struct Queue
{
QNode* _head;
QNode* _tail;
int size;
}Queue;
// 初始化队列
void QueueInit(Queue* q);
// 队尾入队列
void QueuePush(Queue* q, QDataType data);
// 队头出队列
void QueuePop(Queue* q);
// 获取队列头部元素
QDataType QueueFront(Queue* q);
// 获取队列队尾元素
QDataType QueueBack(Queue* q);
// 获取队列中有效元素个数
int QueueSize(Queue* q);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
int QueueEmpty(Queue* q);
// 销毁队列
void QueueDestroy(Queue* q);
#include"Queue.h"
// 初始化队列
void QueueInit(Queue* q)
{
assert(q);
q->_head = q->_tail = NULL;
q->size = 0;
}
// 队尾入队列
void QueuePush(Queue* q, QDataType data)
{
assert(q);
QNode* node = (QNode*)malloc(sizeof(QNode));
if (node == NULL)
{
perror("malloc error!\n");
return;
}
node->_data = data;
node->_next = NULL;
if (q->_head == NULL)
{
q->_head = q->_tail = node;
}
else
{
q->_tail->_next = node;
q->_tail = node;
}
q->size++;
}
// 队头出队列
void QueuePop(Queue* q)
{
assert(q);
//不为空
assert(q->_head);
//只有1给节点,直接free
if (q->_head->_next == NULL)
{
free(q->_head);
q->_head = NULL;
}
else
{
QNode* del = q->_head;
q->_head = q->_head->_next;
free(del);
del = NULL;
}
q->size--;
}
// 获取队列头部元素
QDataType QueueFront(Queue* q)
{
assert(q);
assert(q->_head);
return q->_head->_data;
}
// 获取队列队尾元素
QDataType QueueBack(Queue* q)
{
assert(q);
assert(q->_tail);
return q->_tail->_data;
}
// 获取队列中有效元素个数
int QueueSize(Queue* q)
{
assert(q);
return q->size;
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
int QueueEmpty(Queue* q)
{
assert(q);
if (q->_head == NULL)
return 1;
return 0;
}
// 销毁队列
void QueueDestroy(Queue* q)
{
assert(q);
QNode* del = q->_head;
while (del)
{
QNode* next = del->_next;
free(del);
del = NULL;
del = next;
}
q->_head = q->_tail = NULL;
q->size = 0;
}
#include"Queue.h"
int main()
{
Queue qn;
QueueInit(&qn);
QueuePush(&qn, 1);
QueuePush(&qn, 2);
QueuePush(&qn, 3);
QueuePush(&qn, 4);
QueuePush(&qn, 5);
while (!QueueEmpty(&qn))
{
printf("%d ", QueueFront(&qn));
QueuePop(&qn);
}
QueueDestroy(&qn);
return 0;
}
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
typedef int QDataType;
typedef struct QListNode
{
struct QListNode* _next;
QDataType _data;
}QNode;
typedef struct Queue
{
QNode* _front;
QNode* _rear;
int size;
}Queue;
void QueueInit(Queue* q)
{
assert(q);
q->_front=q->_rear=NULL;
q->size=0;
}
void QueuePush(Queue* q, QDataType data)
{
assert(q);
QNode* node=(QNode*)malloc(sizeof(QNode));
node->_data=data;
node->_next=NULL;
if (q->_front == NULL)
{
q->_front = q->_rear = node;
}
else
{
q->_rear->_next = node;
q->_rear = node;
}
q->size++;
}
void QueuePop(Queue* q)
{
assert(q);
assert(q->_front);
if(q->_front->_next==NULL)
{
free(q->_front);
q->_front=NULL;
}
else{
QNode* del = q->_front;
q->_front = q->_front->_next;
free(del);
del = NULL;
}
q->size--;
}
QDataType QueueFront(Queue* q)
{
assert(q);
assert(q->_front);
return q->_front->_data;
}
QDataType QueueBack(Queue* q)
{
assert(q);
assert(q->_rear);
return q->_rear->_data;
}
int QueueSize(Queue* q)
{
assert(q);
return q->size;
}
int QueueEmpty(Queue* q)
{
assert(q);
if(q->_front==NULL)
{
return 1;
}
return 0;
}
void QueueDestroy(Queue* q)
{
assert(q);
QNode* del=q->_front;
while(del)
{
QNode* next=del->_next;
q->_front=q->_front->_next;
free(del);
del=NULL;
del=next;
}
}
typedef struct {
Queue q1;
Queue q2;
} MyStack;
MyStack* myStackCreate() {
MyStack* st=(MyStack*)malloc(sizeof(MyStack));
QueueInit(&st->q1);
QueueInit(&st->q2);
return st;
}
//不为空,则存储数据
void myStackPush(MyStack* obj, int x) {
if(!QueueEmpty(&obj->q1))
{
QueuePush(&obj->q1,x);
}
else
{
QueuePush(&obj->q2,x);
}
}
//将n-1个元素删除
int myStackPop(MyStack* obj) {
//假设法
Queue* empty=&obj->q1;
Queue* unempty=&obj->q2;
if(!QueueEmpty(&obj->q1))
{
empty=&obj->q2;
unempty=&obj->q1;
}
while(QueueSize(unempty)>1)
{
QueuePush(empty,QueueFront(unempty));
QueuePop(unempty);
}
//只剩1个元素
int top=QueueFront(unempty);
QueuePop(unempty);
return top;
}
int myStackTop(MyStack* obj) {
if(!QueueEmpty(&obj->q1))
{
return QueueBack(&obj->q1);
}
else{
return QueueBack(&obj->q2);
}
}
bool myStackEmpty(MyStack* obj) {
if(QueueEmpty(&obj->q1)&&QueueEmpty(&obj->q2))
{
return true;
}
else
{
return false;
}
}
void myStackFree(MyStack* obj) {
QueueDestroy(&obj->q1);
QueueDestroy(&obj->q2);
free(obj);
}
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
typedef int STDataType;
typedef struct Stack
{
STDataType* _a;
int _top; // 栈顶
int _capacity; // 容量
}Stack;
// 初始化栈
void StackInit(Stack* ps)
{
assert(ps);
ps->_a=NULL;
ps->_capacity=ps->_top=0;
}
// 入栈
void StackPush(Stack* ps, STDataType data)
{
assert(ps);
if(ps->_capacity==ps->_top)
{
int newcapacity=ps->_capacity==0?4:2*ps->_capacity;
STDataType* tmp=(STDataType*)realloc(ps->_a,sizeof(STDataType)*newcapacity);
if(tmp==NULL)
{
perror("realloc error!\n");
return ;
}
ps->_a=tmp;
ps->_capacity=newcapacity;
}
ps->_a[ps->_top]=data;
ps->_top++;
}
// 出栈
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->_top);
ps->_top--;
}
// 获取栈顶元素
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(ps->_top);
return ps->_a[ps->_top-1];
}
// 获取栈中有效元素个数
int StackSize(Stack* ps)
{
assert(ps);
return ps->_top;
}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
int StackEmpty(Stack* ps)
{
assert(ps);
if(ps->_top==0)
return 1;
return 0;
}
// 销毁栈
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->_a);
ps->_a=NULL;
ps->_capacity=ps->_top=0;
}
typedef struct {
Stack pushst;
Stack popst;
} MyQueue;
MyQueue* myQueueCreate() {
MyQueue* obj=(MyQueue*)malloc(sizeof(MyQueue));
StackInit(&obj->pushst);
StackInit(&obj->popst);
return obj;
}
void myQueuePush(MyQueue* obj, int x) {
StackPush(&obj->pushst,x);
}
int myQueuePop(MyQueue* obj) {
int top=myQueuePeek(obj);
StackPop(&obj->popst);
return top;
}
int myQueuePeek(MyQueue* obj) {
if(StackEmpty(&obj->popst))
{
while(!StackEmpty(&obj->pushst))
{
StackPush(&obj->popst,StackTop(&obj->pushst));
StackPop(&obj->pushst);
}
}
return StackTop(&obj->popst);
}
bool myQueueEmpty(MyQueue* obj) {
return StackEmpty(&obj->pushst)&&StackEmpty(&obj->popst);
}
void myQueueFree(MyQueue* obj) {
StackDestroy(&obj->popst);
StackDestroy(&obj->pushst);
free(obj);
}
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
所以到底是数组实现还是链表实现呢?
使用数组or链表都一样,都有麻烦的地方
数组:
判断满没满需要取余%,而且数组要借助下标,要自己设想它逻辑循环(是一个环)--解决循环问题
(单向)循环链表:
虽然不需要取余%但是取尾数据不好找(我们多一个空位,需要知道空位前面的数据,因为前面的数据才是真正的有数据的尾节点),所以还需要一个指向空位的前一个的指针,或者双向循环链表(3个指针)
typedef struct {
int* a;
int front;//数组下标
int back;//数组下标
int k;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->a=(int*)malloc(sizeof(int)*(k+1));
obj->front=0;
obj->back=0;
obj->k=k;
return obj;
}
//先判断空和满--->便于插入和删除的时候的判断调用
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->back==obj->front;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return (obj->back+1)%(obj->k+1)==obj->front;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if(myCircularQueueIsFull(obj))
{
return false;
}
obj->a[obj->back++]=value;
//正常来说没满,直接插入数据之后,back++就行了
//但是当back走到尾了,还能back++嘛?不能,back要%回到头
obj->back %=(obj->k+1);
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
{
return false;
}
(obj->front)++;//置为空,没有意义
//同样的,删除的时候正常情况下front++
//但是front走到尾了,还能++往后走嘛?不能,要%回到头再开始
obj->front %=(obj->k+1);
return true;
}
int myCircularQueueFront(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
return obj->a[obj->front];
}
int myCircularQueueRear(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
//return obj->a[obj->back-1];//back指向的是下一个空位
//取尾的时候,正常情况下,之际返回back-1就行了
//但是当back%回到头时,此时back==0,还能(back-1)取-1嘛?
//不能,当back==0时,直接返回k(k就是数组下标---最后一个数据)
if(obj->back==0)
{
return obj->a[obj->k];
}
else
{
return obj->a[obj->back-1];
}
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
free(obj);
}
本次的学习到这里就结束了,通过今天的学习,我们堆数据结构的栈和队列有了一定的了解,也进行了适量的练习。但是我们还是需要课后巩固练习的,加深理解和印象。下篇文章,小江将会带领大家一起学习了解二叉树的相关知识点!!!
本次的分享到这里就结束了!!!
PS:小江目前只是个新手小白。欢迎大家在评论区讨论哦!有问题也可以讨论的!
如果对你有帮助的话,记得点赞+收藏⭐️+关注➕