1.堆排序實現的基石
1.完全二叉樹的性質
- 完全二叉樹各個結點編號之間的對應關係(如圖)
***補充:完全二叉樹的第一個非葉子節點:
int i = (heapSize >> 1) - 1;heaoSize爲總結點個數 ,也就是數組總元素個數
2.完全二叉樹的性質保證了層序遍歷時,各個結點編號是連續的。切一個節點如果沒有左子樹,則必不可能有右子樹。
2.數組的特性
- 對於數組我們只要知道了邊界範圍,就可以隨心所欲的隨機訪問。
- 數組元素的下標剛好可以對應於完全二叉樹節點編號的計算。
- 在對完全二叉樹層序遍歷時,走過的節點編號,剛好是連續的。二者相互對應。(如圖)
2.堆排序實現的步驟
補充
如果任意節點的值總是 ≥ 子節點的值,稱爲:最大堆、大根堆、大頂堆
如果任意節點的值總是 ≤ 子節點的值,稱爲:最小堆、小根堆、小頂堆`
1.原地構建大頂堆或小頂堆(大頂小頂只和之後的插入方向有關)
**1.根據完全二叉樹的性質 ,我選擇自下而上進行下濾從而構建大頂堆**
1.什麼是下濾? |
---|
下濾就是拿到某個節點之後,觀察他的子節點,若子節點都不大於該節點,則不作操作,若子節點中有大於該節點的子節點,則該節點和最大的那個子節點交換位置。交換完成之後,在觀看交換之後的該節點與他的新的子節點。重複上述步驟。知道子節點都不大於該節點 |
2.爲什麼要下濾? |
---|
下濾是爲了維護大頂堆(或者是小頂堆,但是如果是小頂堆的話就需要改變下濾條件)的性質。暨爲了保證每一個父節點都大於他的所有子節點 |
3.什麼是自下而上的下濾? |
---|
見名知意 。就是從樹梢往樹根走,逐一下濾。也可以理解爲從數組後面往前面逐一下濾。但是這個下濾的起點並不是最後一個節點或者數組末元素。而是根據完全二叉樹的性質計算出來的第一個非葉子節點的位置開始。 暨nt i = (heapSize >> 1) - 1; |
4.這樣做有啥好處? |
---|
由於是下濾 ,所以從int i = (heapSize >> 1) - 1;開始的話 會根據i計算出他的左右子節點的下標。這樣剛好就能覆蓋到整個樹(也可以說是完全覆蓋到整個數組) ,如果i的子節點有違反大頂堆的性質,就會發生下濾。從int i = (heapSize >> 1) - 1;開始。會減少一半的比較次數(這個減少是相對自上而下的上濾 和從最後開始的盲目下濾而言),這樣會對性能進行優化, |
2.對已經構建 好的大頂堆進行相關操作
1. 交換堆頭與隊尾的元 素 | 這一步其實是將最大的元素放到數組的最後(因爲大頂堆的性質決定了堆頂一定是最大的——因爲父節點一定大於子節點)。 |
2.對堆頂元素精心下濾 | 這一步是爲了維護大頂堆的性質,確保堆頂一定是最大的。保證了相對大小(相對於已經出堆的,他是最小的,相對於還未出堆的 他是最大的) |
3.沒進行一次上述操作,要確保堆得大小-1 | 因爲已經出堆了的元素一定大於未出堆的,你如果不減小的話,在下一次下濾的過程中,本來在數組末尾的元素,因爲過大,又會被濾到數組堆頭 |
4.重複上述操作直到堆內爲空 |
2.實現的代碼
1.c(我愛c語言)這個就很不規範
#include<stdio.h>
#include<Windows.h>
#include <stdlib.h>
#pragma warning (disable:4996)
#define A 50
int * random_num()
{
srand((unsigned)time(NULL));
int * a = (int *)malloc(sizeof(int) * A);
if (!a)return NULL;
else
{
for (int i = 0; i < A; i++)
{
a[i] = rand() % 100;
}
}
return a;
}
void siftDown(int i,int * a,int heapsize)
{
int x = a[i];
while (i<heapsize/2)
{
int childIndex = i*2+1;
int L_C_Index = i * 2 + 1;
int R_C_Index = L_C_Index + 1;
int child = a[L_C_Index];
if (R_C_Index<heapsize&&child<a[R_C_Index])
{
child = a[childIndex=R_C_Index];
}
if (x >= child)break;
a[i] = child;
i = childIndex;
}
a[i] = x;
}
void main(){
int * array = random_num() != NULL ? random_num():NULL;
int ab[A] = { 0 };
for (int i = 0; i < A; i++)
{
ab[i] = array[i];
}
for (int i = A / 2 - 1; i >= 0; i--)
{
siftDown(i,ab,A);
}
int z = A;
while (z > 1)
{
int tmp = ab[z - 1];
ab[z - 1] = ab[0];
ab[0] = tmp;
siftDown(0, ab, --z);
}
system("pause");
}
2.java
public class HeapSort<T extends Comparable<T>> extends Sort<T> {
private int heapSize;
@Override
protected void sort() {
// 原地建堆
heapSize = array.length;
for (int i = (heapSize >> 1) - 1; i >= 0; i--) {
siftDown(i);
}
while (heapSize > 1) {
// 交換堆頂元素和尾部元素
swap(0, --heapSize);
// 對0位置進行siftDown(恢復堆的性質)
siftDown(0);
}
}
private void siftDown(int index) {
T element = array[index];
int half = heapSize >> 1;
while (index < half) { // index必須是非葉子節點
// 默認是左邊跟父節點比
int childIndex = (index << 1) + 1;
T child = array[childIndex];
int rightIndex = childIndex + 1;
// 右子節點比左子節點大
if (rightIndex < heapSize &&
cmp(array[rightIndex], child) > 0) {
child = array[childIndex = rightIndex];
}
// 大於等於子節點
if (cmp(element, child) >= 0) break;
array[index] = child;
index = childIndex;
}
array[index] = element;
}
}
4.拓展
我個人感覺大頂堆對選擇排序的優化作用很大。首先是降低了複雜度,其次他是一種原地算法
大頂堆和小頂堆還可以用來處理一組數據中找出最小或最大的多少個數。這種相較於全排序來說大大降低了複雜度。