前面的博文介紹了堆的實現堆的介紹,今天主要主要介紹索引堆,以及索引堆的優化。php
索引堆是對堆進行了優化。算法
在堆中,構建堆、插入、刪除過程都須要大量的交換操做。在以前的實現中,進行交換操做是直接交換datas數組中兩個元素。而索引堆交換的是這兩個元素的索引,而不是直接交換元素。數組
主要有兩個好處:微信
索引堆使用了一個新的int類型的數組,用於存放索引信息。部分代碼以下:dom
// 屬性
$data = array();// 存放數據的數組 datas[1..n]
$indexes = array(); // 索引數組
複製代碼
這裏這個indexes數組,存放的是什麼信息呢?它是如何工做的呢?假如咱們有這樣一個最小堆:函數
那麼用數組表示就是:性能
datas: [-, 1, 15, 20, 34, 7]
複製代碼
如今要維護最小堆的有序性,就須要交換15和7這兩個元素。交換以後的元素數組是:測試
datas: [-, 1, 7, 20, 34, 15]
複製代碼
而此時,咱們再想找到原來在datas[2]位置的元素,已經找不到了。由於此時data[2]已經換成了7,而系統並無記錄15被換到了什麼地方。優化
可不能夠既保持$data的原始特性(讀取O(1))想要獲得i位置的元素,直接datas[i]就能夠了, 也保持堆的特性。能夠的,使用索引堆。ui
使用索引堆後,初始化兩個數組應該是這樣的:
$datas: [-, 1, 15, 20, 34, 7]
$indexes: [-, 1, 2, 3, 4, 5]
複製代碼
這個時候,咱們就交換indexes數組裏面的索引2和5,而不操做datas數組。交換後兩個數組是這個樣子:
$datas: [-, 1, 15, 20, 34, 7]
$indexes: [-, 1, 5, 3, 4, 2]
複製代碼
這個時候,想要獲得i位置的元素,就須要indexes[i]]來獲取。
<?php
// require('../Library/SortTestHelper.php');
require('../SortingAdvance/QuickSort.php');
/** * 索引堆 */
class IndexMaxHeap{
private $data;
private $count;
private $indexes;
public function __construct(){
$this->data = array();
$this->indexes = array();
$this->count = 0;
}
// public function __construct($arr){
// }
public function insert($item){
//從1開始
$this->data[$this->count + 1] = $item;
$this->indexes[$this->count + 1] = $item;
$this->_shiftUp($this->count+1);
$this->count++;
}
public function extractMax(){
$ret = $this->data[$this->indexes[1]];
swap( $this->indexes, 1 , $this->count);
$this->count--;
$this->_shiftDown(1);
return $ret;
}
/** * [extractMaxIndex 讓外界感受從0開始] * @return [type] [description] */
public function extractMaxIndex(){
$ret = $this->indexes[1] - 1;
swap( $this->indexes, 1 , $this->count);
$this->count--;
$this->_shiftDown(1);
return $ret;
}
public function getMaxIndex(){
return $this->indexes[1] - 1;
}
public function getMax(){
return $this->data[1];
}
public function isEmpty(){
return $this->count == 0;
}
public function getData(){
return $this->data;
}
/** * [change 修改一個元素的值] * @param [type] $i [description] * @param [type] $newItem [description] * @return [type] [description] */
public function change( $i , $newItem ){
$i += 1;
$this->data[$i] = $newItem;
// 找到indexes[j] = i, j表示data[i]在堆中的位置
// 以後shiftUp(j), 再shiftDown(j)
for( $j = 1 ; $j <= $this->count ; $j ++ ){
if( $this->indexes[$j] == $i ){
shiftUp($j);
shiftDown($j);
return;
}
}
}
/** * [_shiftUp 新加入到堆中的元素直接放在數組後面,再與父元素比較後交換位置,直到根節點] * @param [type] $k [description] * @return [type] [description] */
private function _shiftUp($k){
//若是葉子節點的值比父元素大交換位置,並更新k的值
while( $k > 1 && $this->data[$this->indexes[(int)($k/2)]] < $this->data[$this->indexes[$k]] ){
// swap( $this->data[(int)($k/2)], $this->data[$k] );
swap( $this->indexes, (int)($k/2) , $k);
$k = (int)($k/2);
}
}
/** * [_shiftDown 元素出堆的時候,須要維護此時的堆依然是一個大根堆, 此時將數組元素的最後一個值與第一個值交換,後從上往下維護堆的性質] * @param [type] $k [description] * @return [type] [description] */
private function _shiftDown($k){
//2k表明該節點的左子節點
while( 2*$k <= $this->count ){
$j = 2*$k;
//判斷右節點是否存在,而且右節點大於左節點
if( $j+1 <= $this->count && $this->data[$this->indexes[$j+1]] > $this->data[$this->indexes[$j]] ) $j ++;
if( $this->data[$this->indexes[$k]] >= $this->data[$this->indexes[$j]] ) break;
// swap( $this->data[$k] , $this->data[$j] );
swap( $this->indexes, $k , $j );
$k = $j;
}
}
}
function heapSortUsingIndexMaxHeap($arr, $n){
$indexMaxHeap = new IndexMaxHeap();
for( $i = 0 ; $i < $n ; $i ++ ){
$indexMaxHeap -> insert($arr[$i] );
}
print("造成大根索引堆後, 從大大小輸出爲:\n");
for( $i = $n-1 ; $i >= 0 ; $i -- ){
// $arr[$i] = $indexMaxHeap -> extractMax();
$tmp = $indexMaxHeap -> extractMax();
print($tmp."\n");
}
}
$n = 10;
$arr = generateRandomArray($n, 0, $n);
print_r("生成的元素數組爲:\n");
print_r( $arr);
$arr = heapSortUsingIndexMaxHeap($arr, $n);
?>
複製代碼
生成的元素數組爲:
Array
(
[0] => 5
[1] => 7
[2] => 3
[3] => 2
[4] => 1
[5] => 6
[6] => 6
[7] => 3
[8] => 7
[9] => 9
)
造成大根索引堆後, 從大大小輸出爲:
7
7
6
6
6
6
5
3
3
1
複製代碼
接着上面的Case,咱們如今可以得到相似於這樣的數據:arr排序後,第2大的數
arr[indexes[1]]
而如今有這樣一個需求:我想知道原來arr數組中第i個位置,排好序後在哪一個位置。應該怎樣作?
常規的方法是遍歷indexes數組,像這樣:
for( $j = 1 ; $j <= $this->count ; $j ++ ){
if( $this->indexes[$j] == $i ){
shiftUp($j);
shiftDown($j);
return;
}
}
複製代碼
這個複雜度最差爲O(N);
那麼有沒有什麼方法能夠提升性能呢?
有,那就是再一用一個數組reverses,做爲反向索引。反向索引存放的數據通俗來說就是這樣:
reverses[i] == j
indexes[j] == i
複製代碼
進而推導出:
reverses[indexes[i]] = i;
indexes[reverses[i]] = i;
複製代碼
看這個例子:
indexes[1] = 10; 而reverses[1]存儲的是在indexes數組中值爲10的索引1在indexes中的位置,它的值爲8,有 reverses[1] = 8;表明index數組中第8個
雖然使用反向索引提升了某些時候的查詢效率,但會使得程序變得更加複雜。由於在插入和刪除時都要維護這個數組。
核心思想是:無論任何操做,都要維護indexes數組和reverse數組的性質。
像操做系統的進程管理:每次都使用堆找到優先級最高的進程執行,若是來了新的進程只須要將其插入堆中,若是須要更改進行的優先級,只須要使用change函數進行更改
能夠將須要攻擊的敵人放入堆中,使用堆選擇最須要攻擊的敵人。若是有新的敵人進入則插入堆。
在100萬個元素中選出前100名(在N個元素中選出前M個元素)
多路歸併排序
* merge的時候,將各個分割的字塊的第一個元素造成一個最小堆,每次取堆頂元素進行merge
* 若是n個元素進行n路歸併,其實歸併算法就成了,堆排序算法。
複製代碼
-------------------------華麗的分割線--------------------
看完的朋友能夠點個喜歡/關注,您的支持是對我最大的鼓勵。
想了解更多,歡迎關注個人微信公衆號:番茄技術小棧