目录
栈的定义
- 栈是受限制的线性表
- 具有后进先出的特性(LIFO:Last In First Out),又称为LIFO结构
- 只能在一端进行一端插入操作、删除操作
- 插入数据与删除数据称为压栈与出栈,在栈顶操作
- 进行插入删除操作的一端称为栈顶,另一端称为栈底
上面的表现形式更为抽象,其物理内存实际上是一片连续的空间
从上面两张图可以直到,压栈,出栈都是栈顶在移动,栈底保持不变,那么根据这个逻辑,就好实现增删函数了,下面给出几个常用的栈函数功能
栈的函数实现
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* data;
int top;
int capacity;
}ST;
void StackInit(ST* ps);//初始化栈
void StackDestroy(ST* ps);//销毁栈
void StackPush(ST* ps, STDataType data);//数据压栈
void StackPop(ST* ps);//数据出栈
STDataType StackTop(ST* ps);//获取栈顶元素
int StackSize(ST* ps);//获取栈中有效元素个数
bool StackEmpty(ST* ps);//判断栈是否为空,若为空,则返回1,不为空返回0
大部分函数的功能在注释中有详细说明,以及参数的使用
void StackInit(ST* ps)//初始化栈
{
assert(ps);
ps->data = NULL;
ps->capacity = 0;
ps->top = 0;
}
void StackDestroy(ST* ps)//销毁栈
{
assert(ps);
free(ps->data);
//此处不能对ps进行free,因为数据是在ps->data里面进行的realloc的,free要与该函数成对出现
ps->data = NULL;
ps->top = ps->capacity = 0;
}
void StackPush(ST* ps, STDataType data)//数据压栈一个
{
assert(ps);
if (ps->top == ps->capacity)
{
//定义一个新变量来当做新空间大小,以免在创建时对原数据造成影响,当创建成功再去修改原数据
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
//此处同上,目的是为了保证原数据的安全性
STDataType* tmp = (STDataType*)realloc(ps->data, newcapacity * sizeof(STDataType));
//进行一次断言,判断是否创建成功
if (!tmp)
{
perror("realloc newcapacity error ::");
exit(-1);
}
ps->capacity = newcapacity;
ps->data = tmp;
}
ps->data[ps->top] = data;
ps->top++;
}
void StackPop(ST* ps)//数据出栈一个
{
assert(ps);
//如果当前栈内栈顶为0,就代表栈为空,不能出数据,进行以下强制断言
assert(ps->top > 0);
//如果栈内连空间长度都还没有说,那就更不能出栈
assert(ps->capacity > 0);
//对于数组来说,只要将栈顶数减一,就能够出栈
ps->top--;
}
STDataType StackTop(ST* ps)//获取栈顶元素
{
assert(ps);
//判断栈不能为空栈
assert(!StackEmpty(ps));
assert(ps->capacity);
//因为top表示的是目前栈中的元素个数,而下标从0开始
return ps->data[ps->top - 1];
}
int StackSize(ST* ps)//获取栈中有效元素个数
{
assert(ps);
return ps->top;
}
bool StackEmpty(ST* ps)//判断栈是否为空,若为空,则返回1,不为空返回0
{
assert(ps);
return ps->top == 0;
}
队列定义
1、只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表
2、队列具有先进先出,FIFO(First In First Out)
3、入队列:进行插入操作的一端称为队尾
4、出队列:进行删除操作的一端称为队头
其抽象结构可表示为下图形式
其头文件内容如下:
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
typedef int QeuueDataType;
typedef struct QueueNode
{
QeuueDataType data;
struct QueueNode* next;
}QueueNode;
typedef struct Queue
{
QueueNode* head;
QueueNode* tail;
}Queue;
void QueueInit(Queue* pq);//初始化队列
void QueueDestroy(Queue* pq);//销毁队列
QueueNode* CreateQueueNode(QeuueDataType data);//增加一个节点
void QueuePush(Queue* pq, QeuueDataType data);//插入队列尾部
void QueuePop(Queue* pq);//弹出队列头部
QeuueDataType QueueFront(Queue* pq);//获取首部数据
QeuueDataType QueueBack(Queue* pq);//获取尾部数据
int QueueSize(Queue* pq);//获取队列长度
bool QueueEmopty(Queue* pq);//返回队列是否为空 1 == 空 || 0 == 非空
链表在前几章有过具体的一片文章,感兴趣的小伙伴也可以去看一看这篇文章:初闻链表深似海
在这篇文章里面,个人发表了更为全面的理解
队列相对于链表来说,是受限制的链表,链表可以首尾插,也可以首尾删除,但是队列只能在首部删除,尾部插入
队列的源代码如下
#define _CRT_SECURE_NO_WARNINGS 1
#include "Queue.h"
void QueueInit(Queue* pq)//初始化队列
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
}
void QueueDestroy(Queue* pq)//销毁队列
{
assert(pq);
QueueNode* cur = pq->head;
while (cur)
{
QueueNode* next = cur->next;
free(cur);
cur = next;
}
//以下一定要置空,以免野指针访问
pq->head = pq->tail = NULL;
}
QueueNode* CreateQueueNode(QeuueDataType data)//增加一个节点
{
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (!newnode)
{
perror("newnode malloc faild ::");
exit(-1);
}
newnode->data = data;
newnode->next = NULL;
return newnode;
}
void QueuePush(Queue* pq, QeuueDataType data)//插入队列尾部
{
assert(pq);
QueueNode* newnode = CreateQueueNode(data);
if ((pq->head == NULL) && (pq->tail == NULL))
{
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
}
void QueuePop(Queue* pq)//弹出队列头部
//在弹出过程中有可能会出现野指针访问的情况,当队列中只有一个元素了的时候
//再次弹出,此时tail将会被释放掉,但是tail如果不赋予一个空指针,那么就会出现野指针访问
{
assert(pq);
assert(pq->head);
QueueNode* next = pq->head->next;
if (!next)
{
free(pq->head);
pq->tail = NULL;
pq->head = NULL;
}
else
{
free(pq->head);
pq->head = next;
}
}
QeuueDataType QueueFront(Queue* pq)//获取首部数据
{
assert(pq);
assert(pq->head);
return pq->head->data;
}
QeuueDataType QueueBack(Queue* pq)//获取尾部数据
{
assert(pq);
assert(pq->tail);
return pq->tail->data;
}
int QueueSize(Queue* pq)//获取队列长度
{
assert(pq);
assert(pq->head);
QueueNode* cur = pq->head;
int Queuesize = 0;
while (cur)
{
cur = cur->next;
++Queuesize;
}
return Queuesize;
}
bool QueueEmopty(Queue* pq)//返回队列是否为空 1 == 空 || 0 == 非空
{
assert(pq);
return pq->head == NULL;
}
用栈实现队列
栈的性质是,后进先出,那么一个栈是肯定做不到队列的功能的,那么我们借助两个栈来实现这种功能,先来梳理以下逻辑。
队列的先进先出应该如何通过栈来实现?可以把栈想象成饭店里面的两堆盘子,一堆盘子是待清洗的脏盘子,一堆盘子是清洗好的干净盘子,盘子的使用规则是:现将脏盘子,从下至上的堆起来,再清洗的时候从上往下取,符合栈的后进先出规则;清洗好的盘子再从下往上堆起来,用的时候从上往下取,符合栈的后进先出规则。那么把脏盘子一堆设置为入栈区,干净盘子设置为出栈区,到此,队列的先进先出就通过这个实例很好地说明了那么下面一张例图会更加的清晰
具体阐述:入栈的所有数据全部放在入栈区,当要进行出栈的时候,从出栈区取数据,当出栈区还有数据的时候,入栈区的内容不能放过来,只有在出栈区为空的时候才可以放进出栈区,整体思路理清晰了,接下来分析需要哪些函数:栈初始化,入栈函数,出栈函数,栈顶获取函数,栈判空函数,栈中元素个数函数
接下来就开始编写代码
typedef int STDataType;
typedef struct Stack
{
STDataType* data;
int top;
int capacity;
}ST;
void StackInit(ST* ps);//初始化栈
void StackDestroy(ST* ps);//销毁栈
void StackPush(ST* ps, STDataType data);//数据压栈一个
void StackPop(ST* ps);//数据出栈一个
STDataType StackTop(ST* ps);//获取栈顶元素
int StackSize(ST* ps);//获取栈中有效元素个数
bool StackEmpty(ST* ps);//判断栈是否为空,若为空,则返回1,不为空返回0
void StackInit(ST* ps)//初始化栈
{
assert(ps);
ps->data = NULL;
ps->capacity = 0;
ps->top = 0;
}
void StackDestroy(ST* ps)//销毁栈
{
assert(ps);
free(ps->data);//此处不能对ps进行free,因为数据是在ps->data里面进行的realloc的,free要与改函数进行成对出现
ps->data = NULL;
ps->top = ps->capacity = 0;
}
void StackPush(ST* ps, STDataType data)//数据压栈一个
{
assert(ps);
if (ps->top == ps->capacity)
{
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;//定义一个新变量来当做新空间大小
STDataType* tmp = (STDataType*)realloc(ps->data, newcapacity * sizeof(STDataType));
if (!tmp)
{
perror("realloc newcapacity error ::");
exit(-1);
}
ps->capacity = newcapacity;
ps->data = tmp;
}
ps->data[ps->top] = data;
ps->top++;
}
void StackPop(ST* ps)//数据出栈一个
{
assert(ps);
assert(ps->top > 0);
assert(ps->capacity > 0);
ps->top--;
}
STDataType StackTop(ST* ps)//获取栈顶元素
{
assert(ps);
assert(!StackEmpty(ps));
return ps->data[ps->top - 1];
}
int StackSize(ST* ps)//获取栈中有效元素个数
{
assert(ps);
return ps->top;
}
bool StackEmpty(ST* ps)//判断栈是否为空,若为空,则返回1,不为空返回0
{
assert(ps);
return ps->top == 0;
}
typedef struct {
ST pushstack;
ST popstack;
} MyQueue;
MyQueue* myQueueCreate() {
MyQueue* queue = (MyQueue*)malloc(sizeof(MyQueue));
StackInit(&queue->pushstack);
StackInit(&queue->popstack);
return queue;
}
void myQueuePush(MyQueue* obj, int x) {
StackPush(&obj->pushstack, x);
}
int myQueuePop(MyQueue* obj) {
if(StackEmpty(&obj->pushstack) && StackEmpty(&obj->popstack))
return NULL;
// if(StackEmpty(&obj->popstack))
// {
// while(!StackEmpty(&obj->pushstack))
// {
// StackPush(&obj->popstack, StackTop(&obj->pushstack));
// StackPop(&obj->pushstack);
// }
// }
int tail = myQueuePeek(obj);//此处不能传&obj,因为这是一个解引用就得到了一个值,而不是一个地址,如果传过去就相当于对这个值进行访问,那么在函数里面一旦调用&obj就会越界访问
StackPop(&obj->popstack);
return tail;
}
//判断popstack是否为空栈,如果是那么就把pushstack中的全部数据一个一个的push过去
//如果不是空栈那么就直接返回popstack中的栈顶元素
int myQueuePeek(MyQueue* obj) {
if(StackEmpty(&obj->popstack))
{
while(!StackEmpty(&obj->pushstack))
{
StackPush(&obj->popstack, StackTop(&obj->pushstack));
StackPop(&obj->pushstack);
}
}
return StackTop(&obj->popstack);
}
bool myQueueEmpty(MyQueue* obj) {
return StackEmpty(&obj->pushstack) && StackEmpty(&obj->popstack);
}
void myQueueFree(MyQueue* obj) {
StackDestroy(&obj->pushstack);
StackDestroy(&obj->popstack);
free(obj);
}
队列实现栈
队列的性质是,先进先出,单独一个队列也是无法实现栈的功能的,那么我们借助两个队列来实现这个功能,先来梳理以下逻辑。
可以想象一个场景,在地上有两根水管A、B,假设我们要往水管里面放冰块,先从水管A左端往右端放进3块冰块,此时想要取出最左端的一块冰块,该如何操作呢?把前两块冰块依次取出来,再从水管B的左端依次放进去,再把水管A的最后一个冰块送出去,此时就达到栈的特性了,同时也符合队列的先进先出特性。
具体阐述:有两个队列,在第一次入队的时候随意选择哪个空队列入队,同一时刻保证最多只有一个队列不为空,当要出队列的时候,就将非空队列的n - 1个数据出队列入空队列,再将最后一个队列出队,此时一次出队完成。
代码如下:
typedef int QeuueDataType;
typedef struct QueueNode
{
QeuueDataType data;
struct QueueNode* next;
}QueueNode;
typedef struct Queue
{
QueueNode* head;
QueueNode* tail;
}Queue;
void QueueInit(Queue* pq);//初始化队列
void QueueDestroy(Queue* pq);//销毁队列
QueueNode* CreateQueueNode(QeuueDataType data);//增加一个节点
void QueuePush(Queue* pq, QeuueDataType data);//插入队列尾部
void QueuePop(Queue* pq);//弹出队列头部
QeuueDataType QueueFront(Queue* pq);//获取首部数据
QeuueDataType QueueBack(Queue* pq);//获取尾部数据
int QueueSize(Queue* pq);//获取队列长度
bool QueueEmopty(Queue* pq);//返回队列是否为空 1 == 空 || 0 == 非空
/*************************************************************************/
void QueueInit(Queue* pq)//初始化队列
{
assert(pq);
pq->head = NULL;
pq->tail = NULL;
}
void QueueDestroy(Queue* pq)//销毁队列
{
assert(pq);
QueueNode* cur = pq->head;
while (cur)
{
QueueNode* next = cur->next;
free(cur);
cur = next;
}
//以下一定要置空,以免野指针访问
pq->head = pq->tail = NULL;
}
QueueNode* CreateQueueNode(QeuueDataType data)//增加一个节点
{
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (!newnode)
{
perror("newnode malloc faild ::");
exit(-1);
}
newnode->data = data;
newnode->next = NULL;
return newnode;
}
void QueuePush(Queue* pq, QeuueDataType data)//插入队列尾部
{
assert(pq);
QueueNode* newnode = CreateQueueNode(data);
if ((pq->head == NULL) && (pq->tail == NULL))
{
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
}
void QueuePop(Queue* pq)//弹出队列头部
//在弹出过程中有可能会出现野指针访问的情况,当队列中只有一个元素了的时候
//再次弹出,此时tail将会被释放掉,但是tail如果不赋予一个空指针,那么就会出现野指针访问
{
assert(pq);
assert(pq->head);
QueueNode* next = pq->head->next;
if (!next)
{
free(pq->head);
pq->tail = NULL;
pq->head = NULL;
}
else
{
free(pq->head);
pq->head = next;
}
}
QeuueDataType QueueFront(Queue* pq)//获取首部数据
{
assert(pq);
assert(pq->head);
return pq->head->data;
}
QeuueDataType QueueBack(Queue* pq)//获取尾部数据
{
assert(pq);
assert(pq->tail);
return pq->tail->data;
}
int QueueSize(Queue* pq)//获取队列长度
{
assert(pq);
assert(pq->head);
QueueNode* cur = pq->head;
int Queuesize = 0;
while (cur)
{
cur = cur->next;
++Queuesize;
}
return Queuesize;
}
bool QueueEmopty(Queue* pq)//返回队列是否为空 1 == 空 || 0 == 非空
{
assert(pq);
return pq->head == NULL;
}
/*************************************************************************/
typedef struct {
Queue q1;
Queue q2;
} MyStack;
MyStack* myStackCreate() {
MyStack* stack = (MyStack*)malloc(sizeof(MyStack));
QueueInit(&stack->q1);
QueueInit(&stack->q2);
return stack;
}
void myStackPush(MyStack* obj, int x) {
if(QueueEmopty(&obj->q1))
QueuePush(&obj->q2, x);
else
QueuePush(&obj->q1, x);
}
int myStackPop(MyStack* obj) {
Queue* EmptyQ = &obj->q1;
Queue* NoneEmptyQ = &obj->q2;
if(!QueueEmopty(&obj->q1))
{
EmptyQ = &obj->q2;
NoneEmptyQ = &obj->q1;
}
while(NoneEmptyQ->head != NoneEmptyQ->tail)
{
QueuePush(EmptyQ, QueueFront(NoneEmptyQ));
QueuePop(NoneEmptyQ);
}
int top = QueueFront(NoneEmptyQ);
QueuePop(NoneEmptyQ);
return top;
}
int myStackTop(MyStack* obj) {
if (QueueEmopty(&obj->q1) && QueueEmopty(&obj->q2))
return NULL;
Queue* EmptyQ = &obj->q1;
Queue* NoneEmptyQ = &obj->q2;
if(!QueueEmopty(&obj->q1))
{
EmptyQ = &obj->q2;
NoneEmptyQ = &obj->q1;
}
return QueueBack(NoneEmptyQ);
}
bool myStackEmpty(MyStack* obj) {
return QueueEmopty(&obj->q1) && QueueEmopty(&obj->q2);
}
void myStackFree(MyStack* obj) {
QueueDestroy(&obj->q1);
QueueDestroy(&obj->q2);
free(obj);
}