算法與數據結構之索引堆

前面的博文介紹了堆的實現堆的介紹,今天主要主要介紹索引堆,以及索引堆的優化。php

何爲索引堆?

索引堆是對堆進行了優化。算法

優化了什麼?

在堆中,構建堆、插入、刪除過程都須要大量的交換操做。在以前的實現中,進行交換操做是直接交換datas數組中兩個元素。而索引堆交換的是這兩個元素的索引,而不是直接交換元素。數組

有什麼好處?

主要有兩個好處:微信

  • 減少交換操做的消耗,尤爲是對於元素交換須要不少資源的對象來講,好比大字符串。
  • 能夠根據原位置找到元素,即使這個元素已經換了位置。

如何作到的?

索引堆使用了一個新的int類型的數組,用於存放索引信息。部分代碼以下:dom

// 屬性
$data = array();// 存放數據的數組 datas[1..n]
$indexes = array(); // 索引數組
複製代碼

這裏這個indexes數組,存放的是什麼信息呢?它是如何工做的呢?假如咱們有這樣一個最小堆:函數

paste image

那麼用數組表示就是:性能

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位置的元素,就須要datas[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;
複製代碼

看這個例子:

paste image

indexes[1] = 10; 而reverses[1]存儲的是在indexes數組中值爲10的索引1在indexes中的位置,它的值爲8,有 reverses[1] = 8;表明index數組中第8個

反向索引的維護

雖然使用反向索引提升了某些時候的查詢效率,但會使得程序變得更加複雜。由於在插入和刪除時都要維護這個數組。

核心思想

核心思想是:無論任何操做,都要維護indexes數組和reverse數組的性質。

和堆相關的一些問題

使用堆實現優先隊列

  • 動態選擇優先級最高的任務執行

paste image

像操做系統的進程管理:每次都使用堆找到優先級最高的進程執行,若是來了新的進程只須要將其插入堆中,若是須要更改進行的優先級,只須要使用change函數進行更改

  • 在遊戲中選擇攻擊的對象

paste image

能夠將須要攻擊的敵人放入堆中,使用堆選擇最須要攻擊的敵人。若是有新的敵人進入則插入堆。

  • 在100萬個元素中選出前100名(在N個元素中選出前M個元素)

    • 咱們能夠使用快速排序算法排序, 複雜度爲:O(n*logN)
    • 使用優先隊列:O(NlogM)
      • 使用一個最小堆,保證每次這個堆的元素都不大於100;初始先將100個元素放入這個堆中,造成最小堆,後面每加入一個元素,首先將最小的元素踢出,而後加入新的元素(須要保持堆的結構,複雜度爲O(logN),遍歷後面每個元素,直到最後一個元素,最後造成的100個元素的堆,將爲這100萬中元素最大的100個元素。
  • 多路歸併排序

paste image

* merge的時候,將各個分割的字塊的第一個元素造成一個最小堆,每次取堆頂元素進行merge
* 若是n個元素進行n路歸併,其實歸併算法就成了,堆排序算法。
複製代碼
  • d叉堆 d-ary heap

paste image

堆的實現細節優化

  • shiftup 和shiftDown 中使用複製操做替換swap操做

-------------------------華麗的分割線--------------------

看完的朋友能夠點個喜歡/關注,您的支持是對我最大的鼓勵。

我的博客番茄技術小棧掘金主頁

想了解更多,歡迎關注個人微信公衆號:番茄技術小棧

番茄技術小棧
相關文章
相關標籤/搜索