学完了链表的数据结构,现在我们就可以开始学习新的知识–栈与队列。
一、栈的实现。
栈:一种特殊的线性表,其只允许在固定的一端进行插入与删除元素的操作,我们将进行插入与删除的这一端叫做栈顶,另一端就叫做栈底。栈中的数据遵循先进后出(last in first out)原则。
它的效果有点像我们生活中的羽毛球筒,最后放入的羽毛球最先被取出;
栈与队列的实现都可以用链表或者顺序表来完成,但是栈用顺序表会简单许多,那么我们就采用顺序表的方式来实现,并且这一部分在前面的https://blog.csdn.net/2301_81205182/article/details/138163952
写过,并且代码基本相同,所以后面的代码我不会讲的特别仔细;
首先就是栈的功能也就是.h文件:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>//判空返回值为bool类型,必须包含该头文件
typedef int datatype;
typedef struct Stack
{
datatype* arr;
int size;
int capacity;
}Stack;
void StackInit(Stack* ptr);//初始化栈
void StackDesTory(Stack* ptr);//销毁栈
void StackPush(Stack* ptr, datatype x);//入栈
void StackPop(Stack* ptr);//出栈
datatype StackTop(Stack* ptr);//取栈顶元素
bool Stackiskong(Stack* ptr);//判断栈是否为空
void StackPrintf(Stack* ptr);
int StackSize(Stack* ptr);//栈的大小
栈也就是要完成入栈,出栈,取栈顶元素,判空以及栈的大小(也就是当前栈中存入的元素个数)。
1、首先一个栈需要初始化与销毁,那我们就先完成初始化与销毁。
void StackInit(Stack* ptr)
{
assert(ptr);
ptr->arr = NULL;
ptr->capacity = ptr->size = 0;
}
void StackDesTory(Stack* ptr)
{
assert(ptr);
free(ptr->arr);
ptr->arr = NULL;
ptr->capacity = ptr->size = 0;
}
栈的初始化就是让这个数组的指针指向空,以及让size、capacity都置为0;
栈的销毁同理需要释放这个数组的指针,并且置空,然后让size、capacity为0;
2、当我们初始化完我们的栈,我们就可以完成我们的入栈与出栈。当然我们知道栈是后入先出的,所以不存在顺序表里的头删,尾删,直接就删除与插入即可。
入栈:
void StackPush(Stack* ptr, datatype x)
{
assert(ptr);
if (ptr->capacity == ptr->size)//如果栈被存满,或者栈为空
{
int newcapacity = ptr->capacity == 0 ? 4 : 2 * ptr->capacity;//如果栈为空就开辟4个datatype类型的空间
datatype* head = (datatype*)realloc(ptr->arr, newcapacity * sizeof(datatype));//开辟空间
//这里之所以用head接收数组指针是为了防止开辟空间不成功,反而将原来的数据丢失
if (head == NULL)//如果为空就退出
{
perror("realloc");//报错
return;
}
ptr->arr = head;
ptr->capacity = newcapacity;//ptr—>capacity已经变为newcapacity
}
ptr->arr[ptr->size] = x;
ptr->size++;//注意这里一定要++,因为我们存入了数据,栈顶也应该随之变化
}
出栈:
void StackPop(Stack* ptr)
{
assert(ptr);
ptr->size--;
}
出栈只需要将栈顶向下移动一位即可,当然如果考虑逻辑性的话可以将原栈顶的元素改为无实义的值,然后在让栈顶下移;
3、取栈顶元素
datatype StackTop(Stack* ptr)
{
assert(ptr);
return ptr->arr[ptr->size-1];
}
取栈顶元素的时候需要注意,我们这里的size是指向栈顶元素的下一位,所以取的时候需要-1;
4、取栈的大小
int StackSize(Stack* ptr)
{
assert(ptr);
return ptr->size;
}
在这里直接返回size即可,size就为我们的栈的大小。
5、判空
bool Stackiskong(Stack* ptr)
{
assert(ptr);
return ptr->size == 0;
}
在这里如果按照一般的想法我们会判断size是否为0,如果不为0返回false,为0返回true,但在这里返回其等不等于0即可。
总的来说呢,栈的实现我们顺序表里基本都已经实现过,所以这一部分显得很简单。
二、队列
队列也是一种特殊的线性表,它与栈的队列的差别在于,队列是先进先出(first in first out),我们将进行插入的一端成为队头,删除数据的一端称为队尾,我们将插入数据的操作称为入队,删除数据称出队。
在生活中有点类似于我们点餐时的的排号,先点餐先排号,同时也最先出餐,最后点餐的最后出餐。
队列的实现也能用数组与链表实现,但是如果用数组实现的话,出队的时候涉及到后面的元素向前移,时间复杂度较大,而用链表的话只需要释放掉第一个节点,然后head指针后移。所以在这里我们选择链表的形式更好,那我们知道链表有单链表有双链表,那我们用什么呢?双链表已经相较于单链表优化的很好了,在刷题的过程中遇到的链表题目基本都是单链表,所以我在这里使用单链表。
用链表的形式实现队列与单链表的实现也基本一致,不会的可以看下我之前写的单链表的博客。
首先我们需要实现一下功能:
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int datatype;
typedef struct Queue
{
datatype data;
struct Queue* next;
}Queue;
typedef struct Queueptr
{
Queue* phead;
Queue* ptail;
int size;
}Queueptr;
void QueueptrInit(Queueptr* ptr);//初始化队列指针
void QueueptrDestory(Queueptr* ptr);//销毁队列
void QueuePush(Queueptr* ptr, datatype x);//入队
void print(Queueptr* ptr);//打印
void QueuePop(Queueptr* ptr);//出队列
datatype QueueTop(Queueptr* ptr);//取队头
bool IsKong(Queueptr* ptr);//判空
int HandSize(Queueptr* ptr);//取队列大小
在这里可以看到我们定义了两个结构体,第一个结构体是队列的节点的结构体,第二个结构体存放的是指向队列的头的指针与指向尾的指针,以及队列大小的size。
那么这样定义有什么好处呢?
1、如果只定义一个结构体的话,我们需要使用二级指针,但是我们定义第二个结构体的话,只需要传入一级指针即可。
2、让大家见识一下其他的写法,增长见识。
1、第一步初始化
void QueueptrInit(Queueptr* ptr)
{
assert(ptr);
ptr->phead = NULL;
ptr->ptail = NULL;
ptr->size = 0;
}
将头指针与尾指针置空,并将size置0;
2、入队
上面我们说队列是先入先出,那么入队很明显指的是尾插。
void QueuePush(Queueptr* ptr, datatype x)
{
assert(ptr);
Queue* node = (Queue*)malloc(sizeof(Queue));
if (node == NULL)
{
perror("malloc");
return;
}
node->next = NULL;
node->data = x;
if (ptr->ptail== NULL)
{
ptr->phead = ptr->ptail = node;
}
else
{
ptr->ptail->next = node;
ptr->ptail = node;
}
ptr->size++;
}
尾插需要注意的有以下的地方:
1、队列为空时,我们需要将创建的第一个节点置为头结点与尾节点,当队列不为空时尾插。
2、我们需不需要将创建节点的这一部分写为一个函数?在这里其实没有必要,因为我们队列的插入只有尾插这一个插入,所以只会在这个入队的代码中才会创建新的节点,所以并不需要将它放入函数。
3、出队
void QueuePop(Queueptr* ptr)
{
assert(ptr);
if (ptr->phead == ptr->ptail)
{
free(ptr->phead);
free(ptr->ptail);
ptr->phead = ptr->ptail = NULL;
}
else
{
Queue* next = ptr->phead->next;
free(ptr->phead);
ptr->phead = next;
}
ptr->size--;
}
出队列的时候也有一个需要注意的地方,那就是当队列中只剩一个节点了,那我们直接就放头与尾都释放,并且一定要置空。
4、取队头
datatype QueueTop(Queueptr* ptr)
{
assert(ptr);
ptr->size--;
return ptr->phead->data;
}
这一部分很简单,因为队列是先入先出,所以我们直接将头节点的data返回即可。
5、判空
bool IsKong(Queueptr* ptr)
{
assert(ptr);
return ptr->size == 0;
}
这一部分与栈相似,看size为不为0即可。
6、队列的销毁
void QueueptrDestory(Queueptr* ptr)
{
assert(ptr);
Queue* pcur = ptr->phead;
while (pcur)
{
Queue* next = ptr->phead->next;
free(pcur);
pcur = next;
}
ptr->phead = NULL;
ptr->ptail = NULL;
}
这里需要注意的是我们写这个代码的时候是调用外层的结构体里的phead与ptail指针
如果我们直接释放掉外层的ptr的话,里面的节点并没有被释放,所以我们需要一个一个节点的释放,最后释放ptr。
到此栈与队列已经实现完成。
下面是所有的代码:
queue.c
#include"queue.h"
void QueueptrInit(Queueptr* ptr)
{
assert(ptr);
ptr->phead = NULL;
ptr->ptail = NULL;
ptr->size = 0;
}
void QueuePush(Queueptr* ptr, datatype x)
{
assert(ptr);
Queue* node = (Queue*)malloc(sizeof(Queue));
if (node == NULL)
{
perror("malloc");
return;
}
node->next = NULL;
node->data = x;
if (ptr->ptail== NULL)
{
ptr->phead = ptr->ptail = node;
}
else
{
ptr->ptail->next = node;
ptr->ptail = node;
}
ptr->size++;
}
void print(Queueptr* ptr)
{
assert(ptr);
while (ptr->phead)
{
printf("%d ", ptr->phead->data);
ptr->phead = ptr->phead->next;
}
}
void QueuePop(Queueptr* ptr)
{
assert(ptr);
if (ptr->phead == ptr->ptail)
{
free(ptr->phead);
free(ptr->ptail);
ptr->phead = ptr->ptail = NULL;
}
else
{
Queue* next = ptr->phead->next;
free(ptr->phead);
ptr->phead = next;
}
ptr->size--;
}
datatype QueueTop(Queueptr* ptr)
{
assert(ptr);
ptr->size--;
return ptr->phead->data;
}
bool IsKong(Queueptr* ptr)
{
assert(ptr);
return ptr->size == 0;
}
int HandSize(Queueptr* ptr)
{
assert(ptr);
return ptr->size;
}
void QueueptrDestory(Queueptr* ptr)
{
assert(ptr);
Queue* pcur = ptr->phead;
while (pcur)
{
Queue* next = ptr->phead->next;
free(pcur);
pcur = next;
}
ptr->phead = NULL;
ptr->ptail = NULL;
}
test.c
#include"queue.h"
void test()
{
Queueptr q;
QueueptrInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QueuePush(&q, 4);
QueuePop(&q);
QueuePop(&q);
QueuePop(&q);
//QueuePop(&q);
/*int ret = QueueTop(&q);
printf("%d \n",ret);
bool abs = IsKong(&q);
printf("%d\n", abs);*/
//int count = HandSize(&q);
//printf("%d\n", count);
print(&q);
}
int main()
{
test();
return 0;
}
stack.c
#include"stack.h"
void StackInit(Stack* ptr)
{
assert(ptr);
ptr->arr = NULL;
ptr->capacity = ptr->size = 0;
}
void StackDestory(Stack* ptr)
{
assert(ptr);
free(ptr->arr);
ptr->arr = NULL;
ptr->capacity = ptr->size = 0;
}
void CheckCapacity(Stack* ptr)
{
assert(ptr);
if (ptr->size == ptr->capacity)
{
int newcapacity = ptr->capacity == 0 ? 4 : 2 * ptr->capacity;
datatype* tem = (datatype*)realloc(ptr->arr, newcapacity * sizeof(Stack));
if (tem == NULL)
{
perror("realloc");
return;
}
ptr->arr = tem;
ptr->capacity = newcapacity;
}
}
void StackPush(Stack* ptr, datatype x)
{
assert(ptr);
CheckCapacity(ptr);
ptr->arr[ptr->size] = x;
ptr->size++;
}
void Stackprintf(Stack* ptr)
{
for (int i = 0; i < ptr->size; i++)
{
printf("%d ", ptr->arr[i]);
}
printf("\n");
}
int StackIsEmpty(Stack* ptr)
{
assert(ptr);
return ptr->size == 0;
}
int Satcksize(Stack* ptr)
{
return ptr->size;
}
void StackPop(Stack* ptr)
{
assert(ptr);
ptr->size--;
}
datatype StackTop(Stack* ptr)
{
assert(ptr);
return ptr->arr[ptr->size - 1];
}
#include"stack.h"
int main()
{
Stack stack;
StackInit(&stack);
StackPush(&stack, 1);
StackPush(&stack, 2);
StackPush(&stack, 3);
StackPush(&stack, 4);
StackPop(&stack);
Stackprintf(&stack);
return 0;
}
下一篇博客我们来完成用两个栈实现队列与两个队列实现栈。