(Java資料結構和演算法)堆---優先佇列、堆排序
阿新 • • 發佈:2018-12-01
堆主要用於實現優先佇列。
利用有序陣列可以實現優先佇列(從小到大或從大到小的陣列),刪除的時間複雜度是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
(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();
}
}
堆排序實現
思路:既然上述大頂堆最大的數永遠在堆的頂部(樹根),那就不斷刪除堆頂,直到樹空,刪除的這個序列就是從大到小排序的!
當然,使用大頂堆也可以實現從小到大排序,只需要把刪除的序列逆序放在數組裡就可以了。