数据结构之,栈与队列

数据结构之,栈与队列

1、栈

1.1、什么是" 栈 "

栈,是一种特殊的线性表。

栈,只能在该结构固定的一端,实现数据的添加,与删除。这一端,也叫做栈顶。对应的,另一端叫做栈底。

往栈中插入数据,叫做入栈 / 压栈 / 进栈。插入数据在栈顶。如图:

删除栈中的数据,叫做出栈。删除数据也在栈顶。如图:

栈中元素的改变,遵循后进先出( Last In First Out )的原则。

1.2、栈的实现

1.2.1、栈的定义

栈的元素,可以用类似于顺序表元素的结构体,也可以用类似于链表节点的结构体。

如果用类似链表的结构:

c 复制代码
struct stack
{
	int data;
	struct stack* next;
};

我们在之前的学习中就知道,链表头删、头插的时间复杂度为O(1)

那么在栈中,为了降低时间复杂度,我们只能把第一个栈元素,所在的一端,当作栈顶。这样一来,时间复杂度也为O(1)

但是,如果要插入新的栈元素,每次就要申请一个栈元素(结构体)类型大小的空间。

如果用类似顺序表的结构:

c 复制代码
struct stack
{
	int* arr;
	int size;
	int capacity;
};

结合之前所学,我们只能将数组的末端,作为栈顶,以降低时间复杂度( O(1) )。

然而,如果我们想在栈中插入数据,只需申请对应数据元素类型大小的空间。

随着要插入数据的不断增多,这种利用数组创建栈元素的方法,在申请次数和开辟空间大小上,将会比上一个结构更少。

所以,我们采用的创建栈的方法:

c 复制代码
typedef int STADataType;
typedef struct stack
{
	STADataType* arr;
	int top;         //栈顶位置
	int capacity;    //最大容量
}stack;

1.2.2、栈的初始化

由于栈存在空间不够要扩容,导致栈本身(连同栈中的数组一起)(的地址)改变,那么我们像操作函数中,输入的是栈的地址。

代码演示:

c 复制代码
void STAInit(stack* pst)
{
	assert(pst);
	pst->arr = NULL;
	pst->top = pst->capacity = 0;
}

1.2.3、栈的销毁

思路:

  1. 首先判断数组成员是否为NULL,再释放空间
  2. 各项参数初始化

代码演示:

c 复制代码
void STADestroy(stack* pst)
{
	if (pst->arr)
		free(pst->arr);
	pst->arr = NULL;
	pst->top = pst->capacity = 0;
}

1.2.4、入栈

已知栈顶为数组末尾。

思路:

  1. 判断指针有效性
  2. 判断是否需要扩容。如果需要:
    • 重新确定最大容量capacity
      • 如果为0,赋值一个初始值(比如4)
      • 如果不为0,2倍扩容
    • realloc()
    • 更改参数
  3. 先赋值,后top++。

代码实现:

c 复制代码
void STAPush(stack* pst, STADataType val)
{
	assert(pst);
	if (pst->top == pst->capacity)
	{
		int newcapacity = (capacity == 0) ? 4 : 2*capacity;
		STADataType* tmp = (STADataType*)realloc(newcapacity * sizeof(STADataType));
		if (tmp == NULL)
		{
			perror("realloc failed\n");
			exit(1);
		}
		pst = tmp;
		pst->capacity = newcapacity;
	}
	pst->arr[pst->top++] = val;
}

1.2.5、判断栈是否为空

在出栈之前,我们有必要判断当前栈是否可以出栈,即判断栈是否为空。

判断的依据是top是否为0。这里我们借助布尔类型值,更方便理解。

代码演示:

c 复制代码
#include<stdbool.h>
bool STAEmpty(stack* pst)
{
	assert(pst);
	return (pst->top == 0);
}

这里如果栈为空,返回ture;否则返回false

1.2.6、出栈

思路:

  1. 判断栈是否为空
  2. 如果不为空,直接top--就可以。这个位置还有有效值,无需担心,后续添加一个新元素就可以覆盖这个位置。

代码演示:

c 复制代码
void STAPop(stack* pst)
{
	assert(!STAEmpty(pst));
	pst->top--;
}

1.2.7、取栈顶元素

很简单,就是返回top下标的前一个下标对应的元素:

c 复制代码
STADataType STATop(stack* pst)
{
	assert(!STAEmpty(pst));
	return pst->arr[pst->top - 1];
}

1.2.8、获取栈中有效元素个数

就是返回top

c 复制代码
int STASize(stack* pst)
{
	assert(pst);
	return pst->top;
}

2、队列

提到了栈,就不得不提到队列。

2.1、什么是队列

队列,也是线性表的一种。

队列,只能在其一端插入数据,而在另一端删除数据。

插入数据的操作,为入队列,入队列的一端为队尾。

删除数据的操作,为出队列,出队列的一端为队头。

2.2、队列的实现

2.2.1、队列的定义

这时,我们又回到了类似的问题:

实现队列,是用数组呢,还是用链表呢?

从空间复杂度看,

如果我们使用数组,那么,入队列和出队列,必有一种操作,其空间复杂度为O(N)(遍历数组,挪数腾位),似乎不太理想。

而如果我们使用链表,只要我们能够实现快速找到队头节点,和队尾节点的方法,我们就能够打下空间复杂度:

c 复制代码
typedef int QueueDataType;
typedef struct ListNode
{
	QueueDataType data;
	struct ListNode* next;
}ListNode;
typedef struct Queue
{
	ListNode* phead;//定位队头节点
	ListNode* ptail;//定位队尾节点
	int size;       //统计节点个数
}Queue;

2.2.2、队列的初始化

代码演示:

c 复制代码
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->size = 0;
	pq->phead = pq->ptail = NULL;
}

2.2.3、入队列

入队列,有点像链表的尾插:

  1. 创建新节点
  2. ptail后插入
  3. size++

只不过,我们还要另外判断一下,当前节点是否为空。

代码演示:

c 复制代码
void QueuePush(Queue* pq, QueueDataType val)
{
	assert(pq);
	//创建新节点
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	if (newnode == NULL)
	{
		perror("malloc failed\n");
		exit(1);
	}
	newnode->data = val;
	newnode->next = NULL;
	//判断节点是否为空
	if (pq->phead == NULL)
	{
		pq->phead = pq->ptail = newnode;
	}else{
		pq->ptail = newnode;
		pq->ptail = pq->ptail->next;
	}
	//节点个数记得更改
	pq->size++;
}

2.2.4、判断队列是否为空

在出队列之前,有必要判断队列是否可以删除数据。

判断的依据是,phead是否为NULL,或者size是否为0:

c 复制代码
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->size == 0;
}

2.2.5、出队列

phead所在位置为队首。

先存下一个节点,然后释放,再更新、phead

c 复制代码
void QueuePop(Queue* pq)
{
	//判断是否可删
	assert(!QueueEmpty(pq));
	//判断是否只剩下一个节点
	if (pq->phead == pq->ptail)
	{
		//此时,头尾指向同一个节点,就全部初始化
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}else{
		//正常出队列
		QueueNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
	}
	//节点个数记得更改
	pq->size--;
}

2.2.6、返回队首和队尾

首先也是要判断队列是否为空。

然后返回pheadptail中对应的数据即可。

返回队首:

c 复制代码
QueueDataType QueueHead(Queue* pq)
{
	assert(!QueueEmpty(pq));
	return pq->phead->data;
}

返回队尾:

c 复制代码
QueueDataType QueueTail(Queue* pq)
{
	assert(!QueueEmpty(pq));
	return pq->ptail->data;
}

2.2.7、返回节点个数

这一步操作,回答了我们为什么要在队列中添加size成员。

即降低时间复杂度。

c 复制代码
int QueueSize(Queue* pq)
{
	assert(pq);
	return pq->size;
}

2.2.8、销毁队列

这里的队列,由一个个节点组成。那么我们就可以利用之前销毁链表的做法:

  1. 存放下一个节点
  2. 释放当前节点
  3. 节点向前

代码演示:

c 复制代码
void QueueDestroy(Queue* pq)
{
	assert(pq);
	//遍历
	QueueNode* pcur = pq->phead;
	while (pcur)
	{
		QueueNode* next = pcur->next;
		free(pcur);
		pcur = pcur->next;
	}
	//出循环后,记得头尾指针及时置空
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

3、完整代码

栈:

c 复制代码
//stack.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<errno.h>
#include<stdbool.h>

//定义栈
typedef int STADataType;
typedef struct stack
{
	STADataType* arr;
	int top;         //定义栈顶的位置
	int capacity;    //定义最大容量
}stack;

//栈的初始化
void StackInit(stack* pst);

//栈的销毁
void StackDestroy(stack* pst);

//入栈
void StackPush(stack* pst, STADataType val);

//判断栈是否为空
bool STAEmpty(stack* pst);

//出栈
void StackPop(stack* pst);

//取栈顶元素
STADataType STATop(stack* pst);

//获取栈中有效元素个数
int STASize(stack* pst);
c 复制代码
//stack.c
#include"stack.h"

//栈的初始化
void StackInit(stack* pst)
{
	//判断
	assert(pst);
	//各个成员初始化
	pst->arr = NULL;
	pst->capacity = pst->top = 0;
}

//栈的销毁
void StackDestroy(stack* pst)
{
	//首先判断数组成员是否为NULL,再释放
	if (pst->arr)
		free(pst->arr);
	pst->arr = NULL;
	pst->capacity = pst->top = 0;
}

//入栈
void StackPush(stack* pst, STADataType val)
{
	//判断
	assert(pst);
	//首先判断空间够不够,不够,扩容
	//扩容前,还先判断capacity是否为0
	if (pst->top == pst->capacity)
	{
		int newcapacity = (pst->capacity == 0) ? 4 : 2 * pst->capacity;
		STADataType* tmp = (STADataType*)realloc(pst->arr, newcapacity * sizeof(STADataType));
		//申请失败的判断
		if (tmp == NULL)
		{
			perror("realloc falled\n");
			exit(1);
		}
		pst->arr = tmp;
		pst->capacity = newcapacity;
	}
	//放数
	pst->arr[pst->top++] = val;//先放数,后top++
}

//判断栈是否为空
bool STAEmpty(stack* pst)
{
	assert(pst);
	return pst->top == 0;
}

//出栈
void StackPop(stack* pst)
{
	//判断当前栈是否可删
	assert(!STAEmpty(pst));
	pst->top--;
}

//取栈顶元素
STADataType STATop(stack* pst)
{
	assert(pst);
	return pst->arr[pst->top - 1];
}

//获取栈中有效元素个数
int STASize(stack* pst)
{
	assert(pst);
	return pst->top;
}
c 复制代码
//test.c
#include"stack.h"

void test0()
{
	stack st;
	StackInit(&st);
	StackPush(&st, 1);
	StackPush(&st, 2);
	StackPush(&st, 3);
	StackPush(&st, 4);
	StackPush(&st, 5);
	//StackPop(&st);
	//printf("%d\n", STATop(&st));
	//printf("%d\n", STASize(&st));
}

int main()
{
	test0();

	return 0;
}

队列:

c 复制代码
//Queue.h
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

//利用节点创建队列,就定义节点与队列
typedef int QueueDataType;
typedef struct QueueNode
{
	QueueDataType data;
	struct QueueNode* next;
}QueueNode;
typedef struct Queue
{
	QueueNode* phead;
	QueueNode* ptail;
	int size;
}Queue;

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

//入队列
void QueuePush(Queue* pq, QueueDataType val);

//判断队列是否为空
bool QueueEmpty(Queue* pq);

//出队列
void QueuePop(Queue* pq);

//获取队首元素
QueueDataType QueueHead(Queue* pq);

//获取队尾元素
QueueDataType QueueTail(Queue* pq);

//返回队列有效节点个数
int QueueSize(Queue* pq);

//销毁队列
void QueueDestroy(Queue* pq);
c 复制代码
//Queue.c
#include"Queue.h"

//队列初始化
void QueueInit(Queue* pq)
{
	//判断
	assert(pq);
	//置空
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}


//入队列
void QueuePush(Queue* pq, QueueDataType val)
{
	//判断
	assert(pq);
	//创建新节点,节点在堆上申请
	QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
	//判断是否申请失败
	if (newnode == NULL)
	{
		perror("malloc failed\n");
		exit(1);
	}
	newnode->data = val;
	newnode->next = NULL;
	//入队列之前,判断队列是否为空
	if (pq->phead == NULL)
	{
		//为空,注意头尾和一
		pq->phead = pq->ptail = newnode;
	}
	else {
		//否则,正常入队列
		pq->ptail->next = newnode;
		pq->ptail = pq->ptail->next;
	}
	pq->size++;
}

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

//出队列
void QueuePop(Queue* pq)
{
	//首先判断能不能出队列
	assert(!QueueEmpty(pq));
	//如果能
	//先判断是否只剩下一个节点
	if (pq->phead == pq->ptail)
	{
		//直接释放,置空
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}else{
		//先存下第一个节点的下一个节点
		QueueNode* next = pq->phead->next;
		//再释放
		free(pq->phead);
		//最后,更新第一个节点
		pq->phead = next;
	}
	pq->size--;
}

//获取队首元素
QueueDataType QueueHead(Queue* pq)
{
	//首先判断队列是否为空
	assert(!QueueEmpty(pq));
	//然后返回
	return pq->phead->data;
}

//获取队尾元素
QueueDataType QueueTail(Queue* pq)
{
	//首先判断队列是否为空
	assert(!QueueEmpty(pq));
	//然后返回
	return pq->ptail->data;
}

//返回队列有效节点个数
int QueueSize(Queue* pq)
{
	assert(pq);
	//可以直接遍历,但是时间复杂度较大
	//可以在队列的定义中,加入统计节点个数的变量size,每次操作过后再变化
	return pq->size;
}

//销毁队列
void QueueDestroy(Queue* pq)
{
	assert(pq);
	//遍历,存下一个节点,释放前一个结点
	QueueNode* pcur = pq->phead;
	while (pcur)
	{
		QueueNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	//野指针置空
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}
c 复制代码
//test.c
#include"Queue.h"

int main()
{
	Queue queue;
	QueueInit(&queue);
	QueuePush(&queue, 1);
	QueuePush(&queue, 2);
	QueuePush(&queue, 3);
	QueuePush(&queue, 4);

	QueuePop(&queue);
	//QueuePop(&queue);
	//QueuePop(&queue);
	//QueuePop(&queue);
	//QueuePop(&queue);

	printf("%d\n", QueueHead(&queue));
	printf("%d\n", QueueTail(&queue));
	printf("%d\n", QueueSize(&queue));

	QueueDestroy(&queue);

	return 0;
}
相关推荐
MOONICK4 小时前
数据结构——哈希表
数据结构·哈希算法·散列表
FMRbpm6 小时前
链表5--------删除
数据结构·c++·算法·链表·新手入门
努力学习的小全全7 小时前
【CCF-CSP】05-01数列分段
数据结构·算法·ccf-csp
遗憾是什么.8 小时前
数据结构 -- 栈
数据结构·算法·链表
liuhuapeng03048 小时前
GetMapping自动截取List<String>字符
数据结构·windows·list
仰泳的熊猫9 小时前
1013 Battle Over Cities
数据结构·c++·算法·pat考试
AI科技星10 小时前
宇宙膨胀速度的光速极限:基于张祥前统一场论的第一性原理推导与观测验证
数据结构·人工智能·经验分享·python·算法·计算机视觉
liu****12 小时前
16.udp_socket(三)
linux·开发语言·数据结构·c++·1024程序员节
Rock_yzh13 小时前
LeetCode算法刷题——49. 字母异位词分组
数据结构·c++·学习·算法·leetcode·职场和发展·哈希算法