目录
栈和队列的概念
什么是栈
栈:可以想象成桌面上的一堆文件或纸张,如果你要从顶部添加或者移除文件或纸张,那么最后一个放上去的文件会被第一个取出来,这就符合栈的特点(后进先出)
栈的特点:后进先出或先进后出
核心操作:入栈,出栈,获取栈顶的数据
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
栈不像链表可以在两端进行操作,而栈只能在固定的一端进行操作
栈出栈的时机会影响出队的顺序:假设数组左边是栈低,右边是栈顶
比如:入栈 1 2 3 4 5
最后:出栈的顺序就是 5 4 3 2 1
比如:入栈 1 2 3出栈一次,出栈顶的数据 3
再入 4 5 6
最后:出栈顺序就是 3 6 5 4 3 2 1
如图所示:
什么是队列
队列:我们可以想象成在超市买东西需要排队,最前面的人先出队,最后的人后出队,就和队列的特点匹配上(先进先出或后进后出)
队列的特点:先进先出或后进后出
核心操作:入队,出队,获取队头的数据
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头队列出队的时机不会影响出队的顺序
入队: 1 2 3出队一次,出的是对头的数据 1,入队 4 5 6
最后出队结果:1 2 3 4 5 6
如图所示:
栈的实现
实现栈可以用链表也可以用数组
实现栈建议用数组:数组更简单
栈的实现和顺序表的实现类似
栈顶top和顺序表的size一个意思,用来表示数据的有效个数,在栈中用top表示,指向最后一个元素的下一个位置
用数组表示:
用链表表示:
代码实现
Stack.h
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* _a;
int _top; // size = top // 栈顶下标
int _capacity;
}Stack;
//初始化
void StackInit(Stack* pst);
// 入栈
void StackPush(Stack* pst, STDataType x);
//出栈
void StackPop(Stack* pst);
//求数据个数
int StackSize(Stack* pst);
//返回1是空,返回0是非空
int StackEmpty(Stack* pst);
//获取栈顶数据
STDataType StackTop(Stack* pst);
//销毁栈
void StackDestory(Stack* pst);
Stack.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Stack.h"
//初始化
void StackInit(Stack* pst)
{
assert(pst);
/*pst->_a = NULL;
pst->_top = 0;
pst->_capacity = 0;*/
pst->_a = (STDataType*)malloc(sizeof(STDataType) * 4);
if (pst->_a == NULL)
return;
pst->_top = 0;
pst->_capacity = 4;
}
// 入栈
void StackPush(Stack* pst, STDataType x)
{
assert(pst);
if (pst->_top == pst->_capacity)
{
pst->_capacity *= 2;
STDataType* tmp = realloc(pst->_a, sizeof(STDataType) * pst->_capacity);
if (tmp != NULL)
{
pst->_a = tmp;
}
}
pst->_a[pst->_top] = x;
pst->_top++;
}
//出栈
void StackPop(Stack* pst)
{
assert(pst);
assert(pst->_top > 0);
--(pst->_top);
}
//求数据个数
int StackSize(Stack* pst)
{
assert(pst);
return pst->_top;
}
//返回1是空,返回0是非空
int StackEmpty(Stack* pst)
{
assert(pst);
return pst->_top == 0 ? 1 : 0;
//return !(pst->_top);
}
//获取栈顶数据
STDataType StackTop(Stack* pst)
{
assert(pst);
assert(pst->_top > 0);
return pst->_a[pst->_top - 1];
}
//销毁栈
void StackDestory(Stack* pst)
{
assert(pst);
free(pst->_a);
pst->_a = NULL;
pst->_top = 0;
pst->_capacity = 0;
}
Test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Stack.h"
//栈的作用:
// 1,如果有后进先出需求的地方(比如迷宫问题、递归改成非递归)
void TestStack()
{
Stack st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
printf("%d ", StackTop(&st));
StackPop(&st);
StackPush(&st, 3);
StackPush(&st, 4);
while (!StackEmpty(&st))
{
printf("%d ", StackTop(&st));
StackPop(&st);
}
printf("\n");
StackDestory(&st);
}
int main()
{
TestStack();
return 0;
}
我们在入栈1 2 然后打印栈顶的数据 2,打印完之后出栈(出栈顶的数据2),再次入栈 3 4 while循环判断栈是否为空,StackEmpty 返回 0 表示栈里面有数据,所以取反,为真,打印栈顶的数据,再出栈(出栈顶的数据),不断循环直到栈中出栈到没有数据。最后销毁栈。
打印结果: 2 4 3 1
队列的实现
实现队列可以用链表也可以用数组
实现队列建议用链表:链表操作更简单
队列的实现和单链表的实现类似
用数组表示:
用链表表示:
代码实现
Queue.h
#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int QDataType;
//队列中的结点
typedef struct QueueNode
{
struct QueueNode* _next;
QDataType _data;
}QueueNode;
//队列
typedef struct Queue
{
QueueNode* _head;
QueueNode* _tail;
}Queue;
//初始化
void QueueInit(Queue* pq);
//入队
void QueuePush(Queue* pq, QDataType x);
//出队,出队头的数据
void QueuePop(Queue* pq);
//返回队头的数据
QDataType QueueFront(Queue* pq);
//返回队尾的数据
QDataType QueueBack(Queue* pq);
//判断队列是否为空,如果为空返回 1,存在数据返回 0
int QueueEmpty(Queue* pq);
//求队列的有效数据个数
int QueueSize(Queue* pq);
//销毁队列
void QueueDestory(Queue* pq);
有一个Queue结构体,里面存储两个指针,用来指向队头和队尾,初始化结构体Queue中的指针_head==_tail ==NULL。
我们学习过单链表,外部有一个结构体指针,用来指向头结点,在队列中,外面有两个指针,一个用了存储头,一个用来存储尾,两者类似,只不过队列中外面的两个结构体指针在Queue结构体中
如图:
Queue.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"
//队列,先进先出的场景,比如要保持序列公平,排队抽号机
// 广度优先遍历
void QueueInit(Queue* pq)
{
assert(pq);
pq->_head = pq->_tail = NULL;
}
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (newnode == NULL)
{
printf("内存不足\n");
exit(-1);
}
newnode->_data = x;
newnode->_next = NULL;
if (pq->_head == NULL)
{
pq->_head = pq->_tail = newnode;
}
else
{
pq->_tail->_next = newnode;
pq->_tail = newnode;
}
}
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->_head);
QueueNode* next = pq->_head->_next;
free(pq->_head);
pq->_head = next;
if (pq->_head == NULL)
{
pq->_tail = NULL;
}
}
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->_head);
return pq->_head->_data;
}
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->_tail);
return pq->_tail->_data;
}
//返回1是空,返回0是非空
int QueueEmpty(Queue* pq)
{
assert(pq);
return pq->_head == NULL ? 1 : 0;
}
int QueueSize(Queue* pq)
{
assert(pq);
QueueNode* cur = pq->_head;
int size = 0;
while (cur)
{
++size;
cur = cur->_next;
}
return size;
}
void QueueDestory(Queue* pq)
{
assert(pq);
QueueNode* cur = pq->_head;
while (cur)
{
QueueNode* next = cur->_next;
free(cur);
cur = next;
}
pq->_head = pq->_tail = NULL;
}
入队操作QueuePush
如图:
出队QueuePop
如图:
Test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"
void QueueTest()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
printf("%d ", QueueFront(&q));
QueuePop(&q);
QueuePush(&q, 3);
QueuePush(&q, 4);
while (!QueueEmpty(&q))
{
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
QueueDestory(&q);
}
int main()
{
QueueTest();
return 0;
}
打印结果: 1 2 3 4
栈和队列的相关例题(leetCode)
有效的括号
利用栈的性质来判断括号是不是有效,C语言中我们先写一个栈,才能初始化栈和调用栈的函数
bool isValid(char* s) {
Stack st;
StackInit(&st);
bool ret;
while (*s != '\0')
{
if (*s == '[' || *s == '{' || *s == '(')
{
StackPush(&st, *s);
++s;
}
else
{
if (StackEmpty(&st))
{
ret = false;
break;
}
char top = StackTop(&st);
if (*s == ']' && top != '[')
{
ret = false;
break;
}
if (*s == '}' && top != '{')
{
ret = false;
break;
}
if (*s == ')' && top != '(')
{
ret = false;
break;
}
StackPop(&st);
++s;
}
}
// 如果都匹配上了,栈为空
if (*s == '\0')
ret = StackEmpty(&st);
StackDestory(&st);
return ret;
}
用队列实现栈
在实现用队列实现栈的过程中,我们需要先实现一个队列
思路:
typedef struct {
Queue _q1;
Queue _q2;
} MyStack;
MyStack* myStackCreate()
{
MyStack* st = (MyStack*)malloc(sizeof(MyStack));
if (st == NULL)
return NULL;
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);
}
}
int myStackPop(MyStack* obj)
{
Queue* empty = &obj->_q1;
Queue* noempty = &obj->_q2;
if (!QueueEmpty(&obj->_q1))
{
empty = &obj->_q2;
noempty =&obj->_q1;
}
while (QueueSize(noempty) > 1)
{
QueuePush(empty, QueueFront(noempty));
QueuePop(noempty);
}
int top = QueueFront(noempty);
QueuePop(noempty);
return top;
}
int myStackTop(MyStack* obj)
{
if (!QueueEmpty(&obj->_q1))
{
return QueueBack(&obj->_q1);
}
else
{
return QueueBack(&obj->_q2);
}
}
bool myStackEmpty(MyStack* obj)
{
return QueueEmpty(&obj->_q1) && QueueEmpty(&obj->_q2);
}
void myStackFree(MyStack* obj)
{
QueueDestory(&obj->_q1);
QueueDestory(&obj->_q2);
free(obj);
}
用栈实现队列
用栈实现队列和用队列实现栈原理类似,用两个栈实现队列的特点
思路:
//用栈实现队列
MyQueue* myQueueCreate()
{
MyQueue* q = (MyQueue*)malloc(sizeof(MyQueue));
if (q == NULL)
return NULL;
StackInit(&q->_PushST);
StackInit(&q->_PopST);
return q;
}
void myQueuePush(MyQueue* obj, int x)
{
StackPush(&obj->_PushST, x);
}
int myQueuePop(MyQueue* obj)
{
int front = myQueuePeek(obj);
StackPop(&obj->_PopST);
return front;
}
int myQueuePeek(MyQueue* obj)
{
if (!StackEmpty(&obj->_PopST))
{
return StackTop(&obj->_PopST);
}
else
{
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)
{
StackEmpty(&obj->_PushST);
StackEmpty(&obj->_PopST);
free(obj);
}
设计循环队列
什么是循环队列
1,如图所示,逻辑结构是我们想象出来的,但是循环队列可以使链表也可以是数组。
2,在这个题目中循环队列的大小是固定的,不需要动态增长,所以我们使用数组来设计循环队列。
3,在数组中物理结构中,数组的 front 或者 rear 下标走到数组末尾,最后再走到 0 下标,逻辑上就是首尾相接。
4,队列满了就不允许入队。
逻辑结构:
物理结构:
设计循环队列的问题
这样的情况下我们无法区分队列满了还是队列为空的情况
针对无法区分队满还是对空的情况如何解决 ?
思路:我们需要留出一个空位置来,也就是数组的大小多开一个位置,以下图示都是在证明多开一个空间的好处。
只有入队操作:
存在入队和出队操作:
不断出队:
// 设计循环队列
typedef struct {
int* _a;
int _front;
int _rear;
int _k;
} MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k);
// 插入数据,成功返回真,否则返回假
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value);
bool myCircularQueueDeQueue(MyCircularQueue* obj);
int myCircularQueueFront(MyCircularQueue* obj);
int myCircularQueueRear(MyCircularQueue* obj);
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);
void myCircularQueueFree(MyCircularQueue* obj);
// 设计循环队列
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* q = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
if (q == NULL) {
return NULL;
}
q->_a = (int*)malloc(sizeof(int) * (k + 1));
q->_front = 0;
q->_rear = 0;
q->_k = k;
return q;
}
// 插入数据,成功返回真,否则返回假
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if (myCircularQueueIsFull(obj)) {
return false;
} else {
obj->_a[obj->_rear] = value;
++obj->_rear;
obj->_rear = obj->_rear % (obj->_k + 1);
return true;
}
}
// 删除
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj)) {
return false;
} else {
++obj->_front;
obj->_front = obj->_front % (obj->_k + 1);
return true;
}
}
int myCircularQueueFront(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj))
return -1;
else
return obj->_a[obj->_front];
}
int myCircularQueueRear(MyCircularQueue* obj) {
if (myCircularQueueIsEmpty(obj)) {
return -1;
} else {
int tail = obj->_rear - 1;
if (tail == -1)
tail = obj->_k;
return obj->_a[tail];
}
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->_front == obj->_rear;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return obj->_front == (obj->_rear + 1) % (obj->_k + 1);
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->_a);
free(obj);
}