数据结构之栈与队列

目录

前言

1.栈的实现

1.1栈的概念

1.2栈结构的选择

1.3数组栈的结构

1.4接口实现

初始化栈接口函数

销毁栈接口函数

入栈操作接口

出栈操作接口

出栈数据接口函数(不改变栈顶位置)

求栈数据个数

判空函数

2.队列的实现

2.1队列的概念

2.2队列结构的选择

2.3利用单链表实现队列的结构

2.4接口实现

初始化队列接口函数

销毁队列接口函数

入队列接口函数

出队列接口函数

求队头元素函数

求队尾元素函数

求队列数据个数函数

判空函数

小结


前言

又到了数据结构上新啦,本期内容:栈与队列。一起来看看吧~

1.栈的实现

1.1栈的概念

栈的定义是只允许在一端进行插入或删除操作的线性表,也称为后进先出(LIFO:last in first out)的线性表。

栈顶:线性表允许进行插入和删除的那一端。

栈底:固定的,不允许进行插入和删除的另一端。

空栈:不含任何元素的空表。

1.2栈结构的选择

栈其实质结构是一个线性结构,可通过数组或链表来实现,下面对其两者实现的区别进行分析。

数组栈:因为栈只能在一端插入删除,数组在末端插入删除的复杂度都是O(1),且数组的缓存命中率更高。但可能会造成空间浪费

链表栈:需要多少空间就开辟多少,不会浪费空间。如果用链表的尾作为栈顶,则需要将其设计成双向链表,因为单链表尾插尾删效率低(需要找尾);如果用链表的头作为栈顶,则可以将其设计成单链表

结论:相比两种结构,从优中选择更优的,数组栈的实现会更加好一点。

1.3数组栈的结构

1.4接口实现

初始化栈接口函数

void StackInit(ST* ps)
{
	assert(ps);//断言
	ps->a = NULL;
	ps->capacity = 0;
	ps->top = 0;
}

销毁栈接口函数

void StackDestroy(ST* ps)
{
	ps->capacity = 0;
	ps->top = 0;
	free(ps->a);
	ps->a = NULL;
}

入栈操作接口

void StackPush(ST* ps, STDataType x)
{
	assert(ps);
	if (ps->capacity == ps->top)//是否需要增容
	{
		int newcapacity = ps->capacity == 0 ? 4 : (ps->capacity) * 2;
		STDataType* tmp = realloc(ps->a, sizeof(STDataType)*newcapacity);
		if (tmp == NULL)
		{
			printf("realloc fail\n");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity = newcapacity;
	}
	ps->a[ps->top] = x;//数据存入栈顶,再改变栈顶的位置
	ps->top++;
}

出栈操作接口

void StackPop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));//栈不为空才能出栈
	ps->top--;
}

出栈数据接口函数(不改变栈顶位置)

STDataType StackTop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));//栈不为空才能出数据
	return ps->a[ps->top - 1];
}

求栈数据个数

int StackSize(ST* ps)
{
	assert(ps);
	return ps->top;//初始化时top设置为0,top的值即为栈的数据数
                   //初始化时top设置为-1,则栈的数据数应为top+1
}

判空函数

bool StackEmpty(ST* ps)
{
	return ps->top == 0;//top=0代表栈无数据,所以表达式成立时返回true,不成立时代表栈不为空,返回false
}

2.队列的实现

2.1队列的概念

队列是一种特殊的线性表,特殊之处在于它只允许在表的一端进行删除操作,而在表的另一端进行插入操作,也称为先进先出(FIFO:first in first out)线性表。和栈一样,队列是一种操作受限制的线性表。

队头:进行删除操作的端。

队尾:进行插入操作的端。

2.2队列结构的选择

从队列的概念介绍中可知,若用数组来实现队列明显不太妥,因为数组在其尾端插入删除简单,但在其头端插入删除时需要挪动数据,效率就会大打折扣

而链表则可以解决这些问题,其可以在链表的头部出数据,在链表的尾部入数据,多定义一个尾结点可以帮助我们更方便的实现这一结构。

综上,队列的结构实现利用链表来实现更优。

2.3利用单链表实现队列的结构

2.4接口实现

初始化队列接口函数

void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = NULL;//初始链表结点均为空
	pq->tail = NULL;
}

销毁队列接口函数

void QueueDestroy(Queue* pq)
{
	assert(pq);
	QueueNode* cur = pq->head;//遍历链表销毁结点,与单链表销毁一致
	while (cur != NULL)
	{
		QueueNode* next = cur->next;//保存下一结点
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
}

入队列接口函数

void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));//申请新结点
	newnode->data = x;
    newnode->next = NULL;
	//从队尾插入新结点即可,需讨论第一次插入
	if (pq->head == NULL)//head为空代表链表为第一次插入
	{
		pq->head = pq->tail = newnode;
	}
	else//链表不为空直接将其插入到tail的后面,然后改变tail即可
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
}

出队列接口函数

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));//队列不能为空,为空了就没东西出了

	//从队头出数据,链表的头删操作即可
	QueueNode* next = pq->head->next;//保存头结点的下一结点,防止释放头结点后找不到
	free(pq->head);
	pq->head = next;
	if (pq->head == NULL)//如果head为空,证明队列已经无数据了,需要更新一下tail的指向,防止成为野指针
	{
		pq->tail = NULL;
	}
}

求队头元素函数

QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));//队列不能为空

	return pq->head->data;
}

求队尾元素函数

QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));//队列不能为空

	return pq->tail->data;
}

求队列数据个数函数

int QueueSize(Queue* pq)
{
	assert(pq);

	int n = 0;
	QueueNode* cur = pq->head;//遍历链表求个数
	while (cur)
	{
		++n;
		cur = cur->next;
	}
	return n;
}

判空函数

bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL;
}

小结

本次栈与队列的结构与实现就讲到这里啦~

下期内容:栈与队列的OJ题介绍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值