首頁 >Java >java教程 >Java中關於堆排序演算法的實例分析

Java中關於堆排序演算法的實例分析

黄舟
黄舟原創
2017-09-30 10:20:362108瀏覽

這篇文章主要為大家詳細介紹了Java堆排序演算法的相關程式碼,具有一定的參考價值,有興趣的小夥伴們可以參考一下

堆是資料結構中的重要結構,了解「堆」的概念和操作,可以幫助我們快速掌握堆排序。

堆的概念

堆是一種特殊的完全二元樹(complete binary tree)。如果一棵完全二元樹的所有節點的值都不小於其子節點,稱為大根堆(或大頂堆);所有節點的值都不大於其子節點,稱為小根堆(或小頂堆)。

在陣列(在0號下標儲存根節點)中,容易得到下面的式子(這兩個式子很重要):

1.下標為i的節點,父節點座標為(i-1)/2;

2.下標為i的節點,左子節點座標為2*i+1,右子節點為2*i+2。

堆的建立和維護

堆可以支援多種操作,但現在我們關心的只有兩個問題:

1 .給定一個無序數組,如何建立為堆?

2.刪除堆頂元素後,如何調整數字組成為新堆?

先看第二個問題。假定我們已經有一個現成的大根堆。現在我們刪除了根元素,但並沒有移動別的元素。想想發生了什麼事:根元素空了,但其它元素仍保持著堆的性質。我們可以把最後一個元素(代號A)移到根元素的位置。如果不是特殊情況,則堆的性質被破壞。但這只是由於A小於其某個子元素。於是,我們可以把A和這個子元素調換位置。如果A大於其所有子元素,則堆調整好了;否則,重複上述過程,A元素在樹狀結構中不斷“下沉”,直到合適的位置,數組重新恢復堆的性質。上述過程一般稱為“篩選”,方向顯然是自上而下。

刪除一個元素是如此,插入一個新元素也是如此。不同的是,我們把新元素放在末尾,然後和其父節點做比較,也就是自下而上篩選。

那麼,第一個問題要怎麼解決呢?

我看過的資料結構的書很多都是從第一個非葉子結點向下篩選,直到根元素篩選完畢。這個方法叫“篩選法”,需要循環篩選n/2個元素。

但我們也可以藉鏡「無中生有」的想法。我們可以視第一個元素為一個堆,然後不斷在其中加入新元素。這個方法叫做“插入法”,需要循環插入(n-1)個元素。

由於篩選法和插入法的方式不同,所以,相同的數據,它們建立的堆疊一般不同。

大致了解堆之後,堆排序就是水到渠成的事情了。

演算法概述/想法

我們需要一個升序的序列,怎麼辦呢?我們可以建立一個最小堆,然後每次輸出根元素。但是,這個方法需要額外的空間(否則將造成大量的元素移動,其複雜度會飆升到O(n^2))。如果我們需要就地排序(即不允許有O(n)空間複雜度),怎麼辦?
有辦法。我們可以建立最大堆,然後我們倒著輸出,在最後一個位置輸出最大值,次末位置輸出次大值…由於每次輸出的最大元素會騰出第一個空間,因此,我們恰好可以放置這樣的元素而不需要額外空間。很漂亮的想法,是不是?


public class HeapSort 
{
  public static void main(String[] args) 
  {
    int[] arr = { 50, 10, 90, 30, 70, 40, 80, 60, 20 };
    System.out.println("排序之前:"); 
    for (int i = 0; i < arr.length; i++) 
      System.out.print(arr[i] + " "); 
    
    // 堆排序
    heapSort(arr); 
    System.out.println(); 
    System.out.println("排序之后:"); 
    for (int i = 0; i < arr.length; i++) 
      System.out.print(arr[i] + " ");
  }
  
  /** 
  * 堆排序 
  */
  private static void heapSort(int[] arr) 
  {
    // 将待排序的序列构建成一个大顶堆 
    for (int i = arr.length / 2; i >= 0; i--)
      heapAdjust(arr, i, arr.length);
    
    // 逐步将每个最大值的根节点与末尾元素交换,并且再调整二叉树,使其成为大顶堆 
    for (int i = arr.length - 1; i > 0; i--)
    {
      swap(arr, 0, i); // 将堆顶记录和当前未经排序子序列的最后一个记录交换 
      heapAdjust(arr, 0, i); // 交换之后,需要重新检查堆是否符合大顶堆,不符合则要调整
    }
  }
  /** 
  * 构建堆的过程 
  * @param arr 需要排序的数组 
  * @param i 需要构建堆的根节点的序号 
  * @param n 数组的长度 
  */
  private static void heapAdjust(int[] arr, int i, int n) 
  {
    int child;
    int father;
    for (father = arr[i]; leftChild(i) < n; i = child)
    {
      child = leftChild(i);
      // 如果左子树小于右子树,则需要比较右子树和父节点 
      if (child != n - 1 && arr[child] < arr[child + 1])
        child++; // 序号增1,指向右子树
      // 如果父节点小于孩子结点,则需要交换 
      if (father < arr[child]) 
        arr[i] = arr[child];
      else
        break; // 大顶堆结构未被破坏,不需要调整
    }
    arr[i] = father;
  }
  
  // 获取到左孩子结点 
  private static int leftChild(int i) 
  {
    return 2 * i + 1;
  }
  
  // 交换元素位置 
  private static void swap(int[] arr, int index1, int index2) 
  {
    int tmp = arr[index1];
    arr[index1] = arr[index2];
    arr[index2] = tmp;
  }
}

以上是Java中關於堆排序演算法的實例分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn