* 堆 排 序(Heap Sort)*
堆
序列 ( a 1 , a 2 , … , a n ) (a_1,a_2,…,a_n) (a1,a2,…,an)当且仅当满足以下关系时,称之为堆(heap)。{其中 k i k_i ki是元素 a i a_i ai的关键字}
{ k i ≤ k 2 i k i ≤ k 2 i + 1 \begin{cases} k_i\le k_{2i} \\ k_i\le k_{2i+1} \end{cases} {ki≤k2iki≤k2i+1或 { k i ≥ k 2 i if 2 i ≤ n k i ≥ k 2 i + 1 if 2 i + 1 ≤ n \begin{cases} k_i\ge k_{2i} &\text{if } {2i}\le n \\ k_i\ge k_{2i+1} &\text{if } {2i+1}\le n \end{cases} {ki≥k2iki≥k2i+1if 2i≤nif 2i+1≤n
1)大根堆:堆顶(树根)值最大的堆
2)小根堆:堆顶(树根)值最小的堆
关键思想
1)将无序序列构建成堆的形式;
2)依次提取堆顶(树根)元素置序列末尾。
注意:每一轮堆排序提取堆顶元素时都需调整堆使得其继续满足大(小)根堆的性质!
时间复杂度
Time Complexity | Value |
---|---|
最优时间复杂度 | O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n) |
最差时间复杂度 | O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n) |
平均时间复杂度 | O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n) |
解析:
已知深度是 k k k的堆,构建过程中比较次数至多是 2 ( k − 1 ) 2(k-1) 2(k−1)次, n n n个节点的完全二叉树高度是 l o g 2 ( n + 1 ) log_{2}{(n+1)} log2(n+1),那么堆排序(共 n − 1 n-1 n−1轮)总共进行的比较次数不超过 l o g 2 ( n − 1 ) + l o g 2 ( n − 2 ) + … + 1 log_{2}{(n-1)} +log_{2}{(n-2)}+…+1 log2(n−1)+log2(n−2)+…+1次,而构建初始堆所进行的比较次数不超过 4 n 4n 4n次,因此堆排序算法的时间复杂度是 O ( n l o g 2 n ) O(nlog_{2}n) O(nlog2n)。
空间复杂度
Space Complexity | Value |
---|---|
空间复杂度 | O ( 1 ) O(1) O(1) |
稳定性 ×
解析:
不稳定性:若原序列中数 i i i位于数 j j j前面,且 i = j i=j i=j,整个排序过程结束之后数 i i i可能位于数 j j j后面。
实例:[12 15 30 80 100 46 78](Java)
public static void main(String[] args) {
// TODO 自动生成的方法存根
System.out.println("请键盘输入整数序列[例如:12 15 30 80 100 46 78 end]"); // 从键盘接收输入信息
Scanner List = new Scanner(System.in);
ArrayList<Integer> inputArray = new ArrayList<Integer>();
while (List.hasNextInt()) {
inputArray.add(List.nextInt());
}
List.close();
System.out.println("输入:" + inputArray);
ConstructBigHeap(inputArray); // 将输入序列构建成大根堆
// ConstructSmallHeap(inputArray); // 将输入序列构建成小根堆
}
Case1:大根堆
注意:
1)从小到大排序的本质是将大根堆转换为特殊的小根堆形式输出;
2)从大到小排序则需要申请额外空间依次存储每一轮堆排序的堆顶元素。
Construct Big Heap
public static void ConstructBigHeap(ArrayList<Integer> inputArray) { // 构建大根堆
System.out.println("Construct Big Heap");
ArrayList<Integer> bigHeap = new ArrayList<Integer>(); // 申请大根堆数组空间
int index = 1; // 当前遍历输入序列元素地址
bigHeap.add(inputArray.get(0)); // 初始状态:默认输入序列中第一个元素是父节点
while ((index != 0) && (index < inputArray.size())) {
System.out.print("Step" + index + ": ");
bigHeap.add(inputArray.get(index)); // 默认顺序添加输入序列的下一个元素
System.out.print(bigHeap + " → ");
int parent = (index -1) / 2; // 当前遍历输入序列元素父节点元素地址
int value = bigHeap.get(index); // 当前遍历输入序列元素值
int location = index; // 当前遍历输入序列元素地址
while ((value > bigHeap.get(parent)) && (parent >= 0)) { // 当父节点元素值小于当前遍历输入序列的元素值
bigHeap.set(location, bigHeap.get(parent));
bigHeap.set(parent, value); // 互换父节点元素值与当前遍历输入序列的元素值
location = parent; // 当前遍历输入序列的元素地址替换成父节点元素地址
parent = (parent - 1) / 2; // 继续向上遍历直至无父节点
}
index += 1;
System.out.println(bigHeap);
}
SmallToBig_BigHeap(bigHeap); // 从小到大排序
// BigToSmall_BigHeap(bigHeap); // 从大到小排序
}
Case1.1:从小到大排序
<1> 第1轮堆排序
<2> 第2轮堆排序
<3> 第3轮堆排序
<4> 第4轮堆排序
<5> 第5轮堆排序
<6> 第6轮堆排序
End:[12 15 30 46 78 80 100]
public static void SmallToBig_BigHeap(ArrayList<Integer> bigHeap) { // 从小到大排序(大根堆)
for (int i = bigHeap.size() - 1; i > 0; i--) {
System.out.print("第" + (bigHeap.size() - i) +"轮堆排序:");
int value = bigHeap.get(i); // 大根堆中未输出序列的末尾元素值
bigHeap.set(i, bigHeap.get(0)); // 每一轮堆排序过程中输出大根堆堆顶元素(即:将大根堆堆顶元素置于末尾元素地址)
bigHeap.set(0, value); // 将大根堆中未输出序列的末尾元素置于堆顶
for (int j = 1; j < i; j++) { // 检查(除输出元素之外)剩余元素序列是否满足大根堆
int parent = (j - 1) / 2; // 父节点元素位置
if (bigHeap.get(j) > bigHeap.get(parent)) { // 子节点元素值大于父节点元素值
int temp = bigHeap.get(j);
bigHeap.set(j, bigHeap.get(parent));
bigHeap.set(parent, temp);
}
}
System.out.println(bigHeap);
}
System.out.println("输出:" + bigHeap);
}
Case1.2:从大到小排序
<1> 第1轮堆排序(Extract 100)
<2> 第2轮堆排序(Extract 80)
<3> 第3轮堆排序(Extract 78)
<4> 第4轮堆排序(Extract 46)
<5> 第5轮堆排序(Extract 30)
<6> 第6轮堆排序(Extract 15)
<7> 第7轮堆排序(Extract 12)
End:[100 80 78 46 30 15 12]
public static void BigToSmall_BigHeap(ArrayList<Integer> bigHeap) { // 从大到小排序(大根堆)
int i = 0;
ArrayList<Integer> outputArray = new ArrayList<Integer>();
while (bigHeap.size() > 1) {
System.out.println("第" + (i + 1) + "轮堆排序(Extract " + bigHeap.get(0) + ")");
outputArray.add(bigHeap.get(0));
System.out.println(outputArray);
bigHeap.set(0, bigHeap.get(bigHeap.size() - 1)); // 将堆中最后一个元素置于堆顶
bigHeap.remove(bigHeap.size() - 1); // 释放堆序列的末尾位置空间
for (int j = 1; j < bigHeap.size(); j++) {
int parent = (j - 1) / 2; // 父节点元素位置
if (bigHeap.get(j) > bigHeap.get(parent)) { // 子节点元素值大于父节点元素值
int temp = bigHeap.get(j);
bigHeap.set(j, bigHeap.get(parent));
bigHeap.set(parent, temp);
}
}
System.out.println("堆 → " + bigHeap);
i += 1;
}
outputArray.add(bigHeap.get(0));
System.out.println("输出:" + outputArray);
}
Case2:小根堆
注意:
1)从大到小排序的本质是将小根堆转换为特殊的大根堆形式输出;
2)从小到大排序则需要申请额外空间依次存储每一轮堆排序的堆顶元素。
Construct Small Heap
public static void ConstructSmallHeap(ArrayList<Integer> inputArray) { // 构建小根堆
System.out.println("Construct Small Heap");
ArrayList<Integer> smallHeap = new ArrayList<Integer>(); // 申请小根堆数组空间
int index = 1; // 当前遍历输入序列元素地址
smallHeap.add(inputArray.get(0)); // 初始状态:默认输入序列中第一个元素是父节点
while ((index != 0) && (index < inputArray.size())) {
System.out.print("Step" + index + ": ");
smallHeap.add(inputArray.get(index)); // 默认顺序添加输入序列的下一个元素
System.out.print(smallHeap + " → ");
int parent = (index -1) / 2; // 当前遍历输入序列元素父节点元素地址
int value = smallHeap.get(index); // 当前遍历输入序列元素值
int location = index; // 当前遍历输入序列元素地址
while ((value < smallHeap.get(parent)) && (parent >= 0)) { // 当父节点元素值大于当前遍历输入序列的元素值
smallHeap.set(location, smallHeap.get(parent));
smallHeap.set(parent, value); // 互换父节点元素值与当前遍历输入序列的元素值
location = parent; // 当前遍历输入序列的元素地址替换成父节点元素地址
parent = (parent - 1) / 2; // 继续向上遍历直至无父节点
}
index += 1;
System.out.println(smallHeap);
}
BigToSmall_SmallHeap(smallHeap); // 从大到小排序
// SmallToBig_SmallHeap(smallHeap); // 从小到大排序
}
Case2.1:从大到小排序
<1> 第1轮堆排序
<2> 第2轮堆排序
<3> 第3轮堆排序
<4> 第4轮堆排序
<5> 第5轮堆排序
<6> 第6轮堆排序
End:[100 80 78 46 30 15 12]
public static void BigToSmall_SmallHeap(ArrayList<Integer> smallHeap) { // 从大到小排序(小根堆)
for (int i = smallHeap.size() - 1; i > 0; i--) {
System.out.print("第" + (smallHeap.size() - i) +"轮堆排序:");
int value = smallHeap.get(i); // 小根堆中未输出序列的末尾元素值
smallHeap.set(i, smallHeap.get(0)); // 每一轮堆排序过程中输出小根堆堆顶元素(即:将小根堆堆顶元素置于末尾元素地址)
smallHeap.set(0, value); // 将小根堆中未输出序列的末尾元素置于堆顶
for (int j = 1; j < i; j++) { // 检查(除输出元素之外)剩余元素序列是否满足小根堆
int parent = (j - 1) / 2; // 父节点元素位置
if (smallHeap.get(j) < smallHeap.get(parent)) { // 子节点元素值小于父节点元素值
int temp = smallHeap.get(j);
smallHeap.set(j, smallHeap.get(parent));
smallHeap.set(parent, temp);
}
}
System.out.println(smallHeap);
}
System.out.println("输出:" + smallHeap);
}
Case2.2:从小到大排序
<1> 第1轮堆排序(Extract 12)
<2> 第2轮堆排序(Extract 15)
<3> 第3轮堆排序(Extract 30)
<4> 第4轮堆排序(Extract 46)
<5> 第5轮堆排序(Extract 78)
<6> 第6轮堆排序(Extract 80)
<7> 第7轮堆排序(Extract 100)
End:[12 15 30 46 78 80 100]
public static void SmallToBig_SmallHeap(ArrayList<Integer> smallHeap) { // 从小到大排序(小根堆)
int i = 0;
ArrayList<Integer> outputArray = new ArrayList<Integer>();
while (smallHeap.size() > 1) {
System.out.println("第" + (i + 1) + "轮堆排序(Extract " + smallHeap.get(0) + ")");
outputArray.add(smallHeap.get(0));
System.out.println(outputArray);
smallHeap.set(0, smallHeap.get(smallHeap.size() - 1)); // 将堆中最后一个元素置于堆顶
smallHeap.remove(smallHeap.size() - 1); // 释放堆序列的末尾位置空间
for (int j = 1; j < smallHeap.size(); j++) {
int parent = (j - 1) / 2; // 父节点元素位置
if (smallHeap.get(j) < smallHeap.get(parent)) { // 子节点元素值小于父节点元素值
int temp = smallHeap.get(j);
smallHeap.set(j, smallHeap.get(parent));
smallHeap.set(parent, temp);
}
}
System.out.println("堆 → " + smallHeap);
i += 1;
}
outputArray.add(smallHeap.get(0));
System.out.println("输出:" + outputArray);
}