数据结构之栈与队列

发布于:2022-07-19 ⋅ 阅读:(277) ⋅ 点赞:(0)

目录

前言

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;
	//从队尾插入新结点即可,需讨论第一次插入
	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题介绍。


网站公告

今日签到

点亮在社区的每一天
去签到