資料結構之佇列
佇列
- 用陣列實現一個順序佇列
- 用連結串列實現一個鏈式佇列
- 實現一個迴圈佇列
用陣列實現一個順序佇列
幾個問題:
- 佇列方法:入隊、出隊
- 佇列的儲存:即隊首隊尾兩個指標,
- 擴容:如果佇列容量不夠了,應該擴容,如果隊尾沒有位置了,隊首有位置,應該把元素往前移
主要是上面三個問題,在程式碼中都有體現,上面的擴容方法借鑑了ArrayList
的擴容方法。
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;
}
}
測試用例#
Copypublic 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());
}
}
結果:#
Copyhelius1
helius2
helius3
helius4
helius5
helius6
helius7
helius8
null
helius9
helius10
迴圈佇列#
用java實現迴圈佇列的方法:
-
增加一個屬性size用來記錄目前的元素個數。目的是當head=rear的時候,通過size=0還是size=陣列長度,來區分佇列為空,或者佇列已滿。
-
陣列中只儲存陣列大小-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());
}
}
}