數據結構-----優先級隊列(堆)

一、PriorityQueue 的使用

  • 概念
    隊列是一種先進先出(FIFO)的數據結構,但有時候,數據可能帶有優先級,出隊列時,可能需要優先級高的元素先出隊列,在這種情況下,使用隊列顯然不合適,比如:在手機上玩遊戲的時候,如果有來電,那麼系統應該優先處理打進來的電話。
  1. 常用接口介紹
    1、JDK:
    提供了兩種類型的優先級隊列:①PriorityQueue是線程不安全的,②PriorityBlockingQueue是線程安全的。

2、PriorityQueue使用前,應先導入包

import java.util.PriorityQueue;

【注意】

  • 插入的元素不能爲null,且元素之間必須要能夠進行比較;
  • 插入、刪除------在插入和刪除元素期間優先級隊列中的元素會自定進行調整(不論怎麼調整,0號位置的元素始終是最小的);插入和刪除的時間複雜度: O(logN)。
  • 底層結構—堆(暫時不用管什麼是堆) 也可以通過某種方式使首元素始終最大

3、PriorityQueue包含的接口:

  • 構造方式:

  • PriorityQueue()空的優先級隊列:默認的容量爲11 ;

PriorityQueue q1 = new PriorityQueue<>();

  • PriorityQueue(capacity):指定初始化容量大小 (capacity不能小於1,如果小於1會拋異常 )

PriorityQueue p2 = new PriorityQueue<>(20);

  • PriorityQueue(集合對象):用一個集合中的元素來構造優先級隊列
ArrayList<Integer> list = new ArrayList<>();
     list.add(4);
     list.add(3);
     list.add(2);
     list.add(1);
  //用ArrayList對象來構造一個優先級隊列的對象
 //q3中已經包含了三個元素
PriorityQueue<Integer> q3 = new PriorityQueue<>(list);
  • 常用接口:
  • offer(x) :插入元素---->O(logN)
  • poll():刪除元素
  • size():獲取有效元素個數
  • isEmpty(): 檢測是否爲空
  • clear():將優先級隊列中的元素清空
 public static void TestPriorityQueue() {
        int[] arr = {4, 1, 9, 2, 8, 0, 7, 3, 6, 5};
// 一般在創建優先級隊列對象時,如果知道元素個數,建議就直接將底層容量給好
// 否則在插入時需要不多的擴容
// 擴容機制:開闢更大的空間,拷貝元素,這樣效率會比較低
        PriorityQueue<Integer> q = new PriorityQueue<>(arr.length);
        for (int e : arr) {
            q.offer(e);
        }
        System.out.println(q.size()); // 打印優先級隊列中有效元素個數
        System.out.println(q.peek()); // 獲取優先級最高的元素
        // 從優先級隊列中刪除兩個元素之和,再次獲取優先級最高的元素
        q.poll();
        q.poll();
        System.out.println(q.size()); // 打印優先級隊列中有效元素個數
        System.out.println(q.peek()); // 獲取優先級最高的元素
        q.offer(0);
        System.out.println(q.peek()); // 獲取優先級最高的元素
// 將優先級隊列中的有效元素刪除掉,檢測其是否爲空
        q.clear();
        if (q.isEmpty()) {
            System.out.println("優先級隊列已經爲空!!!");
        } else {
            System.out.println("優先級隊列不爲空");
        }
    }

二、堆的概念及實現

  • 概念
    有一個集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉樹的順序存儲方式存儲 在一個一維數組中,並滿足:Ki <= K2i+1 且 Ki<= K2i+2,則稱爲 小堆(或大堆)。將根節點最大的堆叫做最大堆或大根堆,根節點最小的堆叫做最小堆或小根堆。
    在這裏插入圖片描述
    如圖所示爲小堆,根節點的值小於左右子樹的值,反之則爲大堆。

  • 存儲方式
    堆是一棵完全二叉樹,因此可以按照層序的規則採用順序的方式存儲。對於非完全二叉樹,則不適合使用順序方式進行存儲,因爲爲了能夠還原二叉樹,空間中必須要存儲空節點,就會導致空間利用率比較低。

對於具有n個結點的完全二叉樹,按照從上到下、從左到右的順序對節點從0開始編號,假設i爲節點在數組中的下標,則有:

  • 若i>0,雙親序號: (i-1)/2; i=0, i爲根節點編號,無雙親節點.
  • 2i+1<n, 左孩子序號: 2i+1,否則無左孩子
  • 2i+2<n, 右孩子序號: 2i+2,否則無右孩子

堆的創建

  • 向下調整
    如圖, 對於集合{ 27,15,19,18,28,34,65,49,25,37 },根節點的左右子樹已經完全滿足堆的性質,因此只需將根節點向下調整好即可。
    在這裏插入圖片描述
    過程(以小堆爲例):
    1、parent爲本次調整節點的下標,調整以parent爲根的二叉樹;
    (調整之前,要保證parent的左右子堆已經滿足小堆的性質)

2、檢測parent是否滿足小堆性質:將parent與其孩子的比較

  • 滿足:以parent爲根節點的二叉樹已經是小堆
  • 不滿足:說明parent比其孩子大,將parent與其較小的孩子進行交換,交換後,parent的向下移動,
    可能導致其子樹不滿足小堆性質,繼續調整;
private  void shiftDown(int parent){
        //使用child標記parent較小的孩子
        //默認情況下,先標記左孩子(完全二叉樹:可能有左孩子沒有右孩子)
        int child = parent * 2 + 1;

        while (child<size){
            //找parent中較小的孩子
            if(res[child+1] < res[child]){//如果右孩子比左孩子小
                child+=1;
            }

            //比較parent與child的大小
            if(res[parent]>res[child]){
                swap(parent,child);

                //調整後,如果子樹不滿足小堆的性質,繼續調整
                parent=child;
                child=parent*2+1;

            }else{
                return;//以parent爲根的二叉樹已經滿足小堆性質
            }
        }
    }
        private void swap(int parent,int child){
           int temp = res[parent];
           res[parent]=res[child];
           res[child]=temp;
        }

  • 堆的創建
    對於根節點的左右子樹不滿足堆的特性,我們需要不斷地進行調整:
public static void createHeap(int[] array) {
 //找到第一個非葉子節點(根) child=parent*2+1 ----- parent = (child-1)/2
        int lastLeaf= (size-1)-1>>1;
        //調整很多個根節點 從下往上找葉子節點
        for (int root = lastLeaf; root >=0 ; root--) {
            shiftDown(root);//每一個根節點都要向下調整
        }
}
  • 堆的插入
    堆的插入總共需要兩個步驟:
  1. 先將元素放入到底層空間中(注意:空間不夠時需要擴容)
  2. 將最後新插入的節點向上調整,直到滿足堆的性質
 private boolean offer(int x){
            grow();//擴容

            //1、將元素尾插到數組中
            res[size++] = x;

            //2、檢測新元素插入後是否破壞小堆的性質
            shiftUp(size-1);//向上調整
            return true;
        }
        
  private void shiftUp(int child) {
            int parent = (child - 1) >> 1;

            if (res[parent] > res[child]) {
                swap(parent, child);


                //調整後,可能導致不滿足堆的性質 繼續向上調整
                child = parent;
                parent = (child - 1) >> 1;
            } else {
                return;
            }
        }
        
  private void swap(int parent,int child){
           int temp = res[parent];
           res[parent]=res[child];
           res[child]=temp;
        }
   private void grow(){
          if(size>= res.length){
              int oldCapacity = res.length;
              int newCapacity = oldCapacity+ ((oldCapacity<64)?(oldCapacity + 2):(oldCapacity >> 1));
            }
        }
  • 堆的刪除
    堆的刪除一定刪除的是堆頂元素:
  1. 將堆頂元素對堆中最後一個元素交換;
  2. 將堆中有效數據個數減少一個;
  3. 對堆頂元素進行向下調整;
private int poll(){
          int a = res[0];
          swap(0,size-1);//0號元素和新插入元素交換
          size--;

          shiftDown(0);//向下調整
          return a;
        }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章