栈和队列及其多种接口实现-c语言

今天我们来完成栈和队列,首先我们要明白什么是栈,什么是队列。

目录

栈的选择

栈的结构

栈的初始化

栈的销毁

入栈

出栈

返回栈顶元素

计算数据个数

判断是否为空

队列的选择

队列的结构

入队列

出队列

判断是否为空

取队头元素

取队尾元素

计算数据个数

队列的销毁


我们之前写过顺序表,链表,这都是数据结构,而栈和队列,也是数据结构,栈的特殊地方在于,栈是后进先出,也叫LIFO(last in first out),比如我们平时在桌子上堆放的书,我们堆放了十本书,如果不直接把书抽出来的话,我们要拿到最下面那本书,需要把上面九本先拿下去,也就是先要从最上面的那一本开始拿,而最上面的那一本,是我们最后放上去的,这就是一个栈。再说队列,队列的特殊地方在于先进先出,也叫FIFO(first in first out),比如我们日常生活里的排队就是一个队列,不考虑插队这些,正常情况下,第一个来的人一定第一个完成自己的业务,最后一个来的人最后完成,这就是一个队列。

我们还是采取工程化的写法。

 首先我们来完成栈,栈的实现可以使用数组或者链表,那我们选取哪个比较好呢?

栈的选择

我们来比较一下二者的优缺点。

 

首先我们看数组: 使用数组就相当于之前顺序表的尾插和尾删,用尾去做栈顶,非常合适,唯一的缺点是空间不足时需要扩容。完美避开了顺序表插入删除时需要挪动数据的缺点。

我们再来看链表,链表又有多种结构,我们介绍一种使用单链表来完成的,插入和删除都只有O(1)。

使用这种结构即可完成,最初栈顶和栈底都为空,插入第一个元素后,栈顶栈底都指向第一个元素,每次插入新元素后,只需将其赋给栈顶,删除时,先删除节点,再把栈顶指向栈顶的next即可。

如果用尾做栈顶,那么使用双向链表最好,如果使用头做栈顶,那么单链表最好,插入删除都是O(1)。

知道了两种结构完成栈的优缺点,大家会选择哪一个呢?我们这里选择数组来完成栈,因为使用数组的话各方面效率都会更优,这里知识和预加载有关,这里就不多介绍,我们举个例子,使用数组和链表的区别,学生时代家里每个月都会给你打生活费,数组就像是一个月给你打一次生活费,够你一个月花,链表就像每天给你打一次,很明显,使用数组要比链表效率高。

决定好了用什么来实现栈,接着我们来完成栈的结构和各类接口

栈的结构

typedef int STDataType;

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

我们将其写为动态栈,即a不直接写为数组,不然就写死了,并且我们使用top来记录栈顶位置

接着我们来完成栈的初始化

栈的初始化

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

这里的top选取0还是-1看自己喜欢,选取-1代表的是top就是当前栈顶元素,选取0表示top为栈顶元素的后一位,我这里选择初始赋0,同时,如果初始化失败,我们输出提示并结束程序

栈的销毁

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

入栈

void StackPush(ST* ps, STDataType x)//入栈
{
	assert(ps);
	if (ps->top == ps->capacity) {//满了扩容
		STDataType* tmp= (STDataType*)realloc(ps->a,ps->capacity*2*sizeof(STDataType));
		if (tmp == NULL) {
			printf("realloc fail\n");
			exit(-1);
		}
		else {
			ps->a = tmp;
			ps->capacity *= 2;
		}
	}
	ps->a[ps->top] = x;
	ps->top++;
}

入栈时我们要判断栈是否满了,满了需要扩容,扩容时我们扩容到原来的二倍,并且要判断是否扩容失败,失败的话我们给出提示并且结束程序,入栈后要记得给top+1

出栈

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

出栈我们直接使top-1,有人可能会先让栈顶元素置为0,但是这句是不需要的,因为入栈元素有时候本身就可能为0,并且我们不能随意出栈,比如top为0时,即空栈,所以我们加一句断言

返回栈顶元素

STDataType StackTop(ST* ps)//返回栈顶元素
{
	assert(ps);
	assert(ps->top > 0);
	return ps->a[ps->top - 1];
}

和出栈同理,栈为空时不能返回,我们加一句断言

计算数据个数

void StackSize(ST* ps)//计算数据个数
{
	assert(ps);
	return ps->top;
}

判断是否为空

bool StackEmpty(ST* ps)//判断是否为空
{
	assert(ps);
	return ps->top == 0;
}

我们直接return ps->top==0,使用bool类型,如果top==0,为真,返回true,否则返回false

我们来测试一下我们的代码

可以看到,没有问题。

 完成了栈,我们接着来看队列

队列的选择

和栈一样,队列也需要在数组和链表里选择一个,队列只允许在它的一端插入数据,在另一端删除数据

 这次其实我们要选择链表,因为数组会出现假溢出现象,比如这个数组可以放5个元素,现在已经放了四个,我们出队列一个元素,再入队列一个,就会发现数组下标0的位置是空的,但队列却显示已满,如果要挪动数据的话也非常麻烦,还有一种办法就是写成循环队列,但那是后续的事情,而且也有局限性。选取链表的话,用单链表就可以解决,采用上图结构,队列就只需要尾插和头删,效率都很高。

选择好了用什么来实现队列后,我们来设计队列的结构吧

队列的结构

typedef int QDataType;

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

typedef struct Queue
{
	QNode* head;
	QNode* tail;
}Queue;

我们设计好队列的节点,然后采取两个指针,队头和队尾来设计队列即可。而且采用这样的结构可以避免使用二级指针(后续接口如果参数传QNode的话是需要二级指针的)

入队列

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

入队列是在队尾入元素,入队列我们需要判断队列是否为空,入队列需要一个新节点,我们给节点申请一个空间,申请失败我们输出提示并且报错,然后将x赋给节点的data,节点的next置为空,我们还要判断队列是否有节点,如果一个没有,那么队头和队尾都指向这个新节点,否则队尾的next指向新节点,再把队尾指向新节点

出队列

void QueuePop(Queue* pq)//出队列
{
	assert(pq);
	assert(pq->head);
	if (pq->head->next == NULL) {
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else {
		Queue* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
}

出队列是出队头元素,我们要进行断言,我们出队列需要把头节点的空间释放,然后让head指向它的next,但在释放前我们需要把head的next保存起来,否则我们就找不到next了,这是正常情况,如果队列只有一个元素,出队列后会造成tail的野指针现象,所以我们需要额外判断,所以我们把出队列分为队列有一个元素和多个元素的情况

判断是否为空

bool QueueEmpty(Queue* pq)//判断是否为空
{
	assert(pq);
	return pq->head == NULL;
}

我们直接返回该判断语句即可,为空返回true,否则返回false

取队头元素

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

我们直接返回队头元素即可,最好再加一句断言,有时候会很有用,比如初始化后,队列里没有元素,这种情况没有第二句断言我们就不知道程序哪里出现了错误

取队尾元素

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

取队尾元素与取队头元素同理

计算数据个数

int QueueSize(Queue* pq)//计算数据个数
{
	assert(pq);
	int size = 0;
	QNode* cur = pq->head;
	while (cur) {
		cur = cur->next;
		size++;
	}
	return size;
}

这个接口有人可能会把循环条件写成cur!=pq->tail,但这样是错误的,会少计算一个元素

队列的销毁

void QueueDestory(Queue* pq)//销毁
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur) {
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
}

销毁需要注意先要保存cur的下一个,不然会找不到,然后就是最后要把head和tail置为空

我们最后再来测试一下代码

没有问题,到这里,我们的栈和队列以及他们的接口就全部实现了,希望大家可以有所收获,下面我会附上全部代码

#pragma once
//Stack.h
#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 StackInit(ST* ps);//初始化
void StackDestory(ST* ps);//销毁
void StackPush(ST* ps,STDataType x);//入栈
void StackPop(ST* ps);//出栈
STDataType StackTop(ST* ps);//返回栈顶元素
void StackSize(ST* ps);//计算数据个数
bool StackEmpty(ST* ps);//判断是否为空

 

//Stack.c
#include "Stack.h"
void StackInit(ST* ps)//初始化
{
	assert(ps);
	ps->a = (STDataType*)malloc(sizeof(STDataType) * 4);
	if (ps->a == NULL) {
		printf("malloc fail\n");
		exit(-1);
	}
	ps->capacity = 4;
	ps->top = 0;
}
void StackDestory(ST* ps)//销毁
{
	assert(ps);
	free(ps->a);
	ps->a = NULL;
	ps->top = ps->capacity = 0;
}
void StackPush(ST* ps, STDataType x)//入栈
{
	assert(ps);
	if (ps->top == ps->capacity) {//满了扩容
		STDataType* tmp= (STDataType*)realloc(ps->a,ps->capacity*2*sizeof(STDataType));
		if (tmp == NULL) {
			printf("realloc fail\n");
			exit(-1);
		}
		else {
			ps->a = tmp;
			ps->capacity *= 2;
		}
	}
	ps->a[ps->top] = x;
	ps->top++;
}
void StackPop(ST* ps)//出栈
{
	assert(ps);
	assert(ps->top>0);
	ps->top--;
}
STDataType StackTop(ST* ps)//返回栈顶元素
{
	assert(ps);
	assert(ps->top > 0);
	return ps->a[ps->top - 1];
}
void StackSize(ST* ps)//计算数据个数
{
	assert(ps);
	return ps->top;
}
bool StackEmpty(ST* ps)//判断是否为空
{
	assert(ps);
	return ps->top == 0;
}
#pragma once
//Queue.h
#include <stdio.h>
#include<stdbool.h>
#include<assert.h>
#include<stdlib.h>
typedef int QDataType;

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

typedef struct Queue
{
	QNode* head;
	QNode* tail;
}Queue;

void QueueInit(Queue* pq);//初始化
void QueueDestory(Queue* pq);//销毁
void QueuePush(Queue* pq, QDataType x);//入队列
void QueuePop(Queue* pq);//出队列
QDataType QueueFront(Queue* pq);//取队头元素
QDataType QueueBack(Queue* pq);//取队尾元素
int QueueSize(Queue* pq);//取数据个数
bool QueueEmpty(Queue* pq);//判断是否为空
//Queue.c
#include"Queue.h"

void QueueInit(Queue* pq)//初始化
{
	assert(pq);
	pq->head = pq->tail = NULL;
}
void QueueDestory(Queue* pq)//销毁
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur) {
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->head = pq->tail = NULL;
}
void QueuePush(Queue* pq, QDataType x)//入队列
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL) {
		printf("malloc fail\n");
		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;
	}
}
void QueuePop(Queue* pq)//出队列
{
	assert(pq);
	assert(pq->head);
	if (pq->head->next == NULL) {
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else {
		Queue* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
}
QDataType QueueFront(Queue* pq)//取队头元素
{
	assert(pq);
	assert(pq->head);
	return pq->head->data;
}
QDataType QueueBack(Queue* pq)//取队尾元素
{
	assert(pq);
	assert(pq->head);
	return pq->tail->data;
}
int QueueSize(Queue* pq)//计算数据个数
{
	assert(pq);
	int size = 0;
	QNode* cur = pq->head;
	while (cur) {
		cur = cur->next;
		size++;
	}
	return size;
}
bool QueueEmpty(Queue* pq)//判断是否为空
{
	assert(pq);
	return pq->head == NULL;
}
//test.c
#include "Stack.h"
#include"Queue.h"
void testStack() {
	ST st;
	StackInit(&st);
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);
	while (!StackEmpty(&st)) {
		printf("%d ", StackTop(&st));
		StackPop(&st);
	}

	StackDestory(&st);
}

void testQueue() {
	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);
	}
	QueueDestory(&q);
}
int main() {
	//testStack();
	testQueue();
	return 0;
}

如有错误,还请指正。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值