E118. Рандомизированная куча

e-maxx algorithm original: C/C++ #algorithm #data-structures #emaxx
El texto de la tarea se traduce del ruso para el idioma seleccionado. El código no cambia.

Источник: e-maxx.ru/algo, страница PDF 391.

Рандомизированная куча (randomized heap) — это куча, которая за счёт Applications генератора случайных чисел позволяет выполнять все необходимые операции за логарифмическое ожидаемое время. Кучей называется бинарное árbol, для любой вершины которого справедливо, что значение в этой вершине меньше либо равно значений во всех её потомках (это куча для минимума; разумеется, симметрично можно определить кучу для максимума). Таким образом, в корне кучи всегда находится минимум. Стандартный набор операций, определяемый для куч, следующий:

● Добавление elementа

● Нахождение минимума

● Извлечение минимума (удаление его из дерева и возврат его значения)

● Слияние двух куч (returnsся куча, содержащая elementы обеих куч; дубликаты не удаляются)

● Удаление произвольного elementа (при известной позиции в дереве)

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

при очень

простой реализации.

Структура данных

Сразу опишем структуру данных, описывающую бинарную кучу:

struct tree {

T value;

tree * l, * r;

};

В вершине дерева хранится значение

некоторого типа

, для которого определён оператор

сравнения (

). Кроме того, хранятся указатели на левого и правого сыновей (которые равны 0, если соответствующий сын отсутствует).

Выполнение операций

Нетрудно понять, что все операции над кучей сводятся к одной операции: слиянию двух куч в одну. Действительно, добавление elementа в кучу равносильно слиянию этой кучи с кучей, состоящей из единственного добавляемого elementа. Нахождение минимума вообще не требует никаких действий — минимумом просто является корень кучи. Извлечение минимума эквивалентно тому, что куча заменяется результатом слияния левого и правого поддерева корня. Наконец, удаление произвольного elementа аналогично удалению минимума: всё подárbol с корнем в этой вершине заменяется результатом слияния двух поддеревьев-сыновей этой вершины. Итак, нам фактически надо реализовать только операцию слияния двух куч, все остальные операции тривиально сводятся к этой операции.

Пусть given две кучи

и

, it is required вернуть их объединение. Понятно, что в корне каждой из этих куч находятся их минимумы, поэтому в корне результирующей кучи будет находиться минимум из этих двух значений. Итак, мы сравниваем, в корне какой из куч находится меньшее значение, его помещаем в корень результата, а теперь мы должны объединить сыновей выбранной вершины с оставшейся кучей. Если мы по какому-то признаку выберем одного из двух сыновей, то тогда нам надо будет просто объединить подárbol в корне с этим сыном с кучей. Таким образом, мы снова пришли к операции слияния. Рано или поздно этот процесс остановится (на это понадобится, понятно, не более чем сумма высот куч). Таким образом, чтобы достичь логарифмической асимптотики в среднем, нам надо указать способ выбора одного из двух сыновей с тем, чтобы в среднем длина проходимого пути получалась бы порядка логарифма от количества elementов в куче. Нетрудно догадаться, что производить этот выбор мы будем случайно, таким образом, Implementación операции слияния получается такой:

tree * merge (tree * t1, tree * t2) {

if (!t1 || !t2)
return t1 ? t1 : t2;
if (t2->value < t1->value)

swap (t1, t2);

if (rand() & 1)

swap (t1->l, t1->r);

t1->l = merge (t1->l, t2);

return t1;

} Здесь сначала проверяется, если хотя бы одна из куч пуста, то никаких действий по слиянию производить не надо.

Иначе, мы делаем, чтобы куча

была кучей с меньшим значением в корне (для чего обмениваем

и

, если

надо). Наконец, мы считаем, что вторую кучу

будем сливать с левым сыном корня кучи

, поэтому мы

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

Asymptotic complexity

Введём случайную величину

, обозначающую длину случайного пути от корня до листа (длина в

числе рёбер). Понятно, что Algoritmo

выполняется за

операций. Поэтому

для исследования асимптотики Algoritmoа надо исследовать случайную величину .

Математическое ожидание

Утверждается, что математическое ожидание

оценивается сверху логарифмом от числа

вершин в этой куче:

Доказывается это легко по индукции. Пусть

и

— соответственно левое и правое поддеревья корня кучи

, а

и

— количества вершин в них (понятно, что

). Тогда справедливо:

что и требовалось доказать.

Превышение ожидаемой оценки

Докажем, что вероятность превышения полученной выше оценки мала:

для любой положительной константы

.

Обозначим через

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

Заметим, что для любого пути

длины

вероятность того, в качестве случайного пути будет выбран именно он,

равна

. Тогда получаем: что и требовалось доказать.

Asymptotic complexity Algoritmoа

Таким образом, Algoritmo

, а, значит, и все остальные выраженные через него операции, выполняется

за

в среднем.

Более того, для любой положительной константы

найдётся такая положительная константа

, что вероятность того,

что операция потребует больше чем

операций, меньше

(это в некотором смысле описывает

худшее поведение Algoritmoа).

C# solución

borrador automático, revisar antes de enviar
using System;
using System.Collections.Generic;
using System.Linq;

public static class AlgorithmDraft
{
    // Auto-generated C# draft from the original e-maxx C/C++ listing. Review before production use.
    struct tree {
            T value;
            tree * l, * r;
    };
    tree * merge (tree * t1, tree * t2) {
            if (!t1 || !t2)
                    return t1 ? t1 : t2;
            if (t2->value < t1->value)
                    swap (t1, t2);
            if (rand() & 1)
                    swap (t1->l, t1->r);
            t1->l = merge (t1->l, t2);
            return t1;
    }
}

C++ solución

coincidente/original
struct tree {
        T value;
        tree * l, * r;
};
tree * merge (tree * t1, tree * t2) {
        if (!t1 || !t2)
                return t1 ? t1 : t2;
        if (t2->value < t1->value)
                swap (t1, t2);
        if (rand() & 1)
                swap (t1->l, t1->r);
        t1->l = merge (t1->l, t2);
        return t1;
}

Java solución

borrador automático, revisar antes de enviar
import java.util.*;
import java.math.*;

public class AlgorithmDraft {
    // Auto-generated Java draft from the original e-maxx C/C++ listing. Review before production use.
    struct tree {
            T value;
            tree * l, * r;
    };
    tree * merge (tree * t1, tree * t2) {
            if (!t1 || !t2)
                    return t1 ? t1 : t2;
            if (t2->value < t1->value)
                    swap (t1, t2);
            if (rand() & 1)
                    swap (t1->l, t1->r);
            t1->l = merge (t1->l, t2);
            return t1;
    }
}

Материал разбит как Algoritmoическая Tarea: изучить постановку, понять асимптотику и реализовать Algoritmo на выбранном языке.

Vacantes para esta tarea

Se muestran vacantes activas con etiquetas coincidentes.

Todas las vacantes
Todavía no hay vacantes activas.