数据结构-队列(queue)

本文详细介绍了队列这种数据结构的实现方式,包括数组和链表两种实现,并探讨了循环数组的应用及其解决队列满与空的问题。同时,文章提供了具体的代码实现案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

队列(queue)是一种采用先进先出(FIFO)策略的抽象数据结构,它的想法来自于生活中排队的策略。顾客在付款结账的时候,按照到来的先后顺序排队结账,先来的顾客先结账,后来的顾客后结账。

这里写图片描述

队列实现

同栈的实现一样,队列的实现也有数组实现和链表实现两种方式。

数组实现

先来看看数组实现的方法。栈使用top变量记录栈顶的位置,而队列则使用front和rear分别队列第一个元素和最后一个元素的位置。

这里写图片描述

#define SIZE 20
typedef struct queue
{
    int arr[SIZE];
    int front;
    int rear;

} Queue;

入队、出队操作很简单。入队时,通过rear的位置判断队列是否已满。如果没有满,则将rear后移一位,将新元素放置在rear所在位置。出队时,也可以通过rear的位置判断队列是否为空。如果不空,则只需将front后移一位即可。 获取队头元素,判断队列不空,则只需返回front指向位置的元素即可。

哇塞!好简单啊!这样就完成了队列的实现啊!太天真了。。。想一下,通过出队操作将数据弹出队列后,front之前的空间还能够再次得到吗?不能。所以使用普通数组实现队列,就再也不能使用front之前的空间了,这会导致大量空间丢失。

循环数组

为了解决这个问题,将普通数组换成循环数组。在循环数组中,末尾元素的下一个元素不是数组外,而是数组的头元素。这样就能够再次使用front之前的存储空间了。

循环数组

哇塞!有解决了这个问题。可以开始码代码了!且慢,在实现修改入队和出队操作之前,再深思一步,这时是否还能简单通过判断rear的位置来判断队列空或满呢?当然不可以。那是否可以考虑front和rear之间的位置关系呢?考虑如下两种边界情况:

这里写图片描述

在这两种情况下,rear都在front前一个位置,无法判断此时队列满还是空。为了解决这个问题,通常采用的最简单的方法就是,使用一个counter记录队列中元素个数。修改队列定义为下:

#define SIZE 20

typedef struct queue
{
    int arr[SIZE];
    int front;
    int rear;
    int counter;

} Queue;

Init(Queue *q)
{
    q->front = 0;
    q->rear = -1;
    q->counter = 0;
} 


bool IsFull(Queue *q)
{
    return (q->counter >= SIZE);
}

void EnQueue(Queue *q, int val)
{
    if(IsFull(q))
        return;

    q->rear = (q->rear + 1) % SIZE;
    q->arr[q->rear] = val;
    q->counter++;
}


bool IsEmpty(Queue *q)
{
    return (q->counter <= 0);
}


void DeQueue(Queue *q)
{
    if (IsEmpty(q))
        return;

    q->front = (q->front + 1) % SIZE;
    q->counter--;

}

//get the front element from queue
int Front(Queue *q)
{
    if (IsEmpty(q))
        return;

    return q->arr[q->front];
}

另外一种方法是,在数组中空出一个位置,用以标记队列是否已满。
这里写图片描述

这里写图片描述

这里写图片描述

bool IsFull(Queue *q)
{
    return ((q->rear + 2) % SIZE == q->front);
}

bool IsEmpty(Queue *q)
{
    return ((q->rear + 1) % SIZE == q->front);
}

有时候,rear表示队尾元素的下一个位置,即表示即将插入元素的位置。
这里写图片描述

此时,判断队列的操作应该修改如下:

#define SIZE 20

typedef struct queue
{
    int arr[SIZE];
    int front;
    int rear;

} Queue;


Init(Queue *q)
{
    q->front = 0;
    q->rear = 0;
} 


bool IsFull(Queue *q)
{
    return ((q->rear + 1) % SIZE == q->front);
}

void EnQueue(Queue *q, int val)
{
    if(IsFull(q))
        return;

    q->arr[q->rear] = val;
    q->rear = (q->rear + 1) % SIZE;
}


bool IsEmpty(Queue *q)
{
    return (q->rear == q->front);
}


void DeQueue(Queue *q)
{
    if (IsEmpty(q))
        return;

    q->front = (q->front + 1) % SIZE;

}


int Front(Queue *q)
{
    if (IsEmpty(q))
        return;

    return q->arr[q->front];
}

链表实现

队列的数组实现比较麻烦,需要考虑各种边界情况,所以通常使用链表形式来实现队列。

使用单向链表来实现链式队列,链式队列中存储front和rear即可。

typedef struct node
{
    int val;
    struct node *next;
}Node;

typedef struct queue
{
    Node *front;
    Node *rear;
};

为了方便实现,链式队列中的front表示链表的头节点,而front的next才表示队头。链式队列的入队和出队操作如下图所示。从图中可以看出链式队列的实现采用尾进头出的策略。

链式队列

在链式队列中,不需要考虑队列是否已满,只要内存足够就可以一直分配空间。而当front和rear都指向头节点的时候,则表示此时队列为空。

bool IsEmpty(Queue *q)
{
    return (q->front == q->rear);
}

入队时移动rear指向最后一个元素即可。

Node* CreateNode(int val)
{
    Node *newNode = (Node*)malloc(sizeof(Node));
    if (newNode == NULL)
        return NULL;
    else {
        newNode->val = val;
        newNode->next = NULL;
        return newNode;
    }
}


void EnQueue(Queue *q, int val)
{
    Node *newNode = CreateNode(val);
    if (newNode == NULL)
        return;
    else {
        q->rear->next = newNode;
        q->rear = q->rear->next;
    }
}

出队时,不需要移动front指针,只需将其next指针指向下一个next元素即可。q->front->next = q->front->next->next; 但是需要考虑一种特殊情况,即当队列中只剩下一个元素时,还需要使rear指针重新指向头节点。

int Front(Queue *q)
{
    if (IsEmpty(q))
        return -1;
    else
        return (q->front->next->val);
}

void DeQueue(Queue *q)
{
    if (IsEmpty(q))
        return;
    else 
    {
        Node *deNode = q->front->next;
        q->front->next = deNode->next;

        if (q->rear == deNode) //only one element
            q->rear = q->front;

        free(deNode);
    }
}
### 阻塞队列数据结构及其用法 #### 1. 阻塞队列的核心概念 阻塞队列是一种特殊的队列,它支持在队列入口和出口处的阻塞操作。如果队列为空,则尝试移除元素的操作会被阻塞;如果队列为满,则尝试插入新元素的操作也会被阻塞[^1]。 #### 2. Java 中的标准实现 Java 提供了 `java.util.concurrent.BlockingQueue` 接口来定义阻塞队列的行为,并提供了多种具体实现类: - **ArrayBlockingQueue**: 基于固定大小数组的有界阻塞队列,遵循 FIFO(先进先出)原则[^3]。 - **LinkedBlockingQueue**: 基于链表的可选限界阻塞队列,默认情况下无界。 - **PriorityBlockingQueue**: 支持优先级排序的无界阻塞队列,允许按照自然顺序或自定义比较器进行排序。 - **SynchronousQueue**: 特殊类型的阻塞队列,不保存任何元素,仅用于传递数据。 - **DelayedWorkQueue**: 一种延迟队列,只有当元素达到其设定的时间阈值时才能被取出。 #### 3. C++ 的简单实现 虽然 C++ 标准库未提供内置的阻塞队列,但可以通过组合 `std::queue` 和互斥锁手动构建一个简单的阻塞队列。以下是其实现示例: ```cpp #include <queue> #include <mutex> #include <condition_variable> template<typename T> class BlockingQueue { private: std::queue<T> queue_; mutable std::mutex mutex_; std::condition_variable cond_; public: void push(T value) { std::lock_guard<std::mutex> lock(mutex_); queue_.push(value); cond_.notify_one(); // 唤醒等待线程 } T pop() { std::unique_lock<std::mutex> lock(mutex_); while (queue_.empty()) { // 如果队列为空则阻塞当前线程 cond_.wait(lock); } T result = queue_.front(); queue_.pop(); return result; } bool empty() const { std::lock_guard<std::mutex> lock(mutex_); return queue_.empty(); } }; ``` 上述代码通过条件变量实现了基本的阻塞功能,在多线程环境下能够安全地管理资源访问。 #### 4. 应用场景分析 阻塞队列广泛应用于并发编程领域,特别是在生产者-消费者模式中表现尤为突出。在这种模式下,多个生产者向共享队列添加任务项,而多个消费者从中提取并处理这些任务项[^4]。这种设计不仅提高了系统的吞吐量,还降低了开发复杂度。 例如,在 Web 服务后台任务调度系统中,可以利用阻塞队列缓存待执行的任务请求,由专门的工作线程池逐一完成计算密集型作业[^2]。 --- ###
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值