数据结构之队列

队列

  • 用数组实现一个顺序队列
  • 用链表实现一个链式队列
  • 实现一个循环队列

用数组实现一个顺序队列

几个问题:

  • 队列方法:入队、出队
  • 队列的存储:即队首队尾两个指针,
  • 扩容:如果队列容量不够了,应该扩容,如果队尾没有位置了,队首有位置,应该把元素往前移

主要是上面三个问题,在代码中都有体现,上面的扩容方法借鉴了ArrayList的扩容方法。

Copy
package com.helius.structure.queue;

import java.util.Arrays;

/**
 * 用数组实现一个队列,即顺序队列
 */
public class ArrayQueue {
    // 存储数据的数组
    private Object[] elements;
    //队列大小
    private int size;
    // 默认队列容量
    private int DEFAULT_CAPACITY = 10;
    // 队列头指针
    private int head;
    // 队列尾指针
    private int tail;
    
    private int MAX_ARRAY_SIZE  = Integer.MAX_VALUE-8;

    /**
     * 默认构造函数 初始化大小为10的队列
     */
    public ArrayQueue(){
        elements = new Object[DEFAULT_CAPACITY];
        initPointer(0,0);
    }

    /**
     * 通过传入的容量大小创建队列
     * @param capacity
     */
    public ArrayQueue(int capacity){
        elements = new Object[capacity];
        initPointer(0,0);
    }

    /**
     * 初始化队列头尾指针
     * @param head
     * @param tail
     */
    private void initPointer(int head,int tail){
        this.head = head;
        this.tail = tail;
    }

    /**
     * 元素入队列
     * @param element
     * @return
     */
    public boolean enqueue(Object element){
        ensureCapacityHelper();
        elements[tail++] = element;//在尾指针处存入元素且尾指针后移
        size++;//队列元素个数加1
        return true;
    }

    private void ensureCapacityHelper() {
        if(tail==elements.length){//尾指针已越过数组尾端
            //判断队列是否已满 即判断数组中是否还有可用存储空间
            //if(size<elements.length){
            if(head==0){
                //扩容
                grow(elements.length);
            }else{
                //进行数据搬移操作 将数组中的数据依次向前挪动直至顶部
                for(int i= head;i<tail;i++){
                    elements[i-head]=elements[i];
                }
                //数据搬移完后重新初始化头尾指针
                initPointer(0,tail-head);
            }
        }
    }
    /**
     * 扩容
     * @param oldCapacity 原始容量
     */
    private void grow(int oldCapacity) {
        int newCapacity = oldCapacity+(oldCapacity>>1);
        if(newCapacity-oldCapacity<0){
            newCapacity = DEFAULT_CAPACITY;
        }
        if(newCapacity-MAX_ARRAY_SIZE>0){
            newCapacity = hugeCapacity(newCapacity);
        }
        elements = Arrays.copyOf(elements,newCapacity);
    }
    private int hugeCapacity(int newCapacity) {
        return (newCapacity>MAX_ARRAY_SIZE)? Integer.MAX_VALUE:newCapacity;
    }

    /**
     * 出队列
     * @return
     */
    public Object dequeue(){
        if(head==tail){
            return null;//队列中没有数据
        }
        Object obj=elements[head++];//取出队列头的元素且头指针后移
        size--;//队列中元素个数减1
        return obj;
    }

    /**
     * 获取队列元素个数
     * @return
     */
    public int getSize() {
        return size;
    }
}

测试用例#

Copy
public class TestArrayQueue {

    public static void main(String[] args) {
        ArrayQueue queue = new ArrayQueue(4);
        //入队列
        queue.enqueue("helius1");
        queue.enqueue("helius2");
        queue.enqueue("helius3");
        queue.enqueue("helius4");
        //此时入队列应该走扩容的逻辑
        queue.enqueue("helius5");
        queue.enqueue("helius6");
        //出队列
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        //此时入队列应该走数据搬移逻辑
        queue.enqueue("helius7");
        //出队列
        System.out.println(queue.dequeue());
        //入队列
        queue.enqueue("helius8");
        //出队列
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
        //入队列
        queue.enqueue("helius9");
        queue.enqueue("helius10");
        queue.enqueue("helius11");
        queue.enqueue("helius12");
        //出队列
        System.out.println(queue.dequeue());
        System.out.println(queue.dequeue());
    }
}

结果:#

Copy
helius1
helius2
helius3
helius4
helius5
helius6
helius7
helius8
null
helius9
helius10

循环队列#

用java实现循环队列的方法:

  1. 增加一个属性size用来记录目前的元素个数。目的是当head=rear的时候,通过size=0还是size=数组长度,来区分队列为空,或者队列已满。

  2. 数组中只存储数组大小-1个元素,保证rear转一圈之后不会和head相等,也就是队列满的时候,rear+1=head,中间刚好空一个元素。

    当rear=head的时候,一定是队列空了。

队列(Queue)两端允许操作的类型不一样:

可以进行删除的一端称为队头,这种操作也叫出队dequeue;

可以进行插入的一端称为队尾,这种操作也叫入队enqueue。

队列的示意图

img

实现队列时,要注意的是假溢出现象,如上图的最后一幅图。

如图所示的假溢出现象,顺序队列可以如此,循环队列我们可以让这个尾指针指向front前面的元素,这也正符合我们想要的循环队列的定义。

img

解决办法:使用链式存储,这显然可以。在顺序存储时,我们常见的解决办法是把它首尾相接,构成循环队列,这可以充分利用队列的存储空间。

循环队列示意图:

img

在上图中,front指向队列中第一个元素,rear指向队列队尾的下一个位置。

但依然存在一个问题:当front和rear指向同一个位置时,这代表的是队空还是队满呢?大家可以想象下这种情景。

解决这种问题的常见做法是这样的:

使用一标记,用以区分这种易混淆的情形。

牺牲一个元素空间。当front和rear相等时,为空;当rear的下一个位置是front时,为满。

如下图:

imgimg

下面我们给出循环队列,并采用第二种方式,即牺牲一个元素空间来区分队空和队满的代码.

几个重点:

1、front指向队头,rear指向队尾的下一个位置。

2、队为空的判断:frontrear;队为满的判断:(rear+1)%MAXSIZEfront。

上面说的rear即为代码中的的tail

Copy
/**
 * 使用数组实现循环队列
 * @author Helius
 */
public class CirculiQueue {
    //存储队列数据的数组
    private Object[] elements;
    //默认数组容量
    private int DEFAULT_CAPACITY=10;
    //队列中元素个数
    private int size;
    // 队列头指针
    private int head;
    //队列尾指针
    private int tail;

    /**
     * 默认构造函数
     */
    public CirculiQueue(){
        elements = new Object[DEFAULT_CAPACITY];
    }

    /**
     * 通过传入的容量参数构造队列
     * @param capacity
     */
    public CirculiQueue(int capacity){
        elements = new Object[capacity];
    }

    /**
     * 元素入队列
     * @param element
     * @return
     */
    public boolean enqueue(Object element){
        //判断队列是否已满
        if(head == (tail+1)%elements.length){
            //队列已满
            return false;
        }
        //将元素存入tail位置上
        elements[tail]=element;
        //尾指针后移
        /*tail++;
        if(tail==elements.length){
            tail = 0;
        }*/
        tail = (tail+1)%elements.length;
        size++;
        return true;
    }

    /**
     * 元素出队列
     * @return
     */
    public Object dequeue(){
        //判断队列是否为空
        if(head==tail){
            return null;
        }
        //获取head位置上的元素
        Object element = elements[head];
        //头指针后移
        /*head++;
        if(head==elements.length){
            head = 0;
        }*/
        head = (head+1)%elements.length;
        size--;
        return element;
    }

    /**
     * 获取队列大小
     * @return
     */
    public int getSize() {
        return size;
    }
}
#include<iostream>

using namespace std;

int maxSize = 100;

// 定义
template <class T>
class SqListClass
{
    private:
        T *data; // 存放顺序表中的元素
        int length; // 存放顺序表的长度

    public:
        SqListClass(); // 构造函数
        ~SqListClass(); // 析构函数
        void CreateList(T a[], int n); // 由a数组中的元素建造顺序表
        void DispList(); // 输出顺序表L中的所有元素
        int ListLength(); // 求顺序表的长度
        bool GetElem(int i, T &e); // 求顺序表中某序列号的元素值
        int LocateElem(T e); // 按元素查找其第一个序号位置
        bool ListInsert(int i, T e); // 在位置i插入数据元素e
        bool ListDelete(int i); // 在位置i删除数据元素
        void ReverseList(SqListClass<T> &L); // 翻转顺序表
};

// 线性表的初始化
template<class T>
SqListClass<T>::SqListClass() // 构造函数
{
    data = new T[maxSize];
    length = 0;
}

// 线性表的销毁
template<class T>
SqListClass<T>::~SqListClass() // 析构函数
{
    delete [] data;
}

// 实现

// 线性表的创建,时间复杂度为O(n)
template<class T>
void SqListClass<T>::CreateList(T a[], int n)
{
    int i;
    for(i=0; i<n; i++){
        data[i] = a[i];
    }
    length = i;
}

// 输出线性表的所有元素,时间复杂度为O(n)
template<class T>
void SqListClass<T>::DispList(){
    cout << "Out:" << endl;
    for(int i=0; i<length; i++){
        cout << data[i] << " ";
    }
    cout << endl;
}

// 求线性表的长度,时间复杂度为O(1)
template<class T>
int SqListClass<T>::ListLength(){
    return length;
}

// 求顺序表中某序列号的元素值,,时间复杂度为O(1)
template<class T>
bool SqListClass<T>::GetElem(int i, T &e){
    if(i<0 || i>length) return false;
    e = data[i-1];
    return true;
}

// 按元素查找其第一个序号位置,时间复杂度为O(n)
template<class T>
int SqListClass<T>::LocateElem(T e){
    int i = 0;
    while(i<length && data[i]!=e) i++;
    if(i>=length) return 0;
    else return i+1;
}

// 在位置i插入数据元素e,时间复杂度为O(n)
template<class T>
bool SqListClass<T>::ListInsert(int i, T e){
    if(i<0 || i>length) return false;
    for(int j=length; j>=i; j--){
        data[j]=data[j-1];
    }
    data[i-1] = e;
    length++;
    return true;
}

// 在位置i删除数据元素,时间复杂度为O(n)
template<class T>
bool SqListClass<T>::ListDelete(int i){
    if(i<0 || i>length) return false;
    for(int j=i-1; j< length; j++){
        data[j] = data[j+1];
    }
    length--;
    return true;
}

// 翻转顺序表
template<class T>
void SqListClass<T>::ReverseList(SqListClass<T> &L){
    T temp;
    for(int j=0; j<L.length/2; j++){
        temp = L.data[j];
        L.data[j] = L.data[length-j-1];
        L.data[length-j-1] = temp;
    }
}

// 主函数
int main(){
    SqListClass<int> sqList;
    int arr[3] = {3,4,5};
    // 创建线性表
    sqList.CreateList(arr, 3);
    // 输出线性表
    sqList.DispList();
    // 输出线性表的长度
    cout << "sqList length is " << sqList.ListLength() << endl;
    // 求第二个位置的元素
    int a;
    sqList.GetElem(2, a);
    cout <<"The 2 local is elem " << a << endl;
    // 查找元素5的位置
    cout << "The elem 5 local is " << sqList.LocateElem(5) << endl;
    // 在位置4插入元素6
    sqList.ListInsert(2, 6);
    sqList.DispList();
    // 在位置1删除数据元素
    sqList.ListDelete(1);
    sqList.DispList();
    // 翻转顺序表
    sqList.ReverseList(sqList);
    sqList.DispList();
    return 0;
}
用链表实现一个链式队列

使用链表实现队列,需要一个对头指向对列头部管理数据出对,一个队尾管理数据入队;还需要队列的数据区域

那么就需要用两个结构管理队列,一个是数据节点,一个队列

队列节点结构,专门管理数据的

typedef struct queueNode{

  int  data;   //数据域,存放的是有效数据

  struct queueNode  * next; //指向队列的下一个节点

}queueNode;

 

队列管理结构:

typedef struct linkqueue{

   struct queueNode  *front; // 指向队列头部 

   struct queueNode  *rear; // 指向队列尾部

}linkqueue;

1. front 只指向队列的头节点,通过头节点的next指针去访问数据节点,实现出对操作,

2. 链式队列没有满的情况,当队列为空时,头和尾都指向头节点(头节点只是用来管理这个链式对列,并不存放有效数据)

3. 队尾用来插入队列,对头用来出入操作

 

创建一个空的队列:

 

插 入队列一个数据

 

这样通过队尾rear 一直指向链表的尾部管理的数据插入队列操作

 举例说明: 队列 linkqueue *qe;

(1) 插入一个新节点 queueNode *pnew 

(2)qe->rear->next 是当前节点的next指针,用来连接新节点的 qe->rear->next = pnew

(3)新节点的next指针指向空NULL , pnew->next = NULL;

(4)最后是把尾指针,移动指向尾部节点 qe->rear = qe->rear->next;

 linkqueue.c文件:

复制代码
#include "linkqueue.h"

linkqueue *create_linkqueue(void)
{
    //创建队列
    linkqueue *qe=NULL;
    qe = (linkqueue*)malloc(sizeof(linkqueue));
    if(qe == NULL)
    {
        printf("create queue malloc error \n");
        return NULL;
    }

    //创建队列节点
    qe->front = (queueNode*)malloc(sizeof(queueNode));
    if(qe->front == NULL)
    {
        free(qe);
        printf("create node malloc error\n");
        return NULL;
    }
    qe->front->next = NULL;//队列头的next指向实际的数据节点
    qe->front->data = 0;
    qe->rear = qe->front; //队列空时,对头和对尾指向同一个位置
    return qe;
}

//插入数据,入队列,对尾入对
int in_linkqueue(linkqueue *qe, u16 value)
{
    if(qe == NULL)
    {
        printf("in lingkqueue is null\n");
        return -1;
    }
    queueNode *pnew = NULL;//入对的新节点
    pnew = (queueNode*)malloc(sizeof(queueNode));
    if(pnew == NULL)
    {
        printf("in pnew malloc is fail\n");
        return -1;
    }
    pnew->data = value;//入对的数据
    pnew->next = NULL;
    qe->rear->next = pnew;//把入对的节点链接到队列上
    qe->rear = qe->rear->next;//把指向对尾的指针,继续移动到队尾,即指向新插入的节点位置
    return 1;
}

//判断队列是否空,空返回1,非空返回0, 其他返回-1
int is_empty_linkqueue(linkqueue *qe)//判空
{
    if(qe == NULL)
    {
        printf("is empty lingkqueue is null\n");
        return -1;
    }
    return ((qe->front == qe->rear) ? 1 : 0);
}

int out_linkqueue(linkqueue *qe, u16 *dat)//出队列
{
    if(qe == NULL)
    {
        printf("out lingkqueue is null\n");
        return -1;
    }
    if(is_empty_linkqueue(qe) == 1)//队列为空
    {
        printf("out lingkqueue is empty\n");
        return 0;
    }
    queueNode *pdel = NULL;//出对的节点
    if(qe->front->next == NULL) //出对列,到对尾时
    {
        qe->rear = qe->front;
        return 0;
    }
    pdel = qe->front->next;//对头front永远头节点,出对时是头节点的下一个节点
    qe->front->next = pdel->next;//把要删除的节点的下一个节点地址链接到对列头上
    *dat = pdel->data; //对头的数据
    free(pdel);
    pdel = NULL;
    return 1; 
}

//显示队列内容,从对头开始显示
void show_linkqueue(linkqueue *qe)//显示队列内容
{
    if(qe == NULL)
    {
        printf("show lingkqueue is null\n");
        return;
    }
    if(is_empty_linkqueue(qe) == 1)//队列为空
    {
        printf("show lingkqueue is empty\n");
        return;
    }
    queueNode *pcur = qe->front->next;//找到数据节点开始
    while(pcur != NULL)
    {
        printf("%d\n",pcur->data);
        pcur = pcur->next;
    }
}
复制代码

linkqueue.h文件:

复制代码
#ifndef __LINKQUEUE_H
#define __LINKQUEUE_H

#include <stdio.h>
#include <stdlib.h>

typedef int u16;

//数据节点
typedef struct queueNode{
    u16 data;
    struct queueNode *next;
}queueNode;

//队列结构
typedef struct linkqueue{
    queueNode *front; //对列头节点
    queueNode *rear;  //队列尾节点
}linkqueue, *linkqueue_p;

linkqueue *create_linkqueue(void);
int in_linkqueue(linkqueue *qe, u16 value);//插入数据,入对列
int is_empty_linkqueue(linkqueue *qe);//判空
int out_linkqueue(linkqueue *qe, u16 *dat);//出队列
void show_linkqueue(linkqueue *qe);//显示队列内容

#endif
复制代码

测试文件main.c:

复制代码
#include "linkqueue.h"

int main(int argc, const char *argv[])
{
    linkqueue *s = NULL;

    s=create_linkqueue();
    in_linkqueue(s,1);
    show_linkqueue(s);

    putchar(10);

    in_linkqueue(s,2);
    in_linkqueue(s,3);
    in_linkqueue(s,4);
    in_linkqueue(s,5);
    show_linkqueue(s);

    putchar(10);
    int a=0;
    out_linkqueue(s,&a);
    printf("-------test------!\n");
    out_linkqueue(s,&a);
    show_linkqueue(s);


    return 0;
}

//circular Queue 循环队列实现
 
#include <stdlib.h>
#include <stdio.h>
 
#define MAXSIZE 100
typedef int ElemType ;
 
typedef struct  
{
    ElemType *base; //存储内存分配基地址
    int front;      //队列头索引
    int rear;       //队列尾索引
}circularQueue;
 
//初始化队列
InitQueue(circularQueue *q)
{
    q->base = (ElemType *)malloc((MAXSIZE) * sizeof(ElemType));
    if (!q->base) exit(0);
    q->front = q->rear = 0;
}
 
//入队列操作
InsertQueue(circularQueue *q, ElemType e)
{
    if ((q->rear + 1) % MAXSIZE == q->front) return; //队列已满时,不执行入队操作
    q->base[q->rear] = e;  //将元素放入队列尾部
    q->rear = (q->rear + 1) % MAXSIZE; //尾部元素指向下一个空间位置,取模运算保证了索引不越界(余数一定小于除数)
}
 
//出队列操作
DeleteQueue(circularQueue *q, ElemType *e)
{
    if (q->front == q->rear)  return;  //空队列,直接返回
    *e = q->base[q->front];            //头部元素出队
    q->front = (q->front + 1) % MAXSIZE;
}
import java.io.*;
    public class QueueArray {   
    Object[] a; //对象数组,队列最多存储a.length-1个对象   
    int front;  //队首下标   
    int rear;   //队尾下标   
    public QueueArray(){   
        this(10); //调用其它构造方法   
    }   
    public QueueArray(int size){   
        a = new Object[size];   
        front = 0;   
        rear =0;   
    }   
    /**  
     * 将一个对象追加到队列尾部  
     * @param obj 对象  
     * @return 队列满时返回false,否则返回true  
     */  
    public boolean enqueue(Object obj){   
        if((rear+1)%a.length==front){   
            return false;   
        }   
        a[rear]=obj;   
        rear = (rear+1)%a.length;   
        return true;   
    }   
    /**  
     * 队列头部的第一个对象出队  
     * @return 出队的对象,队列空时返回null  
     */  
    public Object dequeue(){   
        if(rear==front){   
            return null;   
        }   
        Object obj = a[front];   
        front = (front+1)%a.length;   
        return obj;   
    }   
    public static void main(String[] args) {   
        QueueArray q = new QueueArray(4);   
        System.out.println(q.enqueue("张三"));   
        System.out.println(q.enqueue("李斯"));   
        System.out.println(q.enqueue("赵五"));   
        System.out.println(q.enqueue("王一"));//无法入队列,队列满   
        for(int i=0;i<4;i++){   
            System.out.println(q.dequeue());   
        }   
    }   
}





posted on 2020-07-20 16:15  滚雪球效应  阅读(346)  评论(0)    收藏  举报