11-数据结构探险系列—队列篇

数据结构探险系列—队列篇

这是c++远征系列的进阶课程。

少了基础语法,对于程序语言的应用,思考。

什么是数据结构?

一群数据集合和数据之间的关系。
是指相互之间存在一种或多种特定关系的数据元素集合,

我们将学到:

  • 数据结构的原理
  • c++完整的实现每种数据结构的编码

队列

特点:先入先出(FIFO)

例子: 火车站买票时,队头队尾。售票员从头开始卖票,先到的人先买到票就可以离开。

队列的分类:

  • 普通队列
  • 环形队列
队列

左侧窗口想象为售票员。然后第一个人排队,它既是队列头,又是队列尾。之后再进来人, 队列尾为最后一个人。由于内存的限制,队列不可能无限长。

两种方式:

  • 售票员会动,卖完一个票往前走一位,浪费了已经走入的前半部分空间。
  • 售票员不动,所有人走一位,速度慢。
img_0322395a02beb50e108667360b05f731.jpe

环形队列的优缺点:

  • 屏蔽了普通队列的缺点,充分利用空间
  • 不好理解

-有队列头,有队列尾。只有一个元素时,它既是队列头,又是队列尾,有顺时针排队和逆时针排队之说的,类似贪吃蛇自己吃自己尾巴

队列的用途;自动排号机

自动排号机的单子:

拍号机单据
面向对象的队列设计

实现环形队列,与c语言略有不同。

MyQueue设计(左边为c++,右侧注释情况为c语言):

#ifndef MYQUEUE_H
#define MYQUEUE_H

class MyQueue 
{
public:
    MyQueue(int queueCapacity); //InitQueue(&Q)创建队列
    virtual ~MyQueue();           //DestroyQueue(&Q)销毁队列
    void ClearQueue();            //ClearQueue(&Q)清空队列
    bool QueueEmpty() const;    //QueueEmpty(Q)判空队列
    int  QueueLength() const;   //QueueLength(Q) 队列长度
    bool EnQueue(int element);  //EnQueue(&Q,&element) 新元素加入
    bool DeQueue(int &element); //DeQueue(&Q,&element) 首元素出队
    void QueueTraverse();       //QueueTraverse(Q,visit()) 遍历队列
    //简单类型不需要实现visit方法。
private:
    int *m_pQueue;             //队列数组指针
    int m_iQueueLen;               //队列元素个数
    int m_iQueueCapacity;      //队列数组容量
    int m_iHead;               //数组的下标:队头
    int m_iTail;               //数组的下标:队尾
};

#endif

上述代码左侧为c++中队列实现所需要的函数,右侧注释部分为c语言。

一个队列的实现对于c++必须要有以下数据成员及成员函数:

  • 创建队列的构造函数(传入队列容量)
  • 销毁队列的析构函数
  • 清空队列
  • 判断队列是否为空
  • 新元素加入
  • 首元素出队
  • 遍历队列
  • 私有数据:容量、数组指针、容量
环形队列代码实现
一个只有4元素的环形队列

编程思路: 红色圈为指定起始插入点。插入第一个时,头尾均为q[0].队尾指向的位置就是要插入的位置,队尾由q[0]变成了q[1]
每次插入之前判断有没有占满, 取出元素从队列头位置开始取。

4-1-CircleQueue

MyQueue.h:

#ifndef MyQueue_h
#define MyQueue_h

class MyQueue
{
public:
    MyQueue(int queueCapacity);   //InitQueue(&Q)创建队列
    virtual ~MyQueue();           //DestroyQueue(&Q)销毁队列
    void ClearQueue();            //ClearQueue(&Q)清空队列
    bool QueueEmpty() const;      //QueueEmpty(Q)判空队列
    bool QueueFull() const;       //判断满
    int  QueueLength() const;     //QueueLength(Q) 队列长度
    bool EnQueue(int element);    //EnQueue(&Q,&element) 新元素加入
    bool DeQueue(int &element);   //DeQueue(&Q,&element) 首元素出队
    void QueueTraverse();         //QueueTraverse(Q,visit()) 遍历队列
    //简单类型不需要实现visit方法。
private:
    int *m_pQueue;             //队列数组指针
    int m_iQueueLen;           //队列元素个数
    int m_iQueueCapacity;      //队列数组容量
    int m_iHead;               //数组的下标:队头
    int m_iTail;               //数组的下标: 队尾
};

#endif /* MyQueue_h */

MyQueue.cpp:

#include "MyQueue.h"
#include <iostream>
using namespace std;

MyQueue::MyQueue(int queueCapacity)
{
    m_iQueueCapacity = queueCapacity;//将用户定义的容积传递给内部变量容积
    
    //初始化队头队尾
    m_iHead = 0;
    m_iTail = 0;
    
    //队列元素个数初始化
    m_iQueueLen = 0;
    //分配内存,大小由容量决定
    m_pQueue = new int[m_iQueueCapacity]; // 申请内存的失败处理略
    ClearQueue();
}

MyQueue::~MyQueue()
{
    delete[]m_pQueue;
    m_pQueue = NULL;
}

void MyQueue::ClearQueue()
{
    // 重置队头队尾
    m_iHead = 0;
    m_iTail = 0;
    // 队列元素个数重置
    m_iQueueLen = 0;
}

bool MyQueue::QueueEmpty() const
{
    // 长度为0为空,head和tail会移动,此时id为0不一定是空
    /*if (m_iQueueLen == 0)
    {
        return true;
    }
    else
    {
        return false;
    }*/
    return (m_iQueueLen == 0) ? true : false;
}

int MyQueue::QueueLength() const
{
    return m_iQueueLen;
}

bool MyQueue::QueueFull() const
{
    return (m_iQueueLen == m_iQueueCapacity) ? true : false;
}


// 插入元素
bool MyQueue::EnQueue(int element)
{
    //先判断满,容量和长度相同
    if (QueueFull())
    {
        return false;
    }
    else
    {
        //插入新元素到当前队尾位置
        m_pQueue[m_iTail] = element;
        //队尾后移
        m_iTail++;
        m_iTail = m_iTail % m_iQueueCapacity;//循环
        //插入元素改变len
        m_iQueueLen++;
        return true;
    }

}


//这里的element必须是一个引用
bool MyQueue::DeQueue(int &element)
{
    if (QueueEmpty())
    {
        return false;
    }
    else {
        element = m_pQueue[m_iHead];
        m_iHead++;
        m_iHead = m_iHead % m_iQueueCapacity;
        m_iQueueLen--;
        return true;
    }
}

//使用const函数是为了防止这个函数对数据成员进行改变,这个函数只可以读取数据成员,所以,当不希望函数对数据成员改变时就需要使用从const函数

void MyQueue::QueueTraverse()
{
    for (int i=m_iHead;i<m_iHead+m_iQueueLen;i++)
        //要加上头的数字。不然次数不够
        //当前i是3.len是满。则i++越界
    {
        cout << m_pQueue[i%m_iQueueCapacity] << endl;
    }
}

几点要注意的地方:

  • 遍历时的起始位置是队列头部,而不是0
  • 要在队列的入队和出队操作中,对于队列的长度进行维护
  • 遍历的循环次数为头部位置加上队列长度

入队时,如果此时的队尾位置已经超出长度了,就应该使用该数对于长度做取余操作,以此达成循环的效果(轮播图实现)

出队时,如果此时的队头位置已经超过长度了,也做相同处理。

m_iTail = m_iTail % m_iQueueCapacity;//循环
bool MyQueue::DeQueue(int &element)
{
    if (QueueEmpty())
    {
        return false;
    }
    else {
        element = m_pQueue[m_iHead];
        m_iHead++;
        m_iHead = m_iHead % m_iQueueCapacity;
        m_iQueueLen--;
        return true;
    }
}
void MyQueue::QueueTraverse()
{
    for (int i=m_iHead;i<m_iHead+m_iQueueLen;i++)
        // 要加上头的数字。不然次数不够
        // 当前i是3.len是满,则i++越界。
    {
        cout << m_pQueue[i%m_iQueueCapacity] << endl
        //这里也要对于容量进行取余操作。否则因为我们的i并不是从0开始,加上长度会越界的。
    }
}

注意循环终止条件添加m_ihead;否则会造成当前head位置为3,长度为4.只遍历一次。

main.cpp:

#include <iostream>
#include <stdlib.h>
#include "MyQueue.h"
using namespace std;
int main()
{
    MyQueue *p = new MyQueue(4);
    p->EnQueue(10);
    p->EnQueue(12);
    p->EnQueue(16);
    p->EnQueue(18);
    p->EnQueue(20);
    p->QueueTraverse();
    
    int e = 0;
    p->DeQueue(e);
    cout << endl;
    cout << e << endl;
    
    p->DeQueue(e);
    cout << e << endl;
    cout << "**** del 10 12" <<endl;
    p->QueueTraverse();
    
    p->ClearQueue();
    cout << "**** after clear" << endl;
    p->QueueTraverse();
    cout << endl;
    
    p->EnQueue(20);
    p->EnQueue(30);
    cout << "**** after enqueue 20 30"<<endl;
    p->QueueTraverse();
    
    delete p;
    p = NULL;
    
    return 0;
}

运行结果:

img_0d9313d42b15a097f5f67bd129c1ebbe.jpe
队列的实际应用

5-1-QueueCustomer

Customer.h

#ifndef Customer_h
#define Customer_h
#include <string>
using namespace std;

class Customer
{
public:
    Customer(string name=" ", int age=0);
    void printInfo() const; // 打印顾客的个人信息
private:
    string m_strName;
    int m_iAge;
};

#endif /* Customer_h */

Customer.cpp

#include <iostream>
#include "Customer.h"
using namespace std;

Customer::Customer(string name, int age)
{
    m_strName = name;
    m_iAge = age;
}

void Customer::printInfo() const
{
    cout << "姓名:" << m_strName << endl;
    cout << "年龄:" << m_iAge << endl;
    cout << endl;
}

MyQueue.h

改造: 数据成员指针,插入时,出队时,遍历函数

#ifndef MyQueue_h
#define MyQueue_h

#include "Customer.h"

class MyQueue
{
public:
    MyQueue(int queueCapacity);     //InitQueue(&Q)创建队列
    virtual ~MyQueue();             //DestroyQueue(&Q)销毁队列
    void ClearQueue();              //ClearQueue(&Q)清空队列
    bool QueueEmpty() const;        //QueueEmpty(Q)判空队列
    bool QueueFull() const;         //判断满
    int  QueueLength() const;       //QueueLength(Q) 队列长度
    bool EnQueue(Customer element); //EnQueue(&Q,&element) 新元素加入
    bool DeQueue(Customer &element);//DeQueue(&Q,&element) 首元素出队
    void QueueTraverse();           //QueueTraverse(Q,visit()) 遍历队列
    //简单类型不需要实现visit方法。
private:
    Customer *m_pQueue;        //队列数组指针
    int m_iQueueLen;           //队列元素个数
    int m_iQueueCapacity;      //队列数组容量
    int m_iHead;               //数组的下标:队头
    int m_iTail;               //数组的下标: 队尾
};
#endif /* MyQueue_h */

MyQueue.cpp

#include "MyQueue.h"
#include <iostream>
using namespace std;

MyQueue::MyQueue(int queueCapacity)
{
    m_iQueueCapacity = queueCapacity;//将容积传递
                                     初始化队头队尾
                                     //m_iHead = 0;
                                     //m_iTail = 0;
                                     队列元素个数初始化
                                     //m_iQueueLen = 0;
                                     //分配内存
    m_pQueue = new Customer[m_iQueueCapacity];
    ClearQueue();
}

MyQueue::~MyQueue()
{
    delete[]m_pQueue;
    m_pQueue = NULL;
}

void MyQueue::ClearQueue()
{
    //重置队头队尾
    m_iHead = 0;
    m_iTail = 0;
    //队列元素个数重置
    m_iQueueLen = 0;
}

bool MyQueue::QueueEmpty() const
{
    return (m_iQueueLen == 0) ? true : false;
}

int MyQueue::QueueLength() const
{
    return m_iQueueLen;
}

bool MyQueue::QueueFull() const
{
    return (m_iQueueLen == m_iQueueCapacity) ? true : false;
}

bool MyQueue::EnQueue(Customer element)
{
    //先判断满
    if (QueueFull())
    {
        return false;
    }
    else
    {
        //插入尾部
        m_pQueue[m_iTail] = element;
        //队尾后移
        m_iTail++;
        m_iTail = m_iTail % m_iQueueCapacity;//循环
                                             //插入元素改变len
        m_iQueueLen++;
        return true;
    }

}

bool MyQueue::DeQueue(Customer &element)
{
    if (QueueEmpty())
    {
        return false;
    }
    else {
        element = m_pQueue[m_iHead];
        m_iHead++;
        m_iHead = m_iHead % m_iQueueCapacity;
        m_iQueueLen--;
        return true;
    }
}
//使用const函数是为了防止这个函数对数据成员进行改变,这个函数只可以读取数据成员,所以,当不希望函数对数据成员改变时就需要使用从const函数

void MyQueue::QueueTraverse()
{
    for (int i = m_iHead; i<m_iHead + m_iQueueLen; i++)
        //要加上头的数字。不然次数不够
        //当前i是3.len是满。则i++越界
    {
        m_pQueue[i%m_iQueueCapacity].printInfo();
        cout << "前面还有:" << (i - m_iHead) << "人" << endl;
        cout << endl;
    }
}

main.cpp:

#include <iostream>
#include <stdlib.h>
#include "MyQueue.h"
#include "Customer.h"//myqueue中包含过了
using namespace std;
int main()
{
    MyQueue *p = new MyQueue(4);
    
    // 插入custom对象类型
    Customer c1("mtianyan", 21);
    Customer c2("lisi", 30);
    Customer c3("wangwu", 40);
    p->EnQueue(c1);
    p->EnQueue(c2);
    p->EnQueue(c3);

    p->QueueTraverse();
    cout << “3 customer” << endl;
    
    Customer c4("", 0); // 临时变量用来存放被删除的值
    
    p->DeQueue(c4);
    c4.printInfo();
    cout << "c4 del" << endl;

    p->QueueTraverse();
    
    return 0;
}

运行结果:

img_fe28fececc298ddbecb3d9e8dcf27682.jpe

扩展知识:

  • 队列的类模板实现:扩展出k可以接收多种数据类型的类模板
  • 插入具体其他信息的对象,如插入编号。可以查到前面还有几个元素 (放在遍历部分)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值