總結(jié)
再講堆和堆排序之前先講優(yōu)先隊列
優(yōu)先隊列
- 優(yōu)先隊列是什么:
與常見的隊列不同的是,優(yōu)先隊列并不遵循“先進先出”的原則,反而是根據(jù)優(yōu)先級來確定是否先出。優(yōu)先級高的先出,優(yōu)先級低的后出。 - 為什么使用優(yōu)先隊列:
優(yōu)先隊列是用在,處理動態(tài)的數(shù)據(jù)排序。也就是優(yōu)先隊列所處理的數(shù)據(jù)可能時刻在變化的。
例子:醫(yī)院排隊,病情緊急的病人,先去治療。該情景中:1.病人是可能時刻在增加的,也就是動態(tài)的。其次,是按優(yōu)先級進行進出的
優(yōu)先隊列的實現(xiàn)
預(yù)先知識
為什么使用堆
和所有的隊列一樣,隊列包含兩種操作:1.入隊,2.出隊
使用普通數(shù)組或者順序數(shù)組也可以達到優(yōu)先隊列的操作,但是使用堆的效率最高
也許你的表情是
大佬息怒先看這個表(優(yōu)先級最高的數(shù)據(jù)進行操作的算法復(fù)雜度)
入隊 | 出隊 | |
---|---|---|
普通數(shù)組 | O(1) | O(n) |
順序數(shù)組 | O(n) | O(1) |
堆 | O(lgn) | O(lgn) |
從上表可以得到,用堆的算法復(fù)雜度是最低的。
在最差的情況下,使用數(shù)組是O(n^2),但是使用堆O(nlgn)
現(xiàn)在是不是應(yīng)該
堆的基礎(chǔ)知識
我們實現(xiàn)的方式是使用二叉堆(完全二叉樹)
完全二叉樹
特點:
- 每個根節(jié)點最多只有兩個子節(jié)點
- 子節(jié)點小于根節(jié)點
- 除去最后一層,其他層的節(jié)點必須為最大值
- 最后一層可以不是最大值,但它必須在二叉樹的最左邊
使用數(shù)組來完成一個堆
數(shù)組堆
由圖可以的到如下性質(zhì):
- 左子節(jié)點的索引是根節(jié)點的兩倍
- 右子節(jié)點的索引是根節(jié)點的兩倍加一
因此我們可以得到的公式:
- 得到當(dāng)前索引i的父節(jié)點是
i/2
- 得到當(dāng)前索引的左子節(jié)點是
i*2
- 得到當(dāng)前索引的右子節(jié)點是
i*2+1
- 得到當(dāng)前堆最后一個帶有子節(jié)點的父節(jié)點索引是
conut/2
其中conut
為堆的長度
常用操作
插入一個新元素在堆的最后并排序shiftUp
操作
shiftUp
如圖我們在最大堆中插入一個新元素30,那么它先與其父節(jié)點(父節(jié)點位置公式在上面)進行比較,也就是圖中的16,30明顯大于16,進行交換位置。新元素繼續(xù)和其父節(jié)點也就是41,進行比較大小。很顯然,小于41,那么shiftUp操作結(jié)束
在父節(jié)點中存在一個小于子節(jié)點的數(shù),要進行的操作shiftDown
shiftDown
如圖,一共為兩個步驟
- 左右子節(jié)點相互比較,選出最大值
- 與父節(jié)點進行比較,如果大于父節(jié)點進行替換
優(yōu)先隊列的代碼實現(xiàn)(使用堆的方式)
- 滿足一個數(shù)據(jù)結(jié)構(gòu)
public class MaxHeap<Item extends Comparable> {
protected Item[] datas;
protected int count; //元素的個數(shù)
private int capacity; //預(yù)先申請空間大小
public MaxHeap(int capacity) {
this.capacity = capacity;
datas= (Item[]) new Comparable[capacity+1];//加一的原因是
count=0;
}
public int size(){
return count;
}
public boolean isEmpty(){
return count == 0;
}
}
- 常用操作
shiftUp
和shiftDown
實現(xiàn)(具體操作描述在上面已經(jīng)講述)
private void shiftUp(int k) {
while (k>1 && datas[k/2].compareTo(datas[k])<0){ //與它的父節(jié)點進行比較
swap(k/2,k); //交換位置
k/=2;
}
}
private void shiftDown(int k){
while (2*k<=count){
int j=2*k;
if (j+1<=count && datas[j+1].compareTo(datas[j])>0)//兩個子節(jié)點比較
j++;
if (datas[k].compareTo(datas[j])>=0)//父節(jié)點與較大子節(jié)點比較
break;
swap(k,j);
k=j;
}
}
- 插入一個元素(相當(dāng)于增加一個索引并執(zhí)行
shiftUp
操作)
public void insert(Item item){
assert count + 1<=capacity;
datas[count+1]=item;
count++;
shiftUp(count);
}
- 出隊最大的元素(相當(dāng)于將最大元素與第
conut
(conut
為堆的長度)個元素進行交換,然后,進行shiftDown
操作)
public Item extractMax(){
assert count>0;
Item ret=datas[1];
swap(1,count);
count--;
shiftDown(1);
return ret;
}
所有代碼
public class MaxHeap<Item extends Comparable> {
protected Item[] datas;
protected int count;
private int capacity;
public MaxHeap(int capacity) {
this.capacity = capacity;
datas= (Item[]) new Comparable[capacity+1];
count=0;
}
public MaxHeap(Item[] arr,int n){
datas= (Item[]) new Comparable[n];
capacity=n;
for (int i = 0; i < arr.length ; i++)
datas[i+1]=arr[i];
count=n;
for (int i = count/2; i >=1; i--)
shiftDown(i);
}
public int size(){
return count;
}
public boolean isEmpty(){
return count == 0;
}
public void insert(Item item){
assert count + 1<=capacity;
datas[count+1]=item;
count++;
shiftUp(count);
}
public Item extractMax(){
assert count>0;
Item ret=datas[1];
swap(1,count);
count--;
shiftDown(1);
return ret;
}
private void shiftUp(int k) {
while (k>1 && datas[k/2].compareTo(datas[k])<0){
swap(k/2,k);
k/=2;
}
}
private void shiftDown(int k){
while (2*k<=count){
int j=2*k;
if (j+1<=count && datas[j+1].compareTo(datas[j])>0)
j++;
if (datas[k].compareTo(datas[j])>=0)
break;
swap(k,j);
k=j;
}
}
private void swap(int i, int count) {
Item t=datas[i];
datas[i]=datas[count];
datas[count]=t;
}
}
優(yōu)先隊列的優(yōu)化
起因:上例實現(xiàn)的優(yōu)先隊列,顯然需要創(chuàng)建新的空間。
解決方法:(總體使用數(shù)組的方式來解決)
-
當(dāng)前堆的最大值與數(shù)組最后一個值進行交換
-
交換后,除最后一個元素其他的元素進行
ShiftDown
操作,保持前面部分依舊是一個最大堆
-
再重復(fù)所有步驟第一步操作
全部代碼(
ShiftDown
函數(shù)與上次的例子不同的原因是因為索引值是從0開始,而不是從1開始)
public static void LocalMaxHeapFunc(Comparable[] arr){
//1.進行一次Heapify的過程
int n=arr.length;
//注意:其中我們的堆是從零開始索引的
//從(最后一個元素的索引-1)/2開始
//其中最后一個索引是n-1
for (int i = (n-1-1)/2; i >=0 ; i--) {
shiftDown(arr,n,i);
}
for (int i = n-1; i > 0; i--) {
Utils.swap2(arr,0,i);
shiftDown(arr,i,0);
}
}
private static void shiftDown(Comparable[] datas,int count,int k) {
while ((2*k+1)<count){
int j=2*k+1;
if (j+1< count && datas[j+1].compareTo(datas[j])>0)
j++;
if (datas[k].compareTo(datas[j])>=0)
break;
Utils.swap2(datas,k,j);
k=j;
}
}