数据结构与算法——栈和队列

发布于:2025-05-18 ⋅ 阅读:(23) ⋅ 点赞:(0)

概念与结构

栈:⼀种特殊的线性表,其只允许在固定的⼀端进⾏插⼊和删除元素操作。进⾏数据插⼊和删除操作的⼀端称为栈顶,另⼀端称为栈底。栈中的数据元素遵守后进先出的原则。(先进的后出,后进的先出)

压栈:栈的插⼊操作叫做进栈/压栈/⼊栈,⼊数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。

栈的底层结构选型:链表 or 数组?
  • 使用链表来说,栈顶是经常操作的一端,为了降低时间复杂度,将链表的头看作栈顶,尾看作栈底,入栈的时间复杂度则为O(1),出栈时间复杂度O(1)。

  • 使用数组来说,尾部看作栈顶,头部看作栈顶,入栈、出栈时间复杂度为O(1)。

他们的入栈、出栈操作时间复杂度都一样,但是我们选择数组更好,因为链表是独立的结点,每次操作都要申请一个新的结点,8个字节,空间的消耗更大,所以底层选择数组。所以物理、逻辑结构都是是线性的。

栈的实现

栈的初始化

首先定义栈的结构,然后定义一个初始化函数,因为对形参的改变需要影响实参,所以传址调用,且在函数中用一级指针接收。

头文件

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

//定义栈的结构
typedef int StackDataType;
typedef struct Stack {
	StackDataType* arr;
	int top;     //指向栈顶的位置
	int capacity;//栈的容量
}Stack;

//初始化
void StcakInit(Stack* ps);//形参的变化要影响实参,所以要传传址,用一级指针接受

实现文件

#include"Stack.h"

//初始化
void StcakInit(Stack* ps) //ps是形参,形参的初始化要改变实参,所以传址
{
	ps->arr = NULL;
	ps->top = ps->capacity = 0;
}

测试文件

#include"Stack.h"

void test01()
{
	Stack st;
	StackInit(&st);//传址,st是实参
}

int main()
{
	test01();
	return 0;
}

栈的销毁

//销毁栈
void StackDestroy(Stack* ps)
{
	if (ps->arr)
		free(ps->arr);
	ps->arr = NULL;
	ps->capacity = ps->top = 0;
}

判断栈是否为空

bool 函数是一种返回 布尔值(true 或 false) 的函数,通常用于逻辑判断。在 C 语言中,bool 类型需要包含 <stdbool.h> 头文件才能使用。
检查栈是否为空:

  • 如果 ps->top == 0(栈空),返回 true。

  • 否则返回 false。

//判断栈是否为空
bool StackEmpty(Stack* ps)
{
	assert(ps);
	return ps->top == 0; //如果 top == 0,返回 true(栈空);否则返回 false
}

入栈

定义一个入栈的函数,一个保存结构体地址的指针变量,一个需要插入的数据,因为进行了初始化,所以要进行增容,这里需要使用realloc(指向之前分配的内存块的指针,新的内存块大小(字节数)),然后将新增容的空间和重新分配内存块的大小赋值给原来的capacity和arr。

//插入数据-栈顶入数据
void StackPush(Stack* ps, StackDataType x)
{
	assert(ps);
	if (ps->top == ps->capacity)
	{
		//增容
		int newCapacity = ps->capacity = 0 ? 4 : 2 * ps->capacity;
		StackDataType * tmp = (StackDataType*)realloc(ps->arr, newCapacity * sizeof(StackDataType));
		if (tmp == NULL)
		{
			perror("relloc fail!");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = newCapacity;
	}
	ps->arr[ps->top++] = x;
}

出栈

因为栈为空返回的是true,所以这里要取反

//出栈-后进的先出,先进的后出
void StackPop(Stack* ps)
{
	assert(!StackEmpty(ps));
	--ps->top;
}

取栈顶元素

不同于出栈的是不会减少栈中的元素个数

//取栈顶元素
StackDataType* StcakTop(Stack* ps)
{
	assert(!StackEmpty(ps));
	return ps->arr[ps->top - 1];
}

栈中有效元素个数

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

队列

概念与结构

概念:只允许在⼀端进⾏插⼊数据操作,在另⼀端进⾏删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)

⼊队列:进⾏插⼊操作的⼀端称为队尾
出队列:进⾏删除操作的⼀端称为队头

在这里插入图片描述
队列的底层结构如何选型?

数组:选择最后为队尾时->入队O(1),出队O(N)    
     选择第一个数据为队尾时->入队O(N),出队O(1)
链表:单链表时,第一个结点为队头,尾结点为队尾时,入队O(N)(因为需要找尾结点),出队O(1)
     双向链表时,这时入队,出队的时间复杂度都为O(1),但是每个结点的空间消耗的太大。

优化单链表:在定义第一个结点的指针为phead的基础上,定义尾结点的指针为ptail,入队时候直接再ptail后面插入结点,这样也入队,出队时间复杂度都为O(1)。

队列的实现

队列结点结构

typedef int QNDataType;

//队列结点的结构
typedef struct QueueNode {
	QNDataType data;
	struct QueueNode* next;
}QNode;

队列结构

//队列的结构
typedef struct Queue {
	QNode* phead;
	QNode* ptail;
}Q;

初始化队列

//队列的初始化
void QueueInit(Q* pq)
{
	assert(pq);
	pq->phead = pq->ptail = NULL;
}

队列判空

//判空
bool QueueEmpty(Q* pq)
{
	assert(pq);
	return pq->phead == NULL;
}

销毁队列

pcur从头开始遍历(条件:pcur不为空),然后将pcur的下一个结点存起来。释放pcur,将存起来的结点赋给pcur,最后手动将phead和ptail置空。

//销毁队列
void QueueDestroy(Q* pq)
{
	assert(pq);
	QNode* pcur = pq->phead;
	while(pcur)
	{
		QNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	pq->phead = pq->ptail = NULL;
	//pq->size = 0;
}

入队列,队尾

队列不为空:pq->ptail->next = newnode,然后将pq->ptail = pq->ptail->next
在这里插入图片描述
为空的时候:
phead = ptail = newnode

//队尾入数据
void QueuePush(Q* pq, QNDataType x)
{
	assert(pq);
	//申请结点空间
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	//队列为空
	if (pq->phead == NULL)
	{
		pq->phead = pq->ptail = newnode;
	}
	else {
		pq->ptail->next = newnode;
	}
	pq->ptail = newnode;
}

出队列,队头

先将phead->next保存下来,然后释放phead,然后phead = phead->next ,只有一个结点(头,尾都指向同一个节点)phead和ptail都需要置空
在这里插入图片描述

//队头出数据
void QueuePop(Q* pq)
{
	//判空
	assert(!QueueEmpty(pq));
	//只有一个结点
	if (pq->phead == pq->ptail)
	{
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else {
		QNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
	}
}

取队头数据

//取队头数据
QNDataType QueueFront(Q* pq)
{
	assert(!QueueEmpty(pq));
	return pq->phead->data;
}

取队尾数据

//取队尾数据
QNDataType QueueBack(Q* pq)
{
	assert(!QueueEmpty(pq));
	return pq->ptail->data;
}

队列有效数据个数

第一种方式:遍历链表——适用于不会频繁调用队列有效数据个数的场景

//队列有效元素个数
int QueueSize(Q* pq)
{
	assert(pq);
	//第一种方式:遍历链表——适用于不会频繁调用队列有效数据个数的场景
	QNode* pcur = pq->phead;
	int size = 0;
	while (pcur)
	{
		size++;
		pcur = pcur->next;
	}
	return size;

	
}

第二种方式:遍历链表——适用于频繁调用队列有效数据个数的场景(完整代码)
//return pq->size;

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

typedef int QNDataType;

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

typedef struct Queue {
    QNode* phead;
    QNode* ptail;
    int size;  // 新增:记录队列元素个数
} Q;

void QueueInit(Q* pq);
bool QueueEmpty(const Q* pq);
void QueuePush(Q* pq, QNDataType x);
void QueuePop(Q* pq);
QNDataType QueueFront(const Q* pq);
QNDataType QueueBack(const Q* pq);
int QueueSize(Q* pq);  // 新增:O(1) 方式
void QueueDestroy(Q* pq);
#include "Queue.h"

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

bool QueueEmpty(const Q* pq) {
    assert(pq);
    return pq->size == 0;  // 直接判断 size
}

void QueuePush(Q* pq, QNDataType x) {
    assert(pq);
    QNode* newnode = (QNode*)malloc(sizeof(QNode));
    if (newnode == NULL) {
        perror("malloc fail!");
        exit(1);
    }
    newnode->data = x;
    newnode->next = NULL;

    if (pq->phead == NULL) {
        pq->phead = pq->ptail = newnode;
    } else {
        pq->ptail->next = newnode;
        pq->ptail = newnode;
    }
    pq->size++;  // 更新 size
}

void QueuePop(Q* pq) {
    assert(!QueueEmpty(pq));
    QNode* next = pq->phead->next;
    free(pq->phead);
    pq->phead = next;
    if (pq->phead == NULL) {
        pq->ptail = NULL;
    }
    pq->size--;  // 更新 size
}

QNDataType QueueFront(const Q* pq) {
    assert(!QueueEmpty(pq));
    return pq->phead->data;
}

QNDataType QueueBack(const Q* pq) {
    assert(!QueueEmpty(pq));
    return pq->ptail->data;
}

int QueueSize(Q* pq) {
    assert(pq);
    return pq->size;  // O(1) 直接返回
}

void QueueDestroy(Q* pq) {
    assert(pq);
    QNode* pcur = pq->phead;
    while (pcur) {
        QNode* next = pcur->next;
        free(pcur);
        pcur = next;
    }
    pq->phead = pq->ptail = NULL;
    pq->size = 0;  // 重置 size
}

网站公告

今日签到

点亮在社区的每一天
去签到