栈和队列详解

目录

引言

栈的实现

思考用数组还是链表的底层结构实现?

接口实现

队列

引言

队列的实现

思考用数组还是链表的底层结构实现?

接口实现


引言

现实生活中,有很多先进后出的例子,比如说弹夹,一端进出子弹,先进弹夹的子弹后发射,后进弹夹的子弹先发射;再比如一摞盘子,一端摆放盘子先放到下面的盘子后拿出;再比如烤串,一般不从用手抓的一端串肉,先穿进去的食物后被吃到。

这些物品都遵循先进后出的原则

而我们的就是一种数据在固定一端 先进后出(LIFO)线性表 

出入数据的一端叫栈顶,另一端叫栈底

入数据称为压栈或者入栈,出数据称为出栈

栈的实现

思考用数组还是链表的底层结构实现?

数组:我们要保证先进后出的原则,那么数组的尾部当栈顶,利用尾插和尾删操作就可以实现数据的进入和弹出了

分析:尾插和尾删操作时间复杂度都为O(1),十分快捷

 链表:链表的尾当栈顶尾插和尾删实现数据的进入和弹出

分析:尾插和尾删操作需要找尾,时间复杂度O(N)

若想实现O(1),则需要在接口函数参数中创建尾指针控制尾节点

综合比较,数组更简便,所以我们选择数组来实现栈

接口实现

//初始化栈
void StackInit(Stack* ps) {
	//检查指针有效性
	assert(ps);

	ps->arr = NULL;
	ps->capacity = ps->top = 0;//top指向栈顶的后一个元素
}

//数据入栈
void StackPush(Stack* ps, STDataType val) {
	//检查指针有效性
	assert(ps);

	//检查容量并扩容
	if (ps->top == ps->capacity) {
		//原始容量可能为0,需要利用三目操作符判断并扩容
		size_t newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		//在堆上开辟新的数组
		STDataType* tmp = (STDataType*)malloc(newCapacity * sizeof(STDataType));
		//检查malloc是否成功
		if (tmp == NULL) {
			perror("malloc err!");
			return;
		}
		ps->capacity = newCapacity;
		ps->arr = tmp;
		free(tmp);
	}

	ps->arr[ps->top++] = val;

}

//数据出栈
void StackPop(Stack* ps) {
	//检查指针有效性
	assert(ps);
	
	if (!StackEmpty(ps)) {
		ps->top--;
	}
}

//查找栈顶元素
STDataType StackTopVal(Stack* ps) {
	//检查指针有效性
	assert(ps);

	if (!StackEmpty(ps)) {
		return ps->arr[ps->top - 1];
	}

}

//检查栈的有效数据个数
size_t StackSize(Stack* ps) {
	//检查指针有效性
	assert(ps);

	return ps->top;
}

//检查栈是否为空
bool StackEmpty(Stack* ps) {
	//检查指针有效性
	assert(ps);

	return ps->top == 0;
}

//销毁栈
void StackDestory(Stack* ps) {
	//检查指针有效性
	assert(ps);

	free(ps->arr);
	ps->arr = NULL;
	ps->capacity = ps->top = 0;
}

队列

引言

队列(queue)是一种遵循先入先出(FIFO)规则的线性数据结构。顾名思义,队列模拟了排队现象,即新来的人不断加入队列尾部,而位于队列头部的人逐个离开。

出数据的一端叫队首,入数据的一端叫队尾

出数据称为出队,入数据称为入队

队列的实现

思考用数组还是链表的底层结构实现?

数组:

入队:尾插O(1) 

出队:头删,要依次挪动数据O(N)

链表:

入队:尾插找尾O(N),函数接口多一个尾指针控制尾节点的参数->O(1)
出队:头删O(1) 

 综合对比:数组出队依次挪动数据容易出错,我们可以采用链表的底层结构实现队列
用单链表即可,复杂链表实现队列好比大炮打蚊子了,没必要。

接口实现

#include "Queue.h"

//初始化队列
void QueueInit(Queue* pq) {
	//检查指针有效性
	assert(pq);

	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

//数据入队列
void QueuePush(Queue* pq, QDataType val) {
	//检查指针有效性
	assert(pq);

	//创建新节点
	QNode* newNode = (QNode*)malloc(sizeof(QNode));
	//检查内存是否申请成功
	if (newNode == NULL) {
		perror("malloc err!");
		return;
	}
	//初始化newNode
	newNode->data = val;
	newNode->next = NULL;

	//队列为空的情况
	if (pq->phead == NULL) {
		pq->phead = pq->ptail = newNode;
	}

	//队列不为空,尾插
	pq->ptail->next = newNode;
	pq->ptail = newNode;

	//队列有效数据增加
	pq->size++;

}

//数据出队列
void QueuePop(Queue* pq) {
	//检查指针有效性
	assert(pq);
	//队列保证不能为空,暴力检查
	assert(!QueueEmpty(pq));

	//队列只有一个节点
	if (pq->phead->next == NULL) {
		pq->phead = pq->ptail = NULL;
	}
	//队列有多个节点,头删
	else {
		QNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
	}
	//队列有效元素个数减少
	pq->size--;
}
//获取队列有效元素个数
size_t QueueSize(Queue* pq){
	//检查指针有效性
	assert(pq);

	return pq->size;
}
//获取队首元素值
QDataType QueueFrontVal(Queue* pq) {
	//检查指针有效性
	assert(pq);
	// 暴力检查 
	assert(pq->phead != NULL);

	return pq->phead->data;
}

//获取队尾元素值
QDataType QueueBackVal(Queue* pq) {
	//检查指针有效性
	assert(pq);
	// 暴力检查 
	assert(pq->ptail != NULL);

	return pq->ptail->data;
}

//检查队列是否为空
bool QueueEmpty(Queue* pq) {
	//检查指针有效性
	assert(pq);

	return pq->size == 0;
}

//销毁队列
void QueueDestory(Queue* pq) {
	//检查指针有效性
	assert(pq);


	QNode* cur = pq->phead;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);

		cur = next;
	}

	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vect.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值