【C数据结构】——栈和队列

一.栈(stack) 

1.栈的定义

 首先,栈是一种线性表,一种只允许在表尾进行插入和删除操作的特殊线性表

我们把允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据的栈称为空栈。遵循后进先出LIFO(Last In First Out)的原则

2.栈的相关操作 

  • 压栈:栈的插入操作叫做进栈/压栈/入栈,如同子弹入弹夹,其入数据在栈顶。
  • 出栈:栈的删除操作叫做出栈,如同弹夹中的子弹出夹,其出数据也在栈顶。

它的结构如下图所示:

3.栈的实现

前面提到,栈是一种特殊的线性表,所以,栈应该就可以通过类似顺序表,用数组来实现,这种用数组来实现的栈,被称为顺序栈 还有一种栈是用链式结构来实现的,被称为链栈,这里不做介绍。 

先定义栈:

typedef char STDataType;
typedef struct Stack
{
	STDataType* a;
	int top;		// 栈顶
	int capacity;  // 容量 
}Stack;

3.1 初始化栈

接下来在初始化栈的时候会有一个小问题:栈为空时,如何设置栈顶(top)的值?

既然是数组,那么 top 的值就应该为0,因为数组下标就是从0开始加的,但其实这里 top 的值应设置成 -1

top 的值为 0 和 -1 的区别:

  • 0:当 top 的值为 0 时,容易引发歧义。top == 0的时候,栈里是一个元素还是空?因为数组下标是从0开始算的。
  • -1:而当 top 的值为 -1 时,先 ++top ,再压栈,意思就很明了,a[top] = x。

此时:

栈空条件:top == -1;

栈满条件:top == capacity-1;

栈容量:   top+1;

所以初始化栈:

void StackInit(Stack* ps) {
	assert(ps);
	ps->a = NULL;
	ps->top = -1;
	ps->capacity = 0;
}

解决了这个问题,下面开始实现栈

3.2 入栈和出栈

// 入栈/压栈/进栈 
void StackPush(Stack* ps, STDataType data) {
	assert(ps);    //保证栈不是NULL
	if (ps->capacity == ps->top+1) {    //判断栈是否已满
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity);    //扩容用realloc
		if (tmp == NULL)    //防止开辟失败                                
		{
			perror("realloc");
			return;
		}
		ps->a = tmp;
		ps->capacity = newcapacity;
	}
	ps->top++;
	ps->a[ps->top] = data;
}

// 出栈 
void StackPop(Stack* ps) {
	assert(ps);
	assert(ps->top >= 0);    //保证不是空栈
	ps->top--;
}

3.3 获取栈顶元素及栈中元素个数

操作比较简易。

// 获取栈顶元素 
STDataType StackTop(Stack* ps) {
	assert(ps);
	assert(ps->top >= 0);    //确保栈内有元素
	return ps->a[ps->top];
}

// 获取栈中有效元素个数 
int StackSize(Stack* ps) {
	assert(ps);
	return (ps->top) + 1;
}

3.4销毁栈

直接释放,置空,数据复原即可。

// 销毁栈 
void StackDestroy(Stack* ps) {
	assert(ps);
	free(ps->a);    
	ps->a = NULL;    //别忘了置空
	ps->top = -1;
    ps->capacity = 0;
}

二.队列(queue)

1.队列的定义

和栈一样,队列也是一种线性表,一种只允许在一端进行插入操作,而在另一端进行删除操作的特殊线性表

我们把允许插入的一端称为队尾(rear),允许删除的一端称为队头(front)。队列遵循后进先出FIFO(First In First Out)的原则

2.栈的相关操作

  • 入队列:队列的插入操作叫做入队,其入数据在队尾。
  • 出队列:队列的删除操作叫做出队,其出数据在队头。

它的结构如下图所示:

3.队列的实现 

前面提到的,和栈一样,队列也是一种特殊的线性表,那么,队列是否适合用数组来实现呢

答案是否定的但是循环队列是用数组实现的!!!

使用数组实现队列可能会有以下问题:

  1. 静态大小:数组需要事先声明大小,当队列超过数组大小时,无法继续入队,这限制了队列的大小。

  2. 内存浪费:如果数组大小过大,而实际队列元素数量较少,会造成内存浪费。例如,声明一个大小为100的数组,但实际队列元素数量只有10个,就浪费了90个位置的内存。

  3. 入队和出队效率低:使用数组实现队列时,入队操作(将元素放在队尾)需要移动已有元素来腾出位置,出队操作(从队头取出元素)需要将整个数组向前移动,这样的操作会导致时间复杂度为O(n)的开销

所以,队列采用链式结构存储

定义队列:

//结构体嵌套,Queue是队列本身
typedef int QDataType;
typedef struct QListNode
{
	struct QListNode* next;
	QDataType data;
}QNode;

// 队列的结构 
typedef struct Queue
{
	QNode* front;
	QNode* rear;
}Queue;

3.1 初始化队列

// 初始化队列 
void QueueInit(Queue* q) {
	assert(q);
	q->front = q->rear = NULL;
}

3.2 入队列,出队列

需要注意的是,在每次入队时都需要更新队尾,在每次出队时都需要更新队头

// 队尾入队列 
void QueuePush(Queue* q, QDataType data) {
	assert(q);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = data;
	newnode->next = NULL;
	if (q->rear == NULL)    //检查队列是否为空队列
		q->rear = q->front = newnode;
	else {
		q->rear->next = newnode;
		q->rear = newnode;    //更新队尾
	}
}
// 队头出队列 
void QueuePop(Queue* q) {
	assert(q);
	assert(q->front);
	QNode* del = q->front;
	q->front = q->front->next;    //更新队头
	free(del);
	del = NULL;    //释放,置空
	if (q->front == NULL)    //这时,如果队头为空,就说明队列里没有元素了,队尾也应为空。
		q->rear = NULL;        
}

3.3 获取队头,队尾元素及队列中元素个数

// 获取队列头部元素 
QDataType QueueFront(Queue* q) {
	assert(q);
	assert(q->front);
	return q->front->data;
}
// 获取队列队尾元素 
QDataType QueueBack(Queue* q) {
	assert(q);
	if (q->rear == NULL)
		return 0;
	else
		return q->rear->data;
}
// 获取队列中元素个数 
int QueueSize(Queue* q) {
	assert(q);
	int count = 0;    //计数
	QNode* cur = q->front;    //创建一个指向队头的指针
	while (cur) {
		cur = cur->next;
		count++;    //累加起来就是队列的元素个数
	}
	return count;
}

3.4 销毁队列

如果循环没结束,就代表队列里还有元素,就调用 QueuePop 函数。

// 销毁队列 
void QueueDestroy(Queue* q) {
	assert(q);
	while (q->rear)
		QueuePop(q);
}

三.总结

栈和队列它们都是特殊的线性表,只不过对插入和删除操作做了限制

  • 栈(stack)只允许在表尾进行插入和删除操作。
  • 队列(queue)只允许队尾(rear)进行插入操作,而在队头(front)进行删除操作。

对于二者来说,都可以使用链式存储结构来实现,顺序结构中,队列引入了循环队列(circular queue)。

而它们二者又可以相互实现。用队列实现栈用栈实现队列

  • 24
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值