目 录CONTENT

文章目录

基础版的双向队列(双端队列)的C语言实现

~梓
2025-09-18 / 0 评论 / 0 点赞 / 2 阅读 / 0 字
温馨提示:
部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

算法描述

双向队列(Double-Ended Queue,简称Deque)是一种允许在队列的前端(front)后端(rear) 进行插入和删除操作的线性数据结构。它结合了栈和队列的特性,提供了更灵活的数据操作方式。

核心操作:

  1. pushFront(value) - 在队头插入元素
  2. pushRear(value) - 在队尾插入元素
  3. popFront() - 删除并返回队头元素
  4. popRear() - 删除并返回队尾元素
  5. getFront() - 获取队头元素(不删除)
  6. getRear() - 获取队尾元素(不删除)

实现特点:

  • 循环数组:使用取模运算实现空间的循环利用
  • size计数器:通过size变量判断空/满状态,不浪费空间
  • 边界处理:正确处理空队列和满队列的特殊情况
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define MAX_SIZE 10  // 队列最大容量

typedef struct {
    int data[MAX_SIZE];
    int front;
    int rear;
    int size;        // 当前元素数量
} Deque;

// 初始化队列
void initDeque(Deque *dq) {
    dq->front = -1;
    dq->rear = 0;
    dq->size = 0;
}

// 检查队列是否为空
bool isEmpty(Deque *dq) {
    return dq->size == 0;
}

// 检查队列是否已满
bool isFull(Deque *dq) {
    return dq->size == MAX_SIZE;
}

// 从队头入队
bool pushFront(Deque *dq, int value) {
    if (isFull(dq)) {
        printf("队列已满,无法从队头插入\n");
        return false;
    }
  
    if (isEmpty(dq)) {
        dq->front = 0;
        dq->rear = 0;
    } else {
        dq->front = (dq->front - 1 + MAX_SIZE) % MAX_SIZE;
    }
  
    dq->data[dq->front] = value;
    dq->size++;
    return true;
}

// 从队尾入队
bool pushRear(Deque *dq, int value) {
    if (isFull(dq)) {
        printf("队列已满,无法从队尾插入\n");
        return false;
    }
  
    if (isEmpty(dq)) {
        dq->front = 0;
        dq->rear = 0;
    } else {
        dq->rear = (dq->rear + 1) % MAX_SIZE;
    }
  
    dq->data[dq->rear] = value;
    dq->size++;
    return true;
}

// 从队头出队
bool popFront(Deque *dq, int *value) {
    if (isEmpty(dq)) {
        printf("队列为空,无法从队头删除\n");
        return false;
    }
  
    *value = dq->data[dq->front];
    dq->front = (dq->front + 1) % MAX_SIZE;
    dq->size--;
  
    if (isEmpty(dq)) {
        dq->front = -1;
        dq->rear = -1;
    }
  
    return true;
}

// 从队尾出队
bool popRear(Deque *dq, int *value) {
    if (isEmpty(dq)) {
        printf("队列为空,无法从队尾删除\n");
        return false;
    }
  
    *value = dq->data[dq->rear];
    dq->rear = (dq->rear - 1 + MAX_SIZE) % MAX_SIZE;
    dq->size--;
  
    if (isEmpty(dq)) {
        dq->front = -1;
        dq->rear = -1;
    }
  
    return true;
}

// 获取队头元素
bool getFront(Deque *dq, int *value) {
    if (isEmpty(dq)) {
        printf("队列为空\n");
        return false;
    }
    *value = dq->data[dq->front];
    return true;
}

// 获取队尾元素
bool getRear(Deque *dq, int *value) {
    if (isEmpty(dq)) {
        printf("队列为空\n");
        return false;
    }
    *value = dq->data[dq->rear];
    return true;
}

// 打印队列中的所有元素
void printDeque(Deque *dq) {
    if (isEmpty(dq)) {
        printf("队列为空\n");
        return;
    }
  
    printf("队列元素(从前到后): ");
    int i = dq->front;
    int count = 0;
  
    while (count < dq->size) {
        printf("%d ", dq->data[i]);
        i = (i + 1) % MAX_SIZE;
        count++;
    }
    printf("\n");
}

int main() {
    Deque dq;
    initDeque(&dq);
    int value;
  
    printf("=== 双向队列测试 ===\n");
  
    // 测试从队尾入队
    pushRear(&dq, 10);
    pushRear(&dq, 20);
    pushRear(&dq, 30);
    printf("从队尾插入10,20,30后: ");
    printDeque(&dq);
  
    // 测试从队头入队
    pushFront(&dq, 5);
    pushFront(&dq, 2);
    printf("从队头插入5,2后: ");
    printDeque(&dq);
  
    // 测试获取元素
    getFront(&dq, &value);
    printf("队头元素: %d\n", value);
    getRear(&dq, &value);
    printf("队尾元素: %d\n", value);
  
    // 测试从队头出队
    popFront(&dq, &value);
    printf("从队头删除: %d\n", value);
    printf("删除后: ");
    printDeque(&dq);
  
    // 测试从队尾出队
    popRear(&dq, &value);
    printf("从队尾删除: %d\n", value);
    printf("删除后: ");
    printDeque(&dq);
  
    // 测试继续插入
    pushFront(&dq, 1);
    pushRear(&dq, 40);
    printf("继续插入1(头),40(尾)后: ");
    printDeque(&dq);
  
    return 0;
}

双向队列的特点:

核心操作:

  1. pushFront() - 从队头插入元素
  2. pushRear() - 从队尾插入元素
  3. popFront() - 从队头删除元素
  4. popRear() - 从队尾删除元素
  5. getFront() - 获取队头元素
  6. getRear() - 获取队尾元素

与普通队列的区别:

  • 双向操作:可以从两端进行插入和删除
  • 更灵活:支持更多操作场景
  • 实现稍复杂:需要处理两个方向的指针移动

复杂度分析:

  • 所有操作的时间复杂度都是 O(1)
  • 空间复杂度为 O(MAX_SIZE)

应用场景:

  • 需要频繁在两端操作数据的场景
  • 滑动窗口问题
  • 回文检查
  • 某些算法实现(如BFS的变种)

这个实现保持了代码的简洁性,同时提供了双向队列的所有基本功能。


初始状态

Deque dq;
initDeque(&dq);  // front = -1, rear = 0, size = 0
队列状态: [空]
front = -1, rear = 0, size = 0

第一步:从队尾插入 10

pushRear(&dq, 10);

代码执行:

if (isEmpty(dq)) {    // true
    dq->front = 0;    // front = 0
    dq->rear = 0;     // rear = 0
}
dq->data[dq->rear] = value;  // data[0] = 10
dq->size++;           // size = 1
队列状态: [10]
索引:  0   1   2   3   4   5   6   7   8   9
数据: 10   ?   ?   ?   ?   ?   ?   ?   ?   ?
      ↑
 front=0, rear=0, size=1

第二步:从队尾插入 20

pushRear(&dq, 20);

代码执行:

if (isEmpty(dq)) {    // false
    dq->rear = (dq->rear + 1) % MAX_SIZE;  // rear = (0+1)%10=1
}
dq->data[dq->rear] = value;  // data[1] = 20
dq->size++;           // size = 2
队列状态: [10, 20]
索引:  0   1   2   3   4   5   6   7   8   9
数据: 10  20   ?   ?   ?   ?   ?   ?   ?   ?
      ↑   ↑
front=0 rear=1, size=2

第三步:从队尾插入 30

pushRear(&dq, 30);
dq->rear = (1 + 1) % 10 = 2
data[2] = 30
size = 3
队列状态: [10, 20, 30]
索引:  0   1   2   3   4   5   6   7   8   9
数据: 10  20  30   ?   ?   ?   ?   ?   ?   ?
      ↑       ↑
front=0     rear=2, size=3

第四步:从队头插入 5

pushFront(&dq, 5);

代码执行:

if (isEmpty(dq)) {    // false
    dq->front = (dq->front - 1 + MAX_SIZE) % MAX_SIZE;
    // front = (0 - 1 + 10) % 10 = 9
}
dq->data[dq->front] = value;  // data[9] = 5
dq->size++;           // size = 4
队列状态: [5, 10, 20, 30]
索引:  0   1   2   3   4   5   6   7   8   9
数据: 10  20  30   ?   ?   ?   ?   ?   ?   5
          ↑           ↑
       rear=2      front=9, size=4

第五步:从队头插入 2

pushFront(&dq, 2);
front = (9 - 1 + 10) % 10 = 8
data[8] = 2
size = 5
队列状态: [2, 5, 10, 20, 30]
索引:  0   1   2   3   4   5   6   7   8   9
数据: 10  20  30   ?   ?   ?   ?   ?   2   5
          ↑       ↑
       rear=2  front=8, size=5

第六步:从队头删除

popFront(&dq, &value);  // value = 2

代码执行:

*value = dq->data[dq->front];  // value = data[8] = 2
dq->front = (dq->front + 1) % MAX_SIZE;  // front = (8+1)%10=9
dq->size--;  // size = 4
队列状态: [5, 10, 20, 30]  (删除了2)
索引:  0   1   2   3   4   5   6   7   8   9
数据: 10  20  30   ?   ?   ?   ?   ?   ?   5
          ↑           ↑
       rear=2      front=9, size=4

第七步:从队尾删除

popRear(&dq, &value);  // value = 30

代码执行:

*value = dq->data[dq->rear];  // value = data[2] = 30
dq->rear = (dq->rear - 1 + MAX_SIZE) % MAX_SIZE;  // rear = (2-1+10)%10=1
dq->size--;  // size = 3
队列状态: [5, 10, 20]  (删除了30)
索引:  0   1   2   3   4   5   6   7   8   9
数据: 10  20   ?   ?   ?   ?   ?   ?   ?   5
      ↑   ↑       ↑
   rear=1     front=9, size=3

关键特点:

  1. 循环特性frontrear 指针会循环移动
  2. size计数器:通过 size 来判断空/满,不需要浪费空间
  3. 双向操作:可以从两端插入和删除
  4. 初始值front = -1 表示队列为空

这样的设计避免了普通循环队列需要浪费一个位置的问题,通过 size 计数器来准确判断队列状态。

时间复杂度分析

所有操作的时间复杂度:O(1)

操作 时间复杂度 说明
pushFront() O(1) 常数时间操作,只需计算索引和赋值
pushRear() O(1) 常数时间操作,只需计算索引和赋值
popFront() O(1) 常数时间操作,只需计算索引
popRear() O(1) 常数时间操作,只需计算索引
getFront() O(1) 直接访问数组元素
getRear() O(1) 直接访问数组元素
isEmpty() O(1) 简单比较操作
isFull() O(1) 简单比较操作

原因分析:

  • 数组随机访问:所有操作都基于数组索引的直接访问
  • 数学运算简单:索引计算使用取模运算,是常数时间操作
  • 无循环操作:所有方法中都没有循环结构

空间复杂度分析

空间复杂度:O(MAX_SIZE)

空间使用 复杂度 说明
数据数组 O(MAX_SIZE) 固定大小的数组,主要空间开销
结构体变量 O(1) front, rear, size三个整型变量
临时变量 O(1) 方法中的局部变量
总计 O(MAX_SIZE) 由固定大小的数组决定
0

评论区