前面的博文介绍了堆的实现堆的介绍,今天主要主要介绍索引堆,以及索引堆的优化。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路归并,其实归并算法就成了,堆排序算法。
复制代码
-------------------------华丽的分割线--------------------
看完的朋友能够点个喜欢/关注,您的支持是对我最大的鼓励。
想了解更多,欢迎关注个人微信公众号:番茄技术小栈