Indeksita binara arbo

Indeksita binara arbo

Mi renkontis la jenan tipon de problemo. Estas necese efektivigi datuman stokan ujon, kiu provizas la jenan funkcion:

  • enmeti novan elementon
  • forigi elementon per seria numero
  • akiri elementon per orda nombro
  • datumoj estas konservitaj en ordigita formo

Datumoj estas konstante aldonitaj kaj forigitaj, la strukturo devas certigi rapidan operacion. Komence mi provis efektivigi tian aferon uzante normajn ujojn de horoj. Ĉi tiu vojo ne estis kronita per sukceso kaj venis la kompreno, ke mi bezonas efektivigi ion mem. La nura afero, kiu venis al la menso, estis uzi binaran serĉarbon. Ĉar ĝi plenumas la postulon de rapida enmeto, forigo kaj konservado de datumoj en ordigita formo. Restas nur eltrovi kiel indeksi ĉiujn elementojn kaj rekalkuli la indeksojn kiam la arbo ŝanĝiĝas.

struct node_s {    
    data_t data;

    uint64_t weight; // вес узла

    node_t *left;
    node_t *right;

    node_t *parent;
};

La artikolo enhavos pli da bildoj kaj teorio ol kodo. La kodo videblas ĉe la suba ligilo.

pezo

Por atingi ĉi tion, la arbo spertis ioman modifon, pri kiu aldoniĝis pliaj informoj pezo nodo. La noda pezo estas nombro da posteuloj de ĉi tiu nodo + 1 (pezo de ununura elemento).

Funkcio por akiri nodan pezon:

uint64_t bntree::get_child_weight(node_t *node) {
    if (node) {
        return node->weight;
    }

    return 0;
}

La pezo de la folio estas egale egala al 0.

Poste, ni transiru al vida reprezentado de ekzemplo de tia arbo. Nigra la noda ŝlosilo estos montrita en koloro (la valoro ne estos montrita, ĉar tio ne estas necesa), ruĝa - noda pezo, verda — noda indekso.

Kiam nia arbo estas malplena, ĝia pezo estas 0. Ni aldonu radikan elementon al ĝi:

Indeksita binara arbo

La pezo de la arbo fariĝas 1, la pezo de la radika elemento fariĝas 1. La pezo de la radika elemento estas la pezo de la arbo.

Ni aldonu kelkajn pliajn elementojn:

Indeksita binara arbo
Indeksita binara arbo
Indeksita binara arbo
Indeksita binara arbo

Ĉiufoje kiam nova elemento estas aldonita, ni malsupreniras la nodojn kaj pliigas la pezon de ĉiu nodo preterpasita. Kiam nova nodo estas kreita, pezo estas asignita al ĝi 1. Se nodo kun tia ŝlosilo jam ekzistas, tiam ni anstataŭigos la valoron kaj reiros al la radiko, nuligante la ŝanĝojn en la pezoj de ĉiuj nodoj, kiujn ni pasis.
Se nodo estas forigita, tiam ni malsupreniras kaj malpliigas la pezojn de la preterpasitaj nodoj.

Indeksoj

Nun ni pluiru al kiel indeksi nodojn. Nodoj ne eksplicite stokas sian indekson, ĝi estas kalkulita surbaze de la pezo de la nodoj. Se ili stokis sian indekson, tiam ĝi estus postulata Li) tempo ĝisdatigi la indeksojn de ĉiuj nodoj post ĉiu ŝanĝo en la arbo.
Ni transiru al vida reprezentado. Nia arbo estas malplena, ni aldonu la unuan nodon al ĝi:

Indeksita binara arbo

La unua nodo havas indekson 0, kaj nun 2 kazoj eblas. En la unua, la indekso de la radika elemento ŝanĝiĝos, en la dua ĝi ne ŝanĝiĝos.

Indeksita binara arbo

Ĉe la radiko, la maldekstra subarbo pezas 1.

Dua kazo:

Indeksita binara arbo

La indekso de la radiko ne ŝanĝiĝis ĉar la pezo de ĝia maldekstra subarbo restis 0.

La indekso de nodo estas kalkulita kiel la pezo de ĝia maldekstra subarbo + la nombro pasita de la gepatro. Kio estas ĉi tiu nombro?, Ĉi tio estas la indeksa nombrilo, komence ĝi egalas 0, ĉar la radiko ne havas gepatron. Tiam ĉio dependas de kie ni malsupreniras al la maldekstra infano aŭ al la dekstra. Se maldekstre, tiam nenio estas aldonita al la vendotablo. Se ni aldonas la indekson de la nuna nodo al la dekstra.

Indeksita binara arbo

Ekzemple, kiel la indekso de elemento kun ŝlosilo 8 (la dekstra infano de la radiko) estas kalkulita. Ĉi tio estas "Radika Indekso" + "pezo de maldekstra subarbo de nodo kun ŝlosilo 8" + "1" == 3 + 2 + 1 == 6
La indekso de la elemento kun ŝlosilo 6 estos "Root Index" + 1 == 3 + 1 == 4

Sekve, necesas tempo por akiri kaj forigi elementon per indekso O (log n), ĉar por ricevi la deziratan elementon ni unue devas trovi ĝin (malsupreniri de la radiko al ĉi tiu elemento).

Profundo

Surbaze de la pezo, vi ankaŭ povas kalkuli la profundon de la arbo. Necesas por ekvilibrigi.
Por fari tion, la pezo de la nuna nodo devas esti rondigita al la unua nombro al la potenco de 2 kiu estas pli granda ol aŭ egala al la donita pezo kaj preni la binaran logaritmon de ĝi. Ĉi tio donos al ni la profundon de la arbo, supozante ke ĝi estas ekvilibra. La arbo estas ekvilibrigita post enigo de nova elemento. Mi ne donos teorion pri kiel ekvilibrigi arbojn. La fontkodoj disponigas ekvilibran funkcion.

Kodo por konverti pezon al profundo.

/*
 * Возвращает первое число в степени 2, которое больше или ровно x
 */
uint64_t bntree::cpl2(uint64_t x) {
    x = x - 1;
    x = x | (x >> 1);
    x = x | (x >> 2);
    x = x | (x >> 4);
    x = x | (x >> 8);
    x = x | (x >> 16);
    x = x | (x >> 32);

    return x + 1;
}

/*
 * Двоичный логарифм от числа
 */
long bntree::ilog2(long d) {
    int result;
    std::frexp(d, &result);
    return result - 1;
}

/*
 * Вес к глубине
 */
uint64_t bntree::weight_to_depth(node_t *p) {
    if (p == NULL) {
        return 0;
    }

    if (p->weight == 1) {
        return 1;
    } else if (p->weight == 2) {
        return 2;
    }

    return this->ilog2(this->cpl2(p->weight));
}

Rezultoj

  • enmeto de nova elemento okazas en O (log n)
  • forigo de elemento per seria numero okazas en O (log n)
  • akiri elementon per seria numero okazas en O (log n)

Rapido O (log n) Ni pagas pro tio, ke ĉiuj datumoj estas konservitaj en ordigita formo.

Mi ne scias kie tia strukturo povus esti utila. Nur enigmo por denove kompreni kiel funkcias arboj. Dankon pro via atento.

referencoj

La projekto enhavas testajn datumojn por kontroli la rapidecon de operacio. La arbo pleniĝas 1000000 elementoj. Kaj estas sinsekva forigo, enmeto kaj retrovo de elementoj 1000000 unufoje. Tio estas 3000000 operacioj. La rezulto montriĝis sufiĉe bona ~ 8 sekundoj.

fonto: www.habr.com

Aldoni komenton