• [数据结构]栈和队列


    作者 华丞臧.
    专栏数据结构
    各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注)。如果有错误的地方,欢迎在评论区指出。
    在这里插入图片描述



    一、栈

    1.1 栈的概念及结构

    :一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作进行数据插入和删除操作的一段称为栈顶另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
    压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶
    出栈:栈的删除操作叫做出栈,出数据也在栈顶
    在这里插入图片描述

    1.2 栈的实现

    栈的实现一般可以是用数组或者链表实现。那么选用哪一种呢?

    • 根据栈的特殊性,即后进先出,只能在栈顶入数据,只能从栈顶出数据;那么使用数组就很好对栈进行入栈出栈,其操作的时间复杂度为O(1);而使用链表,对栈进行入栈出栈操作是在栈顶进行的,栈顶就是链表尾部,那我们知道对链表尾部的操作的时间复杂度为O(N);显然选择数组更加合理。
      在这里插入图片描述

    1.3 栈的接口

    // 支持动态增长的栈
    #pragma once
    #include 
    #include 
    #include 
    #include 
    
    typedef int STDataType;
    
    typedef struct Stack
    {
    	STDataType* data;  //数组
    	int top;   //栈顶
    	int capacity; //容量
    }Stack;
    
    
    //初始化栈
    void StackInit(Stack* ps);
    
    //销毁栈
    void StackDestroy(Stack* ps);
    
    //入栈
    void StackPush(Stack* ps, STDataType x);
    
    //出栈
    void StackPop(Stack* ps);
    
    //获取栈顶元素
    STDataType  StackTop(Stack* ps);
    
    //获取栈中有效元素个数
    int StackSize(Stack* ps);
    
    //检查栈是否为空,如果为空返回非0结果,如果不为空返回0
    bool StackEmpty(Stack* ps); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    1.3.1 初始化栈、销毁栈

    //初始化栈
    void StackInit(Stack* ps)
    {
    	assert(ps);
    
    	ps->data = NULL;
    	ps->capacity = ps->top = 0;
    }
    
    //销毁栈
    void StackDestroy(Stack* ps)
    {
    	assert(ps);
    
    	free(ps->data);
    	ps->data = NULL;
    	ps->capacity = ps->top = 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    1.3.2 入栈

    栈的特性是后进先出,栈顶是数组的尾部,栈底是数组的头部;对数据的操作只能在栈顶进行,入栈就是把数据存储在栈顶;既然用数组存放就需要考虑扩容的情况,那么为了达到既不浪费空间又不频繁扩容,每次扩容原先的两倍。

    //入栈
    void StackPush(Stack* ps, STDataType x)
    {
    	assert(ps);
    
    	//判断是否需要扩容
    	if (ps->top == ps->capacity)
    	{
    		int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
    		STDataType* tmp = (STDataType*)realloc(ps->data,sizeof(STDataType) * newCapacity);
    		if (tmp == NULL)
    		{
    			perror("malloc fail");
    			exit(-1);
    		}
    		ps->data = tmp;
    		ps->capacity = newCapacity;
    	}
    	ps->data[ps->top] = x;
    	++ps->top;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    1.3.3 出栈

    出栈即把栈顶的数据推出去,而定义栈的结构体中的成员变量top表示栈中有效元素的个数,top-1就可以理解为出栈一个元素。

    //出栈
    void StackPop(Stack* ps)
    {
    	assert(ps);
    	assert(!StackEmpty(ps));
    
    	--ps->top;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    1.3.4 栈顶元素

    栈顶元素即数组当中最后一个元素。

    //获取栈顶元素
    STDataType  StackTop(Stack* ps)
    {
    	assert(ps);
    	assert(!StackEmpty(ps));
    
    	return ps->data[ps->top - 1];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    1.3.5 元素个数

    结构体当中top表示栈中有效元素的个数,那么top的值就是元素个。

    //获取栈中有效元素个数
    int StackSize(Stack* ps)
    {
    	assert(ps);
    
    	return ps->top;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    1.3.6 判空

    top0表示栈为空。

    //检查栈是否为空,如果为空返回非0结果,如果不为空返回0
    bool StackEmpty(Stack* ps)
    {
    	assert(ps);
    
    	return ps->top == 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    1.3.7 测试

    #include "Stack.h"
    
    void Test1()
    {
    	Stack ps;
    	StackInit(&ps);
    
    	//压栈
    	StackPush(&ps, 1);
    	printf("%d ",StackTop(&ps));
    
    	//压栈
    	StackPush(&ps, 2);
    	printf("%d ", StackTop(&ps));
    
    	//压栈
    	StackPush(&ps, 3);
    	printf("%d ", StackTop(&ps));
    
    	//压栈
    	StackPush(&ps, 4);
    	printf("%d ", StackTop(&ps));
    	
    	//元数个数
    	printf("\nsize = %d\n", StackSize(&ps));
    
    	printf("\n");
    	//出栈
    	printf("%d ", StackTop(&ps));
    	StackPop(&ps);
    
    	//出栈
    	printf("%d ", StackTop(&ps));
    	StackPop(&ps);
    
    	//出栈
    	printf("%d ", StackTop(&ps));
    	StackPop(&ps);
    
    	//出栈
    	printf("%d ", StackTop(&ps));
    	StackPop(&ps);
    	
    	//元数个数
    	printf("\nsize = %d\n", StackSize(&ps));
    	
    	//销毁
    	StackDestroy(&ps);
    }
    
    int main()
    {
    	Test1();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    程序运行结果:
    在这里插入图片描述

    二、队列

    2.1 队列的概念及结构

    队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
    入队列:进行插入操作的一端称为队尾
    出队列:进行删除操作的一端称为队头
    在这里插入图片描述

    2.2 队列的实现

    队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,需要挪动数据,效率会比较低。

    2.2.1 队列的接口

    队列栈不同,队列在队尾入数据在队头出数据;也就是说在操作队列时,既要知道队头又要知道队尾,所以专门定义一个结构体Queue用来保存队列的头尾以及队列的元素个数,这样在对队列操作时,既不需要传二级指针也提高了效率。

    #pragma once
    #include 
    #include 
    #include 
    #include 
    
    typedef int QEDataType;
    
    typedef struct QueueNode
    {
    	struct QueueNode* next;
    	QEDataType data;
    }QNode;
    
    typedef struct Queue
    {
    	QNode* head;
    	QNode* tail;
    	int size;
    }Queue;
    
    //初始化队列
    void QueueInit(Queue* pq);
    
    //销毁队列
    void QueueDestroy(Queue* pq);
    
    //入队列
    void QueuePush(Queue* pq, QEDataType x);
    
    //出队列
    void QueuePop(Queue* pq);
    
    //队列头元素
    QEDataType QueueFront(Queue* pq);
    
    //队列尾元素
    QEDataType QueueBack(Queue* pq);
    
    //队列判空
    bool QueueEmpty(Queue* pq);
    
    //队列元素个数
    int QueueSize(Queue* pq);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    2.2.2 初始化队列

    队列初始化为空,队列元素个数为0。

    //初始化队列
    void QueueInit(Queue* pq)
    {
    	assert(pq);
    
    	pq->size = 0;
    	pq->head = pq->tail = NULL;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.2.3 销毁队列

    保持队列头尾的指针需要置为空指针

    //销毁队列
    void QueueDestroy(Queue* pq)
    {
    	assert(pq);
    
    	QNode* cur = pq->head;
    
    	while (cur)
    	{
    		QNode* tmp = cur;
    		cur = cur->next;
    		free(tmp);
    	}
    	pq->head = NULL;
    	pq->tail = NULL;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2.2.4 入队列

    入队列分为两种情况:

    1. 队列为空,这时需要改变Queue结构体中的headtail指针;
    2. 队列不为空,这时只需要改变tail指针即可。
    //入队列
    void QueuePush(Queue* pq, QEDataType 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->head == NULL)
    	{
    		pq->head = newNode;
    		pq->tail = newNode;
    	}
    	else
    	{
    		pq->tail->next = newNode;
    		pq->tail = newNode;
    	}
    	pq->size++;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    2.2.5 出队列

    在队列头部出数据,即释放队头结点并且改变head指针的指向;当出队列时,队列只有一个元素,操作完需要把headtail指针要置为NULL

    //出队列
    void QueuePop(Queue* pq)
    {
    	assert(pq);
    	assert(!QueueEmpty(pq));//判空
    
    	QNode* tmp = pq->head;
    	pq->head = pq->head->next;
    	free(tmp);
    	tmp = NULL;
    	pq->size--;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.2.6 队头和队尾元素

    headtail分别是指向队列头尾的指针,只需要返回这两个结点的数据即可。

    //队列头元素
    QEDataType QueueFront(Queue* pq)
    {
    	assert(pq);
    
    	return pq->head->data;
    }
    
    //队列尾元素
    QEDataType QueueBack(Queue* pq)
    {
    	assert(pq);
    
    	return pq->tail->data;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2.2.7 队列的判空和元素个数

    当头部指针为NULL时,队列为空;或者在结构当中定义一个整形的成员变量size用来计数,那么size为零队列为空。
    队列的元素个数为size

    //队列判空
    bool QueueEmpty(Queue* pq)
    {
    	assert(pq);
    
    	return pq->size == 0;
    }
    
    //队列元素个数
    int QueueSize(Queue* pq)
    {
    	assert(pq);
    
    	return pq->size;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2.2.8 测试

    #include "Queue.h"
    
    void Test2()
    {
    	Queue pq;
    	QueueInit(&pq);
    
    	//入队列
    	QueuePush(&pq, 1);
    	printf("%d ", QueueFront(&pq));
    	printf("tail = %d\n", QueueBack(&pq));//队尾元素
    
    	QueuePush(&pq, 2);
    	printf("%d ", QueueFront(&pq));
    	printf("tail = %d\n", QueueBack(&pq));//队尾元素
    
    	QueuePush(&pq, 3);
    	printf("%d ", QueueFront(&pq));
    	printf("tail = %d\n", QueueBack(&pq));//队尾元素
    
    	QueuePush(&pq, 4);
    	printf("%d ", QueueFront(&pq));
    	printf("tail = %d ",QueueBack(&pq));//队尾元素
    
    	printf("\nsize = %d\n", QueueSize(&pq));
    
    	//出队列
    	printf("%d ", QueueFront(&pq));
    	QueuePop(&pq);
    
    	printf("%d ", QueueFront(&pq));
    	QueuePop(&pq);
    
    	printf("\nsize = %d\n", QueueSize(&pq));
    
    	QueueDestroy(&pq);
    
    }
    
    int main()
    {
    	Test2();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    程序运行结果:
    在这里插入图片描述

  • 相关阅读:
    Linux进程间通信
    FreeSql 导入数据的各种场景总结 [C#.NET ORM]
    引领自主突围,睿赛德科技正式杀入车载OS赛道
    04-React脚手架 & 集成Axios
    ESXI7.0.0升级到ESXI7.0.3
    【前后缀统计】238. 除自身以外数组的乘积
    语义分割开源工具箱MMSegmentation安装及使用示例
    React18学习
    目标检测:Generalized Focal Loss(2020)
    OpenCV(三十八):二维码检测
  • 原文地址:https://blog.csdn.net/qq_59456417/article/details/127709389