【数据结构】-- 栈和队列

news2024/11/25 1:36:39

在这里插入图片描述

🐇

🔥博客主页: 云曦
📋系列专栏:数据结构

💨吾生也有涯,而知也无涯
💛 感谢大家👍点赞 😋关注📝评论

文章目录

  • 前言
  • 一、栈
    • 📙1.1 栈的概念及结构
    • 📙1.2 栈的实现
      • 📜1.2.1 栈的结构
      • 📜1.2.2 初始化栈
      • 📜1.2.3 入栈
      • 📜1.2.4 出栈
      • 📜1.2.5 获取栈顶元素
      • 📜1.2.6 获取栈中有效元素个数
      • 📜1.2.7 检测栈是否为空
      • 📜1.2.8 销毁栈
      • 📜1.2.9 栈所有接口的测试
  • 二、队列
    • 📙2.1 队列的概念及结构
    • 📙2.2 队列的实现
      • 📜2.2.1 队列的结构
      • 📜2.2.2 初始化队列
      • 📜2.2.3 队尾入队列
      • 📜2.2.4 检测队列是否为空
      • 📜2.2.5 队头出队列
      • 📜2.2.6 获取队列头部元素
      • 📜2.2.7 获取队列尾部元素
      • 📜2.2.8 获取队列中有效元素个数
      • 📜2.2.9 销毁队列
      • 📜2.2.10 队列所有接口的测试
  • 完整代码
    • 📙**Stack.h**
    • 📙**Stack.c**
    • 📙**Queue.h**
    • 📙**Queue.c**
    • 📙**test.c**

前言

在上期中我们把双向链表学完了,这期我们要开始新的一章 “栈和队列” 的讲解,栈和队列基本都是拿我们之前所学的顺序表或链表来实现,可谓是新瓶装旧酒,有了对顺序表和链表的认识,实现栈和队列并不是难事!

一、栈

📙1.1 栈的概念及结构

概念:栈也是一种特殊的线性表,其次栈只允许在固定一端插入删除,进行数据插入或删除操作的一端叫作栈顶,另一端为栈底。 栈中的数据元素遵守后进先出LIFO的原则。

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

结构:
栈结构
注意:栈如果用链表实现的话要考虑找前一个节点的问题,因为栈的插入删除都是在栈顶,所以实现栈时推荐用顺序表来实现,因为顺序表在尾部插入数据的代价比较小。

📙1.2 栈的实现

📜1.2.1 栈的结构

既然用顺序表实现那么栈的结构就是顺序表的结构

typedef int STDataType;

typedef struct Stack
{
	int* arr;
	int top;//栈顶
	int capacity;//容量
}ST;

📜1.2.2 初始化栈

因为在栈的结构里,插入数据只能在栈顶插入,所有只需要实现一个插入的接口即可,把malloc改到插入接口(push)里即可.

void STInit(ST* ps)
{
	assert(ps);
	ps->arr = NULL;
	ps->capacity = 0;
	ps->size = 0;
}

📜1.2.3 入栈

void STPush(ST* ps, STDataType x)
{
	assert(ps);
	//检测容量
	if (ps->top == ps->capacity)
	{
		//使用三目操作符来解决扩容问题
		int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(ps->arr, sizeof(STDataType)*newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		ps->arr = tmp;
		ps->capacity = newcapacity;
	}
	//入栈
	ps->arr[ps->top] = x;
	ps->top++;
}

需要注意的是:realloc有一个特性,如果传入的参数是NULL,则会像malloc一样开辟一个空间出来,相当于调用了malloc。

📜1.2.4 出栈

出栈很简单让栈顶减1即可。

void STPop(ST* ps)
{
	assert(ps);
	//栈为空就不能再删了
	assert(ps->top > 0);
	ps->top--;
}

需要注意的是:插入的指针有可能为空或者栈有可能为空,两个情况都要检查一下

📜1.2.5 获取栈顶元素

top是有效数据个数后一个的位置,减1就为栈顶的元素

STDataType STTop(const ST* ps)
{
	assert(ps);
	return ps->arr[ps->top-1];
}

📜1.2.6 获取栈中有效元素个数

top就是有效数据的个数,直接返回top即可

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

📜1.2.7 检测栈是否为空

返回top是否等于0,等于返回真,不等于返回假

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

📜1.2.8 销毁栈

注意:这里的销毁指得是把这块空间还给操作系统。

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

📜1.2.9 栈所有接口的测试

void TestStack()
{
	ST s;
	STInit(&s);
	STPush(&s, 1);
	STPush(&s, 2);
	STPush(&s, 3);
	STPush(&s, 4);
	STPush(&s, 5);
	printf("栈的有效元素个数%d\n", STSize(&s));
	while (!STEmpty(&s))
	{
		printf("%d ", STTop(&s));
		STPop(&s);
	}
	
	//STPop(&s);
	printf("\n栈的有效元素个数%d\n", STSize(&s));
	STDestroy(&s);

}

测试1

当栈为空还在删除时:
测试2

二、队列

📙2.1 队列的概念及结构

概念:队列也是一种只允许在一端插入数据,在另外一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO。

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

结构:
结构图解
注意:队列也能用顺序表和链表实现,但使用链表的结构实现更优一些,因为如果是顺序表的话,头删要挪移数据才能完成,效率比链表低,所以实现队列推荐用链表来实现。

📙2.2 队列的实现

📜2.2.1 队列的结构

既然使用队列实现,那么结构自然是链表的形式。

typedef int QueDataType;

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

typedef struct Queue
{
	QNode* phead;
	QNode* tail;
	int size;
}Que;

因为是单链表,出队列是头删,入队列是尾插,导致需要一个头指针和尾指针,且单链表计算长度要遍历一遍数组,这样下来:

  1. 要传头和尾的参数
  2. 计算链表长度要遍历链表,复杂度为O(N)
  • 不如直接把头尾指针和定义一个存储链表元素个数的变量整合为一个结构体这样就方便多了

📜2.2.2 初始化队列

void QueueInit(Que* pq)
{
	pq->phead = NULL;
	pq->tail = NULL;
	pq->size = 0;
}

只有一个插入的接口,那么就直接在插入接口获取节点就行。

📜2.2.3 队尾入队列

首先获取并初始化节点,然后就是尾插,尾插分为两种情况:

  1. 链表为空,特殊处理。
  2. 链表不为空,正常尾插。
void QueuePush(Que* pq, QueDataType x)
{
	//获取并初始化节点
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("newnode fail");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;

	//入队列
	if (pq->phead == NULL)
	{
		//链表为空
		pq->phead = newnode;
		pq->tail = newnode;
	}
	else
	{
		//链表不为空
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
	
	pq->size++;
}

📜2.2.4 检测队列是否为空

直接返回pq->phead == NULL,等于返回真,不等于返回假

bool QueueEmpty(Que* pq)
{
	assert(pq);
	return pq->phead == NULL;
}

📜2.2.5 队头出队列

单链表头删很简单,但要注意:链表为空时,不能再删了,否则会出问题

void QueuePop(Que* pq)
{
	assert(pq);
	//检测链表是否为空,为空就不能再删了
	assert(!QueueEmpty(pq));
	//出队列
	QNode* Next = pq->phead->next;
	free(pq->phead);
	pq->phead = Next;
	pq->size--;
}

📜2.2.6 获取队列头部元素

phead指向的就是头节点,返回头节点的元素即可,但要注意:链表有可能为空,所以要检测一下

QueDataType QueueFront(Que* pq)
{
	assert(pq);
	//检测链表是否为空
	assert(!QueueEmpty(pq));

	return pq->phead->data;
}

📜2.2.7 获取队列尾部元素

tail指向的就是尾节点,直接返回尾节点的元素即可,但要注意:链表有可能为空,所以要检测一下

QueDataType QueueBack(Que* pq)
{
	assert(pq);
	//检测链表是否为空
	assert(!QueueEmpty(pq));

	return pq->tail->data;
}

📜2.2.8 获取队列中有效元素个数

size存储的就是队列的有效元素个数,返回size即可。

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

📜2.2.9 销毁队列

  • 这里的销毁指的也是把这片空间还给操作系统
  • 遍历一遍队列,记录下一个节点的位置,释放掉当前节点,最后将头尾指针置空,size置为0即可。
void QueueDestroy(Que* pq)
{
	assert(pq);
	QNode* cur = pq->phead;
	while (cur)
	{
		QNode* Next = cur->next;
		free(cur);
		cur = Next;
	}
	pq->phead = NULL;
	pq->tail = NULL;
	pq->size = 0;
}

📜2.2.10 队列所有接口的测试

TestQueue()
{
	Que obj;
	QueueInit(&obj);
	QueuePush(&obj, 1);
	QueuePush(&obj, 2);
	QueuePush(&obj, 3);
	QueuePush(&obj, 4);
	QueuePush(&obj, 5);
	printf("队尾元素:%d\n", QueueBack(&obj));
	printf("队列的有效数据个数:%d\n", QueueSize(&obj));
	while (!QueueEmpty(&obj))
	{
		printf("%d ", QueueFront(&obj));
		QueuePop(&obj);
	}

	//QueuePop(&obj);
	printf("\n队列的有效数据个数:%d\n", QueueSize(&obj));
	QueueDestroy(&obj);
}

测试1

当队列为空还在删时:
测试2

完整代码

*

《栈》

📙Stack.h

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

//栈
typedef int STDataType;

typedef struct Stack
{
	int* arr;
	int top;//栈顶
	int capacity;//容量
}ST;

//初始化栈
void STInit(ST* ps);
//入栈
void STPush(ST* ps, STDataType x);
//出栈
void STPop(ST* ps);
//获取栈顶元素
STDataType STTop(const ST* ps);
//获取栈中有效元素的个数
int STSize(const ST* ps);
//检测栈是否为空
bool STEmpty(const ST* ps);
//销毁栈
void STDestroy(ST* ps);

📙Stack.c

#include "Stack.h"

void STInit(ST* ps)
{
	assert(ps);
	ps->arr = NULL;
	ps->capacity = 0;
	ps->top = 0;
}

void STPush(ST* ps, STDataType x)
{
	assert(ps);
	//检测容量
	if (ps->top == ps->capacity)
	{
		//使用三目操作符来解决扩容问题
		int newcapacity = ps->capacit==0 ? 4 : ps->capacity*2;
		STDataType* tmp = (STDataType*)realloc(ps->arr, 
						sizeof(STDataType)*newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		ps->arr = tmp;
		ps->capacity = newcapacity;
	}
	//入栈
	ps->arr[ps->top] = x;
	ps->top++;
}

void STPop(ST* ps)
{
	assert(ps);
	//栈为空就不能再删了
	assert(ps->top > 0);
	ps->top--;
}

STDataType STTop(const ST* ps)
{
	assert(ps);
	return ps->arr[ps->top-1];
}

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

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

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

*

《队列》

📙Queue.h

#pragma once

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

//队列
typedef int QueDataType;

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

typedef struct Queue
{
	QNode* phead;
	QNode* tail;
	int size;
}Que;

//初始化队列
void QueueInit(Que* pq);
//入队列
void QueuePush(Que* pq, QueDataType x);
//检测队列是否为空
bool QueueEmpty(Que* pq);
//出队列
void QueuePop(Que* pq);
//获取队列头部元素
QueDataType QueueFront(Que* pq);
//获取队列尾部元素
QueDataType QueueBack(Que* pq);
//获取队列中有效元素的个数
int QueueSize(Que* pq);
//销毁队列
void QueueDestroy(Que* pq);

📙Queue.c

#include"Queue.h"

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

void QueuePush(Que* pq, QueDataType x)
{
	assert(pq);
	//获取并初始化节点
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("newnode fail");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;

	//入队列
	if (pq->phead == NULL)
	{
		//链表为空
		pq->phead = newnode;
		pq->tail = newnode;
	}
	else
	{
		//链表不为空
		pq->tail->next = newnode;
		pq->tail = newnode;
	}

	pq->size++;
}

bool QueueEmpty(Que* pq)
{
	assert(pq);
	return pq->phead == NULL;
}

void QueuePop(Que* pq)
{
	assert(pq);
	//检测链表是否为空,为空就不能再删了
	assert(!QueueEmpty(pq));
	//出队列
	QNode* Next = pq->phead->next;
	free(pq->phead);
	pq->phead = Next;
	pq->size--;
}

QueDataType QueueFront(Que* pq)
{
	assert(pq);
	//检测链表是否为空
	assert(!QueueEmpty(pq));

	return pq->phead->data;
}

QueDataType QueueBack(Que* pq)
{
	assert(pq);
	//检测链表是否为空
	assert(!QueueEmpty(pq));

	return pq->tail->data;
}

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

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

*

《测试》

📙test.c

#include"Stack.h"
#include"Queue.h"

void TestStack()
{
	ST s;
	STInit(&s);
	STPush(&s, 1);
	STPush(&s, 2);
	STPush(&s, 3);
	STPush(&s, 4);
	STPush(&s, 5);
	printf("栈的有效元素个数%d\n", STSize(&s));
	while (!STEmpty(&s))
	{
		printf("%d ", STTop(&s));
		STPop(&s);
	}

	//STPop(&s);
	printf("\n栈的有效元素个数%d\n", STSize(&s));
	STDestroy(&s);

}

TestQueue()
{
	Que obj;
	QueueInit(&obj);
	QueuePush(&obj, 1);
	QueuePush(&obj, 2);
	QueuePush(&obj, 3);
	QueuePush(&obj, 4);
	QueuePush(&obj, 5);
	printf("队尾元素:%d\n", QueueBack(&obj));
	printf("队列的有效数据个数:%d\n", QueueSize(&obj));
	while (!QueueEmpty(&obj))
	{
		printf("%d ", QueueFront(&obj));
		QueuePop(&obj);
	}

	//QueuePop(&obj);
	printf("\n队列的有效数据个数:%d\n", QueueSize(&obj));
	QueueDestroy(&obj);
}

int main()
{
	TestStack();
	TestQueue();

	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/874626.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

windows程序基础

一、windows程序基础 1. Windows程序的特点 1.用户界面统一、友好 2.支持多任务:允许用户同时运行多个应用程序(窗口) 3.独立于设备的图形操作 使用图形设备接口( GDI, Graphics Device Interface )屏蔽了不同硬件设备的差异&#xff0c;提供了设备无关的图形输出能力…

“新基建”重新定义大数据安全

摘要&#xff1a;数字信息经济发展时代,大数据已逐渐成为最新和最重要的生产要素。国家大力支持推进“新基建”发展战略,由此带来的大数据安全挑战俨然愈发严峻。“新基建”重新定义了大数据安全&#xff0c;面对层出不穷的网络安全事件,需要构建主动安全防御体系。 关键词:新…

前端开发环境搭建,换新电脑前端开发的步骤,node环境配置

一、下载安装nodeJS 1、下载地址官方网址 NodeJs 2、下载完成后&#xff0c;双击“node-v11.5.0-x64.msi”&#xff0c;开始安装Node.js&#xff0c;一路next&#xff0c;注意&#xff1a;安装目录看自己需求&#xff0c;本人安装在D:\nodejs目录下 3、测试 键盘按下【winR…

Kotlin读写分离CopyOnWriteArrayList

Kotlin读写分离CopyOnWriteArrayList 基于读写分离思想Copy-On-Write(COW)设计的线程安全ArrayList变体&#xff0c;读读共享、写写互斥、读写互斥、写读互斥。读时直接读&#xff0c;不用加锁同步&#xff0c;线程安全。写/删/修改数据时复制一个副本&#xff0c;在新的List副…

挣钱和赚钱是两个概念

挣钱和赚钱虽然听起来很相似&#xff0c;但实际上是两个完全不同的概念。 挣钱通常指的是通过劳动或者服务换取报酬&#xff0c;比如说工资、兼职、打零工等等。而赚钱则是指通过投资、创业等方式获得利润或者收益。 挣钱是一种被动的收入方式&#xff0c;需要付出时间和劳动才…

政策因子条件列表类型

type:1&#xff08;多选下拉输入&#xff09; 目前选项是本地写死得&#xff0c;传参拼接了carOwner这些东西&#xff0c;形式是多选输入 type:2 &#xff08;普通指定形式&#xff09; type:3&#xff08;普通区间形式&#xff09; type:4 (指定输入) 指定除外得特殊一种&…

Chrome浏览器安装Axure插件无法打开本地axure文件

Chrome浏览器安装Axure插件无法打开本地axure文件 Chrome浏览器按照axure插件即可打开 1&#xff0c;下载axure插件&#xff0c;由于Chrome国内连不了商店&#xff0c;只能另外下载 https://download.csdn.net/download/u013303534/88204888 2&#xff0c;Chrome安装插件&#…

c++11 标准模板(STL)(std::basic_stringbuf)(二)

定义于头文件 <sstream> template< class CharT, class Traits std::char_traits<CharT>, class Allocator std::allocator<CharT> > class basic_stringbuf : public std::basic_streambuf<CharT, Traits> std::basic_stringbuf…

从零开始学习 Java:简单易懂的入门指南之抽象类接口内部类(十一)

面向对象进阶&#xff08;抽象类&接口&内部类&#xff09; 第一章 抽象类1.1 概述1.1.1 抽象类引入 1.2 abstract使用格式1.2.1 抽象方法1.2.2 抽象类1.2.3 抽象类的使用 1.3 抽象类的特征1.4 抽象类的细节1.5 抽象类存在的意义 第二章 接口2.1 概述2.2 定义格式2.3 接…

排序算法——基数排序(C语言)

基数排序的概念&#xff1a; 什么是基数排序&#xff1f;&#xff1f;&#xff1f;基数排序是一种和快排、归并、希尔等等不一样的排序...它不需要比较和移动就可以完成整型的排序。它是时间复杂度是O&#xff08;K*N&#xff09;&#xff0c;空间复杂度是O&#xff08;KM&…

【C语言】memcpy()函数

目录 一.memcpy()函数简介 1.函数功能 2.函数参数 1>.void * destination 2>.const void * source 3>.size_t num 3.函数返回值 4.函数头文件 二.memcpy()函数的具体使用 1.使用memcpy()函数完成拷贝整型数组数据 2.使用memcpy()函数拷贝结构体数据 三.模…

善于用兵的人,军队粮草取自敌人

善于用兵的人&#xff0c;军队粮草取自敌人 【安志强趣讲《孙子兵法》第8讲】 【原文】 善用兵者&#xff0c;役不再籍&#xff0c;粮不三载&#xff1b;取用于国&#xff0c;因粮于敌&#xff0c;故军食可足也。 【注释】 役不再籍&#xff1a;役&#xff0c;兵役&#xff1b;…

横向移动-域控提权

横向移动-域控提权 CVE-2021-42287 由于Active Directory没有对域中计算机和服务器账号进行验证&#xff0c;经过身份验证的攻击者利用该漏洞绕过完全限制&#xff0c;可将域中普通用户权限提升为域管理员权限并执行任意代码。 利用条件 前提条件&#xff1a;一个域内普通账…

js jquery写一个画板 实现撤回、清空、换色的功能

画布的canvas画板的大小就是这个画板图片的大小 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><metaname"viewport&qu…

鸿蒙剥离 AOSP 不兼容 Android 热门问题汇总,不吹不黑不吵

上周发了一篇 《鸿蒙终于不套壳了&#xff1f;纯血 HarmonyOS NEXT 即将到来》的相关资讯&#xff0c;没想到大家「讨&#xff08;fa&#xff09;论&#xff08;xie&#xff09;」的热情很高&#xff0c;莫名蹭了一波流量&#xff0c;虽然流量对我来说也没什么用&#xff0c;但…

HOT82-杨辉三角

leetcode原题链接&#xff1a;杨辉三角 题目描述 给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]示例…

python什么版本比较稳定,python哪个版本用得最多

这篇文章主要介绍了哪个版本的python最好用&#xff0c;具有一定借鉴价值&#xff0c;需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获&#xff0c;下面让小编带着大家一起了解一下。 想学习Python的人都会有一个困惑&#xff0c;那就是Python目前有两个版本Python2和…

windows下dll文件的创建详细教程

1、前言 dll文件是啥&#xff0c;就不作过多赘述了。现在直接教大家如何创建与使用dll文件。 本文基于windows系统&#xff0c;使用的编译相关工具为visual studio 2019。 2、创建dll 2.1 创建dll工程 首先打开visual studio&#xff0c;然后选择创建新项目&#xff0c;在搜…

11、Nvidia显卡驱动、CUDA、cuDNN、Anaconda及Tensorflow Pytorch版本

Nvidia显卡驱动、CUDA、cuDNN、Anaconda及Tensorflow-GPU版本 一、确定版本关系二、安装过程1.安装显卡驱动2、安装CUDA3、安装cudnn4、安装TensorFlow5、安装pytorch 三、卸载 一、确定版本关系 TensorFlow Pytorch推出cuda和cudnn的版本&#xff0c;cuda版本推出驱动可选版本…

【软件测试】Linux环境下Docker搭建+Docker搭建MySQL服务(详细)

目录&#xff1a;导读 前言 一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Linux之docker搭…