隊列,就是排隊,先到的站前面,先離開,后到的排后面,后離開。對應到計算機中,就是添加元素在隊尾,刪除元素是在隊頭,先進先出或后進后出。添加元素也叫入隊(enqueue),刪除元素也叫出隊(dequeue)。當然還可以查看隊頭元素,隊中元素個數,以及是否為空,所以隊列提供了API 就是enqueue, dequeue,getFront, size, isEmpty。
使用單鏈表實現隊列
隊列在尾部添加元素,在頭部刪除元素。那就讓鏈表頭作為隊列的頭部,因為鏈表頭部容易執行刪除操作(出隊)。鏈表尾部只能作為隊列的尾部,執行插入操作(入隊)。但鏈表尾部執行插入操作,有一個問題,那就是每次都要遍歷整個鏈表,找到最后一個元素,才能執行插入操作。為了減少遍歷,要再維護一個尾指針,指向鏈表的尾部。因此,使用單鏈表實現一個隊列,鏈表需要維護兩個指針,頭指針和尾指針。頭指針指向鏈表的頭部,用於出隊。尾指針指向鏈表尾部,用於入隊。
public class LinkedQueue<T> {
private class Node {
T data;
Node next;
Node(T data) {
this.data = data;
}
}
private Node firstNode; //頭指針,隊頭
private Node lastNode; // 尾指針,隊尾
private int size;
public void enqueue(T data){}
public T dequeue(){}
public int size(){}
public boolean isEmpty(){}
public T getFront(){}
public void clear(){}
}
enqueue的實現,就是向鏈表尾部插入一個節點。創建一個新節點,如果隊列(鏈表)為空,直接讓頭尾指針都指向它
如果鏈表不空,讓尾節點的next指向它,同時更新尾指針的指向,讓它指向最新的尾節點
public void enqueue(T data){ Node newNode = new Node(data); if(isEmpty()){ firstNode = lastNode =newNode; } else { lastNode.next = newNode; lastNode = newNode; }
size++; }
dequeue的實現,就是鏈表頭部刪除一個節點。鏈表為空,肯定是不能被刪除的,如果鏈表不空,取出第一個節點,然后讓頭指針指向它的next節點就好了,
要注意的是一直刪除,頭指針會指向null,也就是鏈表中沒有元素了,尾指針也要指向null
public T dequeue(){ if(isEmpty()){ throw new RuntimeException("鏈表為空"); } T frontData = firstNode.data; firstNode = firstNode.next; if(firstNode == null){ lastNode = null; } size--; return frontData; }
其它幾個實現比較簡單
public int size(){ return size; } public boolean isEmpty(){ return firstNode == null; } public T getFront(){ if(isEmpty()){ throw new RuntimeException("鏈表為空"); } return firstNode.data; } public void clear(){ firstNode = null; lastNode = null; }
使用隊列模擬現實的隊列,比如買奶茶,以測算奶茶店的服務能力。如果要統計1小時內的服務能力,可以計算,在一小時內的到達人數,服務人數,等待時間等等。怎么統計呢?1小時,可以分60分鍾,每一分鍾檢測一次,有沒有顧客來,如果有就加到隊列中,如果沒有,就看有沒有顧客在服務,如要有,就繼續服務,如果沒有,服務下一位顧客。怎么知道有沒有人來?由於每一個顧客的到達時間是隨機的,可以使用一個隨機數,如果生成的隨機數小於一個閾值,就說明有顧客到,反之,則沒有顧客到。 由於每個顧客的服務時間也不一樣,可以再使用一個隨機數,計算出服務時間。可以看出有兩個類,WaitLine和Customer,在WaitLine中有到達人數(numberOfArrived),服務人數(numberServed), 等待時間(totalTimeWaited),在Customer中有到達時間(arriveTime),服務時間(transactionTime)和排隊號碼(customerNum)。
public class WaitLine { private int numberOfArrivals; private int numberServed; private int totalTimeWaited; private class Customer { int arriveTime; int transactionTime; int customerNum; Customer(int arriveTime, int transactionTime, int customerNum) { this.arriveTime = arriveTime; this.transactionTime = transactionTime; this.customerNum = customerNum; } } }
現在模擬一下隊列的情形,顧客到來的時間是隨機的,假設有50%的概率會來,那就表示,只要生成的隨機數小於50%,就表明顧客到了,加入隊列。顧客的服務時間也是不固定的,可以聲明一個最大服務時間,然后和隨機數相乘,假設最大服務時間是5s。顧客有沒有在服務,就是看它的服務時間有沒有到0,如果到了,就表示服務完成,到下一位顧客。
// duration: 要統計的服務時間區間,比如60分鍾 // arrivalProbability:每秒鍾顧管到達的概率, 比如50% // maxTransactionTime:每位顧客的最長服務時間 public void simulate(int duration, double arrivalProbability, int maxTransactionTime) { var line = new LinkedQueue<Customer>(); // 創建一個隊列, var transactionTimeLeft = 0; // 每個顧客服務時間的剩余時間,表示一個顧客在不在服務
// clock就是每一秒,用戶的到達時間和用戶的服務時間都用clock記錄 for (int clock = 0; clock < duration; clock++) { // 監測用戶到沒到,隨機數小於規定的概率,表示有顧客到 if (Math.random() < arrivalProbability) { numberOfArrivals++; var transactionTime = (int) (Math.random() * maxTransactionTime + 1);//生成每位顧客的服務時間 var customer = new Customer(clock, transactionTime, numberOfArrivals); // 創建到達的顧客 line.enqueue(customer); } // 某位顧客是否還在服務中 if (transactionTimeLeft > 0) { transactionTimeLeft--; //還在服務中,繼續服務,不過服務時間要減1 } else { if(!line.isEmpty()) { var nextCustomer = line.dequeue(); // 顧客離開隊列,被服務 transactionTimeLeft = nextCustomer.transactionTime - 1; // 賦值服務時間,下次驗證是不是在服務他 var waitingTime = clock - nextCustomer.arriveTime; // 每個用戶等待服務的時間 totalTimeWaited = totalTimeWaited + waitingTime; // 整個隊列中用戶等待的時間 numberServed++; } } } }
測試一下
public static void main(String[] args) { WaitLine customerLine = new WaitLine(); customerLine.simulate(20, 0.5, 5); System.out.println(customerLine.numberServed); }