【数据结构】栈和队列

一、栈的概念和结构

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

压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶;出栈:栈的删除操作叫做出栈,出数据也在栈顶;

在这里插入图片描述

注意:不要把栈区和栈混为一谈:栈区是内存划分的一块区域,属于操作系统学科;而栈是用于管理数据的一种结构,它在堆区上申请空间,属于数据结构学科。

二、顺序栈的实现

1. 结构的定义

栈可以用顺序表实现,也可以用链表实现,这里选用顺序表实现,原因如下:

1、栈的插入和删除操作都在栈顶,即在数据的尾部进行,而顺序表在尾部插入和删除数据的效率为O(1),完美的避开了顺序表的缺陷;

2、顺序表扩容和链表频繁 malloc 在整体上的效率是差不多的,只是顺序表会存在一定的空间浪费;

3、顺序表支持随机访问,且其缓存利用率更高;

综合考虑以上几种因素,我们采用顺序表实现栈;

typedef int ASDataType;
typedef struct Stack
{
	ASDataType* val;
	int top;
	int capacity;
}Stack;

这里的top指针和顺序表的size是等价的

2. 初始化栈

函数调用者需要在外面先定义一个Stack变量,然后把栈的地址传进来,在Init函数中初始化栈为空

void InitStack(Stack* ps)
{
	assert(ps);
	ps->val = NULL;
	ps->top = 0;
	ps->capacity = 0;
}

3. 入栈

由于栈只能在栈顶插入元素,所以我们只需要在 push 函数中进行检查容量并扩容,而不需要把 CheckCapacity 单独封装成一个函数。

void EnStack(Stack* ps, ASDataType x)
{
	assert(ps);
	if (ps->capacity == ps->top)//扩容
	{
		int NewCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		ASDataType* temp = (ASDataType*)realloc(ps->val, NewCapacity * sizeof(ASDataType));
		if (temp == NULL)
		{
			perror("realloc");
			exit(-1);
		}
		ps->val = temp;
		ps->capacity = NewCapacity;
	}
	ps->val[ps->top] = x;
	ps->top++;
}

4. 出栈

出栈之前我们需要检查栈是否为空

void DeStack(Stack* ps)
{
	assert(ps);
	if (ps->top == 0)
	{
		printf("栈为空,删除失败!\n");
		return;
	}
	ps->top--;
}

5. 获取栈顶元素

获取栈顶元素时我们也需要检查栈是否为空,避免造成对空指针的解引用。

ASDataType StackTop(Stack* ps)
{
	assert(ps);
	assert(ps->top > 0);
	return ps->val[ps->top - 1];
}

6. 获取栈的长度

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

7. 判空

bool IsEmptyStack(Stack* ps)
{
	assert(ps);
	return ps->top == 0;
}

8. 销毁栈

void DestroyStack(Stack* ps)
{
	assert(ps);
	free(ps->val);
	ps->val = NULL;
	ps->capacity = ps->top = 0;
}

需要注意的是,我们不需要定义栈的打印函数,因为栈不能遍历,如果我们想得到栈顶的前一个元素,我们就必须先把栈顶的元素给删除掉,让后面一个元素变成栈顶。

三、完整代码

1. Stack.h

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int ASDataType;
typedef struct Stack
{
	ASDataType* val;
	int top;
	int capacity;
}Stack;

//初始化栈
void InitStack(Stack* ps);

//入栈
void EnStack(Stack* ps, ASDataType x);

//出栈
void DeStack(Stack* ps);

//判空
bool IsEmptyStack(Stack* ps);

//取栈顶元素
ASDataType StackTop(Stack* ps);

//求栈的大小
int StackSize(Stack* ps);

//销毁栈
void DestroyStack(Stack* ps);

2. Stack.c

#include"Stack.h"

void InitStack(Stack* ps)
{
	assert(ps);
	ps->val = NULL;
	ps->top = 0;
	ps->capacity = 0;
}

void EnStack(Stack* ps, ASDataType x)
{
	assert(ps);
	if (ps->capacity == ps->top)//扩容
	{
		int NewCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		ASDataType* temp = (ASDataType*)realloc(ps->val, NewCapacity * sizeof(ASDataType));
		if (temp == NULL)
		{
			perror("realloc");
			exit(-1);
		}
		ps->val = temp;
		ps->capacity = NewCapacity;
	}
	ps->val[ps->top] = x;
	ps->top++;
}

void DeStack(Stack* ps)
{
	assert(ps);
	if (ps->top == 0)
	{
		printf("栈为空,删除失败!\n");
		return;
	}
	ps->top--;
}


bool IsEmptyStack(Stack* ps)
{
	assert(ps);
	return ps->top == 0;
}

ASDataType StackTop(Stack* ps)
{
	assert(ps);
	assert(ps->top > 0);
	return ps->val[ps->top - 1];
}

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

void DestroyStack(Stack* ps)
{
	assert(ps);
	free(ps->val);
	ps->val = NULL;
	ps->capacity = ps->top = 0;
}

3. test.c

#include"Stack.h"
int main()
{
	Stack S;
	InitStack(&S);
	EnStack(&S, 1);
	EnStack(&S, 2);
	EnStack(&S, 3);
	EnStack(&S, 4);
	EnStack(&S, 5);
	printf("栈的大小:%d\n", StackSize(&S));
	int n = 5;
	while (n--)
	{
		printf("%d\n", StackTop(&S));
		DeStack(&S);
	}
	printf("%d\n", IsEmptyStack(&S));
	DestroyStack(&S);
	return 0;
}

四、 队列的概念和结构

队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表;队列中的数据元素遵守先进先出 FIFO(First In First Out)的原则;

入队列:进行插入操作的一端称为队尾;出队列:进行删除操作的一端称为队头。

在这里插入图片描述

五、链式队列的实现

1. 队列结构的定义

和栈一样,队列既可以使用顺序表实现,也可以使用链表实现,这里我们使用单链表实现,原因如下:

1、队列需要删除头部的元素,单链表头删的效率为O(1);

2、使用链表可以按需申请空间,避免了空间的浪费;

但是我们发现使用单链表实现队列存在一个问题,那就是单链表尾插以及计算链表长度的效率都为O(N),不符合我们的预期,那么我们需要把单链表改造为循环链表吗?可以是可以,但是这样又把队列的结构搞复杂了;所以综合考虑,这里我们增加三个变量,一个用于记录队尾,一个用于记录队头,还有一个用于记录队列的长度。

typedef int LQDataType;

typedef struct QNode //队列结点
{
	LQDataType val;
	struct QNode* next;
}QNode;

typedef struct Queue //队列
{
	QNode* head; //队头指针
	QNode* tail; //队尾指针
	int size; //记录队列长度
}Queue;

2. 初始化队列

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

3. 入队

由于我们使用了一个结构体来记录队列的头指针和尾尾指针,那么这里我们改变队列的头指针和尾指针时只需要改变结构体即可,所以只需要传递一级指针;

另外,队列也只能从队尾入数据,所以我们也没有必要单独封装一个 BuyNode 函数。

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

4. 出队

这里除了正常地对队列进行判空之外,还有一个需要特别注意的地方:当队列只有一个元素时,我们再次头删虽然会让head指向NULL,但是tail仍然指向头删之前的那个节点,没有置空,会形成野指针,所以我们这里需要单独处理这种情况

void DeQueue(Queue* pq)
{
	assert(pq);
	assert(pq->head);
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = NULL;
		pq->tail = NULL;
	}
	else
	{
		QNode* temp = pq->head->next;
		free(pq->head);
		pq->head = temp;
	}
	pq->size--;
}

5. 获取队头元素

LQDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->head);
	return pq->head->val;
}

6. 获取队尾元素

LQDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->tail);
	return pq->tail->val;
}

7. 获取队列长度

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

8. 判空

bool IsEmptyQueue(Queue* pq)
{
	assert(pq);
	return pq->size == 0;
}

9. 销毁队列

void DestroyQueue(Queue* pq)
{
	assert(pq);
	while (pq->head)
	{
		DeQueue(pq);
	}
}

注意:和栈一样,队列也不需要定义 print 函数,因为队列也不能遍历,要想取出队头后面的元素,我们必须先 pop 掉队头的元素,让该元素成为队头。

六、完整代码

1. Queue.h

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int LQDataType;

typedef struct QNode //队列结点
{
	LQDataType val;
	struct QNode* next;
}QNode;

typedef struct Queue //队列
{
	QNode* head;
	QNode* tail;
	int size;
}Queue;

//初始化队列
void InitQueue(Queue* pq);

//入队
void EnQueue(Queue* pq, LQDataType x);

//出队
void DeQueue(Queue* pq);

//判空
bool IsEmptyQueue(Queue* pq);

//队列大小
int QueueSize(Queue* pq);

//返回队头元素
LQDataType QueueFront(Queue* pq);

//返回队尾元素
LQDataType QueueBack(Queue* pq);

//销毁队列
void DestroyQueue(Queue* pq);

2. Queue.c

#include"Queue.h"

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

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

void DeQueue(Queue* pq)
{
	assert(pq);
	assert(pq->head);
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = NULL;
		pq->tail = NULL;
	}
	else
	{
		QNode* temp = pq->head->next;
		free(pq->head);
		pq->head = temp;
	}
	pq->size--;
}

bool IsEmptyQueue(Queue* pq)
{
	assert(pq);
	return pq->size == 0;
}

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

LQDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(pq->head);
	return pq->head->val;
}

LQDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(pq->tail);
	return pq->tail->val;
}

void DestroyQueue(Queue* pq)
{
	assert(pq);
	while (pq->head)
	{
		DeQueue(pq);
	}
}

3. test.c

#include"Queue.h"
int main()
{
	Queue Q;
	InitQueue(&Q);
	EnQueue(&Q, 1);
	printf("%d\n", QueueBack(&Q));
	EnQueue(&Q, 2);
	printf("%d\n", QueueBack(&Q));
	EnQueue(&Q, 3);
	printf("%d\n", QueueBack(&Q));
	EnQueue(&Q, 4);
	printf("%d\n", QueueBack(&Q));
	EnQueue(&Q, 5);
	printf("%d\n", QueueBack(&Q));
	printf("队列大小为:%d\n", QueueSize(&Q));
	int n = 5;
	while (n--)
	{
		printf("%d\n", QueueFront(&Q));
		DeQueue(&Q);
	}
	printf("%d\n", IsEmptyQueue(&Q));
	DestroyQueue(&Q);
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值