佇列
佇列是最為經典的先進先出(FIFO)形式的資料結構。
所謂的先進先出是形象地來說就好比一個取餐視窗的隊伍,排隊取餐的人們構成了取餐隊伍的元素集合。排進隊伍的動作稱為“入隊”,即元素新增到隊伍的尾部,取到餐後離開隊伍的動作稱為“出隊”,即元素從隊的頭部移除。而這一生活中的現象在資料結構中抽象來說呢就是隊列了。
首先我們來說明白“單向佇列”。
單向佇列
這是一種最簡單的佇列,它的一切屬性都與現實生活中的現象重合,因此一點兒也不抽象。我們來分析一下一個佇列需要做哪些事情,以及完成這些事情需要哪些屬性。依據文章開頭的分析,我們可以得到以下初步的結論。
-
佇列最重要的操作是“入隊”、“出隊”。
-
需要定義一個基礎容器存放資料,不論此處使用陣列或者連結串列都可,為了方便說明,下方程式碼中我們使用ArrayList<Integer>,泛型指定為Integer是為了更簡潔地說明。
-
我們至少需要標記“隊頭”,即隊伍第一個元素是哪個,我們引入一個指標永遠地指向隊伍的第一個元素,不論佇列做了多少次入隊、出隊的操作。
-
我們在入隊的時候需要判斷這個隊伍是否已經滿了,如果滿了顯然不能再入隊了,因此佇列還需要判滿方法。
-
同樣地,我們需要在出隊的時候判斷是否佇列空了,佇列都空了再出隊顯然不合理。
以下是程式碼部分:
class MyQueue{ //定義儲存資料的容器 private List<Integer> data; //隊頭指標,始終指向隊伍自對頭開始數第一個元素 private Integer pStart; //構造器 public MyQueue(){ //初始化一個只允許儲存Integer型別的ArrayList,這樣此佇列就是無限長的,且入隊無需判斷是否隊伍滿了 data = new ArrayList<Integer>(); //初始化時頭指標指向佇列位置0 pStart = 0; } //入隊 public boolean enQueue(Integer el){ //入隊,在隊伍尾部新增需要入隊的元素 data.add(el); //新增成功嚷嚷一聲 return true; } //出隊 public boolean deQueue(){ //判空,如果隊伍都空則說明沒有元素可以出隊了 if(!isEmpty){ //移除頭指標指向的元素 data.remove(pStart); //此時第n+1元素成了隊頭元素,故將指標後移一位 pStart ++; //慣例嚷嚷一聲 return true; } //隊伍空了,就喊一聲沒有元素了 return false; } //判空 public boolean isEmpty(){ //什麼時候才代表隊伍空了?是List 的size為0嗎這只是一種情況,假設建了一個長度為3的ArrayList, //但是一個元素都沒有,此時我們依然說這個隊伍是空的。但是如果我們假設隊伍有限長,當永遠指向頭元素的指標指 return pStart >= data.size(); } }
迴圈佇列
單向佇列有一個顯而易見的缺陷,它的長度是無限增長的,且一旦隊頭出列了後,隊頭指標後移,則原先儲存隊頭元素的位置被棄用了,這是一種很浪費空間的行為。為了提高空間的利用率,我們引入迴圈佇列。所謂迴圈佇列,就是將在出隊操作執行後不再使用的空間作為佇列的後方空間,將來在入隊時,以之前執行出隊操作的次序依次進行入隊操作。
為了達成上述目的,需要構建以下屬性:
- 定長陣列,作為儲存資料的容器。//int [] data;
- 頭指標,指向佇列的頭部。 // int pStart;
- 尾指標,指向佇列的尾部。 //int pEnd;
-
容器容量,儲存容器(定長陣列)的大小。 //int size;
以下是迴圈佇列的程式碼實現:
class CricleQueue{ private int[] data; //容器 private int pStart; //頭指標 private int pEnd; //尾指標 private int size; //容器容量 //構造器 public CricleQueue(int k){ data = new int[k];//初始化容器為k容量的int陣列 pStart = -1;//初始化頭指標 pEnd = -1;//初始化尾指標 size = k;//將容器容量給到變數size,方便後面使用 } //入隊 public boolean enQueue(int el){ //先判滿 if(isFull()){ return false; } //第一次執行入隊操作 if(pStart == -1 && pEnd == -1){ pStart = 0; pEnd = (pEnd + 1)%size;//入隊操作需要關注的是尾指標的移動 data[pEnd] = el; return true; } pEnd = (pEnd + 1)%size; data[pEnd] = el; return true; } //出隊 public boolean deQueue(){ //先判空 if(isEmpty()){ return false; } //如果是最後一個元素準備出隊了 if(pStart == pEnd){ pStart = -1;//將兩個指標重置為初始化狀態 pEnd = -1; return true; } pStart = (pStart + 1)%size; //出隊操作需要關注頭指標的移動,空出來的位置的廢棄元素等待將來元素入隊覆蓋而不必理會 return true; } //獲取隊頭元素 public int getHeadEl(){ //隊中必須還有元素 if(!isEmpty()){ return data[pStart]; } return -1;//否則無元素可出 } //獲取隊尾元素 public int getTailEl(){ if(!isEmpty()){ return data[pEnd]; } return -1; } //判空 public boolean isEmpty(){ return pStart == -1 && pEnd == -1;//指標初始化狀態 } //判滿 public boolean isFull(){ return pStart == (pEnd + 1)%size; //兩個指標的位置關係通過其中一個指標位置與陣列長度取餘得到。 } } ```/