堆排序

還想看更多文章的朋友可以訪問我的個(gè)人博客


一些基礎(chǔ)知識

學(xué)習(xí)堆排序,總要了解的數(shù)據(jù)結(jié)構(gòu)吧,了解堆又需要了解二叉樹的基本知識,了解二叉樹要先知道樹是個(gè)什么東西吧…… 呃,還是太年輕。這里簡單談?wù)劙伞?/p>

二叉樹

  1. 提一下——是一對多的數(shù)據(jù)結(jié)構(gòu),從一個(gè)根結(jié)點(diǎn)開始,生長出它的子結(jié)點(diǎn),而每一個(gè)子結(jié)點(diǎn)又生長出各自的子結(jié)點(diǎn),成為子樹。如果某個(gè)結(jié)點(diǎn)不再生長出子結(jié)點(diǎn)了,它就成為葉子。

    一個(gè)普通的樹結(jié)構(gòu)

  2. 二叉樹是每個(gè)節(jié)點(diǎn)最多有兩個(gè)子樹()的樹結(jié)構(gòu)。通常子樹被稱作“左子樹”(left subtree)和“右子樹”(right subtree),且次序不能顛倒。

    二叉樹
  3. 完全二叉樹除最后一層外,若其余層都是滿的,并且最后一層或者是滿的,或者是在右邊缺少連續(xù)若干節(jié)點(diǎn)。

    完全二叉樹
  1. 滿二叉樹的所有分支結(jié)點(diǎn)都既有左子樹又有右子樹,并且所有葉子都在同一層。每一層上的節(jié)點(diǎn)數(shù)都是最大節(jié)點(diǎn)數(shù) 。

    滿二叉樹

堆排序

  1. 數(shù)組與堆結(jié)構(gòu) (如何用數(shù)組實(shí)現(xiàn)):

    image

    對于任一節(jié)點(diǎn)(若其在數(shù)組中對應(yīng)元素下標(biāo)為 i)而言,都有

    • 子節(jié)點(diǎn):左孩子是2*i+1,右孩子是2*i+2
    • 父節(jié)點(diǎn):(i - 1) / 2
  2. 生成大根堆:

    堆可以分為大根堆和小根堆。在大根堆中,所有子樹中子節(jié)點(diǎn)的值都小于等于父節(jié)點(diǎn);而小根堆的組織方式恰好相反:所有子樹中子節(jié)點(diǎn)的值都大于等于父節(jié)點(diǎn)。在堆排序中,使用的是大根堆;而小根堆常被用作構(gòu)建優(yōu)先隊(duì)列。

    調(diào)整大根堆的過程,遍歷數(shù)組元素,將當(dāng)前節(jié)點(diǎn)與其父節(jié)點(diǎn)進(jìn)行比較:

    • 若當(dāng)前節(jié)點(diǎn)小于等于父節(jié)點(diǎn),則比較下一位置。
    • 若當(dāng)前節(jié)點(diǎn)大于父節(jié)點(diǎn),則將當(dāng)前節(jié)點(diǎn)與父節(jié)點(diǎn)交換,然后比較交換后的父節(jié)點(diǎn)(交換前的“當(dāng)前”節(jié)點(diǎn))與其父節(jié)點(diǎn)進(jìn)行比較(交換)過程。這個(gè)過程很明顯可以是個(gè)遞歸過程。

    下面是該過程的實(shí)現(xiàn):

          /**
            * 遞歸調(diào)整大根堆
            * @param arr array
            * @param index current node index
            * @param comparable 比較原則
            */
          private void heapInsert(T[] arr, int index, Comparator<T> comparable) {
              int parent = (index - 1) >>> 1;
              if (comparable.compare(arr[index], arr[parent]) <= 0) {
                  return;
              }
    
              swap(arr, index, parent);
              heapInsert(arr, parent, comparable);
          }
    
          /**
            * 非遞歸調(diào)整大根堆
            */
          private void heapInsertByLoop(T[] arr, int index, MangoComparable<T> comparable) {
              int parent = (index - 1) / 2;
              while (comparable.compare(arr[index], arr[parent]) > 0) {
                  swap(arr, index, parent);
                  index = parent;
                  parent = (index - 1) / 2;
              }
          }
    
  3. 容易想到的是,調(diào)整完畢后的樹仍然是無序的。但是可以理解的是根節(jié)點(diǎn)一定是整個(gè)樹(數(shù)組)的最大值,那么我們完全可以利用上述過程查找最大值,然后將與樹的最后一個(gè)節(jié)點(diǎn)交換。再對除最后一個(gè)節(jié)點(diǎn)的前n-1個(gè)樹節(jié)點(diǎn)(將樹的規(guī)??s小1,size--)進(jìn)行大根堆調(diào)整,再交換。循環(huán)執(zhí)行此過程,即可使得元素升序排列。

值得注意的是,大根堆生成過程中的調(diào)整策略,是將當(dāng)前元素與父節(jié)點(diǎn)比較(由底向上),利用該策略同樣可以完成上述排序過程,流程如下:(構(gòu)建大根堆 -> 交換)

image

具體實(shí)現(xiàn)如下:


    @Override
    public void sort(T[] array, Comparator<T> comparable) {
        if (array == null || array.length < 2) {
            return;
        }

        for (int i = 0; i < array.length; i++) {
            heapInsert(array, i, comparable);
        }

        int heapSize = array.length;
        swap(array, 0, --heapSize);

        while (heapSize > 0) {
            for (int i = 0; i < heapSize; i++) {
                heapInsert(array, i, comparable);
            }
            swap(array, 0, --heapSize);
        }
    }

而在排序過程中,我們當(dāng)然也可以便利地比較左右子樹與父節(jié)點(diǎn)即,當(dāng)前節(jié)點(diǎn)(由頂向下,畢竟此時(shí)我們已經(jīng)生成了堆結(jié)構(gòu)了)。找到當(dāng)前節(jié)點(diǎn)的左右節(jié)點(diǎn)中較大的子節(jié)點(diǎn),與當(dāng)前節(jié)點(diǎn)比較:

  • 若當(dāng)前節(jié)點(diǎn)大于等于較大的子節(jié)點(diǎn),則判斷下一個(gè)節(jié)點(diǎn)。
  • 若當(dāng)前節(jié)點(diǎn)小于較大的子節(jié)點(diǎn),則將當(dāng)前節(jié)點(diǎn)與較大的節(jié)點(diǎn)交換(我們的目標(biāo)是讓最大值向上浮)。然后比較交換后的所產(chǎn)生的父節(jié)點(diǎn)(交換前的較大的子節(jié)點(diǎn))與其子節(jié)點(diǎn)進(jìn)行比較(交換)。

具體流程如下:

image

具體實(shí)現(xiàn)如下:

 @Override
    public void sort(T[] array, Comparator<T> comparable) {
        // ...
        while (heapSize > 0) {
            heapify(array, 0, heapSize, comparable);
        }
    }

    /**
     * 遞歸版
     */
    private void heapify(T[] arr, int index, int size, Comparator<T> comparable) {
        int left = 2*index + 1;
        if (left < size) {
            int largestChild = left + 1 < size 
                && comparable.compare(arr[left+1], arr[left]) > 0 ? left+1 : left;
            if (comparable.compare(arr[largestChild], arr[index]) < 0) {
                return;
            }

            swap(arr, largestChild, index);
            heapify(arr, largestChild, size, comparable);
        }
    }

 /**
     * 非遞歸版
     */
   private void heapifyByLoop(T[] arr, int index, int size, Comparator<T> comparable) {
        int left = 2*index + 1;
        while (left < size) {
            int largestChild = left + 1 < size 
                && comparable.compare(arr[left+1], arr[left]) > 0 ? left+1 : left;
            if (comparable.compare(arr[largestChild], arr[index]) < 0) {
                return;
            }

            swap(arr, largestChild, index);
            index = largestChild;
            left = 2*index + 1;
        }
    }
  1. 時(shí)間復(fù)雜度分析:我們知道二叉樹結(jié)構(gòu)的基礎(chǔ)操作時(shí)間復(fù)雜度為O(logN),其中N是樹的節(jié)點(diǎn)數(shù)。大根堆的生成過程相當(dāng)于不斷向樹中增加節(jié)點(diǎn)的過程,即

    數(shù)組中下標(biāo)為0的元素,插入高度為0的二叉樹;
    數(shù)組中下標(biāo)為1的元素,插入高度為1的二叉樹;
    數(shù)組中下標(biāo)為2的元素,插入高度為2的二叉樹;
    ……
    數(shù)組中下標(biāo)為n-1的元素,插入高度為n-1的二叉樹;
    

    所以,該操作時(shí)間復(fù)雜度為O(log1 + log2 + ... + logi + ... + logn) = O(logn!),經(jīng)數(shù)學(xué)證明(logn!nlogn "同階函數(shù)"),因此生成大根堆的時(shí)間復(fù)雜度是O(nlogn)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、什么是堆排序 堆排序是將數(shù)組看做一個(gè)完全二叉樹(附錄里有二叉樹的解釋),具有以下的性質(zhì): 1)每個(gè)節(jié)點(diǎn)...
    BoomBoomPia閱讀 454評論 0 7
  • 堆排序可以做什么 首先應(yīng)該弄清楚堆排序可以解決什么問題,答案是顯而易見的:排序。說得通俗點(diǎn)兒就是對一組無序的數(shù)字進(jìn)...
    Springlord888閱讀 30,603評論 11 52
  • 二叉樹 滿二叉樹 國內(nèi)教程定義:一個(gè)二叉樹,如果每一個(gè)層的結(jié)點(diǎn)數(shù)都達(dá)到最大值,則這個(gè)二叉樹就是滿二叉樹。也就是說,...
    簡_愛SimpleLove閱讀 4,400評論 0 3
  • HeapSort 轉(zhuǎn)載自:鏈接:http://www.itdecent.cn/p/719b0de606a7 ...
    須臾之北閱讀 4,149評論 0 1
  • ‘或許有很多人都在喜歡你,可那個(gè)想嫁給你的只有我一個(gè).’ 2017.7.25 星期四 晴 01. 最近家里...
    yz雨霖_慕瀟閱讀 335評論 0 4

友情鏈接更多精彩內(nèi)容