数据结构中的队列(C语言)

05-28 1591阅读

数据结构中的队列(C语言)

一、队列

1、队列的概念及结构

队列:是只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出(First In First Out)的特点。

入队列:进行插入操作的一端称为队尾。

出队列:进行删除操作的一端称为队头。

如图所示:

数据结构中的队列(C语言)

1.2队列的应用:

1.任务调度:多个任务按照顺序排队等待执行。

2.广度优先搜索(BFS):在图或树的遍历中,按层次遍历节点。

3.缓存管理:缓存中的数据按照访问顺序排列,最先进入的数据最先被替换。

4.算法设计:某些算法的设计与队列密切相关,如哈夫曼编码、循环队列等。

1.3队列存在的优缺点:

优点:

1、先进先出(FIFO):队列保持元素的插入顺序,对线插入的元素最先被处理。

2、简单高效:队列的基本操作包括入队和出队,它们的时间复杂度都是O(1),无论队列的大小如何,操作的时间复杂度都是固定的。

3、应用广泛:队列在很多算法和实际应用场景中都有着广泛的应用,如任务调度、缓存管理、广度优先搜索。

缺点:

1、随机访问困难:队列只允许在队头删除元素,在队尾插入元素,不支持随机访问。如果需要在队列的其他位置插入或删除数据,运行效率较低。

2、队列大小的限制:使用数组实现的队列在创建时需要指定最大容量,因此队列中的元素有限。如果队列已满,则无法插入新的数据。

3、存储空间浪费:如果队列中实际的元素个数远小于最大容量,那么可能造成存储空间的浪费,因为队列的空间是固定的。

1.4队列的基本结构

本文队列采用链式存储进行讲解,而非数组存储。

1.4.1链式存储的原因

基于队列先进先出的特点,队列先进的数据从队尾进来,接着从队头出去,链式存储利用指针将一个一个的元素连接起来。插入数据,则用指针在队尾链接数据;删除数据,则释放掉队头元素。

相较于链式存储,数组存储在队尾插入数据简单(时间复杂度为O(1)),但由于删除数据需要在队头进行,还需把后面的数据通通往前移一位,由此以来,时间复杂度就将变为O(N),效率变低,所以不推荐使用。

1.4.2存储结构
typedef int QDatatype;   //暂定为整数类型
typedef struct QueueNode
{
    QDatatype val;      //存放数据
    struct QueueNode* next; //存放链
}QNode;

但仅仅这些还不够,我们还要标记队列的队头和队尾,以方便插入和删除。这样一来,我们队列插入和删除操作的时间复杂度就变为了O(1)。

typedef struct Queue
{
    QNode* head; //标记队头指针,用于删除数据
    QNode* tail; //标记队尾指针,用于插入数据
    int size;    //标记队列中有效元素个数
}Queue; 

ps:这是一种重要的算法思维(以空间换时间),提高算法效率。

1.5队列的基本操作

  • QueInit(&pq);   初始化一个空队列
  • QuePush(&pq, x);   往队列新插入一个数据,并将其放在队尾
  • QuePop(&pq);   删除队列中队头的元素,并释放
  • QueFront(&pq);   获取处在队列队头的元素数据
  • QueBack(&pq);   获取处在队列队尾的元素数据
  • QueEmpty(&pq);   判断队列是否为空
  • QueSize(&pq);   返回获取队列中元素的个数
  • QueDestroy(&pq);   销毁队列,并释放pq所占用的存储空间

    2、队列的实现算法

    1.初始化

    void QueInit(Queue* pq)
    {
        pq->head = NULL; 
        pq->tail = NULL; //将队列的头尾指针置为空
        pq->size = 0;    //队列长度初始化为零
    }

    2.入队列

    void QuePush(Queue* pq, QDatatype x)
    {
        //判空
        assert(pq);
        QNode* newnode = (QNode*)malloc(sizeof(QNode));
        //检验是否创建成功
        if(newnode == NULL)
        {
            perror("malloc fail");
            return;
        }
        newnode->val = x;  //将x值赋给newnode
        newnode->next = NULL;
        //无哨兵位,则需要检验队列中是否为空
        //若不为空
        if(pq->tail == NULL)
        {
            pq->tail->next = newnode;//将元素放在队尾
            pq->tail = newnode;      //将队尾变作新插入的元素
        }
        //若为空
        else
        {
            pq->head = pq->tail = newnode;
        }
        pq->size++;
    }

    3.出队列

    void QuePop(Queue* pq)
    {
        assert(pq);
        //当队列中没有节点时,就会强制返回
        assert(pq->head != NULL);
        
        QNode* cur = pq->head;
        
        //这里需要分情况讨论,若只有一个节点,tail和head会指向同一个节点
        //若有多个节点,tail和head则会分别只想不同的节点,所以需要分开讨论
        //若只有一个节点
        if(pq->tail->next == NULL)
        {
            free(pq->tail);
            pq->tail = pq->head = NULL;
        }
        //若有多个节点
        else
        {
            QNode* next = cur->next;//next指向cur的下一个节点,以方便下次删除找到队头
            free(pq->head);
            cur = next;
        }
        pq->size--;
    }

    出队列需要注意的几点:

    (1)当队列中有元素时才可以进行删除操作,所以要进行判空(assert(pq->head != NULL))

    (2)当队列中只有一个节点时,如果只把tail释放掉是不对的,因为tail和head指向同一个节点,释放掉tail,head就变成了野指针,导致严重错误

    (3)当队列中有多个节点,head和tail指向不同的节点,才可以正常删除数据

    只有一个节点的情况:

    数据结构中的队列(C语言)

    有多个节点的情况:

    数据结构中的队列(C语言)

    4.获取队头数据

    QDatatype QueFront(Queue* pq)
    {
    	assert(pq);
    	assert(pq->head != NULL);
    	return pq->head->val;//直接返回队头节点的数据值
    }

    5.获取队尾数据

    QDatatype QueBack(Queue* pq)
    {
    	assert(pq);
    	assert(pq->head != NULL);
    	return pq->tail->val; //获取队尾数据值
    }
    

    6.判空操作

    bool QueEmpty(Queue* pq)
    {
    	assert(pq);
        
        //判断size是否为空来返回,若为空,则返回true;若非空,则返回false
    	return pq->size == 0;
    }

    7.获取队列节点个数

    int QueSize(Queue* pq)
    {
    	assert(pq);
        //在此不需要循环队列,直接返回,因为size标记了队列中的节点个数
    	return pq->size;
    }

    不需要循环队列,因此时间复杂度为O(1),如果遍历队列链表,时间复杂度为O(N),相比之下,相当于优化了结构的性能

    8.销毁队列

    void QueDestroy(Queue* pq)
    {
    	assert(pq);
    	QNode* cur = pq->head;
    	while (cur)
    	{
    		QNode* Next = cur->next; //Next用于迭代队头
    		free(cur);
    		cur = Next;
    	}
    	pq->head = pq->tail = NULL;//指针都置为空
    	pq->size = 0;
    }

    3、源码呈现

    3.1Queue.h

    #include
    #include
    #include
    #include
    typedef int QDatatype;
    typedef struct QueueNode
    {
    	QDatatype val;
    	struct QueueNode* next;
    }QNode;
    typedef struct Queue
    {
    	QNode* head;
    	QNode* tail;
    	int size;
    }Queue;
    //初始化
    void QueInit(Queue* pq);
    //销毁
    void QueDestroy(Queue* pq);
    void QuePush(Queue* pq, QDatatype x);
    void QuePop(Queue* pq);
    bool QueEmpty(Queue* pq);
    int QueSize(Queue* pq);
    QDatatype QueFront(Queue* pq);
    QDatatype QueBack(Queue* pq);
    

    3.2Queue.c

    #include"Queue.h"
    void QueInit(Queue* pq)
    {
    	assert(pq);
    	pq->head = NULL;
    	pq->tail = NULL;
    	pq->size = 0;
    }
    void QueDestroy(Queue* pq)
    {
    	assert(pq);
    	QNode* cur = pq->head;
    	while (cur)
    	{
    		QNode* Next = cur->next;
    		free(cur);
    		cur = Next;
    	}
    	pq->head = pq->tail = NULL;
    	pq->size = 0;
    }
    //入队列
    void QuePush(Queue* pq, QDatatype x)
    {
    	assert(pq);
    	QNode* newnode = (QNode*)malloc(sizeof(QNode));
    	if (newnode == NULL)
    	{
    		perror("malloc fail");
    		return;
    	}
    	newnode->val = x;
    	newnode->next = NULL;
    	//不带哨兵位,需要进行检验
    	//如果pq->tail不为空
    	if (pq->tail)
    	{
    		pq->tail->next = newnode;
    		pq->tail = newnode;
    	}
    	//如果为空
    	else
    	{
    		pq->head = pq->tail = newnode;
    	}
    	pq->size++;
    }
    //出队列
    void QuePop(Queue* pq)
    {
    	assert(pq);
    	//零个节点
    	assert(pq->head != NULL);
    	//一个节点
    	if (pq->head->next == NULL)
    	{
    		free(pq->head);
    		pq->head = pq->tail = NULL;
    	}
    	//多个节点
    	else
    	{
    		QNode* next = pq->head->next;
    		free(pq->head);
    		pq->head = next;
    	}
    	pq->size--;
    }
    QDatatype QueFront(Queue* pq)
    {
    	assert(pq);
    	assert(pq->head != NULL);
    	return pq->head->val;
    }
    QDatatype QueBack(Queue* pq)
    {
    	assert(pq);
    	assert(pq->head != NULL);
    	return pq->tail->val;
    }
    int QueSize(Queue* pq)
    {
    	assert(pq);
    	return pq->size;
    }
    bool QueEmpty(Queue* pq)
    {
    	assert(pq);
    	return pq->size == 0;
    }

    3.3Test.c

    #include"Queue.h"
    int main()
    {
    	Queue q;
    	
    	QueInit(&q);
    	QuePush(&q, 1);
    	QuePush(&q, 2);
    	QuePush(&q, 3);
    	QuePush(&q, 4);
    	//printf("%d ", QueFront(&q));
    	while (!QueEmpty(&q))
    	{
    		printf("%d ", QueFront(&q));
    		QuePop(&q);
    	}
    	QueDestroy(&q);
    	return 0;
    }

    码字不易,来个一键三连呗~~

VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]