Кучи индексов для алгоритмов и структур данных

задняя часть Операционная система игра

В предыдущем сообщении в блоге описывалась реализация кучиВведение в кучи, сегодня в основном представлена ​​куча индексов и оптимизация кучи индексов.

Что такое индексная куча?

Куча индексов оптимизирована для кучи.

Что оптимизировано?

В куче процессы создания кучи, вставки и удаления требуют большого количества операций подкачки. В предыдущей реализации операция подкачки заключалась в том, чтобы напрямую поменять местами два элемента в массиве данных. Куча индексов меняет местами индексы этих двух элементов, а не меняет местами элементы напрямую.

какая польза?

Есть два основных преимущества:

  • Уменьшите стоимость операций SWAP, особенно для объектов, которые требуют много ресурсов для замены элементов, таких как большие строки.
  • Элементы можно найти на основе их исходного положения, даже если элемент изменил положение.

Как?

Куча индексов использует новый массив типа int для хранения информации индекса. Часть кода выглядит следующим образом:

// 属性
$data = array();// 存放数据的数组 datas[1..n]
$indexes = array(); // 索引数组

Какая информация хранится в массиве индексов здесь? Как это работает? Предположим, у нас есть такая мини-куча:

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], а также сохранить характеристики кучи. Да, используйте индексированную кучу.

Использовать кучу индексов

После использования кучи индексов инициализация двух массивов должна выглядеть так:

$datas: [-, 1, 15, 20, 34, 7]
$indexes: [-, 1, 2, 3, 4, 5]

На данный момент мы меняем местами индексы 2 и 5 в массиве индексов, не манипулируя массивом данных. После замены два массива выглядят так:

$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

обратный индекс

Следуя приведенному выше случаю, теперь мы можем получить данные, подобные этому: после сортировки arr второе по величине число

arr[indexes[1]]

А теперь такое требование: я хочу знать, где находится i-я позиция в исходном массиве arr после сортировки. То, что должно быть сделано?

Обычный способ - перебрать массив индексов следующим образом:

for(  $j = 1 ; $j <= $this->count ; $j ++ ){
  if( $this->indexes[$j] == $i ){
  shiftUp($j);
  shiftDown($j);
  return;
  }
}

Эта сложность в худшем случае O(N);

Так есть ли способ улучшить производительность?

Да, то есть снова использовать массив в обратном порядке в качестве обратного индекса. Данные, хранящиеся в обратном индексе, обычно выглядят так:

reverses[i] == j
indexes[j] == i

А затем вывести:

reverses[indexes[i]] = i;
indexes[reverses[i]] = i;

См. этот пример:

paste image

индексы[1] = 10; И reverses[1] хранит позицию индекса 1 со значением 10 в массиве индексов, а его значение равно 8, есть reverses[1] = 8; представляет 8-е место в индексном массиве

Обслуживание инвертированных индексов

Хотя использование инвертированного индекса в некоторых случаях повышает эффективность запросов, оно усложняет программу. Потому что этот массив сохраняется во время вставки и удаления.

Главная мысль

Основная идея заключается в следующем: независимо от операции должны сохраняться свойства массива индексов и обратного массива.

Некоторые проблемы, связанные с кучей

Реализация приоритетной очереди с использованием кучи

  • Динамически выбирать задачу с наивысшим приоритетом для выполнения

paste image

Подобно управлению процессами операционной системы: используйте кучу, чтобы каждый раз находить процесс с наивысшим приоритетом для выполнения.Если приходит новый процесс, просто вставляйте его в кучу.Если вам нужно изменить приоритет процесса, вы нужно только использовать функцию изменения, чтобы изменить его.

  • Выберите, кого атаковать в игре

paste image

Вы можете поместить врагов, которых нужно атаковать, в кучу, и использовать кучу, чтобы выбрать врагов, которых нужно атаковать больше всего. Вставляется в кучу, если входит новый враг.

  • Выберите 100 лучших из 1 миллиона элементов (выберите M лучших элементов из N элементов)

    • Мы можем использовать алгоритм быстрой сортировки для сортировки, сложность которого составляет: O(n*logN)
    • Использование очереди с приоритетом: O(NlogM)
      • Используйте минимальную кучу, чтобы гарантировать, что элементы этой кучи каждый раз не превышают 100; изначально в эту кучу помещается 100 элементов, чтобы сформировать минимальную кучу, и каждый раз, когда элемент добавляется позже, наименьший элемент удаляется первым. , а затем добавляется новый. Элементы (необходимо поддерживать структуру кучи, сложность - O (logN), проходить каждый последующий элемент до последнего элемента и, наконец, образуют кучу из 100 элементов, которые будут самыми большими 100 элементами из 1 миллиона элементов.
  • многосторонняя сортировка слиянием

paste image

* merge的时候,将各个分割的字块的第一个元素形成一个最小堆,每次取堆顶元素进行merge
* 如果n个元素进行n路归并,其实归并算法就成了,堆排序算法。
  • многомерная куча

paste image

Оптимизация деталей реализации кучи

  • Замените операцию подкачки операцией копирования в shiftup и shiftDown.

------------------------- Великолепная разделительная линия --------------------

Друзья, которые прочитали это, могут нажать «Нравится» / «Подписаться», ваша поддержка — самая большая поддержка для меня.

личный блогСтек томатной технологииа такжеДомашняя страница Наггетс

Если вы хотите узнать больше, обратите внимание на мой публичный аккаунт WeChat: Tomato Technology Stack.

番茄技术小栈