【数据结构】带你认识队列--Queue(带图详解)


一、队列的概念

队列是一种先进先出(FIFO)的数据结构,类似于现实中排队的场景。在队列中,数据从队尾进入,从队首出去。队列的操作包括初始化、销毁、入队、出队、获取队首元素、获取队尾元素以及检查队列是否为空等。

在这里插入图片描述
这也比较符合我们通常生活中的习惯,排在第一个的优先出列,最后来的当然排在队伍最后,如下图所示。再比如用键盘进行各种字母或数字的输入,到显示器上如记事本软件上的输出,其实就是队列的典型应用,假如你本来和女友聊天,想表达你是我的上帝,输入的是god,而屏幕上却显示dog 发了出去,这真是要气死人了。

1.1 队列的基本特性

  • 队列是线性数据结构,只允许在一端插入(队尾)和另一端删除(队首)。
  • 数据元素进队列的过程称为入队,出队列的过程称为出队。
  • 队列是先进先出的数据结构。

1.2 队列的存储结构

队列的存储结构可以通过顺序队列和链队列两种方式实现。

  • 顺序队列使用数组存储,
  • 链队列使用链表存储

二、队列的结点设计与初始化

在链队列中,结点是队列的基本构建单元。一个队列包含头指针和尾指针,它们分别指向队列的头部和尾部。

typedef int QDataType;
typedef struct QueueNode
{
	QDataType data;
	struct QueueNode* next;
}QNode;

typedef struct Queue
{
	QNode* head;
	QNode* tail;
	int size;
}Queue;

2.1 队列的初始化

void QueueInit(Queue* pq)
{
	assert(pq);

	pq->head = NULL;
	pq->tail = NULL;
	pq->size = 0;
}

2.2 判断队列是否为空

判断队列是否为空是一个简单而重要的操作,它可以通过检查队列的头指针是否为空来实现。

bool QueueEmpty(Queue* pq)
{
    assert(pq); // 检查指针是否为空

    return pq->head == NULL && pq->tail == NULL; // 如果头尾节点都为空,队列为空
}

2.3 入队操作

入队操作将一个新的元素插入到队列的尾部。在入队操作中,需要判断队列是否为空,然后根据情况调整队列的头指针和尾指针。
在这里插入图片描述

void QueuePush(Queue* pq, QDataType 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->tail == NULL)
    {
        pq->head = pq->tail = newnode; // 如果尾节点为空,头尾节点都指向新节点
    }
    else
    {
        pq->tail->next = newnode; // 尾节点的下一个节点指向新节点
        pq->tail = newnode; // 尾节点指向新节点
    }

    pq->size++; // 队列大小加1
}

2.4 出队操作

出队操作时,就是头结点的后继结点出队,将头结点的后继改为它后面的结点,若链表除头结点外只剩一个元素,则需将rear指向头结点,如下图所示。
在这里插入图片描述

void QueuePop(Queue* pq)
{
    assert(pq); // 检查指针是否为空
    assert(!QueueEmpty(pq)); // 检查队列是否为空

    if (pq->head->next == NULL)
    {
        free(pq->head); // 释放头节点内存
        pq->head = pq->tail = NULL; // 头尾节点指向空
    }
    else
    {
        QNode* del = pq->head; // 保存头节点
        pq->head = pq->head->next; // 头节点指向下一个节点

        free(del); // 释放节点内存
    }

    pq->size--; // 队列大小减1
}

2.5 打印队列元素(遍历)

遍历操作用于打印队列的所有元素,可以帮助调试和验证队列的操作是否正确。

void printQueue(Queue *q) {
    Node *current = q->front;
    while (current != NULL) {
        printf("%d\t", current->data);
        current = current->next;
    }
    printf("\n");
}

2.6 队列的销毁

通过遍历队列,释放每个节点的内存,并将队列头尾指针置为空,队列大小置零,完成了销毁队列的操作。在释放内存时,没有必要将del指针置为空,因为它在下一次循环中会被重新赋值。

void QueueDestroy(Queue* pq)
{
    assert(pq); // 检查指针是否为空

    QNode* cur = pq->head;
    while (cur)
    {
        QNode* del = cur;
        cur = cur->next;

        free(del); // 释放节点内存
        //del = NULL;  // 此行代码可以省略,因为del在下一次循环中会被重新赋值
    }

    pq->head = pq->tail = NULL; // 头尾节点指向空
    pq->size = 0; // 队列大小为0
}

2.6 队头和队尾

// 返回队头元素
QDataType QueueFront(Queue* pq)
{
    assert(pq); // 检查指针是否为空
    assert(!QueueEmpty(pq)); // 检查队列是否为空

    return pq->head->data; // 返回头节点的数据
}

// 返回队尾元素
QDataType QueueBack(Queue* pq)
{
    assert(pq); // 检查指针是否为空
    assert(!QueueEmpty(pq)); // 检查队列是否为空

    return pq->tail->data; // 返回尾节点的数据
}

2.7 顺序队列的假溢出

顺序队列可能会发生假溢出的问题,即队列的物理空间没有满,但由于出队操作导致队列头指针偏移,无法继续入队。这时,可以通过重新整理队列的存储空间来解决。

void fakeOverflow(Queue *q) {
    if (!isEmpty(q)) {
        Node *temp = q->head;
        q->head = q->tail = NULL;
        while (temp != NULL) {
            enqueue(q, temp->data);
            Node *next = temp->next;
            free(temp);
            temp = next;
        }
    }
}

结语

通过以上介绍,我们了解了队列的基本概念和实现方法,并实现了队列的初始化、判断是否为空、入队、出队、遍历等基本操作。队列作为一种常见的数据结构,在计算机科学中有着广泛的应用,特别是在广度优先搜索、任务调度等场景。希望这篇文章能够帮助你更好地理解和使用队列。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
C是一门非常基础的编程语言,它没有像其他语言那样提供内置的数据结构。但是,C提供了足够的工具和语法来实现不同类型的数据结构。 以下是C中常见的一些数据结构及其实现方式: 1. 数组:C中的数组是存储相同类型数据的一种数据结构。数组的大小在声明时指定,且大小不可改变。 ```c int arr[10]; // 声明一个包含10个整数的数组 ``` 2. 结构体:结构体是一种自定义数据类型,它可以包含不同类型的数据,也可以嵌套其他结构体。 ```c struct student { char name[20]; int age; float score; }; // 定义一个包含姓名、年龄和分数的结构体 struct student s1; // 声明一个结构体变量 ``` 3. 链表:链表是由节点组成的一种数据结构,每个节点包含数据和指向下一个节点的指针。 ```c struct node { int data; struct node *next; }; // 定义一个包含数据和指向下一个节点的指针的结构体 struct node *head = NULL; // 声明一个链表头指针 ``` 4. 栈:栈是一种后进先出(LIFO)的数据结构,可以用数组或者链表来实现。 ```c #define MAX_SIZE 10 int stack[MAX_SIZE]; // 用数组实现栈 int top = -1; // 栈顶指针 void push(int value) { if (top < MAX_SIZE - 1) { stack[++top] = value; // 将数据压入栈中 } } int pop() { if (top >= 0) { return stack[top--]; // 弹出栈顶元素 } } ``` 5. 队列队列是一种先进先出(FIFO)的数据结构,可以用数组或者链表来实现。 ```c #define MAX_SIZE 10 int queue[MAX_SIZE]; // 用数组实现队列 int front = 0, rear = -1; // 队列头和队列尾 void enqueue(int value) { if (rear < MAX_SIZE - 1) { queue[++rear] = value; // 将数据插入队列尾部 } } int dequeue() { if (front <= rear) { return queue[front++]; // 弹出队列头部元素 } } ``` 以上是C中常见的几种数据结构及其实现方式,当然还有其他类型的数据结构,如树、图等。在实际开发中,可以根据需求选择合适的数据结构来实现算法和数据处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SuhyOvO

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值