• 数据结构<4>栈和队列——原理+实现


    该博客某些图片来自 51CTO博主

    栈是一种先进后出(FILO)的数据结构,栈的实现可以使用链表实现和数组实现。栈只能在一端插入和删除数据,这一端叫做栈顶,另一端就是栈底。如下图:
    在这里插入图片描述

    每次入栈的数据都会在栈顶。因此还需要一个top指针来维护栈顶的数据。
    在这里插入图片描述

    栈的模拟实现

    了解了栈的结构,栈的模拟实现有两种方法:
    1,使用数组模拟,因为数组在尾部的插入和删除效率很高,缺点是内存不够了需要增容。
    2,使用链表模拟,将链表的头看作是栈顶,每次入栈是头插,出栈是头删,效率也很不错,相比于内存不够了就申请空间,链表是每次插入都会申请一个空间。
    如果想要使用链表的尾部作为栈顶,那么就需要使用双向链表,方便在删除的时候找到前一个数据。

    这两种结构都可以实现栈,但是这里使用的是数组来模拟。
    因为使用数组的效率会更高一点,这里涉及到了缓存命中率和CPU预加载的问题。在《深入理解计算机系统》这本书内有详细的记载。

    下面来看一下实现的代码:

    //stack.h
    /*
    静态栈的结构体代码
    #define N 100;
    typedef int STDatatype;
    typedef struct stack
    {
    	STDatatype _a[N];
    	int _top;
    	int _capacity;
    }ST;
    */
    
    typedef int STDatatype;
    typedef struct stack
    {
    	STDatatype* _a;
    	int _top;
    	int _capacity;
    }ST;
    
    
    void StackInit(ST* ps);
    void StackPush(ST* ps, STDatatype x);
    void StackPop(ST* ps);
    int StackSize(ST* ps);
    int StackTop(ST* ps);
    bool StackEmpty(ST* ps);
    void StackDestory(ST* 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

    一般情况下我们实现的都是动态可变长度的栈,因为实际工程中,计算机的栈的空间一般都比较小,所以存储不了很多数据。(这里要注意,操作系统里面的栈区,和数据结构里面的栈没有任何关系,这是属于两个不同的学科里面的东西)

    //stack.c
    #define _CRT_SECURE_NO_WARNINGS
    #include"stack.h"
    
    void StackInit(ST* ps)
    {
    	assert(ps);
    	ps->_a = NULL;
    	ps->_top = ps->_capacity = 0;
    }
    void StackPush(ST* ps, STDatatype x)
    {
    	assert(ps);
    	if (ps->_top == ps->_capacity)
    	{
    		int newcap = ps->_capacity == 0 ? 4 : 2 * ps->_capacity;
    		STDatatype* tmp = (STDatatype*)realloc(ps->_a, sizeof(STDatatype) * newcap);
    		if (tmp == NULL)
    		{
    			perror("realloc fail");
    			exit(-1);
    		}
    		ps->_a = tmp;
    		ps->_capacity = newcap;
    	}
    	ps->_a[ps->_top] = x;
    	ps->_top++;
    }
    void StackPop(ST* ps)
    {
    	assert(ps);
    	ps->_top--;
    }
    int StackSize(ST* ps)
    {
    	assert(ps);
    	return ps->_top;
    }
    STDatatype StackTop(ST* ps)
    {
    	assert(ps);
    	return ps->_a[ps->_top - 1];
    }
    bool StackEmpty(ST* ps)
    {
    	assert(ps);
    	return ps->_top == 0;
    }
    
    void StackDestory(ST* ps)
    {
    	assert(ps);
    	free(ps->_a);
    	ps->_top = ps->_capacity = 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

    stack.c这里是每个函数的具体实现

    队列

    队列是一种先进先出(FIFO)的结构,结构的一端是队头另一端是队尾,入数据只能在队尾,出数据在队头。

    在这里插入图片描述

    队列的模拟实现

    队列只能用链表来实现,数组实现栈效率太低,例如:将数组0下标位置当作是队头,那么出队列的时候就要将后面的数据都向前移动时间复杂度是O(N),如果将0下标位置当作是队尾,那么每次插入数据都需要向后移动已经插入好的数据,所以数组不适合用来实现队列。

    //queue.h
    #pragma once
    #include
    #include
    #include
    #include
    
    
    typedef int QDataType;
    typedef struct queue_node
    {
    	QDataType _val;
    	struct queue_node* _next;
    }qnode;
    
    
    typedef struct queue
    {
    	qnode* _front;
    	qnode* _back;
    }queue;
    
    
    void QueueInit(queue* pq);
    void QueuePush(queue* pq, QDataType x);
    void QueuePop(queue* pq);
    int QueueSize(queue* pq);
    QDataType QueueFront(queue* pq);
    QDataType QueueBack(queue* pq);
    bool QueueEmpty(queue* pq);
    void QueueDestory(queue* pq);
    qnode* BuyNewnode(QDataType x);
    
    • 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

    将链表的节点单独封装成一个结构体,然后队列封装成一个结构体。

    #define _CRT_SECURE_NO_WARNINGS
    #include"queue.h"
    
    void QueueInit(queue* pq)
    {
    	assert(pq);
    	pq->_back = NULL;
    	pq->_front = NULL;
    }
    
    qnode* BuyNewnode(QDataType x)
    {
    	qnode* newnode = (qnode*)malloc(sizeof(qnode));
    	newnode->_val = x;
    	newnode->_next = NULL;
    }
    
    
    void QueuePush(queue* pq, QDataType x)
    {
    	assert(pq);
    	qnode* newnode = BuyNewnode(x);
    	if (pq->_front == NULL)
    	{
    		pq->_front = pq->_back = newnode;
    		return;
    	}
    	pq->_back->_next = newnode;
    	pq->_back = newnode;
    }
    void QueuePop(queue* pq)
    {
    	assert(pq);
    	assert(pq->_front);
    	qnode* cur = pq->_front;
    	pq->_front = pq->_front->_next;
    	free(cur);
    	if (pq->_front == NULL)
    		pq->_back = NULL;
    }
    int QueueSize(queue* pq)
    {
    	assert(pq);
    	int size = 0;
    	qnode* cur = pq->_front;
    	while (cur)
    	{
    		size++;
    		cur = cur->_next;
    	}
    	return size;
    }
    QDataType QueueFront(queue* pq)
    {
    	assert(pq);
    	assert(pq->_front);
    	return pq->_front->_val;
    }
    QDataType QueueBack(queue* pq)
    {
    	assert(pq);
    	assert(pq->_back);
    	return pq->_back->_val;
    }
    bool QueueEmpty(queue* pq)
    {
    	assert(pq);
    	return pq->_front == NULL;
    }
    void QueueDestory(queue* pq)
    {
    	assert(pq);
    	if (pq->_front == NULL)
    	{
    		pq->_back = NULL;
    		return;
    	}
    	qnode* cur = pq->_front;
    	while (cur)
    	{
    		qnode* next = cur->_next;
    		free(cur);
    		cur = next;
    	}
    	pq->_back = NULL;
    }
    
    • 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
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    循环队列

    循环队列是一种队列的变形,也就是将队列的首位链接起来成为一个环状的队列,循环队列的长度是一定的,一旦确定了元素个数也就确定了环的大小,这个环就不可以在改变了。

    循环队列一般是使用数组实现比较简单。但是数组的长度要比元素个数大一个,也就是要空一格位置,方便判断队列满了,因为初始的时候front和back指针都是指向同一个位置,这时候队列为空,front代表的就是对头元素的位置。back就是代表队尾元素的下一个位置。因此,当(back+1)%N == front此时队列就是满了的。不可以再插入数据了。
    在这里插入图片描述

    循环队列例题
    代码如下:

    class MyCircularQueue {
    public:
        int front;
        int rear;
        int n;
        int* val;
        MyCircularQueue(int k) {
            val = new int[k+1];
            front = rear = 0;
            n = k+1;
        }
        
        bool enQueue(int value) {
            if((rear+1)%n == front)
                return false;
            val[rear] = value;
            rear++;
            rear %= n;
            return true;
        }
        
        bool deQueue() {
            if(front == rear)
                return false;
            front++;
            front %= n;
            return true;
        }
        
        int Front() {
            if(front == rear)
                return -1;
            return val[front];
        }
        
        int Rear() {
            if(front == rear)
                return -1;
            return val[(rear - 1 + n) % n];
        }
        
        bool isEmpty() {
            if(front == rear)
                return true;
            return false;
        }
        
        bool isFull() {
            if((rear+1)%n == front)
                return true;
            return false;
        }
    };
    
    • 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

    这里使用的是C++语法,new就是相当于malloc动态开辟内存并且初始化。

  • 相关阅读:
    Java中的抽象类和接口(Abstract Class and Interface)的区别
    学生管理系统
    Python 3.11新功能:错误信息回溯
    Hadoop IPC‘s epoch 8 is less than the last promised epoch 9 ; journal id:
    [数据结构与算法] 线性表之数组详解
    js中如何判断一个变量是否为数字类型?
    1.3.6 交换机划分 Vlan 配置
    【亚马逊云科技产品测评】活动征文|10分钟拥有一台AWS Linux系统
    Spring-03-AOP面向切面编程
    php练习05
  • 原文地址:https://blog.csdn.net/qq_62745420/article/details/126630388