堆排序(Heap Sort)-Java常見經典算法詳解

堆排序(Heap Sort)算法簡介:

堆排序是指利用堆這種數據結構所設計的一種排序算法。堆是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。堆排序分爲兩種方法:

  • 大頂堆:每個節點的值都大於或等於其子節點的值,在堆排序算法中用於升序排列;
  • 小頂堆:每個節點的值都小於或等於其子節點的值,在堆排序算法中用於降序排列;

堆(二叉堆)可以視爲一棵完全的二叉樹,完全二叉樹的一個“優秀”的性質是,除了最底層之外,每一層都是滿的,這使得堆可以利用數組來表示(普通的一般的二叉樹通常用鏈表作爲基本容器表示),每一個結點對應數組中的一個元素。如下圖,是一個大頂堆和數組的相互關係:

對於給定的某個結點的下標 i,可以很容易的計算出這個結點的父結點、子結點的下標:

Parent(i) = i/2,i 的父節點下標;Left(i) = 2i,i 的左子節點下標;Right(i) = 2i + 1,i 的右子節點下標。

堆排序(Heap Sort)算法原理:

堆排序的基本思想是:

  1. 將無需序列構建成一個堆,根據升序降序需求選擇大頂堆或小頂堆;
  2. 將堆頂元素與末尾元素交換,將最大元素"沉"到數組末端;
  3. 重新調整結構,使其滿足堆定義,然後繼續交換堆頂元素與當前末尾元素,反覆執行調整+交換步驟,直到整個序列有序。

舉例說明:

  1. 首先,將所有的數字存儲在堆中;
  2. 按大頂堆構建堆,其中大頂堆的一個特性是數據將被從大到小取出,將取出的數字按相反的順序排列,數字就完成了排序;
  3. 在這裏數字 5 先入堆;
  4. 數字 2 入堆;
  5. 數字 7 入堆, 7 此時是最後一個節點,與最後一個非葉子節點(也就是數字 5 )進行比較,由於 7>5 ,所以 7 和 5 交換;
  6. 按照上述的操作將所有數字入堆,然後從左到右,從上到下進行調整,構造出大頂堆;
  7. 入堆完成之後,將堆頂元素取出,將末尾元素置於堆頂,重新調整結構,使其滿足堆定義;
  8. 堆頂元素數字 7 取出,末尾元素數字 4 置於堆頂,爲了維護好大頂堆的定義,最後一個非葉子節點數字 5 與 4 比較,而後交換兩個數字的位置;
  9. 反覆執行調整+交換步驟,直到整個序列有序。

堆排序(Heap Sort)代碼實現:

import java.util.Arrays;
public class Demo {
	public static void main(String[] args){
        int []arr = {5,2,7,3,6,1,4};
        System.out.println("排序前:"+Arrays.toString(arr));
        sort(arr);
        System.out.println("排序前:"+Arrays.toString(arr));
    }
 
    public static void sort(int[] array){
        //1.構建大頂堆
        for(int i=array.length/2-1;i>=0;i--){
            //從第一個非葉子結點從下至上,從右至左調整結構
            adjustHeap(array,i,array.length);
        }
        //2.調整堆結構+交換堆頂元素與末尾元素
        for(int j=array.length-1;j>0;j--){
            swap(array,0,j);//將堆頂元素與末尾元素進行交換
            adjustHeap(array,0,j);//重新對堆進行調整
        }
 
    }

    /**
     * 調整大頂堆(僅是調整過程,建立在大頂堆已構建的基礎上)
     * @param arr
     * @param i
     * @param length
     */
    public static void adjustHeap(int[] array,int i,int length){
        int temp = array[i];//先取出當前元素i
        for(int k=i*2+1;k<length;k=k*2+1){//從i結點的左子結點開始,也就是2i+1處開始
            if(k+1<length && array[k]<array[k+1]){//如果左子結點小於右子結點,k指向右子結點
                k++;
            }
            if(array[k] >temp){//如果子節點大於父節點,將子節點值賦給父節點(不用進行交換)
                array[i] = array[k];
                i = k;
            }else{
                break;
            }
        }
        array[i] = temp;//將temp值放到最終的位置
    }
 
    /**
     * 交換元素
     * @param array
     * @param a
     * @param b
     */
    public static void swap(int[] array,int a ,int b){
        int temp=array[a];
        array[a] = array[b];
        array[b] = temp;
    }
}

 

堆排序(Heap Sort)的時間複雜度:

堆排序一開始需要將n個數據存進堆裏,所需時間爲O(nlogn)。排序過程中,堆從空堆的狀態開始,逐漸被數據填滿。由於堆的高度小於logn,所以插入1個數據所需要的時間爲O(logn)。
每輪取出最大的數據並重構堆所需要的時間爲O(logn)。由於總共有n輪,所以重構後排序的時間也是O(nlogn)。因此,整體來看堆排序的時間複雜度爲0 (nlogn)。這樣來看,堆排序的運行時間比之前講到的冒泡排序、選擇排序、插入排序的時間O(n²)都要短,但由於要使用堆這個相對複雜的數據結構,所以實現起來也較爲困難。

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章