**堆排序**（英語：）是指利用這種数据結構所設計的一種排序算法。是一個近似完全二叉樹的結構，並同時滿足**堆積的性質**：即子節點的键值或索引總是小於（或者大於）它的父節點。

概述
--

若以升序排序說明，把陣列轉換成最大堆積（Max-Heap Heap），這是一種滿足最大堆積性質（Max-Heap Property）的二元樹：對於除了根之外的每個节点i, A[parent(i)] ≥ A[i]。

重複從最大堆積取出數值最大的结点（把根结点和最后一个结点交換，把交換後的最后一个结点移出堆），並讓殘餘的堆積維持最大堆積性質。

節點的訪問
-----

通常是通過一維数组來實現的。在陣列起始位置為0的情形中：

* 父節點i的左子節點在位置{\displaystyle (2i+1)};
* 父節點i的右子節點在位置{\displaystyle (2i+2)};{\displaystyle }
* 子節點i的父節點在位置{\displaystyle \lfloor (i-1)/2\rfloor };

的操作
---

在的資料結構中，中的最大值總是位於根節點（在优先队列中使用的话中的最小值位于根节点）。中定義以下幾種操作：

* 最大調整（Max Heapify）：將的末端子節點作調整，使得子節點永遠小於父節點
* 建立最大（Build Max Heap）：將中的所有數據重新排序
* 堆排序（HeapSort）：移除位在第一個數據的根節點，並做最大調整的遞迴運算

實作範例
----

### C语言

```
#include <stdio.h>
#include <stdlib.h>

void swap(int \*a, int \*b) {
 int temp = \*b;
 \*b = \*a;
 \*a = temp;
}

void max\_heapify(int arr[], int start, int end) {
 // 建立父節點指標和子節點指標
 int dad = start;
 int son = dad \* 2 + 1;
 while (son <= end) { // 若子節點指標在範圍內才做比較
 if (son + 1 <= end && arr[son] < arr[son + 1]) // 先比較兩個子節點大小，選擇最大的
 son++;
 if (arr[dad] > arr[son]) //如果父節點大於子節點代表調整完畢，直接跳出函數
 return;
 else { // 否則交換父子內容再繼續子節點和孫節點比较
 swap(&arr[dad], &arr[son]);
 dad = son;
 son = dad \* 2 + 1;
 }
 }
}

void heap\_sort(int arr[], int len) {
 int i;
 // 初始化，i從最後一個父節點開始調整
 for (i = len / 2 - 1; i >= 0; i--)
 max\_heapify(arr, i, len - 1);
 // 先將第一個元素和已排好元素前一位做交換，再重新調整，直到排序完畢
 for (i = len - 1; i > 0; i--) {
 swap(&arr[0], &arr[i]);
 max\_heapify(arr, 0, i - 1);
 }
}

int main() {
 int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 };
 int len = (int) sizeof(arr) / sizeof(\*arr);
 heap\_sort(arr, len);
 int i;
 for (i = 0; i < len; i++)
 printf("%d ", arr[i]);
 printf("\n");
 return 0;
}

```

### C++

```
#include <iostream>
#include <algorithm>
using namespace std;

void max\_heapify(int arr[], int start, int end) {
 // 建立父節點指標和子節點指標
 int dad = start;
 int son = dad \* 2 + 1;
 while (son <= end) { // 若子節點指標在範圍內才做比較
 if (son + 1 <= end && arr[son] < arr[son + 1]) // 先比較兩個子節點大小，選擇最大的
 son++;
 if (arr[dad] > arr[son]) // 如果父節點大於子節點代表調整完畢，直接跳出函數
 return;
 else { // 否則交換父子內容再繼續子節點和孫節點比較
 swap(arr[dad], arr[son]);
 dad = son;
 son = dad \* 2 + 1;
 }
 }
}

void heap\_sort(int arr[], int len) {
 // 初始化，i從最後一個父節點開始調整
 for (int i = len / 2 - 1; i >= 0; i--)
 max\_heapify(arr, i, len - 1);
 // 先將第一個元素和已经排好的元素前一位做交換，再從新調整(刚调整的元素之前的元素)，直到排序完畢
 for (int i = len - 1; i > 0; i--) {
 swap(arr[0], arr[i]);
 max\_heapify(arr, 0, i - 1);
 }
}

int main() {
 int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 };
 int len = (int) sizeof(arr) / sizeof(\*arr);
 heap\_sort(arr, len);
 for (int i = 0; i < len; i++)
 cout << arr[i] << ' ';
 cout << endl;
 return 0;
}

```

### Java

```
import java.util.Arrays;

public class HeapSort {
 private int[] arr;
 public HeapSort(int[] arr) {
 this.arr = arr;
 }

 /\*\*
 \* 堆排序的主要入口方法，共两步。
 \*/
 public void sort() {
 /\*
 \* 第一步：将数组堆化
 \* beginIndex = 第一个非叶子节点。
 \* 从第一个非叶子节点开始即可。无需从最后一个叶子节点开始。
 \* 叶子节点可以看作已符合堆要求的节点，根节点就是它自己且自己以下值为最大。
 \*/
 int len = arr.length - 1;
 int beginIndex = (arr.length >> 1)- 1;
 for (int i = beginIndex; i >= 0; i--)
 maxHeapify(i, len);
 /\*
 \* 第二步：对堆化数据排序
 \* 每次都是移出最顶层的根节点A[0]，与最尾部节点位置调换，同时遍历长度 - 1。
 \* 然后从新整理被换到根节点的末尾元素，使其符合堆的特性。
 \* 直至未排序的堆长度为 0。
 \*/
 for (int i = len; i > 0; i--) {
 swap(0, i);
 maxHeapify(0, i - 1);
 }
 }

 private void swap(int i, int j) {
 int temp = arr[i];
 arr[i] = arr[j];
 arr[j] = temp;
 }

 /\*\*
 \* 调整索引为 index 处的数据，使其符合堆的特性。
 \*
 \* @param index 需要堆化处理的数据的索引
 \* @param len 未排序的堆（数组）的长度
 \*/
 private void maxHeapify(int index, int len) {
 int li = (index << 1) + 1; // 左子节点索引
 int ri = li + 1; // 右子节点索引
 int cMax = li; // 子节点值最大索引，默认左子节点。
 if (li > len) return; // 左子节点索引超出计算范围，直接返回。
 if (ri <= len && arr[ri] > arr[li]) // 先判断左右子节点，哪个较大。
 cMax = ri;
 if (arr[cMax] > arr[index]) {
 swap(cMax, index); // 如果父节点被子节点调换，
 maxHeapify(cMax, len); // 则需要继续判断换下后的父节点是否符合堆的特性。
 }
 }

 /\*\*
 \* 测试用例
 \*
 \* 输出：
 \* [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9]
 \*/
 public static void main(String[] args) {
 int[] arr = new int[] {3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6};
 new HeapSort(arr).sort();
 System.out.println(Arrays.toString(arr));
 }
}

```

### Python

```
#!/usr/bin/env python
#-\*-coding:utf-8-\*-

def heap\_sort(lst):
    def sift\_down(start, end):
 """最大堆调整"""
        root = start
        while True:
            child = 2 \* root + 1
            if child > end:
                break
            if child + 1 <= end and lst[child] < lst[child + 1]:
                child += 1
            if lst[root] < lst[child]:
                lst[root], lst[child] = lst[child], lst[root]
                root = child
            else:
                break

# 創建最大堆

    for start in range((len(lst) - 2) // 2, -1, -1):
        sift\_down(start, len(lst) - 1)

# 堆排序
    for end in range(len(lst) - 1, 0, -1):
        lst[0], lst[end] = lst[end], lst[0]
        sift\_down(0, end - 1)
    return lst

if \_\_name\_\_ == "\_\_main\_\_":
    l = [9, 2, 1, 7, 6, 8, 5, 3, 4]
    heap\_sort(l)

```

### JavaScript

```
Array.prototype.heap\_sort = function() {
 var arr = this.slice(0);
 function swap(i, j) {
 var tmp = arr[i];
 arr[i] = arr[j];
 arr[j] = tmp;
 }

 function max\_heapify(start, end) {
 //建立父節點指標和子節點指標
 var dad = start;
 var son = dad \* 2 + 1;
 if (son >= end)//若子節點指標超過範圍直接跳出函數
 return;
 if (son + 1 < end && arr[son] < arr[son + 1])//先比較兩個子節點大小，選擇最大的
 son++;
 if (arr[dad] < arr[son]) {//如果父節點小於子節點時，交換父子內容再繼續子節點和孫節點比較
 swap(dad, son);
 max\_heapify(son, end);
 }
 }

 var len = arr.length;
 //初始化，i從最後一個父節點開始調整
 for (var i = Math.floor(len / 2) - 1; i >= 0; i--)
 max\_heapify(i, len);
 //先將第一個元素和已排好元素前一位做交換，再從新調整，直到排序完畢
 for (var i = len - 1; i > 0; i--) {
 swap(0, i);
 max\_heapify(0, i);
 }

 return arr;
};
var a = [3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6];
console.log(a.heap\_sort());

```

### PHP

```
<?php
function swap(&$x, &$y) {
	$t = $x;
	$x = $y;
	$y = $t;
}

function max\_heapify(&$arr, $start, $end) {
	//建立父節點指標和子節點指標
	$dad = $start;
	$son = $dad \* 2 + 1;
	if ($son >= $end)//若子節點指標超過範圍直接跳出函數
		return;
	if ($son + 1 < $end && $arr[$son] < $arr[$son + 1])//先比較兩個子節點大小，選擇最大的
		$son++;
	if ($arr[$dad] <= $arr[$son]) {//如果父節點小於子節點時，交換父子內容再繼續子節點和孫節點比較
		swap($arr[$dad], $arr[$son]);
		max\_heapify($arr, $son, $end);
	}
}

function heap\_sort($arr) {
	$len = count($arr);
	//初始化，i從最後一個父節點開始調整
	for ($i = ceil($len/2) - 1; $i >= 0; $i--)
		max\_heapify($arr, $i, $len);
	//先將第一個元素和已排好元素前一位做交換，再從新調整，直到排序完畢
	for ($i = $len - 1; $i > 0; $i--) {
		swap($arr[0], $arr[$i]);
		max\_heapify($arr, 0, $i);
	}
	return $arr;
}

$arr = array(3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6);
$arr = heap\_sort($arr);
for ($i = 0; $i < count($arr); $i++)
	echo $arr[$i]. ' ';
?>

```

### Go

```
package main

import (
 "fmt"
)

func HeapSort(array []int) {
 m := len(array)
 s := m/2
 for i := s; i > -1; i-- {
 heap(array, i, m-1)
 }
 for i := m-1; i > 0; i-- {
 array[i], array[0] = array[0], array[i]
 heap(array, 0, i-1)
 }
}

func heap(array []int, i, end int){
 l := 2\*i+1
 if l > end {
 return
 }
 n := l
 r := 2\*i+2
 if r <= end && array[r]>array[l]{
 n = r
 }
 if array[i] > array[n]{
 return
 }
 array[n], array[i] = array[i], array[n]
 heap(array, n, end)
}

func main() {
 array := []int{
 55, 94,87,1,4,32,11,77,39,42,64,53,70,12,9,
 }
 HeapSort(array)
 fmt.Println(array)
}

```

### Julia (程式語言)

```
function HeapSort(array)
 function adjust(l,u)
 while true
 j = 2\*l # 左子點
 if (j>u) # 代表沒有子點
 break
 end 
 if ((j+1) <= u) # 有右子點
 if (array[j] < array[j+1]) 
 j += 1 #比較左右子點
 end
 end
 if (array[j] > array[l]) #比較父點跟子點
 array[l], array[j]= array[j], array[l]
 l = j # 有交換
 else
 break # 沒交換跳出迴圈
 end
 end
 end
 n = length(array)
 for i in n:-1:1 # 建 max Heap
 adjust(i,n)
 end 
 #持續把第一個(最大)的元素最後一個交換
 array[n],array[1]=array[1],array[n] 
 for i in n-1:-1:1
 adjust(1,i)
 array[i],array[1]=array[1],array[i]
 end
 return array
end

```

### Rust

```
fn max\_heapify<T: Ord + Copy>(data: &mut [T], pos: usize, end: usize) {
 let mut dad = pos;
 let mut son = dad \* 2 + 1;
 while son <= end {
 if son < end && data[son] < data[son + 1] {
 son += 1;
 }
 if data[dad] > data[son] {
 return;
 } else {
 data.swap(dad, son);
 dad = son;
 son = dad \* 2 + 1;
 }
 }
}

fn heap\_sort<T:Ord+Copy>(data: &mut[T]) {
 let len = data.len();
 for i in (0..=len / 2 - 1).rev() {
 max\_heapify(data, i, len - 1);
 }
 for i in (1..=len - 1).rev() {
 data.swap(0, i);
 max\_heapify(data, 0, i - 1);
 }
}

fn main() {
 let mut nums = vec![9, 2, 1, 7, 6, 8, 5, 3, 4];
 heap\_sort(nums.as\_mut\_slice());
}

```

### 原地堆排序

基于以上相关的操作，我们可以很容易的定义堆排序。例如，假设我们已经读入一系列数据并创建了一个，一个最直观的算法就是反复的调用`del_max()`函数，因为该函数总是能够返回中最大的值，然后把它从中删除，从而对这一系列返回值的输出就得到了该序列的降序排列。真正的原地堆排序使用了另外一个小技巧。堆排序的过程是：

1. 建立一个{\displaystyle H[0..n-1]}
2. 把首（最大值）和尾互换
3. 把的尺寸缩小1，并调用`shift_down(0)`,目的是把新的数组顶端数据调整到相应位置
4. 重复步骤2，直到的尺寸为1

平均复杂度
-----

堆排序的平均时间复杂度为{\displaystyle \mathrm {O} (n\log n)}，空间复杂度为\Theta(1)。

參考
--

1. ↑  R. Schaffer, R. Sedgewick.. Journal of Algorithms. 1993-07, **15** (1): 76–100  [2022-10-02]. doi:10.1006/jagm.1993.1031. （原始内容存档于2022-01-27） （英语）.

外部連結
----

* 常见排序算法 - 堆排序 (Heap Sort)

|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 

| * 
* 论
* 编
排序算法 |
| --- |
|  |
| 理论 | 
* 計算複雜性理論
* 大O符号
* 全序关系
* 数据结构术语列表
* 原地算法
* 稳定性
* 比较排序
* 自适应排序（英语：）
* 排序网络（英语：）
* 整数排序（英语：）
* X+Y排序（英语：）
* 量子排序（英语：）

 |
|  |
| 交换排序 | 
* 冒泡排序
* 鸡尾酒排序
* 奇偶排序
* 梳排序
* 侏儒排序
* 快速排序
* 慢速排序
* 臭皮匠排序
* Bogo排序

 |
|  |
| 选择排序 | 
* 选择排序
* 堆排序
* 平滑排序（英语：）
* 笛卡尔树排序（英语：）
* 锦标赛排序（英语：）
* 圈排序（英语：）
* 弱堆排序（英语：）

 |
|  |
| 插入排序 | 
* 插入排序
* 希尔排序
* 伸展排序
* 二叉查找树排序
* 图书馆排序
* 耐心排序

 |
|  |
| 归并排序 | 
* 归并排序
* 梯级归并排序（英语：）
* 振荡归并排序（英语：）
* 多相归并排序（英语：）

 |
|  |
| 分布排序 | 
* 美国旗帜排序（英语：）
* 珠排序
* 桶排序
* 爆炸排序（英语：）
* 计数排序
* 比較計數排序
* 插值排序
* 鸽巢排序
* 相邻图排序（英语：）
* 基数排序
* 闪电排序（英语：）

 |
|  |
| 并发排序 | 
* 双调排序器（英语：）
* Batcher归并网络
* 两两排序网络（英语：）

 |
|  |
| 混合排序 | 
* 塊排序（英语：）
* Tim排序
* 内省排序
* Spread排序（英语：）
* 归并插入排序（英语：）

 |
|  |
| 其他 | 
* 拓撲排序
* 煎餅排序
* 意粉排序（英语：）

 |

 |

|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 

| * 
* 论
* 编
算法 |
| --- |
|  |
| 排序 | 

|  |  |
| --- | --- |
| 比较排序 | 
* 冒泡排序
* 选择排序
* 插入排序
* 希尔排序
* 快速排序
* 归并排序
* 堆排序
* 鸡尾酒排序
* 梳排序
* 侏儒排序
* 图书馆排序
* 内省排序
* 奇偶排序

 |
|  |
| 线性时间排序 | 
* 鸽巢排序
* 基数排序
* 計數排序
* 桶排序

 |
|  |
| 并行排序 | 
* 排序网络（英语：）
* Batcher归并网络

 |
|  |
| 不实用的 | 
* Bogo排序
* 臭皮匠排序

 |
|  |
| 图 | 
* 拓撲排序

 |

 |
|  |
| 搜索 | 

|  |  |
| --- | --- |
| 列表 | 
* 线性搜索
* 二分搜索
* 插值搜尋

 |
|  |
| 树・图 | 
* 广度优先搜索
	+ 最良優先搜索（英语：）
	+ 均一开销搜索
	+ A\*
* 深度优先搜索
	+ 迭代深化深度优先搜索
	+ 深度限制搜索（日语：）
	+ 双向搜索
* 分枝限定法（英语：）

 |
|  |
| 字符串 | 
* KMP算法
* 博耶-穆尔字符串搜索算法
* AC自动机算法
* 拉宾-卡普算法
* bitap算法

 |

 |
|  |
| 最短路问题 | 
* 戴克斯特拉算法
* 贝尔曼-福特算法
* A\*搜尋演算法
* Floyd-Warshall算法

 |
|  |
| 最小生成树 | 
* 普林姆算法
* 克鲁斯克尔演算法

 |
|  |
| 最大流最小割 | 
* 福特-富尔克森算法
* 埃德蒙兹-卡普算法
* 迪尼茨算法

 |
|  |
| 线性规划 | 
* 单纯形法
* 卡马卡尔算法（英语：）

 |
|  |
| 順序統計量 | 
* 选择算法
* 中位数的中位数（英语：）

 |
|  |
| 種類 | 
* 精确算法
* 近似算法
* 随机化算法

 |
|  |
| 其他 | 
* 分治法
* 动态规划
* 贪心算法

 |
|  |
| **Category:算法** |

 |