1. 程式人生 > 實用技巧 >資料結構之佇列

資料結構之佇列

佇列

  • 用陣列實現一個順序佇列
  • 用連結串列實現一個鏈式佇列
  • 實現一個迴圈佇列

用陣列實現一個順序佇列

幾個問題:

  • 佇列方法:入隊、出隊
  • 佇列的儲存:即隊首隊尾兩個指標,
  • 擴容:如果佇列容量不夠了,應該擴容,如果隊尾沒有位置了,隊首有位置,應該把元素往前移

主要是上面三個問題,在程式碼中都有體現,上面的擴容方法借鑑了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。

佇列的示意圖

實現佇列時,要注意的是假溢位現象,如上圖的最後一幅圖。

如圖所示的假溢位現象,順序佇列可以如此,迴圈佇列我們可以讓這個尾指標指向front前面的元素,這也正符合我們想要的迴圈佇列的定義。

解決辦法:使用鏈式儲存,這顯然可以。在順序儲存時,我們常見的解決辦法是把它首尾相接,構成迴圈佇列,這可以充分利用佇列的儲存空間。

迴圈佇列示意圖:

在上圖中,front指向佇列中第一個元素,rear指向佇列隊尾的下一個位置。

但依然存在一個問題:當front和rear指向同一個位置時,這代表的是隊空還是隊滿呢?大家可以想象下這種情景。

解決這種問題的常見做法是這樣的:

使用一標記,用以區分這種易混淆的情形。

犧牲一個元素空間。當front和rear相等時,為空;當rear的下一個位置是front時,為滿。

如下圖:

下面我們給出迴圈佇列,並採用第二種方式,即犧牲一個元素空間來區分隊空和隊滿的程式碼.

幾個重點:

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());
}
}
}