目录

一、栈

(一)栈的概念及结构

 (二)栈的接口实现

1.栈的初始化与销毁

2.入栈操作

​编辑

3.判断空栈与出栈

(三)完整代码

 二、队列

(一)队列的概念及结构

 (二)队列的接口实现

1.队列的初始化与销毁

2. 入队列操作

3.判断空队列与出队列

(三)完整代码


一、栈

(一)栈的概念及结构

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

        压栈:栈的插入操作叫做压栈(或者入栈、进栈),入数据在栈顶。

        出栈:栈的删除操作叫做出栈。出数据也在栈顶。

        栈既可以通过数组的方法实现,也可以以链表的方式实现。数组的具体的实现方法我们来看下面两幅图:

         如图所示,在通过数组实现时,我们会定义一个top变量来表示堆顶,每插入一个元素,top的值都会发生改变。在删除操作时,由于后进先出原则,最后一个入栈的元素5也就是数组的最后一个元素应该最先出栈,因此我们只需要把top的位置往前挪动,即可实现出栈操作了。

                        ​​​​​​​      

        知道了底层逻辑,我们就可以用代码来实现了。

 (二)栈的接口实现

1.栈的初始化与销毁

        我们首先将要存储的数据类型进行重命名。其次需要定义一个结构体,结构体内应包含:1个指数据存储数组的指针,1个表示堆顶的整形变量,由于需要动态开辟空间,所以还需要1个表示容量的整形变量。

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

        栈的初始化我们通过形参传址来对结构体内的各元素进行初始化。val需要开辟空间,其他参数置零。

//初始化栈
void StackInit(Stack* pmp)
{
	pmp->val = (DataType*)malloc(sizeof(DataType) * 4);
	if (!pmp->val)
	{
		perror("BuyStack::malloc");
		return;
	}
	pmp->top = 0;
	pmp->capacity = 4;
	return;
}

        栈的销毁同样通过传址操作来实现,释放val的空间并置空,将其他参数置零。注意一下,结构体变量并不需要释放空间,因为它的空间不是通过动态开辟取得的。进行销毁操作前要对结构体指针进行断言,指针不能为空,必须有指向的空间。

//销毁栈
void DestoryStack(Stack* pmp)
{
	assert(pmp);
	free(pmp->val);
	pmp->val = NULL;
	pmp->capacity = pmp->top = 0;
	return;
}

2.入栈操作

              如前文所述,入栈时只需要通过调整top的值即可控制数据的存储位置。当然了,入栈前需要对容量进行判断,容量不足则需要扩容,判断方法就是top值与capacity相等时则表示满栈。扩容之后capacity需要变成新的容量大小,存入一个数据需要对top的值++ 。

//入栈
void PushStack(Stack* pmp, DataType x)
{
	if (pmp->top == pmp->capacity)
	{
		DataType* tmp = (DataType*)realloc(pmp->val, sizeof(DataType)*pmp->capacity*2);
		if (!tmp)
		{
			perror("PushStack::realloc");
			return;
		}
		pmp->val = tmp;
		pmp->capacity *= 2;
	}

	pmp->val[pmp->top] = x;
	pmp->top++;
	return;
}

 编译运行没有问题。

3.判断空栈与出栈

        在进行出栈操作前,需要判断栈是否为空如果为空就不能继续出栈。我们bool返回值来表示,空栈则返回1,非空则返回0。对空栈有两种处理办法,一种是if语句的温柔检查,如果空栈就直接返回值停止后续操作;还有一种是assert的暴力检查,空栈就直接error报错,这种方法的好处是可以及时发现程序的问题,找出错误信息。所以我们用assert暴力检查方法:

//判断空栈
bool StackisEmpty(Stack* pmp)
{
	assert(pmp);
	if (!pmp->top)
		return 1;
	return 0;
}

//出栈
DataType PopStack(Stack* pmp)
{
	assert(pmp);
	assert(!StackisEmpty(pmp));
	DataType ret = 0;
	ret = pmp->val[pmp->top-1];
	pmp->top--;
	return ret;
}

        assert()内部的值如果为0就会报错,我们可以看到代码中,如果空栈,StackisEmpty会返回1,!1的值就是0,所以空战时assert()括号内是0,会报错,符合要求。

如图栈内原有5个数据,进行五次出栈操作后变成空栈,此后再出栈时报错了,符合要求。

        至此,就已经完成了栈的实现,最后还有一个栈的打印,用于调试代码时使用,这里就不单独列出来了。

        最后呈上完整代码:

(三)完整代码

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

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

//初始化栈
void StackInit(Stack* pmp);
//销毁栈
void DestoryStack(Stack* pmp);
//入栈
void PushStack(Stack* pmp, DataType x);
//出栈
DataType PopStack(Stack* pmp);
//判断空栈
bool StackisEmpty(Stack* pmp);
//栈的打印
void PrintStack(Stack* pmp);

//Stack.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"

//初始化栈
void StackInit(Stack* pmp)
{
	pmp->val = (DataType*)malloc(sizeof(DataType) * 4);
	if (!pmp->val)
	{
		perror("BuyStack::malloc");
		return;
	}
	pmp->top = 0;
	pmp->capacity = 4;
	return;
}

//销毁栈
void DestoryStack(Stack* pmp)
{
	assert(pmp);
	free(pmp->val);
	pmp->val = NULL;
	pmp->capacity = pmp->top = 0;
	return;
}

//入栈
void PushStack(Stack* pmp, DataType x)
{
	if (pmp->top == pmp->capacity)
	{
		DataType* tmp = (DataType*)realloc(pmp->val, sizeof(DataType)*pmp->capacity*2);
		if (!tmp)
		{
			perror("PushStack::realloc");
			return;
		}
		pmp->val = tmp;
		pmp->capacity *= 2;
	}

	pmp->val[pmp->top] = x;
	pmp->top++;
	return;
}

//判断空栈
bool StackisEmpty(Stack* pmp)
{
	assert(pmp);
	if (!pmp->top)
		return 1;
	return 0;
}

//出栈
DataType PopStack(Stack* pmp)
{
	assert(pmp);
	assert(!StackisEmpty(pmp));
	DataType ret = 0;
	ret = pmp->val[pmp->top-1];
	pmp->top--;
	return ret;
}

//栈的打印
void PrintStack(Stack* pmp)
{
	for (int i = 0; i < pmp->top; i++)
	{
		printf("%d ", pmp->val[i]);
	}
	printf("\n");
	return;
}

 二、队列

(一)队列的概念及结构

        队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)的特点。

        入队列:进行插入操作的一端成为队尾

        出队列:进行删除操作的一端称为队头

         通过上图可知,用数组实现时,用head、tail分别表示队头和队尾,head始终为0,tail在每插入一个数据时就往后移一位。由于先进先出原则,第一个出队列的应该是“1”,在数组中,我们通过挪动数据的方法将第一个数据进行覆盖,这样就实现了出队列,时间复杂度O(N)=n

 (二)队列的接口实现

1.队列的初始化与销毁

        我们首先将要存储的数据类型进行重命名。其次需要定义一个结构体,结构体内应包含:1个指数据存储数组的指针,2个分别表示队头和队尾的整形变量,由于需要动态开辟空间,所以还需要1个表示容量的整形变量。

typedef int DataType;
typedef struct Queue {
	DataType* val;
	int head;
	int tail;
	int capacity;
}Queue;

        栈的初始化我们通过形参传址来对结构体内的各元素进行初始化。val需要开辟空间,其他参数置零。

//初始化队列
void QueueInit(Queue* pmp)
{
	pmp->val = (DataType*)malloc(sizeof(DataType) * 4);
	if (!pmp->val)
	{
		perror("QueueInit::malloc");
		return;
	}
	pmp->head = pmp->tail = 0;
	pmp->capacity = 4;
	return;
}

        栈的销毁同样通过传址操作来实现,释放val的空间并置空,将其他参数置零。注意一下,结构体变量并不需要释放空间,因为它的空间不是通过动态开辟取得的。进行销毁操作前要对结构体指针进行断言,指针不能为空,必须有指向的空间。

//销毁队列
void QueueDestory(Queue* pmp)
{
	assert(pmp);
	free(pmp->val);
	pmp->val = NULL;
	pmp->head = pmp->tail = pmp->capacity = 0;
	return;
}

2. 入队列操作

        如前文所述,入队列时只需要通过调整tail的值即可控制数据的存储位置。当然了,入队列前需要对容量进行判断,容量不足则需要扩容,判断方法就是tail值与capacity相等时则表示满队列。扩容之后capacity需要变成新的容量大小,存入一个数据需要对tail的值++ 。

//入队
void QueuePush(Queue* pmp, DataType x)
{
	if (pmp->tail == pmp->capacity)
	{
		Queue* tmp = (Queue*)realloc(pmp->val, sizeof(Queue) * pmp->capacity * 2);
		if (!pmp)
		{
			perror("QueuePush::realloc");
			return;
		}
		pmp->val = tmp;
		pmp->capacity *= 2;
	}
	pmp->val[pmp->tail] = x;
	pmp->tail++;
	return;
}

编译运行没有问题。

3.判断空队列与出队列

        在进行出队列操作前,需要判断队列是否为空如果为空就不能继续出队列。我们bool返回值来表示,空队列则返回1,非空则返回0。对空队列有两种处理办法,一种是if语句的温柔检查,如果空队列就直接返回值停止后续操作;还有一种是assert暴力检查,空队列就直接报错,这种方法的好处是可以及时发现程序的问题,找出错误信息。所以我们用assert暴力检查方法:

//为空判断
bool QueueisEmpty(Queue* pmp)
{
	assert(pmp);
	if (pmp->tail == pmp->head)
		return 1;
	return 0;
}

//出队
DataType QueuePop(Queue* pmp)
{
	assert(pmp);
	assert(!QueueisEmpty(pmp));
	for (int i = 1; i < pmp->tail; i++)
	{
		pmp->val[i - 1] = pmp->val[i];
	}
	pmp->tail--;
	return;
}

        assert()内部的值如果为0就会报错,我们可以看到代码中,如果空栈,QueueisEmpty会返回1,!1的值就是0,所以空战时assert()括号内是0,会报错,符合要求。

如图队列内原有4个数据,进行四次出栈操作后变成空栈,此后再出栈时报错了,符合要求。

        至此,就已经完成了栈的实现,最后还有一个栈的打印,用于调试代码时使用,这里就不单独列出来了。

        最后呈上完整代码:

(三)完整代码

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

typedef int DataType;
typedef struct Queue {
	DataType* val;
	int head;
	int tail;
	int capacity;
}Queue;

//初始化队列
void QueueInit(Queue* pmp);
//销毁队列
void QueueDestory(Queue* pmp);
//打印队列
void QueuePrint(Queue* pmp);
//入队
void QueuePush(Queue* pmp, DataType x);
//为空判断
bool QueueisEmpty(Queue* pmp);
//出队
DataType QueuePop(Queue* pmp);

//Queue.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Queue.h"

//初始化队列
void QueueInit(Queue* pmp)
{
	pmp->val = (DataType*)malloc(sizeof(DataType) * 4);
	if (!pmp->val)
	{
		perror("QueueInit::malloc");
		return;
	}
	pmp->head = pmp->tail = 0;
	pmp->capacity = 4;
	return;
}

//销毁队列
void QueueDestory(Queue* pmp)
{
	assert(pmp);
	free(pmp->val);
	pmp->val = NULL;
	pmp->head = pmp->tail = pmp->capacity = 0;
	return;
}

//打印队列
void QueuePrint(Queue* pmp)
{
	assert(pmp);
	for (int i = 0; i < pmp->tail; i++)
	{
		printf("%d ", pmp->val[i]);
	}
	printf("\n");
	return;
}

//入队
void QueuePush(Queue* pmp, DataType x)
{
	if (pmp->tail == pmp->capacity)
	{
		Queue* tmp = (Queue*)realloc(pmp->val, sizeof(Queue) * pmp->capacity * 2);
		if (!pmp)
		{
			perror("QueuePush::realloc");
			return;
		}
		pmp->val = tmp;
		pmp->capacity *= 2;
	}
	pmp->val[pmp->tail] = x;
	pmp->tail++;
	return;
}

//为空判断
bool QueueisEmpty(Queue* pmp)
{
	assert(pmp);
	if (pmp->tail == pmp->head)
		return 1;
	return 0;
}

//出队
DataType QueuePop(Queue* pmp)
{
	assert(pmp);
	assert(!QueueisEmpty(pmp));
	for (int i = 1; i < pmp->tail; i++)
	{
		pmp->val[i - 1] = pmp->val[i];
	}
	pmp->tail--;
	return;
}
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐