1,定義+圖解
直接推薦一篇講解比較好的博客吧:徹底弄懂最大堆的四種操作(圖解+程序)(JAVA)
堆排序算法分析:
- 時間複雜度:平均情況 O(nlogn);最好情況O(nlogn);最壞情況O(nlogn);
- 空間複雜度:O(1)。
- 穩定性:不穩定。
2,堆的插入、刪除、初始化、堆排序源碼
package manduner;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author Manduner_TJU
* @version 創建時間:2019年4月9日下午2:47:46
*/
public class 堆 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>(Arrays.asList(null,45,36,18,53,72,30,48,93,15,35));
//初始化最大堆
// System.out.println("構造最大堆如下:");
// List<Integer> heap1 = initialHeap1(list);
// print(heap1);
// System.out.println("排序後如下:");
// heapSort(heap1);
// print(heap1);
//
// System.out.println("刪除index位置上的元素:");
// delete(heap1,3);
// System.out.println("刪除操作後的堆:");
// print(heap1);
// System.out.println("刪除操作後進行排序的堆:");
// heapSort(heap1);
// print(heap1);
initialHeap2(list);
print(list);
// initialHeap3(list);
// print(list);
heapSort(list);
System.out.println("排序後如下:");
print(list);
}
//1,最大堆插入操作
public static void insert(List<Integer> heap, int value) {
//在數組的尾部添加
if(heap.size()==0) heap.add(0);//數組下標爲0的位置不放元素
heap.add(value);
//開始上升操作
heapUp(heap,heap.size()-1);
}
//1.1,上升操作,讓插入的數和父節點的數值比較,當大於父節點的時候就和父節點的值交換
public static void heapUp(List<Integer> heap, int index) {
//注意,由於數值是從下標爲1開始,當index=1的時候,已經是根節點了
if(index>1) {
//求出父節點索引值
int parent = index/2;
//獲取相應位置的數值
int parentValue = heap.get(parent);
int indexValue = heap.get(index);
//如果父親節點比index的數值小,就交換二者的數值
if(parentValue < indexValue) {
//交換數值
swap(heap,parent,index);
//遞歸調用
heapUp(heap,parent);
}
}
}
//1.2,交換操作:把堆中的a,b位置的值互換
public static void swap(List<Integer> heap, int a, int b) {
int temp = heap.get(a);
heap.set(a, heap.get(b));
heap.set(b, temp);
}
//2,最大堆的刪除操作
/**
* 刪除堆中位置是index處的節點
* 操作原理是:當刪除節點的數值時,原來的位置就會出現一個孔
* 填充這個孔的方法就是,把最後的葉子的值賦給該孔,然後進行下沉操作(特殊情況需要上浮操作),最後把該葉子刪除
* 特殊情況: 刪除堆10,9,3,8,5,1,2,7,6中的1節點的時候需要進行上浮操作
* 只允許刪除堆頂元素的時候,只需要執行下沉操作就行,不需要考慮特殊情況
* @param heap
*/
public static void delete(List<Integer> heap, int index) {
if(index>heap.size()-1) return;
//把最後的一個葉子節點的數值複製給index位置
heap.set(index, heap.get(heap.size()-1));
//如果刪除的不是堆頂元素,需要判斷是否需要執行上浮操作
if(index > 1 && heap.get(index) > heap.get(index/2)) {
heapUp(heap,index);
}else {
//下沉操作
//heapDown(heap,index);
heapDown2(heap,index,heap.size()-1);
}
//把最後一個位置的數字刪除
heap.remove(heap.size()-1);
}
//2.1, 下沉操作
/**
* 遞歸實現
* 刪除堆中一個數據的時候,根據堆的性質,應該把相應的位置下移,才能保持住堆性質不變。
* @param heap 保持堆元素的數組
* @param index 待下沉的節點位置或者被刪除的那個節點的位置
*/
public static void heapDown(List<Integer> heap, int index) {
int n = heap.size()-1;
//記錄最大的那個兒子節點的位置
int child=-1;
//2*index>n說明該節點沒有左右兒子節點了,那麼就返回
if(2*index > n) {
return;
}//如果左右兒子都存在(2*index+1<n時,2*index一定是該節點的左孩子)
else if(2*index+1 < n) {
//定義左兒子節點
child = 2*index;
//如果左兒子小於右兒子的數值,取右兒子的下標
if(heap.get(child) < heap.get(child+1)) child++;
}//如果只有一個兒子(左兒子節點)
else if(2*index==n) {
child = 2*index;
}
if(heap.get(child) > heap.get(index)) {
//交換推中的child和index位置的值
swap(heap,child,index);
//完成交換後遞歸調用,繼續下降
heapDown(heap,child);
}
}
//2.1.2 非遞歸下沉方法(可以處理部分節點的辦法,應用於堆排序),很巧妙(其實還是遞歸的思想:將一個節點一直下沉到無法下沉爲止)
public static void heapDown2(List<Integer> heap, int i, int n) {
int child;
while(i<=n/2) {
child = i*2;
//使child指向值較大的孩子
if(child+1<=n && heap.get(child)<heap.get(child+1)) {
child+=1;
}
if(heap.get(i)<heap.get(child)) {
swap(heap,i,child);
//交換後,以child爲根的子樹不一定滿足堆定義,所以從child處開始調整
i =child;
}else break;
}
}
//3, 初始化操作(根據給定序列,構造最大堆的過程)
/**方法1:插入法:
*從空堆開始,依次插入每一個結點,直到所有的結點全部插入到堆爲止。
*時間:O(n*log(n))
*/
public static List<Integer> initialHeap1(List<Integer> list) {
List<Integer> heap = new ArrayList<Integer>();
if(list.isEmpty()) return heap;
for(int i=1; i<list.size();i++) {
insert(heap,list.get(i));
}
return heap;
}
/**方法2:下沉法:
*序列對應一個完全二叉樹;從最後一個分支結點(n/2)開始,到根(1)爲止,依次對每個分支結點進行調整(下沉),
*以便形成以每個分支結點爲根的堆,當最後對樹根結點進行調整後,整個樹就變成了一個堆。
*時間:O(n)
*/
public static void initialHeap2(List<Integer> heap) {
//根據樹的性質建堆,樹節點前一半一定是分支節點,即有孩子的,所以我們從這裏開始調整出初始堆
if(heap.isEmpty()) return;
for(int i=heap.size()/2;i>0;i--) {
heapDown(heap,i);
}
}
/**方法3:調整法:
*序列對應一個完全二叉樹;從最後一個分支結點(n/2)開始,到根(1)爲止,依次對每個分支結點進行調整(下沉),
*以便形成以每個分支結點爲根的堆,當最後對樹根結點進行調整後,整個樹就變成了一個堆。
*時間:O(n)
*/
public static void initialHeap3(List<Integer> heap) {
for(int i=heap.size()/2;i>0;i--) {
heapDown2(heap,i,heap.size()-1);
}
}
//4, 堆排序
/**
* (1)將給定序列初始化爲一個最大堆
* (2)把根節點跟最後一個元素交換位置,調整剩下的n-1個節點,即可排好序
*/
//假設heapSort的輸入heap已經是一個最大堆
public static void heapSort(List<Integer> heap) {
for(int i=heap.size()-1; i>0; i--) {
swap(heap,1,i);
heapDown2(heap,1,i-1);
}
}
//打印鏈表
public static void print(List<Integer> list) {
for (int i = 1; i < list.size(); i++) {
System.out.print(list.get(i) + " ");
}
System.out.println();
}
}