栈和队列的实现

1.1 栈的概念及结构

  • 栈:一种特殊的线性表,只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)原则。
  • 压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶
  • 出栈:栈的删除操作叫做出栈。出数据也在栈顶
    在这里插入图片描述
  • 满足后进先出原则下,可以入一个数据出一个,也可以全部入完后再出,这就可以有多种出栈顺序。

1.2栈的实现

  • 栈一般用链表或数组实现,用数组结构更优,尾部插入和删除数据方便,缓存命中率高

栈的定义

typedef int STDataType;
typedef struct Stack
{
	STDataType* a;//用于存储栈数据的指针成员
	int top;//栈顶
	int capacity;//容量
}ST;

定义支持动态增长的栈

栈的初始化STInit

void STInit(ST* ps)
{
	assert(ps);
	ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
	if (ps->a == NULL)
	{
		perror("malloc fail");
		return;//由于无返回值不能返回空
	}
	ps->capacity = 4;
	ps->top = 0;//top是栈顶元素的下一个位置
	//ps->top=-1;//top是栈顶元素当前位置
}

malloc函数从堆中分配指定字节数的空间来存储栈的数据,并进行容量初始化,注意top选择是0或1,都表示栈为空并且都先自增top然后把元素存入top指向位置,只是下标位置区别

判断栈是否为空STEmpty

bool STEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;//若为空返回非零结果,不为则返回0
}

压栈STPush

void STPush(ST* ps,STDataType x)
{
	assert(ps);
	//检查是否需要扩容
	if (ps->top == ps->capacity)
	{
		STDataType* tmp = (STDataType*)realloc(ps->a,sizeof(STDataType) * ps->capacity * 2);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return ;
		}
		else
		{
			ps->a = tmp;
			ps->capacity *= 2;
		}
	}
	ps->a[ps->top] = x;
	ps->top++;
}

断言确保指针非空,避免造成非法访问等未定义行为。将传入的x元素放入栈顶位置,即ps->a数组的ps->top索引处,然后top自增1,表示栈顶移动到下一个位置,准备下次压入新元素。

出栈STPop

void STPop(ST* ps)
{
	assert(ps);
	//断言下标为0不再减
	assert(!STEmpty(ps));
	ps->top--;
}

注意判断栈不为空,不等于非零就是零,报错,避免数组下标越界访问。进行自减即可。

获取栈中有效元素个数STSize

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

top初始化为0,压栈后有效元素个数就等于top值;top初始化为-1时,压栈后有效元素个数是top+1

获取栈顶元素STDataType

STDataType STTop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));
	return ps->a[ps->top - 1];
}

初始化为-1直接访问top即可

销毁栈STDestroy

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

整体代码

  • 函数声明
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<stdbool.h>
#include<assert.h>
#include<stdlib.h>

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

void STInit(ST* ps);
void STDestroy(ST* ps);
void STPush(ST* ps, STDataType x);
void STPop(ST* ps);
int STSize(ST* ps);
bool STEmpty(ST* ps);
STDataType STTop(ST* ps);
  • 函数定义
#include"Stack.h"

void STInit(ST* ps)
{
	assert(ps);
	ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
	if (ps->a == NULL)
	{
		perror("malloc fail");
		return;//由于无返回值不能返回空
	}
	ps->capacity = 4;
	ps->top = 0;//top是栈顶元素的下一个位置
	//ps->top=-1;//top是栈顶元素当前位置
}

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

//压栈
void STPush(ST* ps,STDataType x)
{
	assert(ps);
	//检查是否需要扩容
	if (ps->top == ps->capacity)
	{
		STDataType* tmp = (STDataType*)realloc(ps->a,sizeof(STDataType) * ps->capacity * 2);
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		else
		{
			ps->a = tmp;
			ps->capacity *= 2;
		}
	}
	ps->a[ps->top] = x;
	ps->top++;
}
//判断栈是否为空
bool STEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;
}

//出栈
void STPop(ST* ps)
{
	assert(ps);
	//断言下标为0不再减
	assert(!STEmpty(ps));
	ps->top--;
}

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

//获取栈顶元素
STDataType STTop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));
	return ps->a[ps->top - 1];
}
  • 主函数实现
#include"Stack.h"
int main()
{
	ST st;
	STInit(&st);
	STPush(&st, 1);
	STPush(&st, 2);

	//printf("%d ", STTop(&st));//可改变栈的出栈顺序,遵循原则即可
	//STPop(&st);

	STPush(&st, 3);
	STPush(&st, 4);
	STPush(&st, 5);

	while (!STEmpty(&st))//由于不知道top指向的栈顶当前位置还是下一位置不要直接访问top,将其封装为函数
	{
		printf("%d ", STTop(&st));
		STPop(&st);
	}
	STDestroy(&st);
	return 0;
}

2.1队列的概念及结构

在这里插入图片描述

  • 队列也可以用数组和链表的结构实现,链表效率更优,数组头部出数据需要整体挪动数据效率低。

2.2队列的实现

队列的定义QDataType

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

定义了一个QueueNode的结构体,用于表示队列中的节点

//对常用的多个数据进行封装,便于传参和修改
typedef struct Queue
{
	QNode* head;
	QNode* tail;
	int size;
}Queue;

定义了一个名为Queue的结构体,表示整个队列
数据封装中设置tail指针为了方便找尾,size为了方便确认元素个数

两者关系:Queue结构体通过head和tail指针来管理一系列QueueNode结构体组成的链式队列。QueueNode结构体通过next指针相互链接,形成队列的链式结构,而 Queue结构体掌控着这个结构体两端指针以及队列大小信息。

队列的初始化QueueInit

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

将头尾指针都置空,元素个数为0,明确初始状态。

队列的销毁QueueDestroy

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

提前保存cur的next指针,避免free后找不到。

队尾入队列QueuePush

void QueuePush(Queue* pq, QDatatype x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	//尾插next置空
	newnode->next = NULL;
	//因为没有哨兵位节点,需要判断第一次尾插时头节点是否为空
	if (pq->head == NULL)
	{
		//初始化时tail和head都为空
		assert(pq->tail == NULL);
		pq->head = pq->tail = newnode;
	}
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
	pq->size++;
}

用malloc创建新节点后赋值有效信息,并就是否有哨兵位头节点进行判断插入。无的情况第一次插入要给head和tail指针赋值,有的情况直接进行插入链接即可。

队头出队列QueuePop

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->head != NULL);
	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--;

}

要判断删除的节点是否为尾节点,若是直接将头尾指针置空即可,不是的话改变头指针指向并将size–。

检查队列是否为空QueueEmpty

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

为空则返回非零值,非空返回0

获取队列中有效元素个数QueueSize

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

由于我们在Queue结构体中设置了size参数,避免了遍历整个队列来获取个数。

获取队列头部元素QueueFront

QDatatype QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->head->data;
}

要多进行一次断言判断队列是否为空,避免造成非法访问。

获取队列尾部元素QueueBack

QDatatype QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->tail->data;
}

整体代码

  • 函数声明
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>

typedef int QDatatype;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDatatype data;
}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);
int QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);
QDatatype QueueFront(Queue* pq);
QDatatype QueueBack(Queue* pq);

  • 函数的定义
#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* next = cur->next;
		free(cur);
		cur = next;
	}
	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");
		return;
	}
	newnode->data = x;
	//尾插next置空
	newnode->next = NULL;
	//因为没有哨兵位节点,需要判断第一次尾插时头节点是否为空
	if (pq->head == NULL)
	{
		//初始化时tail和head都为空
		assert(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(pq->head != NULL);
	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--;

}

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

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

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;
}
  • 主函数实现
#include"Queue.h"
int main()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	QueuePush(&q, 5);
	QueuePush(&q, 6);
	while (!QueueEmpty(&q))
	{
		printf("%d ", QueueFront(&q));
		QueuePop(&q);
	}
	QueueDestroy(&q);
	return 0;
}

结语

  • 感谢支持,祝大家新年快乐,共同进步。
    请添加图片描述
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值