【Queue新技法】用双数组实现一个队列 C++

news2024/12/24 9:05:26

目录

  • 1 常规的队列构建
  • 2 加入一些限制
    • 2-1形式化说明
  • 附录
    • 0 双数组或双链表实现队列
    • 1 单链表与循环缓冲区实现队列
    • 3 参考资料

1 常规的队列构建

  到火车站办理退票,排队的人构成队列。注意到有两个关键动作:

  1. 入队,即自觉站到队伍的末尾。
  2. 出队,从柜台离开。

  单链表:先建立一个漂亮的柜员姐姐(哨兵或额外的头结点),记忆当前队列的第一个人,再来个尾指针,用于添加下一个人。

  循环数组,也叫循环缓冲区,如图灰色部分就是队列的内容。

  这两种方式容易理解,其中入队、出队都是O(1)也容易实现,代码放在末尾附录中。

2 加入一些限制

  现先引入新情境:考虑一种情况在函数式环境不能用变量来记录末尾,怎么办?

  • 如果只能用链表实现(如Haskell语言),这时候入队必须遍历到末尾,时间复杂度O(N)
  • 如果只能用数组实现(如BASIC语言),入队必须把所有元素往后移动,时间复杂度又是O(N)

  如上图所示,左边是原来的队列,右边是翻转后的队列。然后会出现几个问题:

  1. 在队伍末尾直接插入,是O(N)复杂度,虽然翻转后插入是O(1),但翻转不也是O(N)吗?
  2. 翻转后入队是O(N),那出队呢?再翻转回来吗?不是更麻烦了吗?

  两个问题其实一个纠结在一个队列怎么能来回翻转呢?然而,我们可以用两个链表(或数组结构),一个只管出队,一个只管入队,只有在出队的队列为空,才把入队的队列翻转并放到出队(修改指针O(1)),可以看到本质是延时处理,翻转并不针对每次入队操作,其分摊性能可以降低为常数O(1)。

2-1形式化说明

  说明:记入队的列表为F(Front),出队的队列为R(Rear),那完整的队列 Q ( F , R ) Q(F,R) Q(FR),此外定义两个操作,设列表 X = { x 1 , x 2 , x 3 . . . } X=\{x_1,x_2,x_3...\} X={x1,x2,x3...},则

  • 函数 t a i l ( X ) = { x 2 , x 3 , x 4 . . . } tail(X)=\{x_2,x_3,x_4...\} tail(X)={x2,x3,x4...}即去掉首个元素的剩余部分;
  • 函数 b a l a n c e ( F , R ) = Q ( r e v e r s e ( R ) , ∅ ) : F = ∅ balance(F,R)=Q(reverse(R),\emptyset):F=\emptyset balance(F,R)=Q(reverse(R),):F=,此外当出队队列F不空,什么也不做。

  此时,入队和出队操作定义如下:
p u s h ( Q , x ) = b a l a n c e ( F , x ∪ R ) p o p ( Q ) = b a l a n c e ( t a i l ( F ) , R ) push(Q,x)=balance(F,{x} \cup R) \\ \\[2ex] pop(Q)=balance(tail(F),R) push(Q,x)=balance(F,xR)pop(Q)=balance(tail(F),R)  实现是容易的代码放在附录,现在先点这里平摊分析,证明一下两个链表(或数组)的分摊性能的确是常数级别。只有出队操作可能引发翻转,尽管翻转的总复杂度是O(N),可只有几次,但O(1)出队操作却有N个,即均摊到每次出队操作上只有 O ( 1 ) = O ( N ) / N O(1)=O(N)/N O(1)=O(N)/N
p o p ( Q ) = { O ( N ) , L为空集 1 , 其他 pop(Q) = \begin{cases} O(N), & \text{L为空集} \\ 1, & \text{其他} \end{cases} pop(Q)={O(N),1,L为空集其他

附录

  队列的基本的方法说明:

  • 判断是否非空? empty(is_empty)
  • 入队 push 、(append、push_back
  • 出队 pop 、(tail、pop_front
  • 查看头部元素 front (head)

0 双数组或双链表实现队列

#include<iostream>
#include<vector>// 代替数组
#include<algorithm>// 翻转vector
using namespace std;

#define ERROR -1
using Key=int;

//定义节点
struct Node
{
    Key key;
    struct Node *nxt;
    Node(Key k, struct Node *ptr = nullptr) : key(k), nxt(ptr) {}
};
using Nptr = struct Node *;
//定义链表
struct List{
    Nptr sentry;//哨兵节点
    List(){
        sentry=new Node(-1);
    }
};

class dualListQ
{
private:
    List m_front;
    List m_rear;

    int balance();
    Nptr reverse_list(Nptr head);//递归翻转链表
public:
    bool is_empty() const;
    void push_back(const Key &k) ;
    void pop_front();
    Key front();
};
int dualListQ::balance()
{
    if(m_front.sentry->nxt!=nullptr) return 0;
    if(m_rear.sentry->nxt==nullptr){
        cerr<<"balance()-> queue is empty!"<<endl;
        return ERROR;
    }
    m_front.sentry->nxt=reverse_list(m_rear.sentry->nxt);
    m_rear.sentry->nxt=nullptr;
    return 0;
}
//haed 指向哨兵的下一个节点
Nptr dualListQ::reverse_list(Nptr head){
    //递归的出口:空链或只有一个结点,直接返回头指针
    if (head == nullptr || head->nxt == nullptr) 
    {
        return head;
    }
    else
    {
        //一直递归,找到链表中最后一个节点
        Nptr new_head = reverse_list(head->nxt);
        //当逐层退出时,new_head 的指向都不变,一直指向原链表中最后一个节点;
        //递归每退出一层,函数中 head 指针的指向都会发生改变,都指向上一个节点。
        //每退出一层,都需要改变 head->next 节点指针域的指向,同时令 head 所指节点的指针域为 NULL。
        head->nxt->nxt = head;
        head->nxt = nullptr;
        //每一层递归结束,都要将新的头指针返回给上一层。由此,即可保证整个递归过程中,能够一直找得到新链表的表头。
        return new_head;
    }

}

bool dualListQ::is_empty() const
{
    return m_front.sentry->nxt==nullptr&&nullptr==m_rear.sentry->nxt; 
}

void dualListQ::push_back(const Key &k)
{
    Nptr old_=m_rear.sentry->nxt;
    Nptr new_=new Node(k);
    m_rear.sentry->nxt=new_;
    new_->nxt=old_;
    if(ERROR==balance()) return;
}

void dualListQ::pop_front()
{   
    if(ERROR==balance()) return;
    Nptr tmp=m_front.sentry->nxt;
    m_front.sentry->nxt=tmp->nxt;
    delete tmp;
}

Key dualListQ::front()
{
    if(is_empty()){
        cerr<<"front()-> queue is empty!"<<endl;
        return ERROR;
    }
    if(m_front.sentry->nxt==nullptr){
        balance();
    }
    return m_front.sentry->nxt->key;
}



class dualVectorQ
{
private:
    vector<Key> m_front,m_rear;
    int balance();
public:
    bool is_empty() const;
    int push_back(const Key &k) ;
    void pop_front();
    Key front();
};
int dualVectorQ::balance()
{
    if(m_front.empty()){
        if(m_rear.empty()){
            cerr<<"balance()-> queue is empty."<<endl;
            return ERROR;
        }
        reverse(m_rear.begin(),m_rear.end());
        for(auto &k:m_rear){
            m_front.push_back(k);
        }
        m_rear.clear();
    }
    return 0;
}
bool dualVectorQ::is_empty() const
{
    return m_front.size()+m_rear.size()==0;
}

int dualVectorQ::push_back(const Key &k)
{
    m_rear.push_back(k);
    return 0;
}
void dualVectorQ::pop_front(){
    balance();
    m_front.pop_back();// 翻转后,队尾即对头
}
Key dualVectorQ::front(){
    if(is_empty()){
        cerr<<"front()-> Queue is empty..."<<endl;
        return ERROR;
    }
    balance();
    return m_front.back();// 翻转后,队尾即对头
}


#define see(x) cout<<x<<endl
void test(){
    // dualListQ que;//创建空的队列
    dualVectorQ que;
    int arr[]={1,3,5,7,9};
    for (size_t i = 0; i < 5; i++)
    {
        que.push_back(arr[i]);
    }
    see(que.front());//1
    que.pop_front();
    see(que.front());//3
    que.pop_front();
    que.pop_front();
    que.pop_front();
    if(que.is_empty())
        see("queue is empty");//无
    see(que.front());//9
    que.pop_front();
    if(que.is_empty())
        see("queue is empty");//有
    see(que.front());

}

int main(){
    test();
    return 0;
}


1 单链表与循环缓冲区实现队列

#include<iostream>
using namespace std;

#define ERROR -1
using Key=int;
class listQ
{
private:
    struct Node 
    {
        Key key;
        struct Node* nxt;
        Node(Key k,struct Node* ptr=nullptr):key(k),nxt(ptr){}
    };
    using Nptr=struct Node*; 
    Nptr m_sentry=nullptr;//头结点,指向真实的头一个数据
    Nptr m_tail=nullptr;//指向尾节点
public:
    listQ();//创建空队列
    ~listQ();
    bool is_empty() const;
    void push_back(const Key &k) ;
    void pop_front();
    Key front();
};
listQ::listQ()
{
    m_sentry=new Node(-1);
    m_tail=m_sentry;
}

listQ::~listQ()
{
    while(!is_empty()){
        pop_front();
    }

    delete m_sentry;
    cout<<"over   ..."<<endl;
}

bool listQ::is_empty() const
{
    if(m_sentry->nxt==nullptr) return true;
    return false;
}

void listQ::push_back(const Key &k)
{
    Nptr new_node=new Node(k);
    m_tail->nxt=new_node;
    m_tail=new_node;
}

void listQ::pop_front()
{
    Nptr tmp=m_sentry->nxt;
    m_sentry->nxt=tmp->nxt;
    delete tmp;
}

Key listQ::front()
{
    if(is_empty()){
        cerr<<"queue is empty!"<<endl;
        return ERROR;
    }
    return m_sentry->nxt->key;
}


//循环缓冲区
class arrayQ
{
private:
    static const size_t QSIZE=5;
    Key m_buf[100];
    int m_head=0;
    int m_tail=0;//指向队列末尾下一个空位
    int m_length=0;
public:
    bool is_empty() const{ return 0==m_length;}
    int push_back(const Key &k) ;
    void pop_front();
    Key front();

};
void arrayQ::pop_front(){
    --m_length;
    ++m_head;
    m_head -= (m_head < QSIZE) ?  0 : QSIZE;//模拟取余数,因为某些机器取余很慢
}
int arrayQ::push_back(const Key &k){
    if(m_length==QSIZE){
        cerr<<"push-> Queue is full!!"<<endl;
        return ERROR;
    }

    m_buf[m_tail++]=k;
    m_tail -=(m_tail < QSIZE) ?  0 : QSIZE;
    ++m_length;
    return 0;
}
Key arrayQ::front(){
    if(is_empty()){
        cerr<<"front()-> Queue is empty..."<<endl;
        return ERROR;
    }

    return m_buf[m_head];
}


#define see(x) cout<<x<<endl
void test(){
    // listQ que;//创建空的队列
    arrayQ que;
    int arr[]={1,3,5,7,9};
    for (size_t i = 0; i < 5; i++)
    {
        que.push_back(arr[i]);
    }

    // que.push_back(11);
    see(que.front());//1
    que.pop_front();
    see(que.front());//3
    que.pop_front();
    que.pop_front();
    que.pop_front();
    see(que.is_empty());
    see(que.front());//9
    que.pop_front();
    see(que.is_empty());
    see(que.front());

}

int main(){
    test();
    return 0;
}



3 参考资料

刘新宇 《算法新解》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/508646.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一篇文章搞定《ViewPage2离屏加载》

------《ViewPage2离屏加载》 前言离屏加载是什么OffscreenPageLimit设置OffscreenPageLimit表现OffscreenPageLimit值为1OffscreenPageLimit值为3 OffscreenPageLimit值取多大比较合适 前言 这里就不讲ViewPage了&#xff0c;买新不买旧&#xff0c;用新不用旧。 但是会将Vie…

Power BI: 表格显示切片器选中时间之前的数据

例如下面的例子&#xff0c;Year List表和Caleadar表是1对多的关联关系。 Caleadar表&#xff1a; Caleadar VAR StartYear YEAR(NOW())-5 VAR EndYear YEAR(NOW())5 RETURN ADDCOLUMNS (CALENDAR (DATE(StartYear,1,1), DATE(EndYear,12,31)),"Year", YEAR ([…

【设计模式】| 修炼内功 | 23种设计模式——工厂方法模式(含抽象)

设计模式如同织锦之艺术&#xff0c;精心构筑&#xff0c;展示优美。 学习设计模式&#xff0c;犹如追逐清晨的曙光&#xff0c;扉页掀开了人生的新篇章。当你学会设计模式的奥秘&#xff0c;就如同走进了灯火通明的城市&#xff0c;丰富多彩的建筑&#xff0c;让你大开眼界&am…

30个最常用的空间SQL用例

在开始使用空间 SQL 时&#xff0c;至少对我而言&#xff0c;最大的挑战之一是拥有一个快速简便的参考&#xff0c;以将你当前的 GIS 工作流转换为 SQL。 有许多令人惊叹的资源可以扩展这方面的知识&#xff0c;但本指南旨在成为一本真正简单的食谱&#xff0c;以开始将你当前的…

从零开始的强化学习入门学习路线

强化学习是机器学习领域中的一个分支&#xff0c;它是指智能体通过与环境的交互来学习如何采取最佳行动以最大化奖励信号的过程。强化学习在许多领域都有广泛的应用&#xff0c;如游戏、自动驾驶和机器人控制等。如果你对强化学习感兴趣&#xff0c;下面是一个入门强化学习的学…

SequoiaDB分布式数据库2023.4月刊

本月看点速览 赋能产业升级&#xff0c;荣获新睿之星 聚焦金融&#xff0c;进一步探索非结构化数据价值释放 再获肯定&#xff0c;入选2023年中国最佳信创厂商入围名单 青杉计划2023已开启&#xff0c;一起攀登更高的“杉” 赋能产业升级&#xff0c;荣获新睿之星 4月18日…

PyTorch典型函数之gather

PyTorch典型函数之gather 作用描述函数详解典型应用场景(1) 深度强化学习中计算损失函数 参考链接 作用描述 如上图所示&#xff0c;假如我们有一个Tensor A&#xff08;图左&#xff09;&#xff0c;要从A中提取一部分元素组成Tensor B&#xff08;图右&#xff09;&#xff0…

7.外观模式C++用法示例

外观模式 一.外观模式1.原理2.特点3.外观模式与装饰器模式的异同4.应用场景C程序示例 一.外观模式 外观模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;它提供了一个简单的接口&#xff0c;隐藏了一个或多个复杂的子系统的复杂性&#xff0c;并使…

图嵌入表示学习—Node Embeddings随机游走

Random Walk Approaches for Node Embeddings 一、随机游走基本概念 想象一个醉汉在图中随机的行走&#xff0c;其中走过的节点路径就是一个随机游走序列。 随机行走可以采取不同的策略&#xff0c;如行走的方向、每次行走的长度等。 二、图机器学习与NLP的关系 从图与NLP的…

posix线程的优先级测试

如果创建的线程不够多&#xff0c;有些问题是体现不出来的。 优先级打印&#xff1a; 测试目的&#xff1a;输出三种调度模式下的最大优先级和最小优先级 #include <stdio.h> #include <sys/socket.h> #include <sys/types.h> #include <fcntl.h> #…

Kubernetes_容器网络_01_Docker网络原理(二)

文章目录 一、前言二、被隔离的Docker容器三、网桥Bridge四、VethPair网络对五、统一宿主机上的两个Container容器通信六、宿主机访问其上的容器七、宿主机上的容器访问另一个宿主机八、尾声 一、前言 二、被隔离的Docker容器 Linux 网络&#xff0c;就包括&#xff1a;网卡&…

技术选型对比- RPC(Feign VS Dubbo)

协议 Dubbo 支持多传输协议: Dubbo、Rmi、http,可灵活配置。默认的Dubbo协议&#xff1a;利用Netty&#xff0c;TCP传输&#xff0c;单一、异步、长连接&#xff0c;适合数据量小(传送数据小&#xff0c;不然影响带宽&#xff0c;响应速度)、高并发和服务提供者远远少于消费者…

UnityWebGL+阿里云服务器+Apache完成项目搭建展示

一、服务器相关 Step1:租借一台阿里云服务器 我自己租借了一台北京的ECS服务器&#xff0c;有免费一年的活动&#xff0c;1 vCPU 2 GiB&#xff0c;我自己选择的Ubuntu系统&#xff0c;也可以选择Windows系统 Step2:进入远程连接 进入自己的服务器实例后&#xff0c;点击远程…

vue+elementui+nodejs机票航空飞机航班查询与推荐

语言 node.js 框架&#xff1a;Express 前端:Vue.js 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat 开发软件&#xff1a;VScode )本系统主要是为旅客提供更为便利的机票预定方式&#xff0c;同时提高民航的预定机票的工作效率。通过网络平台实现信息化和网络化&am…

关于Android的性能优化,主要是针对哪些方面的问题进行优化

前言 我们在开发Android的时候&#xff0c;经常会遇到一些性能问题&#xff1b;例如&#xff1a;卡顿、无响应&#xff0c;崩溃等&#xff0c;当然&#xff0c;这些问题为我们可以从日志来进行追踪&#xff0c;尽可能避免此类问题的发生&#xff0c;要解决这些问题&#xff0c…

mysql从零开始(05)----锁

全局锁 使用 # 启用全局锁 flush tables with read lock # 释放全局锁 unlock tables开启全局锁后&#xff0c;整个数据库就处于只读状态了&#xff0c;这种状态下&#xff0c;对数据的增删改操作、对表结构的更改操作都会被阻塞。 另外&#xff0c;当会话断开&#xff0c;全…

【1015. 可被 K 整除的最小整数】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给定正整数 k &#xff0c;你需要找出可以被 k 整除的、仅包含数字 1 的最 小 正整数 n 的长度。 返回 n 的长度。如果不存在这样的 n &#xff0c;就返回 -1。 注意&#xff1a; n 不符合 64 位带…

手把手教你在winform中将文本或文件路径拖到控件中

文章目录 前言博主履历介绍&#xff1a;一、将txt文件的所有内容复制到 RichTextBox中二、将txt文件的一行内容移动到RichTextBox中三、将多个文件的全路径复制到 RichTextBox中四 、源码1、[Winform从入门到精通&#xff08;1&#xff09;——&#xff08;如何年入30万&#x…

「MIAOYUN」:降本增效,赋能传统企业数字化云原生转型 | 36kr 项目精选

作为新经济综合服务平台第一品牌&#xff0c;36氪自2019年落地四川站以来&#xff0c;不断通过新锐、深度的商业报道&#xff0c;陪跑、支持四川的新经济产业。通过挖掘本土优质项目&#xff0c;36氪四川帮助企业链接更多资源&#xff0c;助力企业成长&#xff0c;促进行业发展…

分布式系统概念和设计——命名服务设计和落地经验

分布式系统概念和设计 通过命名服务&#xff0c;客户进程可以根据名字获取资源或对象的地址等属性。 被命名的实体可以是多种类型&#xff0c;并且可由不同的服务管理。 命名服务 命名是一个分布式系统中的非常基础的问题&#xff0c;名字在分布式系统中代表了广泛的资源&#…