栈与队列的实现

学完了链表的数据结构,现在我们就可以开始学习新的知识–队列

一、栈的实现。

栈:一种特殊的线性表,其只允许在固定的一端进行插入与删除元素的操作,我们将进行插入与删除的这一端叫做栈顶,另一端就叫做栈底。栈中的数据遵循先进后出(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;
}

下一篇博客我们来完成用两个栈实现队列与两个队列实现栈。

  • 32
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值