日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

28 | 堆和堆排序:为什么说堆排序没有快速排序快?

發布時間:2023/12/10 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 28 | 堆和堆排序:为什么说堆排序没有快速排序快? 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

如何理解“堆”

堆排序是一種原地的、時間復雜度為 O(nlogn) 的排序算法

堆的兩個特點:

  • 一顆完全二叉樹
  • 堆中每個節點都必須大于等于(或者小于等于)其左右子節點的值;
  • 對于每個節點的值都大于等于子樹中每個節點值的堆,叫做“大頂堆”。對于每個節點的值都小于等于子樹中每個節點值的堆,叫做“小頂堆”。

    如何實現一個“堆”

    如何存儲一個堆

    完全二叉樹比較適合用數組來存儲。用數組來存儲完全二叉樹是非常節省存儲空間的。因為不需要存儲左右子節點的指針,單純地通過數組的下標,就可以找到一個節點的左右子節點和父節點。

    堆都支持哪些操作

    堆中插入一個元素

    插入元素放到最后,需要進行堆化操作:堆化實際上有兩種,從下往上和從上往下。這里我先講從下往上的堆化方法——順著節點所在的路徑,向上或者向下,對比,然后交換

    public class Heap {private int[] a; // 數組,從下標1開始存儲數據private int n; // 堆可以存儲的最大數據個數private int count; // 堆中已經存儲的數據個數public Heap(int capacity) {a = new int[capacity + 1];n = capacity;count = 0;}public void insert(int data) {if (count >= n) return; // 堆滿了++count;a[count] = data;int i = count;while (i/2 > 0 && a[i] > a[i/2]) { // 自下往上堆化swap(a, i, i/2); // swap()函數作用:交換下標為i和i/2的兩個元素i = i/2;}}}

    刪除堆頂元素

    思路一:刪除堆頂元素之后,就需要把第二大的元素放到堆頂,那第二大元素肯定會出現在左右子節點中。然后我們再迭代地刪除第二大節點,以此類推,直到葉子節點被刪除。此方法會出現數組空洞

    思路二:把最后一個節點放到堆頂,然后利用同樣的父子節點對比方法。對于不滿足父子節點大小關系的,互換兩個節點,并且重復進行這個過程,直到父子節點之間滿足大小關系為止。這就是從上往下的堆化方法。

    實現代碼:

    public void removeMax() {if (count == 0) return -1; // 堆中沒有數據a[1] = a[count];--count;heapify(a, count, 1); }private void heapify(int[] a, int n, int i) { // 自上往下堆化while (true) {int maxPos = i;if (i*2 <= n && a[i] < a[i*2]) maxPos = i*2;if (i*2+1 <= n && a[maxPos] < a[i*2+1]) maxPos = i*2+1;if (maxPos == i) break;swap(a, i, maxPos);i = maxPos;} }

    如何基于堆實現排序?

    這種排序方法的時間復雜度非常穩定,是 O(nlogn),并且它還是原地排序算法。堆排序的過程分為2步驟:

    1、建堆

    思路一:數組中包含 n 個數據,但是我們可以假設,起初堆中只包含一個數據,就是下標為 1 的數據。然后,我們調用前面講的插入操作,將下標從 2 到 n 的數據依次插入到堆中。這樣我們就將包含 n 個數據的數組,組織成了堆。此方法從前往后處理數組數據,并且每個數據插入堆中時,都是從下往上堆化。不推薦

    思路二:從后往前處理數組,并且每個數據都是從上往下堆化。以下圖示:

    實現代碼

    private static void buildHeap(int[] a, int n) {for (int i = n/2; i >= 1; --i) {heapify(a, n, i);} }private static void heapify(int[] a, int n, int i) {while (true) {int maxPos = i;if (i*2 <= n && a[i] < a[i*2]) maxPos = i*2;if (i*2+1 <= n && a[maxPos] < a[i*2+1]) maxPos = i*2+1;if (maxPos == i) break;swap(a, i, maxPos);i = maxPos;} }

    對下標從 2n? 開始到 1 的數據進行堆化,下標是 2n?+1 到 n 的節點是葉子節點,我們不需要堆化。實際上,對于完全二叉樹來說,下標從 2n?+1 到 n 的節點都是葉子節點

    建堆的時間復雜度就是 O(n)。

    2、排序

    • 建堆結束之后,數組中的數據已經是按照大頂堆的特性來組織的。數組中的第一個元素就是堆頂,也就是最大的元素。我們把它跟最后一個元素交換,那最大元素就放到了下標為 n 的位置。
    • 這個過程有點類似上面講的“刪除堆頂元素”的操作,當堆頂元素移除之后,我們把下標為 n 的元素放到堆頂,然后再通過堆化的方法,將剩下的 n?1 個元素重新構建成堆。堆化完成之后,我們再取堆頂的元素,放到下標是 n?1 的位置,一直重復這個過程,直到最后堆中只剩下標為 1 的一個元素,排序工作就完成了。

    堆排序過程代碼:

    // n表示數據的個數,數組a中的數據從下標1到n的位置。 public static void sort(int[] a, int n) {buildHeap(a, n);int k = n;while (k > 1) {swap(a, 1, k);--k;heapify(a, k, 1);} }

    分析一下堆排序的時間復雜度、空間復雜度以及穩定性

    1、整個堆排序的過程,都只需要極個別臨時存儲空間,所以堆排序是原地排序算法。

    2、堆排序包括建堆和排序兩個操作,建堆過程的時間復雜度是 O(n),排序過程的時間復雜度是 O(nlogn),所以,堆排序整體的時間復雜度是 O(nlogn)。

    3、堆排序不是穩定的排序算法,因為在排序的過程,存在將堆的最后一個節點跟堆頂節點互換的操作,所以就有可能改變值相同數據的原始相對順序。

    解釋:在前面的講解以及代碼中,我都假設,堆中的數據是從數組下標為 1 的位置開始存儲。那如果從 0 開始存儲,實際上處理思路是沒有任何變化的,唯一變化的,可能就是,代碼實現的時候,計算子節點和父節點的下標的公式改變了。如果節點的下標是 i,那左子節點的下標就是 2?i+1,右子節點的下標就是 2?i+2,父節點的下標就是 2i?1?。

    解答標題

    第一點,堆排序數據訪問的方式沒有快速排序友好。對于快速排序來說,數據是順序訪問的。而對于堆排序來說,數據是跳著訪問的。堆排序中,最重要的一個操作就是數據的堆化。比如下面這個例子,對堆頂節點進行堆化,會依次訪問數組下標是 1,2,4,8 的元素,而不是像快速排序那樣,局部順序訪問,所以,這樣對 CPU 緩存是不友好的

    第二點,對于同樣的數據,在排序過程中,堆排序算法的數據交換次數要多于快速排序、

    ?

    ?

    創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

    總結

    以上是生活随笔為你收集整理的28 | 堆和堆排序:为什么说堆排序没有快速排序快?的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。