(排序四)堆排序詳解

1)完全二叉樹;父節點i,子節點對應2*i+1,2*i+2

在一個數組中,可以腦補一個完全二叉樹

2)堆:

1.大根堆:在一個完全二叉樹中,任何一個字樹的最大值都是這顆子樹的頭部(根節點永遠是所在子樹中最大的)

2.建立大根堆:給你一個數組,建立大根堆
遍歷數組,每遍歷一個元素a[i]保證0-i範圍上是大根堆,在遍歷到i+1時,保證0-i+1範圍上是大根堆。(完全二叉樹上每增加一個元素進行調整,保證形成大根堆)直到for循環完畢,數組遍歷完畢

2.調整過程(上浮):構建堆 

進一個數a[i],跟父位置比較,如果大於父位置,交換,然後在新的位置上繼續比較,如果大於新位置上的父位置,交換,直到小於當前父位置停止。
建立一個大根堆的複雜度:每加進一個數,最多swap(logn)次—(當前形成的二叉樹高度爲logn)所以數組中所有數字加進來複雜度是O(N*logN)

第一種方式構建堆代碼_上浮代碼:

for (int i = 0; i < arr.length; i++) {
			heapInsert(arr, i);
		}
public static void heapInsert(int[] arr, int index) {
		while (arr[index] > arr[(index - 1) / 2]) {
			swap(arr, index, (index - 1) / 2);
			index = (index - 1) / 2;
		}
	}

   優化:下沉構建堆

存在構建堆的過程爲O(N)的方法(默認爲此數組已經是一個大根堆,從倒數第二排最右邊的數開始檢驗,是否符合大根堆,不符合的話,進行下沉操作)依次按排檢驗,直到堆頂檢驗完畢,此構建堆的過程是O(n)   

證明:每一層開始檢驗(1*n/4+2*n/8+3*n/16+...+(log2N - 1)*n/(2^ log2N)

 

 

時間複雜度爲O(N)

注意:

第一種入棧,每進入一個數都可能需要上浮log2i次,累計上浮最多次數(2*1+4*2+8*3+16*4+...+n/2*(log2N-1))當二叉樹層數越多的時候,越多的葉子節點個數乘以層數。O(當前層節點個數*距頂距離)求和

第二種入棧:(下沉)從底部開始,向上遍歷,O(當前層節點個數*距底部距離)求和 ,遍歷過程中,離底部變遠,但是子節點數量減少

 

調整過程(下沉)

3.下沉

當此大根堆中有一個數字變小了,怎麼恢復大根堆呢:
這個變小的數字的位置中,找他的兩個孩子,左右孩子進行比計較,找到最大值然後與他比較,子節點>根節點時,與相應子節點交換(下沉一個位置),在新的位置繼續比較,根節點>兩個字節點。

public static void heapify(int[] arr, int index, int size) {
		int left = index * 2 + 1;
		while (left < size) {//左孩子沒有越界(如果左孩子越界,那麼index位置已經是葉節點不許要下沉)
			int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
			largest = arr[largest] > arr[index] ? largest : index;
			if (largest == index) {
				break;
			}
			swap(arr, largest, index);
			index = largest;
			left = index * 2 + 1;
		}
	}

注意:堆的大小在數組中是可以伸縮的,和heapsize表示堆的大小(在下沉時,注意不要訪問到沒入堆的數字,(沒入堆的數字也一樣在數組中,此時堆之形成到了i,i後面的元素仍在數組中,只是沒有還調整成大根堆)

4.減堆操作:彈出堆頂再變成大根堆

堆得最後一個數與堆頂進行交換,heapSize--,然後新的堆頂進行下沉操作

堆排序:

先遍歷數組,使整個數組變成大根堆,然後依次進行減堆操作,(彈出堆頂,重調成大根堆)時間複雜度:O(NlogN)重建堆的過程。額外空間複雜度O(1)

public static void heapSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		for (int i = 0; i < arr.length; i++) {
			heapInsert(arr, i);
		}
		int size = arr.length;
		swap(arr, 0, --size);
		while (size > 0) {
			heapify(arr, 0, size);
			swap(arr, 0, --size);
		}
	}
public static void heapInsert(int[] arr, int index) {
		while (arr[index] > arr[(index - 1) / 2]) {
			swap(arr, index, (index - 1) / 2);
			index = (index - 1) / 2;
		}
	}
public static void heapify(int[] arr, int index, int size) {
		int left = index * 2 + 1;
		while (left < size) {//左孩子沒有越界(如果左孩子越界,那麼index位置已經是葉節點不許要下沉)
			int largest = left + 1 < size && arr[left + 1] > arr[left] ? left + 1 : left;
			largest = arr[largest] > arr[index] ? largest : index;
			if (largest == index) {
				break;
			}
			swap(arr, largest, index);
			index = largest;
			left = index * 2 + 1;
		}
	}
public static void swap(int[] arr, int i, int j) {
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}

 

 

 

     

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