栈和队列(Stack & Queue)

一.栈

     1.栈的概念

      (1).栈的定义(Stack):

只允许在一端进行插入或删除的线性表。但限定这种线性表只能在某一端进行插入和删除操作。

栈顶(Top):线性表允许进行插入删除的那一端。
栈底(Bottom):固定的,不允许进行插入和删除的另一端。
空栈:不含任何元素的空表。

栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构。

      (2).栈的常见基本操作
  • InitStack:初始化一个空栈
  • StackEmpty:判断一个栈是否为空
  • StackPush:进栈(栈的插入操作),栈未满就插入,满了进行扩容操作后再插入。
  • StackPop:出栈(栈的删除操作),若栈非空,则进行删除操作,为空则报警告。
  • StackDestory:栈销毁,释放栈所占用的动态空间。
  • StackTop:读栈顶元素,若栈S非空,则返回栈顶元素,若栈为空则报警告。
  • Stacksize:返回栈中元素的个数。

     这些名字同时也会作为功能函数的命名。

     (3).栈的顺序存储:

采用顺序存储的栈称为顺序栈,它利用一组地址连续的存储单元存放自栈底到栈顶的数据元素,同时设一个数(top)指示当前栈顶元素的位置和数据格式,和另一个数(capacity)来显示当前栈能够存储的数据个数,当top和capacity相等时,说明空间已经满了,需要进行扩容操作。而想要找到存储的数据就需要一个数组来存储这些数据的地址,那就再创建一个指针(data),他的指针类型是我们需要存储的数据类型的指针。

需要能够存储这些数据,首先需要建立一个结构体。

typedef int STDatatype;//为了方标修改存储数据的类型,在这里将类型重定义一下
typedef struct stack {
	STDatatype *data;
	int top;
	int capacity;
}Stack;

2.栈的实现

(1).初始化
 
void InitStack(Stack* phead) {
    assert(phead);//判断指针是否为空
	phead->data = NULL;
	phead->top = 0;
	phead->capacity = 0;
}

空栈的指针初始化为空,栈顶元素的位置没有插入,栈的存储空间没有开拓,那么就把这两个数初始化为0。

(2).判断栈是否为空
bool StackEmpty(Stack* phead) {
	assert(phead);//检查空指针
	if (phead->top == 0)
		return true;//为空返回真
	else
		return false;//不为空返回假
}
(3).将数据入栈
void StackPush(Stack* phead, STDatatype x) {
	assert(phead);//判断指针是否为空
	if (phead->top == phead->capacity) {//判断存储空间有没有满,满了进行扩容操作
		int newcapacity = phead->capacity > 4 ? phead->capacity*2 : 4;
//选出4和capacity和4中的较大值来进行扩容操作
		STDatatype* newnode = (STDatatype*)realloc(phead->data, newcapacity * sizeof(STDatatype));//开辟动态内存空间进行扩容
		if (newnode == NULL) {//判断动态内存是否开辟失败
			perror("realloc");
			return;
		}
		phead->data = newnode;//扩容有可能导致原地址进行变化,重新赋值
		phead->capacity = newcapacity;//将capcacity改为新的内存大小
	}
	phead->data[phead->top] = x;//数据赋值
	phead->top++;//改变栈顶
}
(4).将数据出栈
void StackPop(Stack* phead) {
	assert(phead);//判断指针是否为空
	assert(!StackEmpty(phead));//判断存储的数据是否为空
	phead->top--;//删除栈顶指针
}
(5).读取栈顶元素
STDatatype StackTop(Stack* phead) {
	assert(phead);//判断指针是否为空
	assert(!StackEmpty(phead));//判断存储的数据是否为空
	return phead->data[phead->top - 1];//取出栈顶的指针返回
}
(6).销毁栈
void StackDestory(Stack* phead) {
	assert(phead);//判断指针是否为空
	free(phead->data);//释放动态内存
	phead->data = NULL;//改为空指针
	phead->top = 0;//改为0
	phead->capacity = 0;//改为0
}

销毁栈实际上是将动态的内存返回给操作系统,让程序员不再有这块内存的访问权限,再去访问就是越界了。

(7)返回栈当中数据的个数
int Stacksize(Stack* phead) {
	assert(phead);//判断指针是否为空
	return phead->top;//返回数据个数
}

测试用例

栈的功能是先进后出(后进先出),也就是后入栈的数据是先出栈的,这里输入了五个数,如果是正常出栈的话应该是跟输入的时候反过来,也就是 5 4 3 2 1,但是在中间提前出栈的了一个数据3,那么应该是3先出栈,其他数据按后进先出出栈,也就是 3 5 4 2 1。

可以看到结果和预测的一样。

全部代码

test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"stack.h"
void test1() {
	Stack s;
	InitStack(&s);
	StackPush(&s, 1);
	StackPush(&s, 2);
	StackPush(&s, 3);
	printf("%d ", StackTop(&s));
	StackPop(&s);
	StackPush(&s, 4);
	StackPush(&s, 5);
	while (!StackEmpty(&s)) {
		
		printf("%d ", StackTop(&s));
		StackPop(&s);
	}
	StackDestory(&s);
}
int main() {
	test1();
}

test.c是总体的框架,在这里完成测试用例。

stack.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"stack.h"
void InitStack(Stack* phead) {
	phead->data = NULL;
	phead->top = 0;
	phead->capacity = 0;
}
bool StackEmpty(Stack* phead) {
	assert(phead);
	if (phead->top == 0)
		return true;
	else
		return false;
}
void StackPush(Stack* phead, STDatatype x) {
	assert(phead);
	if (phead->top == phead->capacity) {
		int newcapacity = phead->capacity > 4 ? phead->capacity*2 : 4;
		STDatatype* newnode = (STDatatype*)realloc(phead->data, newcapacity * sizeof(STDatatype));
		if (newnode == NULL) {
			perror("realloc");
			return;
		}
		phead->data = newnode;
		phead->capacity = newcapacity;
	}
	phead->data[phead->top] = x;
	phead->top++;
}
void StackPop(Stack* phead) {
	assert(phead);
	assert(!StackEmpty(phead));
	phead->top--;
}
void StackDestory(Stack* phead) {
	assert(phead);
	free(phead->data);
	phead->data = NULL;
	phead->top = 0;
	phead->capacity = 0;
}
STDatatype StackTop(Stack* phead) {
	assert(phead);
	assert(!StackEmpty(phead));
	return phead->data[phead->top - 1];
}
int Stacksize(Stack* phead) {
	assert(phead);
	return phead->top;
}

stack.c是实现工程所需的函数的部分。

stack.h
#pragma once
#include<stdio.h>
#include<stdbool.h>
#include<assert.h>
#include<stdlib.h>
typedef int STDatatype;
typedef struct stack {
	STDatatype *data;
	int top;
	int capacity;
}Stack;
void InitStack(Stack* phead);
bool StackEmpty(Stack* phead);
void StackPush(Stack* phead, STDatatype x);
void StackPop(Stack* phead);
void StackDestory(Stack* phead);
STDatatype StackTop(Stack* phead);
int Stacksize(Stack* phead);

stack.h是声明函数以及宏定义和创建结构体的部分。

二.队列

1.队列的基本概念

(1).队列的定义

        队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。

队头(Front):允许删除的一端,又称队首。
队尾(Rear):允许插入的一端。
空队列:不包含任何元素的空表。

(2).队列的常见基本操作
  • InitQueue:初始化队列
  • DestroyQueue:销毁队列
  • QueuePush:申请一块动态内存空间插入到队列尾节点的后面
  • QueuePop:删除队列头节点的数据,释放动态内存空间
  • QueueFront:返回队列头部的数据
  • QueueBack:返回队列尾部的数据
  • QueueEmpty:判断队列是否为空
  • QueueSize:返回数据的个数
 (3).队列的存储结构

队列的链式存储结构表示为链队列,它的每一个节点都需要存储数据,并且有指向下一个节点的指针。它实际上是一个同时带有队头指针和队尾指针的单链表,只不过它只能尾进头出而已。为了实现这个功能,需要建立两个结构体,其中一个结构体用来完成节点的构建,另外一个节点用来存储队头指针和队尾指针。在空队列时,队头指针(phead)和队尾指针(ptail)都指向空。.

typedef int QueDataType;//定义数据类型,方便修改
typedef struct Queue {
	struct Queue* next;
	QueDataType data;
}QueNode;//链表的节点结构体
typedef struct Que {
	QueNode* phead;
	QueNode* ptail;
	int size;
}Queue;//用来存储队头指针和队尾指针的结构体

2.队列的实现

(1).初始化
void InitQueue(Queue* pq) {
	assert(pq);//判断指针是否为空
	pq->phead = NULL;
	pq->ptail = NULL;//队列为空时队头指针和队尾指针都指向空
	pq->size = 0;//队列为空时size为0
}
(2).销毁队列(释放动态内存)
void DestroyQueue(Queue* pq) {
	assert(pq);//判断指针是否为空
	QueNode* cur = pq->phead;//创建一个临时指针
	while (cur != NULL)//遍历队列,当cur为空时,遍历结束
	{
		Queue* temp = cur;//拷贝节点,防止释放后无法遍历
		cur = cur->next; //将cur的地址改为下一个节点的地址
		free(temp);//释放当前节点
	}
	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;//释放后就是一个空队列的状态,和初始化一样
}
(3).队列插入数据
void QueuePush(Queue* pq, QueDataType x) {
	assert(pq);//判断指针是否为空
	QueNode* newnode = (QueNode *)malloc(sizeof(QueNode));//开辟动态内存
	if (newnode == NULL) {//判断动态内存是否开辟失败
		perror("malloc");
		return;
	}
	newnode->next = NULL;//队列的插入只能在尾节点之后,所以将新开辟的节点的next指向NULL
	newnode->data = x;//将数据放入节点中
	if (pq->phead == NULL)//队列和单链表一样,需要分两种情况来执行,空队列和非空队列
	{
		pq->phead = newnode;
		pq->ptail = newnode;
	}//将队头指针和队尾指针的地址改为新开辟的动态内存的地址
	else
	{
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}//和单链表的尾插一样,在队列尾部插入一个数据,多了一个将队尾指针改为newnode
	pq->size++;//size是保存数据个数的,这里增加了一个数据
}
(4).队列数据删除
void QueuePop(Queue* pq) {
	assert(pq);//判断指针是否为空
	assert(!QueueEmpty(pq));//判断队列是否为空
	if (pq->phead->next == NULL) {//队列的删除也要分两种情况,只有一个数据和有多个数据
		free(pq->phead);//释放最后的一个数据
		pq->phead = NULL;
		pq->ptail = NULL;//队头指针和队尾指针都指向空
	}
	else {//有多个数据的情况
	QueNode* next = pq->phead->next;//创建临时地址,防止找不到下一个节点
	free(pq->phead);//释放头部指针
	pq->phead = next;//头部指针指向的地址改为原本的第二个节点的地址
	}//队列只能在头部进行删除,所以这就是一个链表的头删
	
	pq->size--;//减少了一个数据
}
(5).返回队头数据
QueDataType QueueFront(Queue* pq) {
	assert(pq);//判断指针是否为空
	assert(!QueueEmpty(pq));//判断队列是否为空
	return pq->phead->data;//返回队头数据
}
(6).返回队尾数据
QueDataType QueueBack(Queue* pq) {
	assert(pq);//判断指针是否为空
	assert(!QueueEmpty(pq));//判断队列是否为空
	return pq->ptail->data;//返回队尾数据
}
(7).判断队列是否为空
bool QueueEmpty(Queue* pq) {
	assert(pq);//判断指针是否为空
	if (pq->phead == NULL)
		return true;
	else
		return false;//判断列表是否为空,为空返回真,不为空返回假
}
(8).返回队列的数据个数
int QueueSize(Queue* pq) {
	assert(pq);//判断指针是否为空
	return pq->size;//返回数据个数
}

测试用例

void test1() {
	Queue q;
	InitQueue(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	QueuePush(&q, 5);
	while (!QueueEmpty(&q)) {
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}
	DestroyQueue(&q);
}

队列遵循的是先进先出的原则,那么这里的顺序应该是 5 4 3 2 1,执行一下

和推断的一样。

那么测试一下提前释放一个数据输出会不会改变

void test1() {
	Queue q;
	InitQueue(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	printf("%d ", QueueFront(&q));
	QueuePop(&q);
	QueuePush(&q, 4);
	QueuePush(&q, 5);
	while (!QueueEmpty(&q)) {
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}
	DestroyQueue(&q);
}

输出顺序还是一样的。

继续测试一下多释放几个数据assert会不会报错

void test1() {
	Queue q;
	InitQueue(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	printf("%d ", QueueFront(&q));
	QueuePop(&q);
	QueuePush(&q, 4);
	QueuePush(&q, 5);
	while (!QueueEmpty(&q)) {
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}
	QueuePop(&q);
	DestroyQueue(&q);
}

报错了,同时很明确的告诉了我这个错误在什么地方。在Que.c这个路径下的第94行,去看一下

可以看到这个assert确实起了作用。

全部代码

test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Que.h"
void test1() {
	Queue q;
	InitQueue(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	printf("%d ", QueueFront(&q));
	QueuePop(&q);
	QueuePush(&q, 4);
	QueuePush(&q, 5);
	while (!QueueEmpty(&q)) {
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}
	QueuePop(&q);
	DestroyQueue(&q);
}
int main() {
	test1();
	return 0;
}
Que.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Que.h"
void InitQueue(Queue* pq) {
	assert(pq);
	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;
}
void DestroyQueue(Queue* pq) {
	assert(pq);
	QueNode* cur = pq->phead;
	while (cur != NULL)
	{
		Queue* temp = cur;
		cur = cur->next; 
		free(temp);
	}
	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;
}
void QueuePush(Queue* pq, QueDataType x) {
	assert(pq);
	QueNode* newnode = (QueNode *)malloc(sizeof(QueNode));
	if (newnode == NULL) {
		perror("malloc");
		return;
	}
	newnode->next = NULL;
	newnode->data = x;
	if (pq->phead == NULL)
	{
		assert(!pq->ptail);
		pq->phead = newnode;
		pq->ptail = newnode;
	}
	else
	{
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}
	pq->size++;
}
void QueuePop(Queue* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));
	if (pq->phead->next == NULL) {
		free(pq->phead);
		pq->phead = NULL;
		pq->ptail = NULL;
	}
	else {
	QueNode* next = pq->phead->next;
	free(pq->phead);
	pq->phead = next;
	}
	
	pq->size--;
}
bool QueueEmpty(Queue* pq) {
	assert(pq);
	if (pq->phead == NULL)
		return true;
	else
		return false;
}
QueDataType QueueFront(Queue* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->phead->data;
}
QueDataType QueueBack(Queue* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->ptail->data;
}
int QueueSize(Queue* pq) {
	assert(pq);
	return pq->size;
}
Que.h
#pragma once
#include<stdio.h>
#include<stdbool.h>
#include<assert.h>
#include<stdlib.h>
typedef int QueDataType;
typedef struct Queue {
	struct Queue* next;
	QueDataType data;
}QueNode;
typedef struct Que {
	QueNode* phead;
	QueNode* ptail;
	int size;
}Queue;
void InitQueue(Queue* pq);
void DestroyQueue(Queue* pq);
void QueuePush(Queue* pq, QueDataType x);
void QueuePop(Queue* pq);
QueDataType QueueFront(Queue* pq);
QueDataType QueueBack(Queue* pq);
bool QueueEmpty(Queue* pq);
int QueueSize(Queue* pq);

  • 46
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值