队列的知识讲解与基本实现(数据结构)

引言

中午在食堂打饭,真是一个令人头疼的事情,去食堂的路上也总是步伐匆匆,为什么啊,这还用说,迟一点去,你就会知道什么叫做人山人海了,在食堂排队的时候,相比较学生来说,打饭阿姨毕竟是少数,在每个窗口都有人的时候,不免我们就得等待,直到前面的一个学生打完饭离开,后面排队的人才可以继续向前走,直到轮到自己,别提多费劲了,但是秩序和规则却是我们每个人都应该遵守的,也只能抱怨自己来的迟了

这种 “先进先出” 的例子就是我们所讲的基本数据结构之一 ”队列“

例子补充:用电脑的时候,有时候机器会处于疑似死机的状态, 鼠标点什么似乎都没有用,双击任何快捷方式都不动,就当你失去耐心,打算reset的时候,突然它就像酒醒了一样,把你刚才点击的所有操作全部按照顺序执行了一遍,这其实是因为操作系统中的多个程序隐需要通过一个通道输出,而按照先后次序排队等待造成的 ——《大话数据结构》

队列的基本定义

定义:队列是一种只允许在一段进行删除操作,在另一端进行插入操作的线性表

允许插入的一段称作队尾 (rear),允许删除的的一端称为队头 (front)

队列的数据元素又叫做队列元素,在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队 ,也正是因为队列只允许在一段插入,另一端删除,所以这也就是我们前面例子中体现出来的先进先出 (FIFO-first in first out) 的概念

补充:除此之外,还有的队列叫做双端队列,也就是可以在表的两边进行插入和删除操作的线性表

双端队列分类:

  1. 输出受限的双端队列:删除操作限制在表的一段进行,而插入操作允许早表的两端进行
  2. 插入操作限制在表的一段进行,而删除操作允许在表的两端进行

队列的抽象数据类型

#ifndef _QUEUE_H_
#define _QUEUE_H_
#include <exception>
using namespace std;

// 用于检查范围的有效性
class outOfRange:public exception {
public:
    const char* what()const throw() {
        return "ERROR! OUT OF RANGE.\n";
    }
};  

// 用于检查长度的有效性
class badSize:public exception {
public:
    const char* what()const throw() {
        return "ERROR! BAD SIZE.\n";
    }
}; 

template <class T>
class Queue {
public:
    //判队空
    virtual bool empty() const = 0;
    //清空队列
    virtual void clear() = 0;
    //求队列长度
    virtual int size() const = 0;
    //入队
    virtual void enQueue(const T &x) = 0;
    //出队
    virtual T deQueue() = 0;
    //读队头元素
    virtual T getHead() const = 0;
    //虚析构函数
    virtual ~Queue(){}
};
#endif 

循环队列

队列作为一个特殊的线性表,自然也有着顺序以及链式存储两种方式,我们先来看看它的顺序存储方式——循环队列

在队列的顺序存储中,我们除了创建一个具有一定空间的数组空间外,还需要两个指针,分别指向队列的前端和微端,下面的代码中,我们选择将队头指针指向头元素的前一个位置,队尾指针指向队尾元素(当然这不是唯一的方式,还可以将头指针指向头元素,队尾指针指向队尾元素的后一个位置,原理是基本一致的)

为什么要这么做,并且为什么这种存储我们叫做循环队列?

我们一步步分析一下:

我们先按照我们一般的想法画出队列元素进出队的过程,例如队列元素出队

这样的设想,也就是根据我们前面食堂排队的例子画出来的,但是我们可以清晰的看到,当a0出队后,a0后的元素全部需要前移,将空位补上,但我们在计算机中讲究性能二字,如何可以提高出队的性能呢?

循环队列就这样被设计出来了,我们如果不再限制队头一定在整个空间的最前面,我们的元素也就不需要集体移动了

问题一

这个时候我们就需要考虑这样的问题了:

① 如何为了解决只有一个元素的时候,队头和队尾重合使得处理变得麻烦?

  • 这时我们前面提到的两个指针就派上用场了(队头指针指向头元素的前一个位置,队尾指针指向队尾元素)当头尾指针相等的时候,代表是空队列

问题二

但是有一个大问题出现了 !

如果前面有空闲的空间还好说,一旦头元素前面没有空间,我们的队头指针就指向到了数组之外,也就会出现数组越界问题,这该怎么办呢?

我们可以看到,虽然我们的表头已经没有了任何空间,但是表的后半部分还有空余空间,这种现象我们称作假溢出,打个比方,接近上课你缓缓走进教室,看到只有前排剩下了两个位置,你总不会转身就走吧,当然可以去前排坐,只有实在没座位了,才考虑离开

我们可以做出这样一种比较可行的方案

  • 我们只需要将这个队列收尾连接起来,当后面的空间满后,接着从前面空出来的空间中进队,同样的,我们的表头指针也找到了可以指向的位置
  • 具体的连接方法,就是将date[0...maxSize] 的单元0认为是maxSize - 1

问题三

我们刚才也提到了,当表头指针和表尾指针相等的时候就解决了空队列的情况,但是在表满的情况下,你会发现,同样也满足表头表尾指针相等,那么又如何解决这个问题呢?(我们给出三种可行的解决方案)

  • A:设置一个标志变量flag,当front = rear的时,且flag = 0的时候为空,若flag = 1 的时候为队列满
  • B:设计一个计数器count统计当前队列中的元素数量,count == 0 队列空,count == maxsSize 队列满
  • C:保留一个存储空间用于区分是否队列已满,也就是说,当一个还空闲一个单元时候,我们就认为表已经满了

我们重点讲解 C 中的方法

我们根据这种方法可以总结出几个条件的运算式

  • 队列为满的条件:(rear+1) % MaxSize == front
  • 队列为空的条件:front == rear
  • 队列中元素的个数:(rear- front + maxSize) % MaxSize
  • 入队:rear = (rear + 1) % maxSize
  • 出队:front = (front + 1) % maxSize

(一) 顺序队列的类型定义

#ifndef _SEQQUEUE_H_
#define _SEQQUEUE_H_
#include "Queue.h"

template <class T>
class seqQueue:public Queue<T> {
private:
    //指向存放元素的数组
    T &data;
    //队列的大小
    int maxSize;
    //定义队头和队尾指针
    int front, rear;
    //扩大队列空间
    void resize();
public:
    seqQueue(int initSize = 100);
    ~seqQueue() {delete []data;}
    //清空队列
    void clear() {front = rear = -1;}
    //判空
    bool empty() const {return front == rear;}
    //判满
    bool full() const {return (rear + 1) % maxSize == front;}
    //队列长度
    int size() const {(rear- front + maxSize) % maxSize;}
    //入队
    void enQueue(const T &x);
    //出队
    T deQueue();
    //取队首元素
    T getHead() const;
}; 

#endif 

(二) 初始化一个空队列

template <class T>
seqQueue<T>::seqQueue(int initSize) {
    if (initSize <= 0) throw badSize();
    data = new T[initSize];
    maxSize = initSize;
    front = rear = -1;
}

(三) 入队

template <class T>
void seqQueue<T>::enQueue(const T &x) {
    //队满则扩容
    if ((rear + 1) % maxSize == front) resize();
    //移动队尾指针
    rear = (rear + 1) % maxSize;
    //x 入队
    data[rear] = x;
}

(四) 出队

template <class T>
T seqQueue<T>::deQueue() {
    //队列为空则抛出异常
    if (empty()) throw outOfRange();
    //移动队尾指针
    front = (front + 1) % maxSize;
    //x入队
    return data[front];
}

(五) 取队首元素

template <class T>
T seqQueue<T>::getHead() const {
    if (empty()) throw outOfRange();
    //返回队首元素,不移动队首指针
    return data[(front + 1) % maxSize];
}

(六) 扩大队列空间

template <class T>
void seqQueue<T>::resize() {
    T *p = data;
    data = new T[2 *maxSize];
    for(int i = 1; i < size(); ++i)
        //复制元素
        data[i] = p[(front + i) % maxSize];
    //设置队首和队尾指针
    front = 0; rear = size();
    maxSize *= 2;
    delete p;
}

链队列

用链式存储结构表示队列我们叫做链队列,用无头结点的单链表表示队列,表头为队头,表尾为队尾,需要两个指针分别指向队头元素和队尾元素,这种存储结构的好处之一就是不会出现队列满的情况

(一) 顺序队列的类型定义

#ifndef _LINKQUEUE_H_
#define _LINKQUEUE_H_
#include <iostream>
#include "Queue.h"

template <class T>
class linkQueue:public Queue<T> {
private:
    struct node {
        T data;
        node *next;
        node (const T &x, node *N = NULL) {
            data = x;
            next = N;
        }
        node ():next(NULL){}
        ~node () {}
    };
    node *front, *rear;
public:
    linkQueue(){front = rear = NULL;};
    ~linkQueue() {clear();}
    //清空队列
    void clear();
    //判空
    bool empty() const {return front == NULL;}
    //队列长度
    int size() const;
    //入队
    void enQueue(const T &x);
    //出队
    T deQueue();
    //取队首元素
    T getHead() const;
};

#endif 

(二) 清空队列

template <class T>
void linkQueue<T>::clear() {
    node *p;
    //释放队列中所有节点
    while(front != NULL) {
        p = front;
        front = front -> next;
        delete p;
    }
    //修改尾指针
    rear = NULL;
} 

(三) 求队列长度

template <class T>
int linkQueue<T>::size() const {
    node *p = front;
    int count = 0;
    while(p) {
        count++;
        p = p -> next;
    }
    return count;
}

(四) 入队

template <class T>
void linkQueue<T>::enQueue(const T &x) {
    if(rear == NULL)
        front = rear = new node(x);
    else {
        rear -> next = new node(x);
        rear = rear -> next;
    }
}

(五) 出队

template <class T>
T linkQueue<T>::deQueue() {
    //队列为空则抛出异常
    if (empty()) throw outOfRange();
    node *p = front;
    //保存队首元素
    T value = front -> data;
    front = front -> next;
    if (front == NULL)
        rear = NULL;
    delete p;
    return value;
}

(六) 取队首元素

template <class T>
T linkQueue<T>::getHead() const {
    if (empty()) throw outOfRange();
    return front -> data;
}

结尾:

如果文章中有什么不足,或者错误的地方,欢迎大家留言分享想法,感谢朋友们的支持!

如果能帮到你的话,那就来关注我吧!如果您更喜欢微信文章的阅读方式,可以关注我的公众号

在这里的我们素不相识,却都在为了自己的梦而努力 ?

一个坚持推送原创开发技术文章的公众号:理想二旬不止

原文地址:https://www.cnblogs.com/ideal-20/p/11755292.html

时间: 2024-11-08 23:00:13

队列的知识讲解与基本实现(数据结构)的相关文章

Html基础知识讲解

Html基础知识讲解 <title>淄博汉企</title> </head> <body bgcolor="#66FFCC" topmargin="200" leftmargin="200px" bottommargin="400px"> <a name="top"></a> 今天<br /> 天气     不错<br

iPhone激活策略知识讲解:官方解锁和黑解

iPhone激活策略知识讲解:官方解锁和黑解 [复制链接]     LEECHY 该用户从未签到 1372 XY豆 438 帖子 440 贡献 苹果花 积分 2250 发消息 电梯直达 楼主  发表于 2016-1-7 22:13:48 | 只看该作者  马上注册,结交更多好友,享用更多功能,让你轻松玩转社区. 您需要 登录 才可以下载或查看,没有帐号?立即注册 x 官方解锁:通过官方正规渠道,修改激活策略,达到无锁的过程,称之为官方解锁.黑解:通过非正规渠道,违规修改激活策略,被苹果官方发现之

RAID知识讲解

RAID知识讲解 目录 一.Raid介绍.... 1 1.什么是Raid?... 1 2.Raid级别介绍.... 1 3.Raid级别的优.缺点比较(图解):... 1 4.       7级RAID的简单定义(图解):... 2 5.冗余介绍.... 2 二.Raid技术分类.... 2 1.软RAID技术:... 2 2.硬RAID技术:... 3 3.Raid和LVM的区别.... 3 3.1.什么是LVM?... 3 3.2.Raid和LVM的区别:... 4 4.我们为什么需要Rai

HTTPS安全证书访问连接知识讲解

HTTPS安全证书访问连接知识讲解 01:网络安全涉及的问题: ①. 网络安全问题-数据机密性问题 传输的数据可能会被第三方随时都能看到 ②. 网络安全问题-数据完整性问题 传输的数据不能随意让任何人进行修改 ③. 网络安全问题-身份验证问题 第一次通讯时,需要确认通讯双方的身份正确 02:网络安全涉及的问题解决: ①. 网络安全问题-数据机密性问题解决 a) 利用普通加密算法解决机密性 利用相应算法,对传输数据(明文数据)进行加密(密文数据):再利用对应算法,将加密数据解密变为 真实数据 优点

2015年12月10日 spring初级知识讲解(三)Spring消息之activeMQ消息队列

基础 JMS消息 一.下载ActiveMQ并安装 地址:http://activemq.apache.org/ 最新版本:5.13.0 下载完后解压缩到本地硬盘中,解压目录中activemq-core-5.13.0.jar,这就是ActiveMQ提供给我们的API. 在bin目录中,找到用于启动ActiveMQ的脚本,运行脚本后ActiveMQ就准备好了,可以使用它进行消息代理. 访问http://127.0.0.1:8161/admin/能看到如下则表示安装成功了. 二.在Spring中搭建消

C++STL泛型编程基础知识讲解--------2015年2月3日

今天学习了C++STL泛型编程的基础知识,我对主要知识整理如下: STL提供三种类型的组件:容器,迭代器,算法.支持泛型程序设计标准.容器主要有两类:顺序容器和关联容器.顺序容器:vector,list,deque,string等都是一系列连续元素的集合.关联容器:set,multiset,map,multimap包含查找元素的键值.迭代器:遍历容器STL算法库:排序算法,不可变序算法,变序性算法,数值算法. /******************************************

数据库基础知识讲解

99%的网站瓶颈都在后端  最主要的瓶颈在于:数据库和存储 存储前面用缓存来减轻压力 数据库前面用memcached缓存来减轻压力 数据库就是存放数据的仓库 比较流行的数据库模型有三种:层次式数据库.网络式数据库和关系型数据库 最常用的有关系型数据库和非关系型数据库 关系型数据库是把数据结构归结为简单的二元关系(二维表格形式),对数据的操作都建立在一个或多个关系表格上. 1.二维表格形式 2.典型代表:MySQL  oracle 3.用sql语句对数据进行操作与管理 非关系型数据库诞生原因:动态

【Android高级】CSDN博客精华知识讲解汇总

1.Activity的启动方式和flag详解  from(任玉刚) 2.android_WebView与Javascript的交互 from(redarmychen的专栏) 3.android-async-http开源项目from(redarmychen的专栏) 4.HTML实训课程笔记 from(redarmychen的专栏) 5.防止Android程序被系统kill掉的处理方法  from(小崔的博客) 6.直接拿来用!最火的Android开源项目系列 from(徐刘根的专栏) 7.安卓框架

Linux系统基本的内存管理知识讲解

内存是Linux内核所管理的最重要的资源之一.内存管理系统是操作系统中最为重要的部分,因为系统的物理内存总是少于系统所需要的内存数量.虚拟内存就是为了克服这个矛盾而采用的策略.系统的虚拟内存通过在各个进程之间共享内存而使系统看起来有多于实际内存的内存容量.Linux支持虚拟内存, 就是使用磁盘作为RAM的扩展,使可用内存相应地有效扩大.核心把当前不用的内存块存到硬盘,腾出内存给其他目的.当原来的内容又要使用时,再读回内存. 一.内存使用情况监测 (1)实时监控内存使用情况 在命令行使用“Free