Heap Sort

Heap Sort,中文有堆積排序或累堆排序。
使用完整二元樹與陣列建立堆積樹與排序。

完整二元樹

完整二元樹(Complete Binary Tree),由上而下由左而右(參考下圖箭頭方向),中間不會有空缺,逐一把節點一個一個放入陣列中。
img

img
對映的陣列如下:

[20, 15, 5, 1, 100]

img

對映的陣列如下:

[20, 15, 5, 1, 100, 6]

img
對映的陣列如下:

[20, 15, 5, 1, 100, 6, 30]

若中間有空缺,就不是完整二元樹。
以下缺少第二層的右子樹,不是完整二元樹。
img

以下有少一個節點,有空缺,不是完整二元樹。 img

陣列索引與樹

下圖,加上對映的陣列索引。
img

陣列:

索引 0 1 2 3 4
數值 20 15 5 1 100

左子節點公式,i為「父」節點索引。

2 * i + 1

右子節點公式,i為「父」節點索引。

2 * i + 2

找出父節點公式,i為「子」節點索引。

(i - 1) / 2

假如我們要找父節點索引為1,值是15,它的左右子節點,要如何找?

左子節點

2 * 1 + 1 = 3
索引 0 1 2 3 4
數值 20 15 5 1 100

右子節點

2 * 1 + 2 = 4
索引 0 1 2 3 4
數值 20 15 5 1 100

找出索引為4的父節點,無條件去掉小數點。

(4 - 1) / 2 = 1
索引 0 1 2 3 4
數值 20 15 5 1 100

最大堆積樹與最小堆積樹

最大堆積樹

根節點都比左右子樹大。
img

最小堆積樹

根節點都比左右子樹小。
img

建立最大堆積樹

最大堆積樹由下往上,把最大值搬到根節點。
所以以下步驟由下往上找。

最後一個非葉子父節點

下圖中,索引1,值為15,就是最後一個非葉子父節點。
img

索引 0 1 2 3 4
數值 20 15 5 1 100

方法1:最後一個葉子的父節點

先前有提過找到「子」索引的父節點。

(i - 1) / 2

陣列中的最後一個元素,一定是葉子節點,可透過它來找父節點。
以下公式為「整數」除法,無條件捨去小數。

(arr.length - 1 - 1) / 2
(5 - 1 - 1) / 2 = 1

方法2:陣列大小/2 - 1

陣列大小除2,左半邊都是父節點,右半邊都是子節點。
陣列大小5除2 = 2,小於2不包含2都是左半邊,左半邊都是父節點,大於等於2,右半邊都是子節點。

父或葉子
索引 0 1 2 3 4
數值 20 15 5 1 100

如果要取得最後一個父節點,也就要把陣列大小/2,再減1,就會取得最後一個父節點。

(arr.length / 2) - 1
(5 / 2) - 1 = 1
父或葉子
索引 0 1 2 3 4
數值 20 15 5 1 100

左右子節點比父節點大就交換。

  1. 左右子節點先比較誰比較大
  2. 比較大的子節點,再跟父節點比大小,若子節點比父節點大,就交換。

img

父或葉子
索引 0 1 2 3 4
數值 20 100 5 1 15

倒數第二個父節點

先前有提過,陣列大小/2 = 第一個葉子節點,陣列大小/2,左半邊都是父節點,右半邊都是葉子節點。

剛才處理完最後一個父節點,索引為1,請問倒數第2個父節點怎麼求?
也就是索引1 - 1 = 0 ,就會得到倒數第2個父節點,索引為0。

父或葉子
索引 0 1 2 3 4
數值 20 100 5 1 15

索引0,先看左右子節點有沒有比索引0的值大,有的話就交換。
img

索引 0 1 2 3 4
數值 100 20 5 1 15

目前最大堆積樹已完成。

迴圈處理,「最後一個」非葉子節點與「倒數第2個」非葉子節點。

1
2
3
4
// 先從最後一個非葉子節點建立最大堆積樹
for (int i = arr.length / 2 - 1; i >= 0; i--) {
  adjust(arr, i , arr.length);
}

陣列由小到大排序

要讓陣列由小到大排序,最大的要放最後面。

把根節點放在陣列最後一個索引位置

索引0的值為最大,要放最後面。

索引 0 1 2 3 4
數值 100 20 5 1 15

img

索引 0 1 2 3 4
數值 15 20 5 1 100

把最後一個節點排除掉。
img

索引 0 1 2 3
數值 15 20 5 1

以下程式碼處理把最大arr[0]放在陣列最後面,然後注意是i>0,也就是i=0剩下一個,就不做了,因為都已經排序完畢。

1
2
3
4
5
6
7
8
9
for (int i = arr.length - 1; i > 0 ; i--) {
  // 索引0的值為最大,要放最後面,交換
  int temp = arr[i];
  arr[i] = arr[0];
  arr[0] = temp;
  // i每次都少1個,形式上就是排除最後一個節點。
  // 最後一個節點不處理
  adjust(arr, 0, i);
}

重新調整最大堆積樹

從索引0根節點開始,去調整最大堆積樹,根節點要為最大。

i變數,指向根節點,目前i = 0
temp變數,暫存i變數指向的值,temp = 15。

1
int temp = arr[i];

k變數,指向比較大的左或右子節點,目前k = 1。

1
2
// k變數一開始指向左子節點
int k = i * 2 + 1;
1
2
3
4
5
若右子節點大於左子節點
if (k + 1 < len && arr[k] < arr[k + 1]) {
  // k變數變右子節點
  k++;
}

temp = 15 小於 arr[k = 1]
arr[i = 0]位置放入arr[k = 1]
i = ki與k指向同一個位置,i = k = 1

1
2
3
4
if (temp < arr[k]) {
  arr[i] = arr[k];
  i = k;
}

img

下一次迴圈,k++,k = 2。
arr[k = 2] = 5 沒有大於temp = 15,直接跳離迴圈。

1
2
3
4
5
6
7
if (temp < arr[k]) {
  arr[i] = arr[k];
  i = k;
} else {
  // 執行以下這行
  break;
}

此時已經找到15要插入的位置,i = 1,把temp=15放入arr[i = 1]img

1
arr[i] = temp;

目前最大堆積樹已完成。

索引 0 1 2 3
數值 20 15 5 1

調整最大堆積樹的程式碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  public static void adjust(int[] arr, int i, int len) {
    int temp = arr[i];
    for (int k = i * 2 + 1; k < len; k++) {
      if (k + 1 < len && arr[k] < arr[k + 1]) {
        k++;
      }
      if (temp < arr[k]) {
        arr[i] = arr[k];
        i = k;
      } else {
        break;
      }
    }
    arr[i] = temp;
  }

把根節點放在陣列最後一個索引位置

索引0的值為最大,要放最後面。

索引 0 1 2 3
數值 20 15 5 1
索引 0 1 2 3
數值 1 15 5 20

img

調整成最大堆積樹

i變數,指向根節點,目前i = 0
temp變數,暫存i變數指向的值,temp = 1。
k變數,指向比較大的左或右子節點,目前k = 1。
temp = 1 小於 arr[k = 1] = 15
arr[i = 0]位置放入arr[k = 1]
i = ki與k指向同一個位置,i = k = 1
img

下一次迴圈,k++,k = 2,i = 1
temp = 1 小於arr[k = 2] = 5
arr[i = 1]位置放入arr[k = 2]
i = ki與k指向同一個位置,i = k = 2
img

此時已經找到1要插入的位置,i = 2,把temp=1放入arr[i = 2]img

目前最大堆積樹已完成。

重覆調整的次數

重覆調整的次數為陣列大小 - 1。

完整程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class HeapSort {
  public static void main(String[] args) {
    // 要排序的陣列
    int[] arr = {20, 15, 5, 1, 100};
    // 前序遍歷
    preOrder(0, arr);
    // 先從最後一個非葉子節點建立最大堆積樹
    for (int i = arr.length / 2 - 1; i >= 0; i--) {
      adjust(arr, i , arr.length);
    }

    for (int i = arr.length - 1; i > 0 ; i--) {
      int temp = arr[i];
      arr[i] = arr[0];
      arr[0] = temp;
      adjust(arr, 0, i);
    }
    System.out.println(Arrays.toString(arr));
  }

  public static void adjust(int[] arr, int i, int len) {
    int temp = arr[i];
    for (int k = i * 2 + 1; k < len; k++) {
      if (k + 1 < len && arr[k] < arr[k + 1]) {
        k++;
      }
      if (temp < arr[k]) {
        arr[i] = arr[k];
        i = k;
      } else {
        break;
      }
    }
    arr[i] = temp;
  }

  public static void preOrder(int i, int[] arr) {
    System.out.print(arr[i] + ",");
    if (i * 2 + 1 < arr.length) {
      preOrder(i * 2 + 1, arr);
    }
    if (i * 2 + 2 < arr.length) {
      preOrder(i * 2 + 2, arr);
    }
  }
}

results matching ""

    No results matching ""