堆的四種操作+堆排序

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();  
    } 
   
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章