数据结构——双向队列(链表实现)

双向队列的概念

在队列中,我们仅能删除头部元素或在尾部添加元素。如图 5-7 所示,双向队列(double-ended queue)提供了更高的灵活性,允许在头部和尾部执行元素的添加或删除操作。

对于双向队列而言,头部和尾部都可以执行入队和出队操作。换句话说,双向队列需要实现另一个对称方向的操作。为此,我们采用“双向链表”作为双向队列的底层数据结构。

如图所示,我们将双向链表的头节点和尾节点视为双向队列的队首和队尾,同时实现在两端添加和删除节点的功能。

代码演示

 下面为大家展示链表实现双向队列的代码

首先准备好头函数:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int QDataType;
typedef struct DuListNode
{
	QDataType data;
	struct Node* prev;
	struct Node* next;
}DuListNode;

typedef struct Deque
{
	size_t size;
	DuListNode* front;
	DuListNode* rear;
}Deque;
void DequeInit(Deque* d);//初始化
bool DequeEmpty(Deque* d);//判断是否为空
QDataType DequeFront(Deque* d);//获取队头元素
QDataType DequeBack(Deque* d);//获取队尾元素
size_t DequeSize(Deque* d);//获取队列长度
void DequeFrontPush(Deque* d, QDataType x);//队首入队
void DequeRearPush(Deque* d, QDataType x);//队尾入队
void DequeFrontPop(Deque* d);//队首出队
void DequeRearPop(Deque* d);//队尾出队
void DequePrint(Deque* d);//打印队列元素
void DequeDestroy(Deque* d);//销毁队列

完整代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include"wknb.h"

// 初始化双端队列
void DequeInit(Deque* d)
{
    assert(d); // 确保传入的队列指针不为空
    d->front = NULL; // 队头指针初始化为空
    d->rear = NULL;  // 队尾指针初始化为空
    d->size = 0;     // 队列大小初始化为0
}

// 判断队列是否为空
bool DequeEmpty(Deque* d)
{
    assert(d); // 确保传入的队列指针不为空
    return (d->front == NULL) && (d->rear == NULL); // 若队头和队尾都为空,则队列为空
}

// 获取队头元素
QDataType DequeFront(Deque* d)
{
    assert(d); // 确保传入的队列指针不为空
    assert(!DequeEmpty(d)); // 确保队列不为空
    return d->front->data; // 返回队头元素
}

// 获取队尾元素
QDataType DequeBack(Deque* d)
{
    assert(d); // 确保传入的队列指针不为空
    assert(!DequeEmpty(d)); // 确保队列不为空
    return d->rear->data; // 返回队尾元素
}

// 获取队列的长度
size_t DequeSize(Deque* d)
{
    return d->size; // 返回队列中元素的个数
}

// 队首插入元素
void DequeFrontPush(Deque* d, QDataType x)
{
    assert(d); // 确保传入的队列指针不为空
    // 创建一个新的节点
    DuListNode* newnode = (DuListNode*)malloc(sizeof(DuListNode));
    if (newnode == NULL)
    {
        perror("malloc fail"); // 如果内存分配失败,输出错误信息
        return;
    }
    newnode->data = x; // 新节点的数据为传入的元素
    newnode->next = NULL; // 初始化next为NULL
    newnode->prev = NULL; // 初始化prev为NULL
    
    // 如果队列为空,则新节点成为队头和队尾
    if (d->front == NULL)
    {
        d->front = d->rear = newnode;
    }
    else
    {
        // 否则将新节点插入到队头
        d->front->prev = newnode;
        newnode->next = d->front;
        d->front = newnode;
    }
    d->size++; // 增加队列大小
}

// 队尾插入元素
void DequeRearPush(Deque* d, QDataType x)
{
    assert(d); // 确保传入的队列指针不为空
    // 创建一个新的节点
    DuListNode* newnode = (DuListNode*)malloc(sizeof(DuListNode));
    if (newnode == NULL)
    {
        perror("malloc fail"); // 如果内存分配失败,输出错误信息
        return;
    }
    newnode->data = x; // 新节点的数据为传入的元素
    newnode->next = NULL; // 初始化next为NULL
    newnode->prev = NULL; // 初始化prev为NULL
    
    // 如果队列为空,则新节点成为队头和队尾
    if (d->front == NULL)
    {
        d->front = d->rear = newnode;
    }
    else
    {
        // 否则将新节点插入到队尾
        d->rear->next = newnode;
        newnode->prev = d->rear;
        d->rear = newnode;
    }
    d->size++; // 增加队列大小
}

// 队首删除元素
void DequeFrontPop(Deque* d)
{
    assert(d); // 确保传入的队列指针不为空
    assert(!DequeEmpty(d)); // 确保队列不为空
    
    // 只有一个节点的情况
    if (d->front == d->rear)
    {
        free(d->front); // 释放队头节点
        d->front = d->rear = NULL; // 队列为空
    }
    // 有多个节点的情况
    else
    {
        DuListNode* next = d->front->next; // 获取队头的下一个节点
        next->prev = NULL; // 更新下一个节点的prev指针
        d->front->next = NULL; // 断开队头节点的next指针
        free(d->front); // 释放队头节点
        d->front = next; // 更新队头指针
    }
    d->size--; // 减少队列大小
}

// 队尾删除元素
void DequeRearPop(Deque* d)
{
    assert(d); // 确保传入的队列指针不为空
    assert(!DequeEmpty(d)); // 确保队列不为空
    
    // 只有一个节点的情况
    if (d->front == d->rear)
    {
        free(d->rear); // 释放队尾节点
        d->front = d->rear = NULL; // 队列为空
    }
    // 有多个节点的情况
    else
    {
        DuListNode* prev = d->rear->prev; // 获取队尾的上一个节点
        prev->next = NULL; // 更新上一个节点的next指针
        d->rear->prev = NULL; // 断开队尾节点的prev指针
        free(d->rear); // 释放队尾节点
        d->rear = prev; // 更新队尾指针
    }
    d->size--; // 减少队列大小
}

// 打印队列的所有元素
void DequePrint(Deque* d)
{
    assert(d); // 确保传入的队列指针不为空
    DuListNode* cur = d->front; // 从队头开始
    DuListNode* tail = d->rear; // 队尾节点
    printf("队头:");
    // 遍历队列并打印元素
    while (cur != tail->next)
    {
        printf("%d<=>", cur->data); // 打印节点数据
        cur = cur->next; // 移动到下一个节点
    }
    printf("队尾\n"); // 打印队尾标识
}

// 销毁队列,释放所有节点
void DequeDestroy(Deque* d)
{
    assert(d); // 确保传入的队列指针不为空
    DuListNode* cur = d->front;
    while (cur)
    {
        DuListNode* del = cur; // 获取当前节点
        cur = cur->next; // 移动到下一个节点
        free(del); // 释放当前节点
    }
    d->front = d->rear = NULL; // 队列为空
}

写一个主函数给大家演示各项操作:


// 主函数,演示队列操作
int main() {
    DuListNode* Q = (DuListNode*)malloc(sizeof(DuListNode)); // 创建队列指针
    DequeInit(Q); // 初始化队列
    
    // 向队列中添加元素
    DequeFrontPush(Q, 1); 
    DequeFrontPush(Q, 3);
    DequeFrontPush(Q, 5);
    DequeFrontPush(Q, 7);
    DequeRearPush(Q, 2);
    
    DequePrint(Q); // 打印队列内容
}

运行结果如图所示:

双向队列的应用

双向队列兼具栈与队列的逻辑,因此它可以实现这两者的所有应用场景,同时提供更高的自由度

我们知道,软件的“撤销”功能通常使用栈来实现:系统将每次更改操作 push 到栈中,然后通过 pop 实现撤销。然而,考虑到系统资源的限制,软件通常会限制撤销的步数(例如仅允许保存 50 步)。当栈的长度超过 50 时,软件需要在栈底(队首)执行删除操作。但栈无法实现该功能,此时就需要使用双向队列来替代栈。请注意,“撤销”的核心逻辑仍然遵循栈的先入后出原则,只是双向队列能够更加灵活地实现一些额外逻辑。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值