【数据结构】栈和队列

栈的概念及结构

栈是一种特殊的线性表。其只允许在固定一端操作数据。进行插入数据和删除数据的一端叫做栈顶,另一端叫做栈底。删除数据操作称为出栈,插入数据称为入栈,压栈,进栈。其进行数据操作遵循LIFO(last in first out) 后进先出的原则。

在这里插入图片描述

栈的实现

根据栈的性质,构建栈的方式用两种:

  1. 链表,插入数据就是头插操作。
  2. 数组,插入数据就是尾插。

这两种方法都可以实现栈。但是,选择数组会跟好一些,为什么?当我们进行插入数据时,链表需要找到最后一个结点,需要遍历一遍,数组直接根据下标找到最后一个位置。

如果在链表中加一个尾指针指向最后一个位置,似乎可以解决这个问题。但是,怎么让尾指针指向上个结点呢?那就要遍历一遍了,这样就增加负担了。所以,使用数组是较好的选择。

那么接下来用数组实现栈

//Stack.h
#pragma once

#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<malloc.h>
#include<stdlib.h>

typedef int STDataType;
#define CAPACITY 4
//栈的结构有数组,有效元素和容量构成
typedef struct Stack {
	int* s;
	int top;
	int capacity;
}ST;

void STInit(ST* ps);//栈的初始化
void STDestroy(ST* ps);//栈的销毁
bool STEmpty(ST* ps);//判断栈是否为空
void STPush(ST* ps, STDataType x);//进行插入数据
void STPop(ST* ps);//删除数据
STDataType STTop(ST* ps);//寻找栈顶的数据
int STSize(ST* ps);//查看栈有几个元素
//Stack.c
#define _CRT_SECURE_NO_WARNINGS

#include"Stack.h"

void STInit(ST* ps)
{
	assert(ps);//栈不能为空
	ps->s = (STDataType*)malloc(sizeof(STDataType) * CAPACITY);//malloc开辟空间
	if (ps->s == NULL)
	{
		perror("malloc fail");//开辟失败报错
		return;
	}
	ps->top = 0;//将top置为0,代表数据的个数。赋值为多少无所谓,看个人习惯
	ps->capacity = CAPACITY;//将容量先置为空
}

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

void STDestroy(ST* ps)
{
	assert(ps);
	free(ps->s);//释放空间
	ps->s = NULL;//同时将指针置为空
	ps->top = 0;
	ps->capacity = 0;
}

void STPush(ST* ps, STDataType x)
{
	assert(ps);
	//要先检查容量,判断为不为满,满了进行扩容
	//如果扩容步骤使用很多次,就可以写个扩容函数,只有一次就没必要
	if (ps->top == ps->capacity)
	{
	//realloc开辟空间失败会返回NULL,所以使用一个新的指针变量
		STDataType* new = (STDataType*)realloc(ps->s, sizeof(STDataType) * ps->capacity * 2);
		if (new == NULL)
		{
			perror("realloc fail");
			return;
		}
		ps->s = new;
		ps->capacity *= 2;//开辟成功,容量变为之前的两倍
	}
	ps->s[ps->top] = x;
	ps->top++;
}

void STPop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));//如果栈为空的话,就不能进行删除
	ps->top--;//直接将个数减一就行了
}

STDataType STTop(ST* ps)
{
	assert(ps);
	assert(!STEmpty(ps));
	return ps->s[ps->top - 1];//返回元素减一的数据
}

int STSize(ST* ps)
{
	assert(ps);
	return ps->top;//返回有效元素
}


//Test.c
#define _CRT_SECURE_NO_WARNINGS

#include"Stack.h"

int main()
{
	ST st;
	STInit(&st);
	STPush(&st, 1);
	STPop(&st);
	STDestroy(&st);
	return 0;
}

队列

队列的概念及结构

队列是一种特殊的线性结构。其只允许在一端插入数据,在另一端删除数据。插入数据操作称为入队列,该位置为队尾删除数据操作称为出队列,该位置为队头。队列的操作原则是FIFO(firtst in first out) 先进先出

在这里插入图片描述

队列的实现

构建队列方式有两种:

  1. 动态数组。
  2. 链表。

使用链表较好。队列的性质是先进先出,数组如何实现先出?后面元素依次向前覆盖,时间复杂度很大,而链表只需要记住头指针就行,必要时加上尾指针。

//Queue.h
#pragma once

#include<stdio.h>
#include<assert.h>
#include<malloc.h>
#include<stdbool.h>

typedef int QDataType;

//这是结点的结构,但是队列是FIFO,所以要记录头指针和尾指针,方便尾入和头出
typedef struct QueueNode {
	struct QueueNode* next;
	QDataType data;
}QNode;
//我们需要头指针和尾指针,尾指针进行插入数据时是需要的,就不用遍历了浪费时间。size记录元素个数很有必要,在一些函数有重大意义
//多个数据可以用结构体来构建
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);//队列的头部删除数据
bool QueueEmpty(Queue* pq);//判断队列为不为空
int QueueSize(Queue* pq);//知道队列有几个有效元素
QDataType QueueFront(Queue* pq);//返回队列的首个元素
QDataType QueueBack(Queue* pq);//返回队列的尾元素
//Queue.c
#define _CRT_SECURE_NO_WARNINGS

#include"Queue.h"

void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = pq->tail = NULL;//队列的初始化将两个指针置为空
	pq->size = 0;//有效元素为0
}

void QueueDestroy(Queue* pq)
{
	assert(pq);
	//将一个结点指针指向头节点,作为循环的条件
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* del = cur->next;//一个要删除的结点
		free(cur);
		//我这里写成了cur = cur->next,这是不对的,cur的内存已经释放了,还指向next,就是野指针。
		//因为这个细节,导致一个题目死活过不了。
		cur = del;//指向下一个结点
	}
	//销毁时及时将两个指针置为空,避免野指针的出现
	pq->head = pq->tail = NULL;
	pq->size = 0;
}

void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));//malloc申请新的结点
	if (newnode == NULL)
	{
		perror("malloc fail");//申请失败报错并返回
		return;
	}
	//如果为空队列,那么就要将指针同时指向新的结点
	if (pq->head == NULL)
	{
		pq->head = pq->tail = newnode;
	}
	else//这里没加else语句,那么if执行了的话就会混乱
	{
		pq->tail->next = newnode;
		pq->tail = newnode;//同时将尾指针指向新的结点
	}
	//将尾结点的next指向新的结点
	newnode->data = x;//新的结点进行赋值
	newnode->next = NULL;
	pq->size++;//有效元素进行加1
}

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));//空的队列就不能删除

 	QNode* del = pq->head;
	pq->head = pq->head->next;//将头指针指向下个结点
	free(del);//释放空间
	del = NULL;
	//这里要考虑只有一个节点的时候,tail可能会是野指针
	if (pq->head == NULL)
	{
		pq->tail = NULL;
	}
	pq->size--;//有效元素减1
}

bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->size == 0;//有效元素为0,那么就是空
}

int QueueSize(Queue* pq)
{
	assert(pq);
	return 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;//返回队尾数据
}
//Test.c
#define _CRT_SECURE_NO_WARNINGS

#include"Queue.h"

int main()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	//根据队列的性质,只能取队头打印,然后删除数据,才能打印下个队头
	printf("%d  ", q.head->data);
	QueuePop(&q);
	printf("%d  ", q.head->data);
	QueuePop(&q);
	printf("%d  ", q.head->data);
	QueuePop(&q);
	printf("%d  ", q.head->data);
	QueuePop(&q);
	QueueDestroy(&q);
	return 0;
}

实现了链表和顺序表的基础上,再来实现栈和队列就简单多了,

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值