C语言数据结构—栈和队列实现

目录

前言

一、栈

1.栈的概念及结构简析

2.栈的实现方式

3.栈的基本功能实现

(1)栈的成员

(2)栈的初始化

(3)入栈出栈操作

(4)获取栈顶元素

(5)获取栈中有效元素个数

(6)检测栈是否为空

(7)销毁栈

二、队列

1.队列的概念及结构简析

2.队列的实现方式

3.队列的基本功能实现

(1)队列的结构体

(2)队列的初始化

(3)入队列出队列操作

(4)获取队头元素

(5)获取队尾元素

(6)获取队列中有效元素个数

(7)检测队列是否为空

(8)销毁队列

三、栈和队列的完整代码参考

1.栈

2.队列

前言

    栈和队列是C语言中的两种容器,学习C语言的栈和队列有助于我们更好地理解栈和队列的底层实现。

一、栈

1.栈的概念及结构简析

  栈是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。
进行数据插入和删除操作的一端,称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出原则。
栈有两大操作方式:
    一是压栈,也叫入栈/进栈,是将元素插入栈顶的操作
    二是出栈,是将元素从栈中删除的操作
    图示:
请添加图片描述

请添加图片描述

2.栈的实现方式

栈有两大实现方式,分别是顺序栈链式栈
顺序栈:用数组来作为栈的结构存储元素
链式栈:用链表来作为栈的结构存储元素
    单链出栈入栈要先找到尾节点,效率低,而双向链表和带头作栈顶的链表都需要额外的空间存储地址。
    顺序栈只需要记录栈顶位置,同时可通过栈顶位置知道有效元素个数,进行出入栈操作比较便捷,因为对于栈来说出入栈类似尾插尾删,而对于数组来说尾插尾删效率很高。
    综上所述,实现栈使用数组来实现更优一些,因为在数组尾插的代价较小,且不需要额外的空间存储地址。

本文采用数组来实现栈。

3.栈的基本功能实现

(1)栈的成员

栈顶:记录栈顶元素的位置,方便进行尾插尾删,这里的top表示的是栈顶元素的后一位,这样top就可以表示有效元素的数量。
容量:存储有效元素的最大数量。

//重定义,方便后续更改存储数据的类型
typedef int STDataType;
typedef struct Stack {
	//用数组来存储
	STDataType* a;
	//栈顶元素的后一位
	int top;
	//容量
	int capacity;
}ST;

​

(2)栈的初始化

​void STInit(ST* ps)
{
	//提前断言,阻止人为(我们)传空指针
	assert(ps);
	//数组初始化是置空,top、容量初始化置零
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;
}

​

(3)入栈出栈操作

  入栈:
入栈注意事项:
1、入栈前先判断是否要扩容,扩容看有效数据个数是否达到了栈的容量,达到了就扩容,没有就不用扩容直接进行入栈操作;
2、传入的数据入栈,要将其存入栈中
3、入栈后有效元素增加,有元素的数量就增加,这里有个小技巧,有效元素数量刚好等于top的值,所以我们改变top就可以了,如果有扩容,容量的值也要对应有更新。

请添加图片描述

void STPush(ST* ps, STDataType x)
{
	//提前断言,阻止人为(我们)传空指针
	assert(ps);
	//入栈前,先判断是否需要扩容
	if(ps->top == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		//扩容
		STDataType* tmpa = (STDataType*)realloc(ps->a, sizeof(STDataType)*newcapacity);
		//扩容失败,抛出警告(几乎不会出现扩容失败现象)
		if (!tmpa)
		{
			perror("realloc fail");
			exit(-1);
		}
		//将原来小数组换成了一个更大的数组
		ps->a = tmpa;
		//容量相应地更新
		ps->capacity = newcapacity;
	}
	//传入数据读入到栈里
	ps->a[ps->top] = x;
	//top值更新,也是有效元素数量更新
	ps->top++;
}

​

   出栈:
出栈注意事项:
1、注意到栈顶其实是一个数组的尾,将top减一,原栈顶的数就失联了,这个数也就被删掉了。
2、出栈前要判断栈是否为空,否则栈空继续减一,top就为-1了,这样造成了越界访问。

void STPop(ST* ps)
{
	//提前断言,阻止人为(我们)传空指针
	assert(ps);
	//提前断言,阻止空栈时再减一
	assert(ps->top > 0);
	--ps->top;
}

​

(4)获取栈顶元素

注意事项:
1、top是有效元素个数将其减一就得到栈顶的下标,用下标访问法即可获取栈顶元素。
2、获取前要判断栈是否为空

​
STDataType STTop(ST* ps)
{
	assert(ps);
	//提前断言,组织栈空时访问
	assert(ps->top > 0);
	//返回栈顶元素
	return ps->a[ps->top - 1];
}

​

(5)获取栈中有效元素个数

前面一直强调top就是有效元素个数,所以直接返回top的值即可。

int STSize(ST* ps)
{
	assert(ps);
	//返回top的值
	return ps->top;
}

(6)检测栈是否为空

由top来判断,top为零就是空栈返回true,否则返回false。

bool STEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

(7)销毁栈

数组空间是连续的,释放数组空间即可,然后将top和capacity置零。

void STDeastroy(ST* ps)
{
	assert(ps);
	//释放空间
	free(ps->a);
	//数组置空
	ps->a = NULL;
	//top和容量capacity置零
	ps->top = ps->capacity = 0;
}

二、队列

1.队列的概念及结构简析

队列:只允许在一端进行插入数据操作在另一端进行删除数据操作的特殊线性表。
队列遵守先进先出FIFO(First In First Out) 的原则:
  进行插入操作一端称为队尾
  进行删除操作一端称为队头
结构图示:
请添加图片描述

2.队列的实现方式

队列可以用数组或链表实现
使用链表更优一些,使用数组的话出队列在数组头上出数据,之后每个数据都要往前移一位,效率会比较低,而链表出数据不需要大量移动数据。

3.队列的基本功能实现

(1)队列的结构体

队列的结构体成员一个是头指针,一个是尾指针(记录尾结点的),而链表的节点包括下一结点地址和自己的数据,所以可以再声明一个结构体来表示队列。
请添加图片描述

//重定义,方便后续更改存储数据的类型
typedef int QDataType;
//结点结构体
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;
//队列结构体
typedef struct Queue
{
	QNode* head;
	QNode* tail;
	int size;
}Que;

(2)队列的初始化

void QueueInit(Que* pq)
{
	//提前断言,阻止人为(我们)传空指针
	assert(pq);
	//头和尾都置空
	pq->head = NULL;
	pq->tail = NULL;
	pq->size = 0;
}

(3)入队列出队列操作

  入队列:
注意事项:
1、入队列要先创建一个新结点,将传入数据存到这个新节点里,该新结点指针域置空;
2、注意新结点的链接和尾结点的更新;
3、单独处理空队列情况,否则就会出现解引用空指针的情况存在。

void QueuePush(Que* pq, QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	
	if (!newnode)
	{
		perror("malloc fail");
		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;
}

  出队列:

注意事项:

1、先保存好原头结点的下一个结点,将下一节点记为新的头结点,然后释放原头结点,再将这个新的头结点更新为当前头结点;
2、执行出队列时,先检查队列是否为空
3、多次置空后忘记置空尾结点,只置空了头结点,会造成野指针问题。

void QueuePop(Que* pq)
{
	assert(pq);
	//提前断言,阻止队列为空时传入
	assert(pq->head);
	//只有一个结点,删除该节点后将头和尾置空
	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		//提前保留下一个位置
		QNode* next = pq->head->next;
		//释放当前结点
		free(pq->head);
		//继续下一个
		pq->head = next;
	}
	pq->size--;
}

(4)获取队头元素

直接返回头结点数据
注意事项:
队列为空时不能获取,否则会造成解引用空指针。

QDataType QueueFront(Que* pq)
{
	assert(pq);
	//断言,队列为空时阻止下一步操作
	assert(pq->head);
	//返回头结点数据
	return pq->head->data;
}

(5)获取队尾元素

直接返回尾结点数据
注意事项:
队列为空时不能获取,否则会造成解引用空指针。

QDataType QueueBack(Que* pq)
{
	assert(pq);
	//断言,队列为空时阻止下一步操作
	assert(pq->head);
	//返回尾结点数据
	return pq->tail->data;
}

(6)获取队列中有效元素个数

有两种实现方法:
1、在队列结构体中多声明一个size成员变量来记录,初始化为零,每次出入队列都更新;
2、尾结点地址减去头结点地址。

int QueueSize(Que* pq)
{
	//这里采用第二种方法
	assert(pq);
	return pq->tail - pq->head;
}

(7)检测队列是否为空

头结点为空,队列即为空

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

(8)销毁队列

注意事项:
1、销毁队列和链表的销毁类似,都是从头结点开始往后一个一个删除;
请添加图片描述

2、释放当前结点时要注意记录下一个结点,否则会找不到下一个结点;
3、头和尾最后都要置空,否则会出现野指针问题

void QueueDestroy(Que* pq)
{
	assert(pq);
	//先记录当前结点
	QNode* cur = pq->head;
	while (cur)
	{
		//先保存下一个结点
		QNode* next = cur->next;
		//再释放
		free(cur);
		//更新当前结点
		cur = next;
	}
	//头和尾都要置空
	pq->head = NULL;
	pq->tail = NULL;
	
	pq->size = 0;
}

三、栈和队列的完整代码参考

1.栈

Stack.h(声明)

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int STDataType;
typedef struct Stack {
	STDataType* a;
	int top;
	int capacity;
}ST;

void STInit(ST* ps);
void STDeastroy(ST* ps);
void STPush(ST* ps, STDataType x);
void STPop(ST* ps);
STDataType STTop(ST* ps);

int STSize(ST* ps);
bool STEmpty(ST* ps);
void Print(ST* ps);

Stack.c(功能实现)

#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"

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

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

void STPush(ST* ps, STDataType x)
{
	assert(ps);
	if(ps->top == ps->capacity)
	{
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STDataType* tmpa = (STDataType*)realloc(ps->a, sizeof(STDataType)*newcapacity);
		if (!tmpa)
		{
			perror("realloc fail");
			exit(-1);
		}
		ps->a = tmpa;
		ps->capacity = newcapacity;
	}
	ps->a[ps->top] = x;
	ps->top++;
}

void STPop(ST* ps)
{
	assert(ps);
	assert(ps->top > 0);
	--ps->top;
}

STDataType STTop(ST* ps)
{
	assert(ps);
	assert(ps->top > 0);

	return ps->a[ps->top - 1];
}

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

bool STEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

void Print(ST* ps)
{
	assert(ps);
	while(ps->top>0)
	{
		printf("%d ", ps->a[ps->top-1]);
		ps->top--;
	}
}

可以自己编写测试代码,这里给出一个参考测试代码。

Stacktest.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"

void test()
{
	ST s;
	STInit(&s);
	STPush(&s, 1);
	STPush(&s, 2);
	STPush(&s, 3);
	STPush(&s, 4);
	STPush(&s, 5);
	STPush(&s, 6);
	Print(&s);
}
int main()
{
	test();

	return 0;
}

2.队列

Queue.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int QDataType;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;

typedef struct Queue
{
	QNode* head;
	QNode* tail;
	int size;
}Que;

void QueueInit(Que* pq);
void QueueDestroy(Que* pq);
void QueuePush(Que* pq, QDataType x);
void QueuePop(Que* pq);
QDataType QueueFront(Que* pq);
QDataType QueueBack(Que* pq);
bool QueueEmpty(Que* pq);
int QueueSize(Que* pq);

void Print(Que* pq);

Queue.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"

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

void QueueDestroy(Que* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}

	pq->head = NULL;
	pq->tail = NULL;
	pq->size = 0;
}

void QueuePush(Que* pq, QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	
	if (!newnode)
	{
		perror("malloc fail");
		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;
}

void QueuePop(Que* pq)
{
	assert(pq);
	assert(pq->head);

	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
	pq->size--;
}

QDataType QueueFront(Que* pq)
{
	assert(pq);
	assert(pq->head);
	return pq->head->data;
}

QDataType QueueBack(Que* pq)
{
	assert(pq);
	assert(pq->head);
	return pq->tail->data;
}

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

int QueueSize(Que* pq)
{
	assert(pq);
	return pq->tail - pq->head;
}

void Print(Que* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
}

可以自己编写测试代码,这里给出一个参考测试代码。

Queuetest.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"
int main()
{
	Que pq;
	QueueInit(&pq);
	QueuePush(&pq, 1);
	QueuePush(&pq, 2);

	QueuePush(&pq, 3);
	QueuePush(&pq, 4);
	QueuePush(&pq, 5);
	QueuePush(&pq, 6);
	QueuePush(&pq, 7);
	Print(&pq);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值