天天看點

優先級隊列------堆(Heap)

一、三種堆的差別:

1、 優先級隊列(堆):是一種特殊的資料結構,本質上是一個完全二叉樹

2、Java中的"堆":JVM持有的記憶體中的一個記憶體區域

3、作業系統中的"堆":每個程序也持有一定的記憶體空間,在這上面也有一個記憶體區域叫做堆

- 優先級隊列:

優先級隊列也是一個隊列,但不是單純的先進先出,而是把優先級最高的先出去,該隊列内部的結構就是’堆’

- 常用接口

1、PriorityQueue

1)單純的整形優先級隊列入隊和出隊—按照數字大小順序來出隊

import java.util.PriorityQueue;

public class Test {
    public static void main(String[] args) {
        //建立一個優先級隊列
        PriorityQueue<Integer> queue=new PriorityQueue<>();
        //建立一個數組
        int[] arr={4,6,1,2};
        for(int x:arr){
            //入隊列
            queue.offer(x);
        }
        while(!queue.isEmpty()){
            //出隊
            System.out.println(queue.poll());
        }
    }
}
           

2)對象在一個類中的比較

例如:建立一個BoyFriend類

import java.util.PriorityQueue;


class BoyFriend{
        public String name;
        public int age;
        public int money;
        public int face;

        public BoyFriend(String name, int age, int money, int face) {
            this.name = name;
            this.age = age;
            this.money = money;
            this.face = face;
        }
    }
    public class Test {

        public static void main(String[] args) {
            BoyFriend[] arr={
                    new BoyFriend("張三",20,10,100),
                    new BoyFriend("李四",40,80,50),
                    new BoyFriend("王五",50,100,10),
            };
            //建立一個優先級隊列
            PriorityQueue<BoyFriend> queue=new PriorityQueue<>();
            for(BoyFriend x:arr){
                queue.offer(x);
            }
            while(!queue.isEmpty()){
                BoyFriend cur=queue.poll();
                System.out.println(cur.name);
            }
        }
    }


           

**注意:**此時會抛出一個異常

優先級隊列------堆(Heap)

------向下轉型異常

優先級隊列------堆(Heap)
  • **Comparable<>**是标準庫内置的一個“接口”(帶泛型參數,用來指定兩個對象之間的大小關系,用來定義比較規則),裡面有一個抽象方法*compare To(Object other),*通過這個方法來指定對象自身和另外一個對象之間的大小關系(即比較this和other之間的大小關系)

    this<other,傳回一個<0的值

    this >other,傳回一個>0的值

    this=other,傳回一個=0的值

修改結果:

1)利用Comparable接口—哪個類需要比較,就讓這個類實作該接口(隻能實作隻有一種比較規則的情況—即隻能用一次(eg:money))

class BoyFriend implements Comparable<BoyFriend>{
        public String name;
        public int age;
        public int money;
        public int face;

        public BoyFriend(String name, int age, int money, int face) {
            this.name = name;
            this.age = age;
            this.money = money;
            this.face = face;
        }

        //實作Comparable裡的抽象方法
    @Override
    public int compareTo(BoyFriend o) {
        //将目前對象和o進行比較
        //注意:此處的減數和被減數不用刻意去記,可根據結果進行調整
        //根據錢的多少來比較
        return o.money-this.money;
    }
}
           

2)利用 Comparator接口—定義一個新的類,實作該接口,裡面的compare方法的參數是兩個對應要比較的類(更加适用于指定了多種比較規則的情況—可建立多個Comparator,用到哪個的時候就使用哪個)

//接口裡的參數是要被比較的類
class BoyFriendComparator implements Comparator<BoyFriend>{
    @Override
    public int compare(BoyFriend o1, BoyFriend o2) {
        return o1.money-o2.money;
    }
}
           

二、 優先級隊列的應用

  • 用來排序(堆排序)
  • 用來解決topk問題(找出前k個最大或者最小元素)

1、堆

1)堆的分類:

分為大堆、小堆

将根節點最大的堆叫做最大堆或者大根堆,将根節點最小的堆叫做最小堆或小根堆

優先級隊列------堆(Heap)

2)堆的性質:

.堆中某個節點的值總是不大于或者不小于其父節點的值

.堆總是一顆完全二叉樹

3)用途:

能夠高效的擷取到最大值/最小值,也能高效擷取到第二大/第二小…

4)堆的存儲

堆實質上是一個完全二叉樹,一般是使用數組(下标從0開始)來存儲這個二叉樹的

對于這種存儲方式,尋找雙親結點和孩子結點的方式(直接計算下标):

…如果i=0,則i表示的結點為根節點,否則i的雙親結點為(i-1)/2

…如果2i+1小于節點個數,則節點i的左孩子下标為2i+1,否則沒有左孩子

…如果2i+2小于節點個數,則節點i的右孩子下标為2i+2,否則沒有右孩子

…最後一個節點的父節點下标為:((length-1)-1)/2

2、堆的建立

  • 給定一個數組,整理成堆這樣的結構,即轉成完全二叉樹并且使用堆的形式來存儲,滿足下标關系,并且滿足堆(大堆/小堆特點)的關系
  • 可以使用向上調整/向下調整實作
  • 1)向下調整
    優先級隊列------堆(Heap)

    如上圖所示:0的左右子樹都是大堆,此時可以進行向下調整

    調整過程:

    .從待調整的位置出發,取出該接待您的左右子樹(通過下标換算方式)

    .該例是大堆,是以找出其左右孩子中的較大值,拿目前節點與該較大值進行比較,看這兩個元素的大小關系是否符合“大堆”的要求,倘若不符合,交換這兩個元素

    .将剛才被交換的位置作為新的起始位置, 再來找兩個子節點的位置,并且找到較大值, 重複上述過程

注意:向下調整建堆時,必須是從後往前循環,從前往後是不行的

因為向下調整的前提條件是左右子樹必須已經滿足堆的要求了,才能向下調整根節點的位置

public class Heap {
    //向下調整(是建立堆的有一個核心操作)
    //前提條件:要求目前被調整節點的左右子樹都已經是堆了
    //參數中傳入數組,數組有效元素長度、待調整的起始位置
    //差別arr.length--->數組總的長度
    //按照大堆方式實作
    // 時間複雜度:O(logN)---(依次*2或者/2,其時間複雜度都與logN有關)
   private static void shiftDown(int[] arr,int size,int index){
        //待調整位置
        int parent=index;
        //待調整位置節點的左子樹
        int child=2*index+1;
        while(child<size) {
            //讓child指向左右子樹值較大者
            if (child + 1 < size && child + 1 > child) {
                child = child+ 1;
            }
            //比較待調整元素和該左子樹的值,如果左子樹節點的值大于待調整位置的值,交換
            if (arr[child] > arr[parent]) {
                int tmp = arr[parent];
                arr[parent] = arr[child];
                arr[child] = tmp;
            } else {
                break;
            }
            //調整之後,更新待調整位置及該位置左子樹的位置,以備下次循環
            parent = child;
            child = 2 * parent + 1;
        }
    }
    //使用向下調整建堆操作
    //時間複雜度O(N)(數學推導出來的)
    public void creatHeap(int[] array)  {
        //基于向下調整建堆
        //(1)從後往前周遊數組,針對每個下标都去向下調整
        for(int i=array.length-1;i>=0;i--){
            shiftDown(array,array.length,i);
        }
        //(2)代碼優化:實際向下調整時,可從第一個非葉子節點進行向下調整(葉子節點沒有向下調整的必要)
        //最後一個非葉子節點即最後一個節點的父節點,下标為(length-1)-1)/2
      for(int i=((array.length-1)-1)/2;i>=0;i--){
            shiftDown(array,array.length,i);
        }
    }
}
           
  • 2)向上調整—與元素插入堆操作相關

    調整過程:

    調整的前提是該堆已經符合大堆/小堆要求,将待調整元素與該元素的父節點進行比較,根據大小堆規則進行交換,若還未達到要求,重複上述過程

//二、 向上調整(以大堆為例)
    public void shiftUp(int[] arr,int size,int index){
        //建立一個child引用指向待調整位置
        int child=index;
        //建立一個parent引用指向其父節點的位置
        int parent=(child-1)/2;
        //當child=0時表示已經通路到根節點
        while(child>0){
            //比較待調整位置節點與父節點的大小
            if(arr[child]>arr[parent]){
                int tmp=arr[child];
                arr[child]=arr[parent];
                arr[parent]=tmp;
            }else{
                break;//滿足要求,直接跳出循環
            }
            //更新child與parent,以便下次循環
            child=parent;
            parent=(child-1)/2;
        }
    }
    //使用向上調整(基于offer)的建堆操作
    public  void createHeap1(int[] arr){
        //最開始時,數組為空
        //循環周遊數組,把元素通過offer方法插入數組即可
        for(int x:arr){
            offer(x);
        }

    }

           

3、堆的核心操作—基于"向上調整"和"向下調整"來展開

1)元素插入堆

//往堆中插入元素
    //目前存儲堆的數組
    private int[] arr=new int[100] ;
    private int size=0;
    //該方法需要用到成員變量,是以不加static,通過該方法向存儲堆的數組末尾插入元素
    public void offer(int val){
        //數組滿時,插入失敗(此處不進行擴容操作)
        if(size>arr.length){
            return;
        }
        //尾插
        arr[size]=val;
        size++;

        //尾插完成之後進行向上調整
        shiftUp(arr,size,size-1);
    }
           

2)取堆頂元素

//擷取堆頂元素
    public Integer peek(){
        if(size==0){
            //數組為空
            return null;
        }
        return arr[0];
    }
           

3) 删除堆頂元素

注意:堆元素的删除操作隻能是删除堆頂元素

思路:(移形換影)

将0号的元素,即堆頂元素與最後一個元素交換,然後size–,再将0号元素進行向下調整即可

//删除堆頂元素
    public Integer poll(){
        if(size==0){
            return null;
        }
        //将0号元素與最後一個元素進行交換
        int result=arr[0];
        int tmp=arr[0];
        arr[0]=arr[size-1];
        arr[size-1]=tmp;
        size--;
        //從0号元素開始,往下進行向下調整
        shiftDown(arr,size,0);
        return result;
    }