【数据结构(C语言版)】栈和队列

本文详细介绍了栈的顺序存储结构,包括静态和动态存储的实现,以及动态顺序栈的初始化、判空、入栈、出栈、读栈顶元素、栈中元素个数和销毁等接口的实现。同样,也阐述了链式队列的概念和接口实现,如初始化、判空、入队、出队、读队头和队尾元素、队列中元素个数以及销毁。最后提到了循环队列的概念,讨论了如何利用循环队列优化空间利用率。
摘要由CSDN通过智能技术生成

目录

1. 栈

1.1 顺序栈的存储

1.1.1 顺序栈的静态存储

1.1.2 顺序栈的动态存储

1.2 动态顺序栈的接口实现

1.2.1 初始化

1.2.2 判空

1.2.3 入栈

1.2.4 出栈

1.2.5 读栈顶元素

1.2.6 栈中元素个数

1.2.7 销毁

2. 队列

2.1 链式队列

2.2 链式队列的接口实现

2.2.1 初始化

2.2.2 判空

2.2.3 入队

2.2.4 出队

2.2.5 读队头元素

2.2.6 读队尾元素

2.2.7 队列中元素个数

2.2.8 销毁

2.3 循环队列


1. 栈

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

栈的数学性质:n个不同元素进栈,出栈元素不同排列的个数为\frac{1}{n+1}C_{2n}^{n}(卡特兰数)。

类似于线性表,栈也有顺序存储和链接存储两种存储结构。常用顺序栈。

顺序栈:

链栈:

表头为栈顶

1.1 顺序栈的存储

1.1.1 顺序栈的静态存储

#define N 10
typedef int STDataType;
typedef struct Stack
{
	STDataType a[N];
	int top;
    // top初始化为0时,表示栈顶下一个,top的数值等于栈中元素个数
    // top初始化为-1时,表示栈顶,top的数值等于栈中元素个数-1
}ST;

1.1.2 顺序栈的动态存储

typedef int STDataType;
typedef struct Stack
{
	STDataType* a;
	int capacity;
	int top;
    // top初始化为0时,表示栈顶下一个,top的数值等于栈中元素个数
    // top初始化为-1时,表示栈顶,top的数值等于栈中元素个数-1
}ST;

1.2 动态顺序栈的接口实现

1.2.1 初始化

void StackInit(ST* ps)
{
	assert(ps);

	// 方法一:初始化时不开辟空间
	ps->a = NULL;
	ps->top = 0; // 初始化为0时,表示栈顶下一个;初始化为-1时,表示栈顶
	ps->capacity = 0;

	/*
    方法二:初始化时开辟4个空间
	ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
	if (ps->a == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
	ps->top = 0;
	ps->capacity = 4;
    */
}

top初始化为0时,表示栈顶下一个:

top初始化为-1时,表示栈顶:

1.2.2 判空

bool StackEmpty(ST* ps)
{
	assert(ps);

	/*
    if (ps->top == 0)
	{
		return true;
	}
	else
	{
		return false;
	}
    */

	return ps->top == 0;
}

1.2.3 入栈

void StackPush(ST* ps, STDataType x)
{
	assert(ps);

	// 检查空间
	if (ps->top == ps->capacity) // 如果栈中元素个数==容量,需要扩容
	{
		// 如果容量为0,则新容量设为4;如果容量不为0,则扩容2倍
		int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		// 扩容,开辟新空间
		STDataType* tmp = (STDataType*)realloc(ps->a, newCapacity * sizeof(STDataType));
		// 如果开辟空间失败,打印错误信息并退出程序
		if (tmp == NULL)
		{
			perror("realloc failed");
			exit(-1);
		}
		// 如果开辟空间成功,将临时指针变量的值赋给原指针变量
		ps->a = tmp;
		// 改变容量的值为新容量
		ps->capacity = newCapacity;
	}

	ps->a[ps->top] = x;
	ps->top++;
}

1.2.4 出栈

void StackPop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	ps->top--;
}

1.2.5 读栈顶元素

STDataType StackTop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	return ps->a[ps->top - 1];
}

1.2.6 栈中元素个数

int StackSize(ST* ps)
{
	assert(ps);
	return ps->top;
}

1.2.7 销毁

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

2. 队列

队列(queue)是一种特殊的线性表,只允许在一端进行插入数据操作,在另一端进行删除数据操作。进行插入操作的一端称为队尾(rear),进行删除操作的一端称为队头(front)。队列中的数据元素遵守先进先出(First In First Out, FIFO)的原则。

类似于线性表,队列也有顺序存储和链接存储两种存储结构。常用链式队列。

1. 顺序队列

自行规定队尾的位置,最后一个元素的位置或最后一个元素下一个的位置,一般规定队尾是最后一个元素下一个的位置。

2. 链式队列

2.1 链式队列

typedef int QDataType;

typedef struct QueueNode // 链式队列的结点
{
	QDataType data;
	struct QueueNode* next;
}QNode;

typedef struct Queue // 链式队列的结构
{
	QNode* head;
	QNode* tail;
	int size;
}Queue;

2.2 链式队列的接口实现

2.2.1 初始化

void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = NULL;
	pq->tail = NULL;
	pq->size = 0;
}

2.2.2 判空

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

2.2.3 入队

void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	if (pq->tail == NULL)
	{
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
	pq->size++;
}

2.2.4 出队

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* del = pq->head;
		pq->head = pq->head->next;
		free(del);
	}
	pq->size--;
}

2.2.5 读队头元素

QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->head->data;
}

2.2.6 读队尾元素

QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->tail->data;
}

2.2.7 队列中元素个数

int QueueSize(Queue* pq)
{
	assert(pq);
	return pq->size;
}

2.2.8 销毁

void QueueDestroy(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* del = cur;
		cur = cur->next;
		free(del);
		// del = NULL;
        // 出作用域后销毁,置不置空都可以
	}
	pq->head = pq->tail = NULL;
	pq->size = 0;
}

2.3 循环队列

将顺序队列臆造为一个环状的空间,即把存储队列元素的表从逻辑上视为一个环,称为循环队列。

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

  • 初始时:Q.front=Q.rear=0
  • 队首指针进1:Q.front=(Q.front+1)%Maxsize
  • 队尾指针进1:Q.rear=(Q.rear+1)%MaxSize
  • 队列长度:(Q.rear+MaxSize-Q.front)%MaxSize

为了能使用Q.front==Q.rear来区分是队空还是队满的情况,有三种处理方式:

1. 牺牲一个单元来区分队空和队满,入队时少用一个队列单元,这是一种较为普遍的做法。

队满:(Q.rear+1)%MaxSize==Q.front

队空:Q.front==Q.rear

2. 类型中增设表示元素个数的数据成员。

队满:Q.size==MaxSize

队空:Q.size==0

此时都有Q.front==Q.rear

3. 类型中增设tag数据成员,以区分是队满还是队空。

tag等于0时,若因删除导致Q.front==Q.rear,则为队空;tag等于1时,若因插入导致Q.front==Q.rear,则为队满。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值