堆及堆排序

什麼是堆?
堆是一棵完全二叉樹,並滿足
n o d e i > = n o d e 2 i + 1 a n d n o d e i > = n o d e 2 i + 2 {node{_i}>=node_{2*i+1} \quad and \quad node_i>=node_{2*i+2}} (大頂堆)
或者
n o d e i < = n o d e 2 i + 1 a n d n o d e i < = n o d e 2 i + 2 {node{_i}<=node_{2*i+1} \quad and \quad node_i<=node_{2*i+2}} (小頂堆)
堆
數組存儲

堆可以做什麼?
可以用來排序,可以用來找到第k大元素

  • 構建堆(以大頂堆爲例)
  1. 整個數據在數組上進行,找到最後一個非葉節點
  2. 將該非葉節點和左右孩子比較,若小於孩子則和孩子交換
  3. 然後從該節點開始遞歸檢查該子樹是否滿足大頂堆,若不滿足則進行交換,直到葉子
  4. 接着從上一個非葉節點檢查,重複1、2
  5. 直到根節點,整棵樹滿足大頂堆
//另注意golang有原生的容器heap
//import "container/heap"
package main
import "fmt"

//加入,加入元素到最後,然後上浮操作
func add(nums[]int,val int)[]int{
	nums=append(nums,val)
	for i:=len(nums)-1;i>0;i--{
		if nums[i]>nums[(i-1)/2]{
			tmp:=nums[i]
			nums[i]=nums[(i-1)/2]
			nums[(i-1)/2]=tmp
		}
	}
	return nums
}
func del(nums[]int)int{ //刪除,將根和最後的元素交換,然後下沉操作
	s:=len(nums)-1
	max:=nums[0];nums[0]=nums[s];nums[s]=max
	for i:=0;i<s;{
		l:=2*i+1;r:=2*i+2;k:=l
		if l<s&&r<s&&nums[l]<nums[r]{
			k=r
		}
		if k<s&&nums[k]>nums[i]{
			tmp:=nums[i];nums[i]=nums[k];nums[k]=tmp
			i=k
		}else{
			break
		}
	}
	return max
}
func main(){
	nums:=[]int{4,6,8,5,9}
	var bigHead []int
	for i:=0;i<len(nums);i++{
		bigHead=add(bigHead,nums[i])
	}
	fmt.Println(bigHead)
	fmt.Println(del(bigHead))
	fmt.Println(bigHead)
	//去除最後一個
	bigHead=bigHead[:len(bigHead)-1]
	fmt.Println(del(bigHead))
	fmt.Println(bigHead)

}
  • 堆排序
  1. 在堆的基礎上,交換根和最後一個節點
  2. 在除最後一個節點的節點上,重新調整堆
  3. 重複0、1,知道堆只剩下根,這是整個數組就都是有序了(大頂堆的操作結果未非遞減,小頂堆的操作結果是非遞增)
  • 參考
  1. 維基百科-堆
  2. 圖解排序算法(三)之堆排序
//以下是按堆的模式寫的程序
//最終是堆,但太浪費了,堆只需要添加和刪除兩個操作
//並且,以爲已經有序,會保持堆的特性,而無序遞歸檢查

package main
import "fmt"
//遞歸監測,交換到底
func change(nums []int,i,j int){
	s:=j
	if i>=s{
		return
	}
	l:=2*i+1
	if l<s{
	  if nums[l]>nums[i] {
		  tmp := nums[l]
		  nums[l] = nums[i]
		  nums[i] = tmp
	  }
	  change(nums, l,j)
	}
	r:=2*i+2
	if r<s{
		if nums[r]>nums[i] {
			tmp := nums[r]
			nums[r] = nums[i]
			nums[i] = tmp
		}
		change(nums, r,j)
	}
}
//從最後一個非葉節點,換到頂
func build(nums []int)[]int{
	s:=len(nums)
	for i:=s/2-1;i>=0;i--{
		//對於非葉節點,要遍歷到葉子,檢查是否滿足
		change(nums,i,s)
	}
	return nums
}
//堆排序
func findAllMax(nums[]int)[]int{
	s:=len(nums)
	for i:=s-1;i>0;i--{
		tmp:=nums[i]
		nums[i]=nums[0]
		nums[0]=tmp
		change(nums,0,i)
	}
	return nums
}
func main(){
	bigHead:= build([]int{4,6,8,5,9})
	fmt.Println(bigHead)
	fmt.Println(findAllMax(bigHead))
}
/* [9 6 8 4 5] [4 5 6 8 9] */