【数据结构】栈和队列的实现

目录

一、栈(顺序表)

   1、栈的概念及结构

   2、栈的实现

     2.1 栈的结构体

     2.2 栈的初始化

     2.3 入栈

     2.4 出栈

     2.5 获取栈顶数据

     2.6 栈中有效数据

     2.7 判断栈空

     2.8 栈的销毁

   3、源代码

     3.1 Stack.h

     3.2 Stack.c

     3.3 test.c

二、队列(单链表)

   1、队列的基本概念及结构

   2、队列的实现

     2.1 队列的结构体

     2.2 队列初始化

     2.3 入队列

     2.4 出队列

     2.5 获取队头元素

     2.6 获取队尾元素

     2.7 队列中有效数据

     2.8 队列是否为空

     2.9 销毁队列

   3、源代码

     3.1 Queue.h

     3.2 Queue.c

     3.3 test.c

三、循环队列


一、栈(顺序表)

   1、栈的概念及结构

栈:是一种线性表,只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶,另一端称为栈底。

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

下图所示可以更好的观察怎么入栈,怎么出栈。

特点:后进先出。

【数据结构】栈和队列的实现_第1张图片

   2、栈的实现

    因为栈是一种线性表,所以存储结构可以是顺序表链表两种形式,相对于顺序表这种结构实现会更优一些。顺序表在尾上插入数据的代价较小。

【数据结构】栈和队列的实现_第2张图片

注意:顺序表就要考虑到这个栈是否会满,如果栈满,就需要扩容,判断的标准为top == capacity.

     2.1 栈的结构体

typedef int STDataType;
typedef struct Stack
{
	STDataType* a;//数组
	int capacity; //栈的容量
	int top;   //初始为0,表示栈顶位置下一个位置的下标
}ST;

     2.2 栈的初始化

为了下面的操作方便,我在这里直接先申请空间,然后栈满时,再去扩容

// 初始化栈
void StackInit(ST* ps)
{
	assert(ps);
	ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
	if (ps->a == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	ps->top = 0;
	ps->capacity = 4;
}

     2.3 入栈

  注意:入栈之前首先要判断一下,栈是否已满,栈满时,需要我们扩容,方可继续入栈。

当top == capacity时,说明栈满,此时,我们2倍的扩容,防止一次申请过多空间,导致空间浪费。注意要将capacity扩大2倍,否则容量空间申请出来,但是大小没变。

//入栈
void StackPush(ST* ps, STDataType x)
{
	assert(ps);
	//扩容
	if (ps->capacity == ps->top)
	{
		STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity *= 2;//注意要将capacity扩大2倍
	}
	ps->a[ps->top] = x;
	ps->top++;
}

     2.4 出栈

 出栈我们首先要暴力检测一波栈是否为空,为空时,就不能出栈。直接将top--就可以。

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

     2.5 获取栈顶数据

注意:刚开始设置top时,它的位置就是栈顶位置的下一个位置。所以去栈顶数据时,要top-1。

//获取栈顶元素
STDataType StackTop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	return ps->a[ps->top - 1];
}

     2.6 栈中有效数据

//获取栈中有效元素个数
int StackSize(ST* ps)
{
	assert(ps);
	return ps->top;
}

     2.7 判断栈空

判断栈是否为空,就是看top是否为0。top=0时,就表示为空。

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

     2.8 栈的销毁

销毁栈时,我们要先将申请的空间释放掉,最好将其置空。

//销毁
void StackDestory(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->capacity = ps->top = 0;
}

   3、源代码

      3.1 Stack.h

#pragma once
#include 
#include 
#include 
#include 

typedef int STDataType;
typedef struct Stack
{
	STDataType* a;//数组
	int capacity;
	int top;   //初始为0,表示栈顶位置下一个位置的下标
}ST;

// 初始化栈
void StackInit(ST* ps);
//销毁
void StackDestory(ST* ps);

//入栈
void StackPush(ST* ps, STDataType x);
//出栈
void StackPop(ST* ps);
//获取栈顶元素
STDataType StackTop(ST* ps);

//获取栈中有效元素个数
int StackSize(ST* ps);

bool StackEmpty(ST* ps);

      3.2 Stack.c

#include "Stack.h"

// 初始化栈
void StackInit(ST* ps)
{
	assert(ps);
	ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
	if (ps->a == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	ps->top = 0;
	ps->capacity = 4;
}
//销毁
void StackDestory(ST* ps)
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->capacity = ps->top = 0;
}

//入栈
void StackPush(ST* ps, STDataType x)
{
	assert(ps);
	//扩容
	if (ps->capacity == ps->top)
	{
		STDataType* tmp = (STDataType*)realloc(ps->a, ps->capacity * 2 * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		ps->a = tmp;
		ps->capacity *= 2;
	}

	ps->a[ps->top] = x;
	ps->top++;
}
//出栈
void StackPop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	ps->top--;
}

//获取栈顶元素
STDataType StackTop(ST* ps)
{
	assert(ps);
	assert(!StackEmpty(ps));
	return ps->a[ps->top - 1];
}

//获取栈中有效元素个数
int StackSize(ST* ps)
{
	assert(ps);
	return ps->top;
}

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

      3.3 test.c

#include "Stack.h"

void TestStack1()
{
	ST st;
	StackInit(&st);
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);
	StackPush(&st, 5);

	StackPop(&st);
	StackPop(&st);
	StackPop(&st);

	printf("%d\n", StackTop(&st));
	printf("size:%d\n", StackSize(&st));
	//printf("size:%d\n", st.top);

	StackDestory(&st);

}
int main()
{
	TestStack1();
	return 0;
}

二、队列(单链表)

   1、队列的基本概念及结构

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

下图所示可以更好的观察怎么入队列,怎么出队列。

特点:先进先出。

【数据结构】栈和队列的实现_第3张图片

   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 队列初始化

我们将队列进行初始化,是要用 head 和 tail 两个指针来控制函数,所以函数的参数是Queue*。

// 初始化队列
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = pq->tail = NULL;
	pq->size = 0;
}

     2.3 入队列

入队列是在尾部插入的。首先是要申请一个结点,然后再进行尾插,尾插分两种情况:(1)如果队列为空时,head = tail = newnode (2)如果队列不为空时,直接将其尾插。最后将size++即可。

// 队尾入队列
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		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++;
}

     2.4 出队列

出队列是在对头出的,需要注意的是:首先要保证队列有数据才可以出队列,所以我们要判断队列是否为空,如果不为空,此时,还有两种情况:(1)只有一个结点时,就相当于只有一个头结点,直接将其释放,然后置空。(2)如果有多个数据时,需要先保存头结点(如果不保存头结点,后面的链表就链接不上),然后将其释放,再置空。

// 队头出队列
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.5 获取队头元素

// 获取队列头部元素
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->head->data;
}

     2.6 获取队尾元素

// 获取队列队尾元素
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->tail->data;
}

     2.7 队列中有效数据

我们在设置队列的结构体时,已经设置有 size ,所以队列中的有效数据,其实就是 size 的大小。 

// 获取队列中有效元素个数
int QueueSize(Queue* pq)
{
    assert(pq);
	return pq->size;
}

     2.8 队列是否为空

当 head 和 tail 指针为空时,就说明此时队列为空。

// 检测队列是否为空,如果为空返回非零,非空返回0 
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL && pq->tail == NULL;
}

     2.9 销毁队列

队列的销毁和栈的销毁还是有区别的,因为栈是用的顺序表结构,它的存储空间是连续的,所以释放是可以一次性全部释放的,而队列是用的链表结构,它的存储空间不是连续的,所以释放空间的时候需要一个一个释放,下面用的是循环方式将每个节点释放掉,最后再将其置空。

// 销毁队列
void QueueDestroy(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* del = cur;
		cur = cur->next;
		free(del);
	}
	pq->head = pq->tail = NULL;
	pq->size = 0;
}

   3、源代码

     3.1 Queue.h

#pragma once
#include 
#include 
#include 
#include 

typedef int	QDataType;
// 链式结构:表示队列
typedef struct QueueNode
{
	QDataType data;
	struct QueueNode* next;
}QNode;
// 队列的结构
typedef struct Queue
{
	QNode* head;
	QNode* tail;
	int size;
}Queue;

// 初始化队列
void QueueInit(Queue* pq);
// 销毁队列
void QueueDestroy(Queue* pq);

// 队尾入队列
void QueuePush(Queue* pq, QDataType x);
// 队头出队列
void QueuePop(Queue* pq);

// 获取队列头部元素
QDataType QueueFront(Queue* pq);
// 获取队列队尾元素
QDataType QueueBack(Queue* pq);

// 获取队列中有效元素个数
int QueueSize(Queue* pq);
// 检测队列是否为空,如果为空返回非零结果,如果非空返回0 
bool QueueEmpty(Queue* pq);

     3.2 Queue.c

#include "Queue.h"

// 初始化队列
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = pq->tail = NULL;
	pq->size = 0;
}
// 销毁队列
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;
}

// 队尾入队列
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		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(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--;
}

// 获取队列头部元素
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->head->data;
}
// 获取队列队尾元素
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->tail->data;
}

// 获取队列中有效元素个数
int QueueSize(Queue* pq)
{
	int size = 0;
	return pq->size;
}
// 检测队列是否为空,如果为空返回非零,非空返回0 
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL && pq->tail == NULL;
}

     3.3 test.c

#include "Queue.h"

void TestQueue1()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	printf("%d\n", QueueSize(&q));
	printf("%d\n", QueueEmpty(&q));//ǿշ0

	printf("%d\n", QueueFront(&q));
	printf("%d\n", QueueBack(&q));

	QueueDestroy(&q);
}
void TestQueue2()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	//бһ
	while (!QueueEmpty(&q))
	{
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}
	printf("\n");
	printf("%d\n", QueueSize(&q));
	printf("%d\n", QueueEmpty(&q));//շطֵ
}
int main()
{
	//TestQueue1();
	TestQueue2();

	return 0;
}

三、循环队列

我们有时还会使用一种队列叫循环队列。如操作系统课程讲解生产者消费者模型时可以就会使用循环队列。环形队列可以使用数组实现,也可以使用循环链表实现。

【数据结构】栈和队列的实现_第4张图片

队满条件:(rear + 1) % Maxsize = front;

队空条件:front = rear;

队列元素个数:(rear + Maxsize - front) % Maxsize;

【数据结构】栈和队列的实现_第5张图片

 


 本文要是有不足的地方,欢迎大家在下面评论,我会在第一时间更正。

【数据结构】栈和队列的实现_第6张图片

老铁们,记着点赞加关注!!!

 

你可能感兴趣的:(数据结构,开发语言)