1. 程式人生 > >(Java資料結構和演算法)堆---優先佇列、堆排序

(Java資料結構和演算法)堆---優先佇列、堆排序

堆主要用於實現優先佇列。

利用有序陣列可以實現優先佇列(從小到大或從大到小的陣列),刪除的時間複雜度是O(1),但是插入的時間複雜度是O(N)。用堆實現優先佇列,插入和刪除的時間複雜度都是O(logN)。

簡介

堆是一種完全二叉樹,且每個節點的值都大於等於子節點值(大根堆)。堆的子樹也是完全二叉樹。注意是用陣列來存放堆
在這裡插入圖片描述

補充下完全二叉樹的規律

(1)n2+1 = n0

設子節點為0、1、2的節點數各是n0,n1,n2,總節點數是n,那麼n = n0 + n1 + n2,同時對一棵樹從上往下看,(假定不是空樹)根節點有1個,依次往下走,有n = 1 + n00 + n1

1 + n2*2。上面兩個等式可以推得:n2+1 = n0,證明得到子節點數是0的節點個數比子節點數是2的節點個數多1個。

(2)n = 2*n0 + n1 -1

這個式子從(1)的兩個式子很容易推匯出來。注意完全二叉樹的子節點數是1的節點數目要麼只有1個要麼有0個,即n1的取值是0或1。這就可以把n1消掉,同時,還可以根據n的奇偶性,逼出n1取值到底是0還是1。

(3) 非葉子數是n/2(商)—在後面堆實現的時候trickleDown函式會用到

注意分析這個式子,當n是奇數的時候,n1必定是0,此時n0=(n+1)/2(葉子節點數),當n是偶數的時候,n1必定是1,此時n0=n/2。舉個例子,比如n=8的時候,葉子節點數是n0=8/2=4,n=7的時候,葉子節點數是n0=(7+1)/2=4。這裡有個規律是,無論n是奇數或偶數,不變的是非葉子節點數是n/2(商)。

移除

在這裡插入圖片描述

插入

在這裡插入圖片描述

堆實現

class Node{
	private int e;
	public Node(int e){
		this.e = e;
	}
	public int getE(){
		return this.e;
	}
	public void setE(int e){
		this.e = e;
	}
}

class Heap{
	private Node[] heapArray;
	private int maxSize;//容量
	private int size;//當前大小
	
	public Heap(int maxSize){
		this.maxSize =
maxSize; size = 0; heapArray = new Node[maxSize]; } public boolean isEmpty(){ return this.size == 0; } public boolean insert(int e){ if(size >= maxSize){ return false; } Node newNode = new Node(e); heapArray[size] = newNode; trickleUp(size); size++; return true; } //插入新節點的時,先把新節點插入最後,再向上調整,下面是向上調整的函式 public void trickleUp(int index){ int parent = (index - 1)/2; Node bottom = heapArray[index];//儲存一份新插入的節點 while(index > 0 && bottom.getE() > heapArray[parent].getE()){ heapArray[index] = heapArray[parent];//孩子比父親大,把父親給孩子,往下面拉,騰出位置 index = parent;//繼續往上找 parent = (parent-1)/2; } heapArray[index] = bottom; } public Node remove(){ if(0 == size){ return null; } Node root = heapArray[0]; heapArray[0] = heapArray[--size]; trickleDown(0);//下移 return root; } public void trickleDown(int index){ int largerChild; Node top = heapArray[index]; while(index < size/2){//仍存在非葉子節點的時候,一個有n個節點的完全二叉樹的非葉子節點樹是size/2(商) int leftChild = 2*index + 1; int rightChild = leftChild + 1; //從左右兩個孩子中找一個較大的孩子,注意有孩子未必存在,需要判定 if(rightChild < size && heapArray[leftChild].getE() < heapArray[rightChild].getE() ){ largerChild = rightChild; }else{ largerChild = leftChild; } if(top.getE() >= heapArray[largerChild].getE()){ break; } heapArray[index] = heapArray[largerChild];//把比父母大的孩子上移到父母處 index = largerChild; } heapArray[index] = top; } public boolean change(int index, int newE){ if(index < 0 || index >= size){ return false; } int oldE = heapArray[index].getE(); heapArray[index].setE(newE); if(newE > oldE){ trickleUp(index); }else{ trickleDown(index); } return true; } public void print(){ for(int i = 0; i < size; i++){ System.out.print(heapArray[i].getE()+" "); } System.out.println(); } } public class Main { public static void main(String[] args){ Heap heap = new Heap(10); int[] a = {4,1,3,6,9,7}; for(int i = 0; i < a.length; i++){ heap.insert(a[i]); } heap.print(); System.out.println(heap.remove().getE()+" removed."); heap.print(); heap.change(2, 66); heap.print(); } }

在這裡插入圖片描述

堆排序實現

思路:既然上述大頂堆最大的數永遠在堆的頂部(樹根),那就不斷刪除堆頂,直到樹空,刪除的這個序列就是從大到小排序的!

當然,使用大頂堆也可以實現從小到大排序,只需要把刪除的序列逆序放在數組裡就可以了。