栈
栈的概念及结构
栈:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。 出栈:栈的删除操作叫做出栈。出数据也在栈顶。
栈的实现
栈的实现一般可以使用数组或者链表实现,相对而言数组的结构实现更优一些。因为数组在尾上插入数据的 代价比较小。
//Stack.h
#pragma once
#include<stdlib.h>
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
typedef int DataType;
typedef struct Stack {
DataType* data;
int top;
int capacity;
}Stack;
void StackInit(Stack* st);//初始化
void StackDestroy(Stack* st);//销毁
void StackPush(Stack* st, DataType x);// 放入数据
void StackPop(Stack* st);//删除数据
DataType StackTop(Stack* st);//取出栈顶数据
int StackSize(Stack* st);//栈中数据的个数
bool StackEmpty(Stack* st);//判空
//Stack.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Stack.h"
void StackInit(Stack* st) {
assert(st);
st->data = NULL;
st->capacity = st->top = 0;
}
//销毁
void StackDestroy(Stack* st) {
assert(st);
free(st->data);
st->capacity = st->top = 0;
}
// 放入数据
void StackPush(Stack* st, DataType x) {
assert(st);
//检查容量
if (st->capacity == st->top) {
//增容
int newCapacity = st->capacity == 0 ? 4 : st->capacity * 2;
DataType* tmp = (DataType*)realloc(st->data, sizeof(DataType) * newCapacity);
if (tmp == NULL) {
perror("realloc");
exit(-1);
}
st->data = tmp;
st->capacity = newCapacity;
}
st->data[st->top] = x;
st->top++;
}
//删除数据
void StackPop(Stack* st) {
assert(st);
assert(!StackEmpty(st));
st->top--;
}
//取出栈顶数据
DataType StackTop(Stack* st) {
assert(st);
assert(!StackEmpty(st));
return st->data[st->top - 1];
}
//统计栈中数据个数
int StackSize(Stack* st) {
assert(st);
return st->top;
}
//判空
bool StackEmpty(Stack* st) {
assert(st);
return st->top == 0;
}
队列
队列的概念及结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出 FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头
队列的实现
队列也可以用数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数 组头上出数据,效率会比较低(这涉及到数组数据向前挪动)。
//Queue.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int DataType;
typedef struct QueueNode {
DataType data;
struct QueueNode* next;
}QueueNode;
typedef struct Queue {
QueueNode* head;
QueueNode* tail;
}Queue;
void QueueInit(Queue* q);// 初始化队列
void QueueDestroy(Queue* q);// 销毁队列
void QueuePush(Queue* q, DataType data);// 队尾入队列
void QueuePop(Queue* q);// 队头出队列
DataType QueueFront(Queue* q);// 获取队列头部元素
DataType QueueBack(Queue* q);// 获取队列队尾元素
int QueueSize(Queue* q);// 获取队列中有效元素个数
bool QueueEmpty(Queue* q);// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
//Queue.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"
// 初始化队列
void QueueInit(Queue* q) {
assert(q);
q->head = q->tail = NULL;
}
// 销毁队列
void QueueDestroy(Queue* q) {
assert(q);
QueueNode* cur = q->head;
while (cur) {
QueueNode* next = cur->next;
free(cur);
cur = next;
}
q->head = q->tail = NULL;
}
// 队尾入队列
void QueuePush(Queue* q, DataType data) {
assert(q);
QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
if (newNode == NULL) {
perror("malloc");
exit(-1);
}
newNode->data = data;
newNode->next = NULL;
if (q->head == NULL) {
q->head = q->tail = newNode;
}
else {
q->tail->next = newNode;
q->tail = newNode;
}
}
// 队头出队列
void QueuePop(Queue* q) {
assert(q && !QueueEmpty(q));
QueueNode* next = q->head->next;
free(q->head);
q->head = next;
if (q->head == NULL) {
q->tail = NULL;
}
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* q) {
return q->head == NULL;
}
// 获取队列头部元素
DataType QueueFront(Queue* q) {
assert(q && !QueueEmpty(q));
return q->head->data;
}
// 获取队列队尾元素
DataType QueueBack(Queue* q) {
assert(q && !QueueEmpty(q));
return q->tail->data;
}
// 获取队列中有效元素个数
int QueueSize(Queue* q) {
assert(q);
QueueNode* cur = q->head;
int count = 0;
while (cur) {
count++;
cur = cur->next;
}
return count;
}
循环队列
实际中我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型 时可能就会使用循环队列。环形队列可以使用数组实现,也可以使用循环链表实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
循环队列符合先进先出且空间大小固定
循环队列无论是用数组实现还是链表实现,都要多开一个空间,否则无法判满和判空。
下图中定义的front记录队头,tail记录队尾。k代表队列的大小。
//定义循环队列
typedef struct {
int* a;//循环队列
int front;//记录队头
int tail;//记录队尾
int k;//记录队列大小
} MyCircularQueue;
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueIsFull(MyCircularQueue* obj);
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue* cq = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
cq->a=(int*)malloc(sizeof(int)*(k+1));
cq->front=cq->tail=0;
cq->k=k;
return cq;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
if(myCircularQueueIsFull(obj)){
return false;
}
obj->a[obj->tail]=value;
obj->tail++;
obj->tail%=obj->k+1;
return true;
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {
if(myCircularQueueIsEmpty(obj)){
return false;
}
obj->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;
}
if(obj->tail==0){
return obj->a[obj->k];
}
else{
return obj->a[obj->tail-1];
}
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
return obj->front==obj->tail;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {
return (obj->tail+1)%(obj->k+1)==obj->front;
}
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
free(obj);
}
队列实现栈
我们知道栈是先入后出的特点,而队列是先入先出的特点。我们要使用队列实现栈,需要使用两个队列,实现先入后出。
因为我们是使用队列实现栈,因此我们需要先实现一个队列,在实现过程中使用队列中函数的接口。
typedef int DataType;
typedef struct QueueNode {
struct QueueNode* next;
DataType data;
}QueueNode;
typedef struct Queue {
QueueNode* head;
QueueNode* tail;
}Queue;
void QueueInit(Queue* q);// 初始化队列
void QueueDestroy(Queue* q);// 销毁队列
void QueuePush(Queue* q, DataType data);// 队尾入队列
void QueuePop(Queue* q);// 队头出队列
DataType QueueFront(Queue* q);// 获取队列头部元素
DataType QueueBack(Queue* q);// 获取队列队尾元素
int QueueSize(Queue* q);// 获取队列中有效元素个数
bool QueueEmpty(Queue* q);// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
// 初始化队列
void QueueInit(Queue* q) {
assert(q);
q->head = NULL;
q->tail = NULL;
}
// 销毁队列
void QueueDestroy(Queue* q) {
QueueNode* cur = q->head;
while (cur) {
QueueNode* next = cur->next;
free(cur);
cur = next;
}
q->head = q->tail = NULL;
}
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0
bool QueueEmpty(Queue* q) {
assert(q);
return q->head == NULL;
}
//创建新节点
QueueNode* BuyNewNode(DataType x) {
QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
if (newNode == NULL) {
perror("malloc");
exit(-1);
}
newNode->data = x;
newNode->next = NULL;
return newNode;
}
// 队尾入队列
void QueuePush(Queue* q, DataType data) {
assert(q);
QueueNode* newNode = BuyNewNode(data);
if (q->head == NULL) {
q->head = q->tail = newNode;
}
else {
q->tail->next = newNode;
q->tail = newNode;
}
}
// 队头出队列
void QueuePop(Queue* q) {
assert(q && !QueueEmpty(q));
QueueNode* next = q->head->next;
free(q->head);
q->head = next;
if (q->head == NULL) {
q->tail = NULL;
}
}
// 获取队列头部元素
DataType QueueFront(Queue* q) {
assert(q && !QueueEmpty(q));
return q->head->data;
}
// 获取队列队尾元素
DataType QueueBack(Queue* q) {
assert(q && !QueueEmpty(q));
return q->tail->data;
}
// 获取队列中有效元素个数
int QueueSize(Queue* q) {
assert(q && !QueueEmpty(q));
QueueNode* cur = q->head;
int count = 0;
while (cur) {
count++;
cur = cur->next;
}
return count;
}
完成一个队列的实现之后,我们需要定义我们需要使用的两个队列,并完成这两个队列的初始化。
typedef struct {
Queue q1;
Queue q2;
} MyStack;
MyStack* myStackCreate() {
MyStack* st=(MyStack*)malloc(sizeof(MyStack));
QueueInit(&(st->q1));//这里的初始化使用的队列初始化函数的接口
QueueInit(&(st->q2));
return st;
}
栈的数据插入
选择一个队列入数据,另外的一个队列保持为空,方便后边栈的删除操作,具体请向下继续浏览。
//MyStackPush操作
void myStackPush(MyStack* obj, int x) {
if(!QueueEmpty(&(obj->q1))){
QueuePush(&(obj->q1),x);
}
else{
QueuePush(&(obj->q2),x);
}
}
栈顶数据的删除
将非空队列中的数据头出,存到非空的队列中,直至非空队列剩下最后一个元素。这时只用将这最后的元素取出来,再pop掉,就行了。
int myStackPop(MyStack* obj) {
Queue* emptyQueue=&(obj->q1);
Queue* nonEmptyQueue=&(obj->q2);
if(!QueueEmpty(&(obj->q1))){
emptyQueue=&(obj->q2);
nonEmptyQueue=&(obj->q1);
}
while(QueueSize(nonEmptyQueue)>1){
QueuePush(emptyQueue,QueueFront(nonEmptyQueue));
QueuePop(nonEmptyQueue);
}
int top=QueueFront(nonEmptyQueue);
QueuePop(nonEmptyQueue);
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) {
QueueDestroy(&(obj->q1));
QueueDestroy(&(obj->q2));
free(obj);
}
栈实现队列
栈的特性是先入后出,队列的特性是先入先出。这里需要使用两个栈来实现队列的先入先出
和上边的一样我们实现队列需要使用两个栈,我们需要先实现一个栈,在实现的过程中使用栈的接口。
typedef int DataType;
typedef struct Stack {
DataType* a;
int top;
int capacity;
}Stack;
void StackInit(Stack* ps);//初始化
void StackDestroy(Stack* ps);//销毁
void StackPush(Stack* ps, DataType x);// 放入数据
void StackPop(Stack* ps);//删除数据
DataType StackTop(Stack* ps);//取出栈顶数据
int StackSize(Stack* ps);//栈中数据的个数
bool StackEmpty(Stack* ps);//判空
//初始化
void StackInit(Stack* ps) {
assert(ps);
ps->a = NULL;
ps->top = 0;//top给的是0,表示top指向的是栈顶数据的下一个。
//top给的是-1,表示top指向的是栈顶数据。
ps->capacity = 0;
}
//销毁
void StackDestroy(Stack* ps) {
assert(ps);
free(ps->a);
ps->a = NULL;
ps->top = ps->capacity = 0;
}
//判空
bool StackEmpty(Stack* ps) {
assert(ps);
return ps->top == 0;
}
//增加数据
void StackPush(Stack* ps, DataType x) {
assert(ps);
if (ps->top == ps->capacity) {
//增容
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
DataType* tmp = (DataType*)realloc(ps->a, sizeof(DataType) * newCapacity);
if (tmp == NULL) {
perror("realloc");
exit(-1);
}
ps->a = tmp;
ps->capacity = newCapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
//删除数据
void StackPop(Stack* ps) {
assert(ps);
assert(!StackEmpty(ps));
ps->top--;
}
//取出栈顶数据
DataType StackTop(Stack* ps) {
assert(ps);
assert(!StackEmpty(ps));
return ps->a[ps->top - 1];
}
//栈中数据的个数
int StackSize(Stack* ps) {
assert(ps);
return ps->top;
}
实现队列的过程中,我们需要使用两个栈,我们需要首先定义这两个栈并完成其初始化。
typedef struct {
Stack stPush;
Stack stPop;
} MyQueue;
MyQueue* myQueueCreate() {
MyQueue* q = (MyQueue*)malloc(sizeof(MyQueue));
StackInit(&(q->stPush));
StackInit(&(q->stPop));
return q;
}
队列的入数据
可以留意一下我们在初始化两个栈的时候,分别命名尾stPush和stPop,也就是说我们有一个栈单独用来入数据,另外一个栈用来出数据。
void myQueuePush(MyQueue* obj, int x) {
StackPush(&(obj->stPush),x);
}
队列的删除数据
我们入数据的时候,一直在一个栈中入数据,另外一个栈为空。我们需要将stPush栈中的全部数据存到stPop栈中,再删除栈顶数据。就完成了队头数据的删除了。
int myQueuePop(MyQueue* obj) {
//如果stpop中没有数据,将stpush的数据导过去
//stpop中的数据就符合先进先出的顺序了
if(StackEmpty(&(obj->stPop))){
while(!StackEmpty(&(obj->stPush))){
StackPush(&(obj->stPop),StackTop(&(obj->stPush)));
StackPop(&(obj->stPush));
}
}
int front = StackTop(&(obj->stPop));
StackPop(&(obj->stPop));
return front;
}
队列拿到头元素
我们注意一下,将stPush栈中的数据拿到stPop栈中的时候,数据的顺序都颠倒了,因此拿到stPop栈中的数据,我们直接取头元素就能拿到队列的头元素了。
int myQueuePeek(MyQueue* obj) {
//如果stpop中没有数据,将stpush的数据导过去
//stpop中的数据就符合先进先出的顺序了
if(StackEmpty(&(obj->stPop))){
while(!StackEmpty(&(obj->stPush))){
StackPush(&(obj->stPop),StackTop(&(obj->stPush)));
StackPop(&(obj->stPush));
}
}
return StackTop(&(obj->stPop));
}
队列的判空
我们使用了两个栈,需要对两个栈都进行判空。
bool myQueueEmpty(MyQueue* obj) {
return StackEmpty(&(obj->stPush)) &&StackEmpty(&(obj->stPop));
}
队列的销毁
我们使用了两个栈,都需要进行销毁。再释放掉队列。
void myQueueFree(MyQueue* obj) {
StackDestroy(&(obj->stPush));
StackDestroy(&(obj->stPop));
free(obj);
}