栈和队列的实现

1.栈

1.1 栈的概念及结构

栈是一种特性的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。

压栈:栈的插入操作也可叫做进栈/压栈/入栈,入数据在栈顶

出栈:栈的删除操作叫做出栈,出数据也在栈顶

总结:从上面的入栈、出栈可以看出栈的“后进先出”的特点,也就是后面进栈的元素会先出栈,而先进栈的元素会后出栈

1.2 栈的实现

栈的实现方式有两种,一种是采用单链表的方式,另一种是采用顺序表的方式(数组),若采用单链表的方式,入栈、出栈的操作采用单链表的头插、头删的方式即可满足栈的后进先出的要求。若采用顺序表(数组)的方式,数组的尾插、尾删操作也可以满足栈的后进先出要求。相对而言数组的结构实现更优一些,因为数组在尾上插入数据的代价比较小。

1.2.1 定义栈与栈内的数据类型

typedef int STDataType;//栈内的数据类型为int类型

typedef struct Stack
{
	STDataType* _a;//定义一个动态数组
	int _top;//栈顶下标
	int _capacity;//数组的容量
}Stack;

1.2.2 初始化栈

//1、初始化栈
void StackInit(Stack* pst)
{
	assert(pst);
	pst->_a = NULL;
	//若栈顶初始化为0,则top指向的是栈顶的下一个元素,入栈时先插入元素,再++top;
	//若栈顶初始化为-1,则top指向的是栈顶的元素,入栈时先++top,再插入元素;
	pst->_capacity = pst->_top = 0;
}

1.2.3 销毁栈

//2、销毁栈
void StackDestroy(Stack* pst)
{
	assert(pst);
	free(pst->_a);
	pst->_a = NULL;
	pst->_capacity = pst->_top = 0;
}

1.2.4 入栈

入栈即往栈中插入元素,在插入元素之前要考虑是否需要扩容,当top==capacity时,表明栈的空间已经满了,此时需要扩容;如果不需要扩容,将元素插入到栈顶位置即可。

//3、入栈
void StackPush(Stack* pst, STDataType val)
{
	assert(pst);

	//插入数据前,先判断空间是否足够
	if (pst->_capacity == pst->_top)
	{
		//先判断数组的容量是否为0,如果容量为0,则初始空间为4个大小;
		//如果不为0,将空间扩容为原来的2倍
		int newCapacity = pst->_capacity == 0 ? 4 : 2 * pst->_capacity;
		STDataType* tmp = (STDataType*)realloc(pst->_a, sizeof(STDataType) * newCapacity);

		//扩容失败,打印错误信息
		if (tmp == NULL)
		{
			perror("StackCreateCapacity:realloc");
			exit(-1);
		}
		pst->_a = tmp;
		pst->_capacity = newCapacity;
	}
	pst->_a[pst->_top] = val;//向栈顶插入元素
	++pst->_top;
}

1.2.5 出栈

出栈即删除栈顶元素,只需要将top--即可(后面再插入的数据会覆盖原来的数据),但是在删除栈顶元素时还需要考虑栈内是否有数据,通过判断top的值即可;top==0表示无数据

//4、出栈
void StackPop(Stack* pst)
{
	assert(pst);
	//判断栈内是否有元素
	assert(pst->_top != 0);
	--pst->_top;
}

1.2.6 获取栈内数据的个数

栈内的数据个数即是 top 的值,返回即可。

//5、获取栈内数据的个数
int StackSize(Stack* pst)
{
	assert(pst);

	return pst->_top;
}

1.2.7 判断栈是否为空

栈为空就返回 true,否则返回 false。  

//6、判断栈是否为空,返回1是空,返回0是非空
bool StackIsEmpty(Stack* pst)
{
	assert(pst);
	//若栈为空则pst->_top == 0,此时pst->_top == 0为true
	//若栈不为空则pst->_top != 0,此时pst->_top == 0为false
	return pst->_top == 0;
}

1.2.8 获取栈顶的数据

top指向的是栈顶元素的下一个位置,故top-1就是栈顶元素的下标。

//7、获取栈顶的数据
STDataType StackTop(Stack* pst)
{
	assert(pst);
	assert(pst->_top != 0);//当栈里没有数据时,访问pst->_a[pst->_top-1]的元素时会越界访问

	return pst->_a[pst->_top - 1];
}

2.队列

2.1 队列的概念及结构

队列也是一种线性表,队列只允许在一端进行插入操作,而在另一端进行删除操作,队列具有先进先出的特征;进行插入操作的一端叫做队尾,即入队列;进行删除操作的一端叫做队头,即出队列。

2.2 队列的实现

队列的实现方式也有两种,一种是采用顺序表(数组)的方式;另外一种是采用链表的方式。使用链表的结构实现更优一些,因为如果使用数组的结构,出队列是在数组头上出数据,效率会比较底。队列是在队头和队尾进行元素的删除和插入操作,所以定义队列时需要记录头指针(head)和尾指针(tail)。

2.2.1 队列结构的构建

与链表的结构一样,队列也是由一个一个的节点构成,所以队列需要定义两个结构体;一个结构体用来表示队列中的每一个节点;另外一个结构体用来记录队列的队头指针、队尾指针以及队列中节点的个数。

//队列结构的构建
typedef int QDataType;//将数据类型重定义方便以后修改

//队列的节点
typedef struct QueueNode
{
	QDataType _data;
	struct QueueNode* _next;
}QueueNode;

//队列的队头和队尾指针
typedef struct Queue
{
	QueueNode* _head;//指向队头的指针
	QueueNode* _tail;//指向队尾的指针
	int _size;//记录队列中节点的个数
}Queue;

2.2.2 队列的初始化

队列的初始化,将队列结构体中的队头、队尾指针置为空,队列中节点的个数置为0即可。

//1、队列的初始化
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->_head = NULL;
	pq->_tail = NULL;
	pq->_size = 0;
}

2.2.3 销毁队列

//2、销毁队列
void QueueDestroy(Queue* pq)
{
	assert(pq);
	
	while (pq->_head)
	{
		QueueNode* cur = pq->_head;
		pq->_head = pq->_head->_next;

		free(cur);
		cur = NULL;
	}
	pq->_tail = NULL;
	pq->_size = 0;
}

2.2.4 队列插入元素(入队列)

往队列中插入数据相当于链表中的尾插,在往队列中插入第一个元素时需要单独考虑,入下图所示。

当插入第一个元素时,队头指针_head和队尾指针_tail都指向第一个元素。

向队列中连续插入节点1、2、3、4,并观察队头指针_head和队尾指针_tail以及_size的变化。

代码实现:

//3、进队列
void QueuePush(Queue* pq, QDataType val)
{
	assert(pq);

	QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));

	if (newNode == NULL)
	{
		perror("QueuePush:malloc");
		exit(-1);
	}

	newNode->_data = val;
	newNode->_next = NULL;

	//在队列的队尾插入数据分两种情况:1、队列为空;2、队列不为空
	if (pq->_head == NULL)
	{
		//若队列为空,则队头指针和队尾指针均指向新插入的节点
		pq->_head = pq->_tail = newNode;
	}
	else
	{
		//若队列不为空,直接将节点插入的队尾指针的next位置
		pq->_tail->_next = newNode;
		pq->_tail = newNode;
	}

	++pq->_size;//每插入一个节点_size都要++
}

2.2.5 队列删除数据(出队列)

队列删除数据对应到链表上来说,就是链表的头删,当_size==0时(即队列中没有数据了)不能进行删除操作。

从上面的顺序可以看出队列的入队可出队顺序:

入队顺序:1、2、3、4;出队顺序:1、2、3、4。

//4、出队列
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->_head);//队头指针不为空,即队列中有节点

	QueueNode* cur = pq->_head;//记录当前要删除的节点
	pq->_head = pq->_head->_next;//头节点往后走

	//如果队列中只有一个节点,头节点完后走便指向了NULL
	//此时队尾节点_tail还指向原来的节点,所以将_tail也置为空,
	//否则_tail指针便成为了野指针
	if (pq->_head == NULL)
	{
		pq->_tail = NULL;
	}

	free(cur);
	cur = NULL;
	--pq->_size;
}

2.2.6 获取队头和队尾的数据

//5、获取队头的数据
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->_head);//队头指针不为空,即队列中有节点

	return pq->_head->_data;
}

//6、获取队尾的数据
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->_tail);

	return pq->_tail->_data;
}

2.2.7 检测队列是否为空

//7、检测队列是否为空,为空返回true,不为空返回false
bool QueueIsEmpty(Queue* pq)
{
	assert(pq);

	return pq->_size == 0;
}

2.2.8 获取队列中有效元素的个数

//8、获取队列中有效元素的个数
int QueueSize(Queue* pq)
{
	assert(pq);

	return pq->_size;
}

栈和队列实现的完整代码如下:栈的实现队列的实现

  • 19
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值