国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

數(shù)據(jù)結(jié)構(gòu) - 6(優(yōu)先級隊列(堆)13000字詳解)

這篇具有很好參考價值的文章主要介紹了數(shù)據(jù)結(jié)構(gòu) - 6(優(yōu)先級隊列(堆)13000字詳解)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報違法"按鈕提交疑問。

一:堆

1.1 堆的基本概念

堆分為兩種:大堆和小堆。它們之間的區(qū)別在于元素在堆中的排列順序和訪問方式。

  1. 大堆(Max Heap):

在大堆中,父節(jié)點(diǎn)的值比它的子節(jié)點(diǎn)的值要大。也就是說,堆的根節(jié)點(diǎn)是堆中最大的元素。大堆被用于實現(xiàn)優(yōu)先級隊列,其中根節(jié)點(diǎn)的元素始終是隊列中最大的元素。

大堆的特點(diǎn):

  • 對于每個父節(jié)點(diǎn),它的值大于或等于其子節(jié)點(diǎn)的值。
  1. 小堆(Min Heap):

在小堆中,父節(jié)點(diǎn)的值比它的子節(jié)點(diǎn)的值要小。也就是說,堆的根節(jié)點(diǎn)是堆中最小的元素。小堆常用于實現(xiàn)優(yōu)先級隊列,其中根節(jié)點(diǎn)的元素始終是隊列中最小的元素。

小堆的特點(diǎn):

  • 對于每個父節(jié)點(diǎn),它的值小于或等于其子節(jié)點(diǎn)的值。

以下是一個示例圖示,展示了一個包含7個元素的大堆和小堆的結(jié)構(gòu):

大堆:
        90
      /    \
    75      30
   /  \    /  \
  20   15  10   7

小堆:
        7
      /   \
    10     30
   /  \   /  \
  20   15 75  90

在大堆中,父節(jié)點(diǎn)的值大于子節(jié)點(diǎn)的值,而在小堆中,父節(jié)點(diǎn)的值小于子節(jié)點(diǎn)的值。

注意:堆總是一顆完全二叉樹

1.2堆的存儲方式

從堆的概念可知,堆是一棵完全二叉樹,因此可以層序的規(guī)則采用順序的方式來高效存儲,

數(shù)據(jù)結(jié)構(gòu) - 6(優(yōu)先級隊列(堆)13000字詳解),數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)結(jié)構(gòu)
注意:對于非完全二叉樹,則不適合使用順序方式進(jìn)行存儲,因為為了能夠還原二叉樹,空間中必須要存儲空節(jié)點(diǎn),就會導(dǎo)致空間利用率比較低。

將元素存儲到數(shù)組中后,可以根據(jù)二叉樹章節(jié)的性質(zhì)5對樹進(jìn)行還原。

假設(shè)i為節(jié)點(diǎn)在數(shù)組中的下標(biāo),則有:

  • 如果i為0,則i表示的節(jié)點(diǎn)為根節(jié)點(diǎn)
  • 節(jié)點(diǎn)i的左孩子下標(biāo)為2 * i + 1 ( 2 * i + 1 小于節(jié)點(diǎn)個數(shù),否則沒有左孩子 )
  • 節(jié)點(diǎn)i的右孩子下標(biāo)為2 * i + 2 ( 2 * i + 2 小于節(jié)點(diǎn)個數(shù),否則沒有右孩子 )

1.3 堆的下沉

對于集合{ 27,15,19,18,28,34,65,49,25,37 }中的數(shù)據(jù),如果將其創(chuàng)建成小堆呢?
數(shù)據(jù)結(jié)構(gòu) - 6(優(yōu)先級隊列(堆)13000字詳解),數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)結(jié)構(gòu)

仔細(xì)觀察上圖后發(fā)現(xiàn):根節(jié)點(diǎn)的左右子樹已經(jīng)完全滿足堆的性質(zhì),因此只需將根節(jié)點(diǎn)向下調(diào)整好即可。

下面是代碼實現(xiàn):

// shiftDown方法用來實現(xiàn)下沉操作
// array參數(shù)是待構(gòu)建小堆的數(shù)組
// parent參數(shù)是當(dāng)前需要下沉的節(jié)點(diǎn)的索引
public void shiftDown(int[] array, int parent) {
    int length = array.length;
    int child = 2 * parent + 1; // 左子節(jié)點(diǎn)索引
    
    while (child < length) {
        // 找到左右孩子中較小的孩子
        if (child + 1 < length && array[child] > array[child + 1]) {
            child++; // 如果右子節(jié)點(diǎn)存在并且小于左子節(jié)點(diǎn)的值,則將child指向右子節(jié)點(diǎn)
        }
        
        // 如果當(dāng)前節(jié)點(diǎn)小于或等于左右子節(jié)點(diǎn)中較小的節(jié)點(diǎn),說明已經(jīng)符合小堆要求
        if (array[parent] <= array[child]) {
            break;
        }
        
        // 否則,交換當(dāng)前節(jié)點(diǎn)和較小子節(jié)點(diǎn)的值,接著需要繼續(xù)向下調(diào)整
        int temp = array[parent];
        array[parent] = array[child];
        array[child] = temp;
        
        parent = child;
        child = 2 * parent + 1;
    }
}

這段代碼實現(xiàn)了堆排序中的下沉操作(也稱為向下調(diào)整或堆化)。下沉操作用于維護(hù)小堆的性質(zhì),確保父節(jié)點(diǎn)永遠(yuǎn)小于或等于其子節(jié)點(diǎn)。

時間復(fù)雜度分析:最壞的情況即圖示的情況,從根一路比較到葉子,比較的次數(shù)為完全二叉樹的高度,即時間復(fù)雜度為O(log n)

數(shù)據(jù)結(jié)構(gòu) - 6(優(yōu)先級隊列(堆)13000字詳解),數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)結(jié)構(gòu)

1.4 堆的創(chuàng)建

那對于普通的序列{ 1,5,3,8,7,6 },即根節(jié)點(diǎn)的左右子樹不滿足堆的特性,又該如何調(diào)整呢?

數(shù)據(jù)結(jié)構(gòu) - 6(優(yōu)先級隊列(堆)13000字詳解),數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)結(jié)構(gòu)
參考代碼:

public static void createHeap(int[] array) {
  // 找倒數(shù)第一個非葉子節(jié)點(diǎn),從該節(jié)點(diǎn)位置開始往前一直到根節(jié)點(diǎn),遇到一個節(jié)點(diǎn),應(yīng)用向下調(diào)整
  int root = ((array.length-2)>>1);
  for (; root >= 0; root--) {
    shiftDown(array, root);
 }
}

1.5建堆的時間復(fù)雜度

因為堆是完全二叉樹,而滿二叉樹也是完全二叉樹,此處為了簡化使用滿二叉樹來證明(時間復(fù)雜度本來看的就是近似值,多幾個節(jié)點(diǎn)不影響最終結(jié)果):

數(shù)據(jù)結(jié)構(gòu) - 6(優(yōu)先級隊列(堆)13000字詳解),數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)結(jié)構(gòu)
因此:建堆的時間復(fù)雜度為O(N)。

1.6堆的插入和刪除

1.6.1 堆的插入

堆的插入總共需要兩個步驟:

  1. 先將元素放入到底層空間中(注意:空間不夠時需要擴(kuò)容)
  2. 將最后新插入的節(jié)點(diǎn)向上調(diào)整,直到滿足堆的性質(zhì)

數(shù)據(jù)結(jié)構(gòu) - 6(優(yōu)先級隊列(堆)13000字詳解),數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)結(jié)構(gòu)
下面是堆的刪除操作的代碼實現(xiàn),包含詳細(xì)注釋:

public void shiftUp(int child) {
  // 找到child的雙親
  int parent = (child - 1) / 2;
 
  while (child > 0) {
    // 如果雙親比孩子大,parent滿足堆的性質(zhì),調(diào)整結(jié)束
    if (array[parent] > array[child]) {
      break;
   }
    else{
      // 將雙親與孩子節(jié)點(diǎn)進(jìn)行交換
      int t = array[parent];
      array[parent] = array[child];
      array[child] = t;
   
      // 小的元素向下移動,可能到值子樹不滿足對的性質(zhì),因此需要繼續(xù)向上調(diào)增
      child = parent;
      parent = (child - 1) / 1;
   }
 }
}

1.6.2 堆的刪除

注意:堆的刪除一定刪除的是堆頂元素。具體如下:

  1. 將堆頂元素對堆中最后一個元素交換
  2. 將堆中有效數(shù)據(jù)個數(shù)減少一個
  3. 對堆頂元素進(jìn)行向下調(diào)整

數(shù)據(jù)結(jié)構(gòu) - 6(優(yōu)先級隊列(堆)13000字詳解),數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)結(jié)構(gòu)
下面是堆的刪除操作的代碼實現(xiàn),包含詳細(xì)注釋:

public void deleteTop() {
  // 將堆頂元素與堆中最后一個元素交換
  array[0] = array[size - 1];
 
  // 堆中有效數(shù)據(jù)個數(shù)減少一個
  size--;
 
  // 對堆頂元素進(jìn)行向下調(diào)整
  int parent = 0; // 當(dāng)前節(jié)點(diǎn)的索引
 
  // 持續(xù)向下調(diào)整,直到滿足堆的性質(zhì)
  while (true) {
    // 左孩子節(jié)點(diǎn)的索引
    int leftChild = parent * 2 + 1;
    // 右孩子節(jié)點(diǎn)的索引
    int rightChild = leftChild + 1;
 
    // 父節(jié)點(diǎn)與左右孩子節(jié)點(diǎn)中值最大的節(jié)點(diǎn)進(jìn)行交換
    int maxChild = parent; // 最大值節(jié)點(diǎn)的索引
 
    // 如果左孩子存在且大于父節(jié)點(diǎn),則更新最大值節(jié)點(diǎn)
    if (leftChild < size && array[leftChild] > array[maxChild]) {
      maxChild = leftChild;
    }
 
    // 如果右孩子存在且大于當(dāng)前最大值節(jié)點(diǎn),則更新最大值節(jié)點(diǎn)
    if (rightChild < size && array[rightChild] > array[maxChild]) {
      maxChild = rightChild;
    }
 
    // 如果最大值節(jié)點(diǎn)是當(dāng)前節(jié)點(diǎn)本身,則調(diào)整結(jié)束 
    if (maxChild == parent) {
      break;
    } else {
      // 交換最大節(jié)點(diǎn)與父節(jié)點(diǎn)
      int temp = array[parent];
      array[parent] = array[maxChild];
      array[maxChild] = temp;
 
      // 更新當(dāng)前節(jié)點(diǎn)為最大值節(jié)點(diǎn)
      parent = maxChild;
    }
  }
}

二:優(yōu)先級隊列

2.1 概念

前面介紹過隊列,隊列是一種先進(jìn)先出(FIFO)的數(shù)據(jù)結(jié)構(gòu).

但有些情況下,操作的數(shù)據(jù)可能帶有優(yōu)先級,出隊列時,可能需要優(yōu)先級高的元素先出隊列,該中場景下,使用隊列顯然不合適,比如:在手機(jī)上玩游戲的時候,如果有來電,那么系統(tǒng)應(yīng)該優(yōu)先處理打進(jìn)來的電話;初中那會班主任排座位時可能會讓成績好的同學(xué)先挑座位。

在這種情況下,數(shù)據(jù)結(jié)構(gòu)應(yīng)該提供兩個最基本的操作,一個是返回最高優(yōu)先級對象,一個是添加新的對象。這種數(shù)據(jù)結(jié)構(gòu)就是優(yōu)先級隊列(Priority Queue)。

2.2堆的使用

Java集合框架中提供了PriorityQueue和PriorityBlockingQueue兩種類型的優(yōu)先級隊列,PriorityQueue是線程不安全的,PriorityBlockingQueue是線程安全的,本文主要介紹PriorityQueue。

數(shù)據(jù)結(jié)構(gòu) - 6(優(yōu)先級隊列(堆)13000字詳解),數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)結(jié)構(gòu)

關(guān)于PriorityQueue的使用要注意:

  1. 使用時必須導(dǎo)入PriorityQueue所在的包

  2. PriorityQueue中放置的元素必須要能夠比較大小,不能插入無法比較大小的對象,否則會拋出ClassCastException異常

  3. 不能插入null對象,否則會拋出NullPointerException

  4. 沒有容量限制,可以插入任意多個元素,其內(nèi)部可以自動擴(kuò)容

  5. PriorityQueue底層使用了堆數(shù)據(jù)結(jié)構(gòu)

  6. PriorityQueue默認(rèn)情況下是小堆—即每次獲取到的元素都是最小的元素

PriorityQueue中常見的構(gòu)造方法:

構(gòu)造器 功能介紹
PriorityQueue() 創(chuàng)建一個空的優(yōu)先級隊列,默認(rèn)容量是11
PriorityQueue(int initialCapacity) 創(chuàng)建一個初始容量為initialCapacity的優(yōu)先級隊列,注意: initialCapacity不能小于1,否則會拋IllegalArgumentException異 常
PriorityQueue(Collection<? extends E> c) 用一個集合來創(chuàng)建優(yōu)先級隊列

下面是使用示例:

static void TestPriorityQueue(){
    // 創(chuàng)建一個空的優(yōu)先級隊列,底層默認(rèn)容量是11
    PriorityQueue<Integer> q1 = new PriorityQueue<>();
    
    // 創(chuàng)建一個空的優(yōu)先級隊列,底層的容量為initialCapacity
    PriorityQueue<Integer> q2 = new PriorityQueue<>(100);
    ArrayList<Integer> list = new ArrayList<>();
    list.add(4);
    list.add(3);
    list.add(2);
    list.add(1);
    
    // 用ArrayList對象來構(gòu)造一個優(yōu)先級隊列的對象
    // q3中已經(jīng)包含了三個元素
    PriorityQueue<Integer> q3 = new PriorityQueue<>(list);
    System.out.println(q3.size());//4
    System.out.println(q3.peek());//1
 }

注意:默認(rèn)情況下,PriorityQueue隊列是小堆,如果需要大堆需要用戶提供比較器

// 用戶自己定義的比較器:直接實現(xiàn)Comparator接口,然后重寫該接口中的compare方法即可
class IntCmp implements Comparator<Integer>{
  @Override
  public int compare(Integer o1, Integer o2) {
    return o2-o1;
 }
}
public class TestPriorityQueue {
  public static void main(String[] args) {
    PriorityQueue<Integer> p = new PriorityQueue<>(new IntCmp());
    p.offer(4);
    p.offer(3);
    p.offer(2);
    p.offer(1);
    p.offer(5);
    System.out.println(p.peek());
 }
}

下面是 PriorityQueue中常用的方法:

函數(shù)名 功能介紹
boolean offer(E e) 插入元素e,插入成功返回true,如果e對象為空,拋出NullPointerException異常,注意:空間不夠時候會進(jìn)行擴(kuò)容
E peek() 獲取優(yōu)先級最高的元素,如果優(yōu)先級隊列為空,返回null
E poll() 移除優(yōu)先級最高的元素并返回,如果優(yōu)先級隊列為空,返回null
int size() 獲取有效元素的個數(shù)
void clear() 清空
boolean isEmpty() 檢測優(yōu)先級隊列是否為空,空返回true

下面是這些方法的使用示例:

import java.util.PriorityQueue;

public class Main {
    public static void main(String[] args) {
        PriorityQueue<Integer> heap = new PriorityQueue<>();
        
        // 測試插入元素
        heap.offer(10); // 返回 true
        heap.offer(5); // 返回 true
        heap.offer(15); // 返回 true
        heap.offer(2); // 返回 true
        
        // 測試獲取優(yōu)先級最高的元素
        // 結(jié)果為 2
        System.out.println(heap.peek());
        
        // 測試移除優(yōu)先級最高的元素
        // 結(jié)果為 2
        System.out.println(heap.poll());
        
        // 測試獲取有效元素的個數(shù)
        // 結(jié)果為 3
        System.out.println(heap.size());
        
        // 測試清空優(yōu)先級隊列
        heap.clear();
        
        // 測試檢測優(yōu)先級隊列是否為空
        // 結(jié)果為 true
        System.out.println(heap.isEmpty());
    }
}

以下是JDK 1.8中,PriorityQueue的擴(kuò)容方式:

優(yōu)先級隊列的擴(kuò)容說明:

  • 如果容量小于64時,是按照oldCapacity的2倍方式擴(kuò)容的
  • 如果容量大于等于64,是按照oldCapacity的1.5倍方式擴(kuò)容的
  • 如果容量超過MAX_ARRAY_SIZE,按照MAX_ARRAY_SIZE來進(jìn)行擴(kuò)容

MAX_ARRAY_SIZE等于Integer.MAX_VALUE - 8,等于 2,147,483,639。

2.3 用堆模擬實現(xiàn)優(yōu)先級隊列

我們通過堆可以模擬實現(xiàn)優(yōu)先級隊列,因為堆具有以下特性:

  1. 大堆或小堆:堆可以是大堆或小堆,其中大堆意味著父節(jié)點(diǎn)的值大于或等于其子節(jié)點(diǎn)的值,而小堆意味著父節(jié)點(diǎn)的值小于或等于其子節(jié)點(diǎn)的值。
  2. 優(yōu)先級定義:我們可以將堆中的元素與優(yōu)先級相關(guān)聯(lián)。較高的優(yōu)先級可以根據(jù)堆類型來確定是最大值還是最小值。

優(yōu)先性在優(yōu)先級隊列中體現(xiàn)在以下方面:

  1. 插入元素:由于堆的性質(zhì),插入的元素會按照其優(yōu)先級找到正確的位置插入。較高優(yōu)先級的元素會被放置在堆的頂部(根節(jié)點(diǎn))。
  2. 刪除元素:從堆中刪除元素時,會刪除具有最高(最大堆)或最低(最小堆)優(yōu)先級的元素。這樣,每次刪除的元素都是具有最高/最低優(yōu)先級的元素。

通過以上方式,可以使用堆來實現(xiàn)優(yōu)先級隊列,確保具有更高優(yōu)先級的元素優(yōu)先被處理或刪除。

下面我們基于堆來模擬實現(xiàn)一個優(yōu)先級隊列:

public class MyPriorityQueue {
  // 演示作用,不再考慮擴(kuò)容部分的代碼
  private int[] array = new int[100];  // 使用數(shù)組來存儲元素
  private int size = 0;  // 當(dāng)前隊列的大小

  // 向優(yōu)先隊列中插入元素
  public void offer(int e) {
    array[size++] = e;  // 將元素插入數(shù)組末尾
    shiftUp(size - 1);  // 對插入的元素進(jìn)行上浮操作,以保持堆的性質(zhì)
  }

  // 彈出并返回優(yōu)先隊列中最高優(yōu)先級的元素
  public int poll() {
    int oldValue = array[0];  // 保存堆頂元素的值
    array[0] = array[--size];  // 將堆尾元素移到堆頂
    shiftDown(0);  // 對堆頂元素進(jìn)行下沉操作,以保持堆的性質(zhì)
    return oldValue;  // 返回彈出的元素值
  }

  // 返回優(yōu)先隊列中最高優(yōu)先級的元素
  public int peek() {
    return array[0];  // 直接返回堆頂元素的值
  }
}

請注意,此處的代碼僅展示了演示目的,沒有考慮擴(kuò)容部分的代碼實現(xiàn)。

三: 堆的應(yīng)用

3.1PriorityQueue的實現(xiàn)

用堆作為底層結(jié)構(gòu)封裝優(yōu)先級隊列,這點(diǎn)我們已經(jīng)講過,在此不過多贅述

3.2堆排序

堆排序即利用堆的思想來進(jìn)行排序,總共分為兩個步驟:

  1. 建堆

升序:建大堆
降序:建小堆

  1. 利用堆刪除思想來進(jìn)行排序

建堆和堆刪除中都用到了向下調(diào)整,因此掌握了向下調(diào)整,就可以完成堆排序。

數(shù)據(jù)結(jié)構(gòu) - 6(優(yōu)先級隊列(堆)13000字詳解),數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)結(jié)構(gòu)

3.3Top-k問題

TOP-K問題:即求數(shù)據(jù)集合中前K個最大的元素或者最小的元素,一般情況下數(shù)據(jù)量都比較大。

比如:專業(yè)前10名、世界500強(qiáng)、富豪榜、游戲中前100的活躍玩家等 , 我們可以通過使用堆來解決TOP-K問題,具體步驟如下:

一: 創(chuàng)建一個大小為K的最小堆(或最大堆,取決于你是求前K個最小值還是最大值)。

二:遍歷數(shù)據(jù)集合,對于每個元素執(zhí)行以下操作:

  • 如果堆的大小小于K,將當(dāng)前元素插入堆中。
  • 如果堆的大小等于K,比較當(dāng)前元素與堆頂元素的大小
  • 如果當(dāng)前元素大于堆頂元素(最小堆),將堆頂元素彈出,將當(dāng)前元素插入堆中。
  • 如果當(dāng)前元素小于堆頂元素(最大堆),跳過當(dāng)前元素。

三: 遍歷完數(shù)據(jù)集合后,堆中的元素即為前K個最小(或最大)的元素。

下面是一個使用Java實現(xiàn)堆解決TOP-K問題的示例代碼:

import java.util.PriorityQueue;

public class TopK {
    public static void main(String[] args) {
        int[] nums = {9, 3, 7, 5, 1, 8, 2, 6, 4};
        int k = 4;

        findTopK(nums, k);
    }

    private static void findTopK(int[] nums, int k) {
        PriorityQueue<Integer> minHeap = new PriorityQueue<>();

        for (int num : nums) {
            if (minHeap.size() < k) {
                minHeap.offer(num);
            } else if (num > minHeap.peek()) {
                minHeap.poll();
                minHeap.offer(num);
            }
        }

        System.out.println("Top " + k + " elements:");
        while (!minHeap.isEmpty()) {
            System.out.print(minHeap.poll() + " ");
        }
    }
}

以上示例中,對數(shù)組nums求前4個最小元素,使用了PriorityQueue實現(xiàn)了一個小頂堆。遍歷數(shù)組時,根據(jù)堆的大小和當(dāng)前元素與堆頂元素的比較進(jìn)行插入和調(diào)整。最后,打印出堆中的元素即為前4個最小的元素。

四:java對象的比較

4.1PriorityQueue中插入對象

優(yōu)先級隊列在插入元素時有個要求:插入的元素不能是null或者元素之間必須要能夠進(jìn)行比較,為了簡單起見,我們只是插入了Integer類型,那優(yōu)先級隊列中能否插入自定義類型對象呢?

class Card {
  public int rank; // 數(shù)值
  public String suit; // 花色
  
  public Card(int rank, String suit) {
    this.rank = rank;
    this.suit = suit;
 }
}
public class TestPriorityQueue {
  public static void TestPriorityQueue()
 {
    PriorityQueue<Card> p = new PriorityQueue<>();
    p.offer(new Card(1, "?"));
    p.offer(new Card(2, "?"));
 }
  public static void main(String[] args) {
    TestPriorityQueue();
 }
}

優(yōu)先級隊列底層使用堆,而向堆中插入元素時,為了滿足堆的性質(zhì),必須要進(jìn)行元素的比較,而此時Card是沒有辦法直接進(jìn)行比較的,因此拋出異常。

數(shù)據(jù)結(jié)構(gòu) - 6(優(yōu)先級隊列(堆)13000字詳解),數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)結(jié)構(gòu)

4.2 元素的比較

4.2.1基本元素的比較

在Java中,基本類型的對象可以直接比較大小。

public class TestCompare {
  public static void main(String[] args) {
    int a = 10;
    int b = 20;
    System.out.println(a > b);
    System.out.println(a < b);
    System.out.println(a == b);
    char c1 = 'A';
    char c2 = 'B';
    System.out.println(c1 > c2);
    System.out.println(c1 < c2);
    System.out.println(c1 == c2);
    boolean b1 = true;
    boolean b2 = false;
    System.out.println(b1 == b2);
    System.out.println(b1 != b2);
 }
}

4.2.2 對象比較的問題

4.2.2.1 equals
class Card {
  public int rank; // 數(shù)值
  public String suit; // 花色
  public Card(int rank, String suit) {
    this.rank = rank;
    this.suit = suit;
 }
}
public class TestPriorityQueue {
  public static void main(String[] args) {
    Card c1 = new Card(1, "?");
    Card c2 = new Card(2, "?");
    Card c3 = c1;
    //System.out.println(c1 > c2);  // 編譯報錯
    System.out.println(c1 == c2);  // 編譯成功 ----> 打印false,因為c1和c2指向的是不同對象
    //System.out.println(c1 < c2);  // 編譯報錯
    System.out.println(c1 == c3);  // 編譯成功 ----> 打印true,因為c1和c3指向的是同一個對象
 }
}

c1、c2和c3分別是Card類型的引用變量,上述代碼在比較編譯時:

  • c1 > c2 編譯失敗
  • c1== c2 編譯成功
  • c1 < c2 編譯失敗

從編譯結(jié)果可以看出,Java中引用類型的變量不能直接按照 > 或者 < 方式進(jìn)行比較。 那為什么== 可以比較?

因為:對于用戶實現(xiàn)自定義類型,都默認(rèn)繼承自O(shè)bject類,而Object類中提供了equal方法,而 == 默認(rèn)情況下調(diào)用的就是equal方法.

但是該方法的比較規(guī)則是:沒有比較引用變量引用對象的內(nèi)容,而是直接比較引用變量的地址,但有些情況下該種比較就不符合題意。

// Object中equal的實現(xiàn),可以看到:直接比較的是兩個引用變量的地址
public boolean equals(Object obj) {
  return (this == obj);
}

有些情況下,需要比較的是對象中的內(nèi)容,比如:向優(yōu)先級隊列中插入某個對象時,需要對按照對象中內(nèi)容來調(diào)整堆,那該如何處理呢?答案是重寫覆蓋基類的equals

public class Card {
  public int rank; // 數(shù)值
  public String suit; // 花色
 
  public Card(int rank, String suit) {
    this.rank = rank;
    this.suit = suit;
 }
 
  @Override
  public boolean equals(Object o) {
    // 自己和自己比較
    if (this == o) {
      return true;
   }
   
    // o如果是null對象,或者o不是Card的子類
    if (o == null || !(o instanceof Card)) {
      return false;
   }
   
    // 注意基本類型可以直接比較,但引用類型最好調(diào)用其equal方法
    Card c = (Card)o;
    return rank == c.rank
      && suit.equals(c.suit);
      }
}

注意: 一般覆寫 equals 的套路就是上面演示的

  1. 如果指向同一個對象,返回 true
  2. 如果傳入的為 null,返回 false
  3. 如果傳入的對象類型不是 Card,返回 false
  4. 按照類的實現(xiàn)目標(biāo)完成比較,例如這里只要花色和數(shù)值一樣,就認(rèn)為是相同的牌
  5. 注意下調(diào)用其他引用類型的比較也需要 equals,例如這里的 suit 的比較

覆寫基類equal的方式雖然可以比較,但缺陷是:equal只能按照相等進(jìn)行比較,不能按照大于、小于的方式進(jìn)行比較。

4.2.2.2 Comparble接口

Comparble是JDK提供的泛型的比較接口類,源碼實現(xiàn)具體如下:

public interface Comparable<E> {
  // 返回值:
  // < 0: 表示 this 指向的對象小于 o 指向的對象
  // == 0: 表示 this 指向的對象等于 o 指向的對象
  // > 0: 表示 this 指向的對象大于 o 指向的對象
  int compareTo(E o);
}

對用用戶自定義類型,如果要想按照大小與方式進(jìn)行比較時:在定義類時,實現(xiàn)Comparble接口即可,然后在類中重寫compareTo方法。

Java中的Comparable接口是用于實現(xiàn)對象的自然排序的接口。它包含一個compareTo方法,用于比較兩個對象的順序。

compareTo方法有以下三種返回值:

  • 如果當(dāng)前對象小于被比較對象,則返回一個負(fù)整數(shù)。
  • 如果當(dāng)前對象等于被比較對象,則返回0。
  • 如果當(dāng)前對象大于被比較對象,則返回一個正整數(shù)。

下面是一個簡單的例子,演示如何使用Comparable接口來自然排序自定義的Person類:

import java.util.ArrayList;
import java.util.Collections;

class Person implements Comparable<Person> {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 實現(xiàn)compareTo方法
    @Override
    public int compareTo(Person other) {
        return this.age - other.age; // 按照年齡排序
    }

    // 其他getter和setter方法

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class Main {
    public static void main(String[] args) {
        ArrayList<Person> people = new ArrayList<>();
        people.add(new Person("Alice", 25));
        people.add(new Person("Bob", 20));
        people.add(new Person("Charlie", 30));

        System.out.println("排序前:");
        for (Person person : people) {
            System.out.println(person);
        }

        Collections.sort(people); // 使用Collections.sort()方法進(jìn)行排序時,會自動調(diào)用compareTo()方法進(jìn)行比較

        System.out.println("排序后:");
        for (Person person : people) {
            System.out.println(person);
        }
    }
}

輸出結(jié)果:

排序前:
Person{name='Alice', age=25}
Person{name='Bob', age=20}
Person{name='Charlie', age=30}
排序后:
Person{name='Bob', age=20}
Person{name='Alice', age=25}
Person{name='Charlie', age=30}

在上面的代碼中,我們定義了一個Person類,并實現(xiàn)了Comparable接口。我們通過compareTo方法比較了兩個Person對象的年齡,并在Main類中使用Collections.sort方法對people列表進(jìn)行了排序。排序后,列表中的Person對象按照年齡的升序排列。

注意:Compareble是java.lang中的接口類,可以直接使用。

4.2.2.3 基于比較器比較

在Java中,如果我們想要比較兩個對象,并根據(jù)它們的屬性值進(jìn)行排序或判斷它們的相對順序,我們可以使用比較器(Comparator)進(jìn)行操作。

比較器是一個用于定義對象之間比較行為的接口。它包含了一個用于比較兩個對象的方法 - compare()。該方法接受兩個參數(shù),分別是要比較的兩個對象,然后返回一個整數(shù)值來表示它們的相對順序。

下面是一個簡單的示例,展示如何使用比較器比較兩個Person對象:

import java.util.*;

// 定義Person類
class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 省略其他屬性和方法

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

// 定義比較器類
class AgeComparator implements Comparator<Person> {
    @Override
    public int compare(Person person1, Person person2) {
        if (person1.getAge() < person2.getAge()) {
            return -1; // person1在前
        } else if (person1.getAge() > person2.getAge()) {
            return 1; // person2在前
        } else {
            return 0; // 保持順序不變
        }
    }
}

public class Main {
    public static void main(String[] args) {
        // 創(chuàng)建Person對象
        Person person1 = new Person("Alice", 25);
        Person person2 = new Person("Bob", 30);
        Person person3 = new Person("John", 20);

        // 創(chuàng)建比較器對象
        AgeComparator comparator = new AgeComparator();

        // 使用比較器比較對象
        int result1 = comparator.compare(person1, person2); // 返回-1,person1在前
        int result2 = comparator.compare(person2, person1); // 返回1,person2在前
        int result3 = comparator.compare(person1, person3); // 返回0,保持順序不變

        System.out.println(result1);//-1
        System.out.println(result2);//1
        System.out.println(result3);//0
    }
}

在上述代碼中,我們定義了一個Person類,其中包含了姓名(name)和年齡(age)屬性。然后,我們創(chuàng)建了一個比較器類AgeComparator,實現(xiàn)了Comparator接口,并重寫了compare()方法來根據(jù)年齡來判斷相對順序。

在main()方法中,我們創(chuàng)建了三個Person對象,并使用AgeComparator比較器對象來比較它們的年齡。最后,我們打印了比較的結(jié)果,驗證了對象之間的比較行為。

注意:Comparator是java.util 包中的泛型接口類,使用時必須導(dǎo)入對應(yīng)的包。

我們要注意區(qū)分Comparable和Comparato:

  • 實現(xiàn)Comparable接口,重寫compareTo方法
  • 實現(xiàn)Comparator接口,重寫compare方法

三種比較方式對比:文章來源地址http://www.zghlxwxcb.cn/news/detail-717708.html

覆寫的方法 說明
Object.equals 因為所有類都是繼承自 Object 的,所以直接覆寫即可,不過只能比較相等與否
Comparable.compareTo 需要手動實現(xiàn)接口,侵入性比較強(qiáng),但一旦實現(xiàn),每次用該類都有順序,屬于內(nèi)部順序,Compareble是java.lang中的接口類,可以直接使用。
Comparator.compare 需要實現(xiàn)一個比較器對象,對待比較類的侵入性弱,但對算法代碼實現(xiàn)侵入性強(qiáng) ,Comparator是java.util 包中的泛型接口類,使用時必須導(dǎo)入對應(yīng)的包。

到了這里,關(guān)于數(shù)據(jù)結(jié)構(gòu) - 6(優(yōu)先級隊列(堆)13000字詳解)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點(diǎn)擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 數(shù)據(jù)結(jié)構(gòu)與算法-優(yōu)先級隊列

    Gitee上開源的數(shù)據(jù)結(jié)構(gòu)與算法代碼庫:數(shù)據(jù)結(jié)構(gòu)與算法Gitee代碼庫 優(yōu)先級隊列,按照優(yōu)先級別依次輸出 計算機(jī)科學(xué)中,堆是一種基于樹的數(shù)據(jù)結(jié)構(gòu),通常用 完全二叉樹 實現(xiàn)。堆的特性如下 在大頂堆中,任意節(jié)點(diǎn) C 與它的父節(jié)點(diǎn) P 符合 P . v a l u e ≥ C . v a l u e P.value geq C.val

    2024年02月13日
    瀏覽(36)
  • 數(shù)據(jù)結(jié)構(gòu) 之 優(yōu)先級隊列(堆) (PriorityQueue)

    數(shù)據(jù)結(jié)構(gòu) 之 優(yōu)先級隊列(堆) (PriorityQueue)

    ??歡迎大家觀看AUGENSTERN_dc的文章(o゜▽゜)o☆?? ??感謝各位讀者在百忙之中抽出時間來垂閱我的文章,我會盡我所能向的大家分享我的知識和經(jīng)驗?? ??希望我們在一篇篇的文章中能夠共同進(jìn)步?。?! ??個人主頁:AUGENSTERN_dc ??個人專欄:C語言?|?Java | 數(shù)據(jù)結(jié)構(gòu) ?個人

    2024年03月20日
    瀏覽(27)
  • 數(shù)據(jù)結(jié)構(gòu)之優(yōu)先級隊列【堆】(Heap)

    數(shù)據(jù)結(jié)構(gòu)之優(yōu)先級隊列【堆】(Heap)

    目錄 1. 優(yōu)先級隊列(Priority Queue) 2.堆的概念 3.堆的存儲方式 4.堆的創(chuàng)建 5.用堆模擬實現(xiàn)優(yōu)先級隊列 ?6.PriorityQueue常用接口介紹 6.1?PriorityQueue的特點(diǎn) 6.2?PriorityQueue幾種常見的構(gòu)造方式 7.top-k問題 8.堆排序 本篇主要內(nèi)容總結(jié) (1)優(yōu)先級隊列底層是堆來實現(xiàn)的 (2)堆的本質(zhì)是

    2024年02月01日
    瀏覽(51)
  • 【一起學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)與算法】優(yōu)先級隊列(堆)

    【一起學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)與算法】優(yōu)先級隊列(堆)

    如果我們給每個元素都分配一個數(shù)字來標(biāo)記其優(yōu)先級,不妨設(shè)較小的數(shù)字具有較高的優(yōu)先級,這樣我們就可以在一個集合中訪問優(yōu)先級最高的元素并對其進(jìn)行查找和刪除操作了。這樣,我們就引入了 優(yōu)先級隊列 這種數(shù)據(jù)結(jié)構(gòu)。 優(yōu)先級隊列(priority queue) 是0個或多個元素的集

    2024年01月19日
    瀏覽(24)
  • 【數(shù)據(jù)結(jié)構(gòu)】 優(yōu)先級隊列(堆)與堆的建立

    【數(shù)據(jù)結(jié)構(gòu)】 優(yōu)先級隊列(堆)與堆的建立

    前面介紹過隊列, 隊列是一種先進(jìn)先出(FIFO)的數(shù)據(jù)結(jié)構(gòu) ,但有些情況下,操作的數(shù)據(jù)可能帶有優(yōu)先級,一般出隊列時,可能需要優(yōu)先級高的元素先出隊列,該中場景下,使用隊列顯然不合適。 比如:在手機(jī)上玩游戲的時候,如果有來電,那么系統(tǒng)應(yīng)該優(yōu)先處理打進(jìn)來的電話

    2024年02月10日
    瀏覽(18)
  • 【數(shù)據(jù)結(jié)構(gòu)與算法】03 隊列(順序隊列--循環(huán)隊列--優(yōu)先級隊列--鏈隊列)

    【數(shù)據(jù)結(jié)構(gòu)與算法】03 隊列(順序隊列--循環(huán)隊列--優(yōu)先級隊列--鏈隊列)

    隊列( queue )是一種常見的數(shù)據(jù)結(jié)構(gòu),它遵循先進(jìn)先出(FIFO)的原則。隊列可以理解為一個具有兩個端點(diǎn)的線性數(shù)據(jù)結(jié)構(gòu),其中一個端點(diǎn)稱為\\\"隊尾\\\"(rear),用于插入新元素,另一個端點(diǎn)稱為\\\"隊首\\\"(front),用于移除元素。新元素被插入到隊尾,而最早插入的元素總是在隊

    2024年02月08日
    瀏覽(21)
  • Java 數(shù)據(jù)結(jié)構(gòu)篇-用數(shù)組、堆實現(xiàn)優(yōu)先級隊列

    Java 數(shù)據(jù)結(jié)構(gòu)篇-用數(shù)組、堆實現(xiàn)優(yōu)先級隊列

    ??博客主頁:?【 小扳_-CSDN博客】 ?感謝大家點(diǎn)贊??收藏?評論? ?? 文章目錄 ? ? ? ? 1.0 優(yōu)先級隊列說明 ? ? ? ? 2.0 用數(shù)組實現(xiàn)優(yōu)先級隊列 ? ? ? ? 3.0?無序數(shù)組實現(xiàn)優(yōu)先級隊列 ? ? ? ? 3.1 無序數(shù)組實現(xiàn)優(yōu)先級隊列 - 入隊列 offer(E value) ? ? ? ? 3.2 無序數(shù)組實現(xiàn)優(yōu)先

    2024年02月04日
    瀏覽(22)
  • 【數(shù)據(jù)結(jié)構(gòu)初階】——第八節(jié).優(yōu)先級隊列(小根堆的模擬實現(xiàn))

    【數(shù)據(jù)結(jié)構(gòu)初階】——第八節(jié).優(yōu)先級隊列(小根堆的模擬實現(xiàn))

    ?作者簡介:大家好,我是未央; 博客首頁: 未央.303 系列專欄:Java初階數(shù)據(jù)結(jié)構(gòu) 每日一句:人的一生,可以有所作為的時機(jī)只有一次,那就是現(xiàn)在?。?! 目錄 文章目錄 前言 引言 一、堆的概念 二、堆的性質(zhì)? 三、堆的操作 3.1 向下調(diào)整算法 3.2?小根堆的創(chuàng)建 3.3?向上調(diào)整

    2024年02月07日
    瀏覽(31)
  • 經(jīng)典TopK問題、優(yōu)先級隊列 與 堆的糾葛一文為你解惑——數(shù)據(jù)結(jié)構(gòu)

    經(jīng)典TopK問題、優(yōu)先級隊列 與 堆的糾葛一文為你解惑——數(shù)據(jù)結(jié)構(gòu)

    前言: 本篇文章以 TopK 問題為引,具體闡述了 PriorityQueue 實現(xiàn)的基本邏輯——堆 數(shù)據(jù)結(jié)構(gòu),以及PriorityQueue 的常用方法。如有問題歡迎看官朋友指正,如果覺得文章還不錯的話,求點(diǎn)贊、收藏、評論 三連。 重點(diǎn): 堆的基本實現(xiàn)邏輯 PriorityQueue 運(yùn)用和源碼分析 TopK 問題的解法

    2023年04月22日
    瀏覽(18)
  • 【堆的認(rèn)識及其優(yōu)先級隊列】java代碼實現(xiàn),保姆級教程學(xué)習(xí)堆和優(yōu)先級隊列

    【堆的認(rèn)識及其優(yōu)先級隊列】java代碼實現(xiàn),保姆級教程學(xué)習(xí)堆和優(yōu)先級隊列

    前言: 大家好,我是 良辰 丫?????,我們又見面了,前面我們講了用鏈表實現(xiàn)的二叉樹,今天我們來接觸 堆 的概念,堆是一種特殊的二叉樹,只不過咱們的對底層原理是數(shù)組,堆也是我們在做題中經(jīng)常見到的,那么,接下來我們就慢慢的去接觸堆, 認(rèn)識堆,理解堆,掌

    2024年02月02日
    瀏覽(32)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包