Indekseret binært træ

Indekseret binært træ

Jeg stødte på følgende type problem. Det er nødvendigt at implementere en datalagringsbeholder, der giver følgende funktionalitet:

  • indsæt nyt element
  • fjern element ved serienummer
  • få element ved ordenstal
  • data gemmes i sorteret form

Data tilføjes og fjernes konstant, strukturen skal sikre hurtig driftshastighed. Først forsøgte jeg at implementere sådan noget ved hjælp af standardbeholdere fra std. Denne vej blev ikke kronet med succes, og forståelsen kom af, at jeg var nødt til at implementere noget selv. Det eneste, der kom til at tænke på, var at bruge et binært søgetræ. Fordi det opfylder kravet om hurtig indsættelse, sletning og lagring af data i sorteret form. Det eneste, der er tilbage, er at finde ud af, hvordan man indekserer alle elementerne og genberegner indeksene, når træet ændres.

struct node_s {    
    data_t data;

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

    node_t *left;
    node_t *right;

    node_t *parent;
};

Artiklen vil indeholde flere billeder og teori end kode. Koden kan ses på nedenstående link.

Vægt

For at opnå dette gennemgik træet en lille ændring, yderligere information blev tilføjet om vægt node. Nodevægten er antallet af efterkommere af denne node + 1 (vægt af et enkelt element).

Funktion til at få nodevægt:

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

    return 0;
}

Vægten af ​​arket er tilsvarende lig med 0.

Lad os derefter gå videre til en visuel repræsentation af et eksempel på et sådant træ. sort node-nøglen vil blive vist i farver (værdien vil ikke blive vist, da dette ikke er nødvendigt), rød - knudevægt, grøn — nodeindeks.

Når vores træ er tomt, er dets vægt 0. Lad os tilføje et rodelement til det:

Indekseret binært træ

Træets vægt bliver 1, vægten af ​​rodelementet bliver 1. Vægten af ​​rodelementet er vægten af ​​træet.

Lad os tilføje nogle flere elementer:

Indekseret binært træ
Indekseret binært træ
Indekseret binært træ
Indekseret binært træ

Hver gang et nyt element tilføjes, går vi ned i noderne og øger vægttælleren for hver node, der passeres. Når en ny node oprettes, tildeles den en vægt 1. Hvis en node med en sådan nøgle allerede eksisterer, vil vi overskrive værdien og gå tilbage til roden og annullere ændringerne i vægten af ​​alle noder, som vi har passeret.
Hvis en node fjernes, så går vi ned og nedsætter vægten af ​​de passerede noder.

Indekser

Lad os nu gå videre til, hvordan man indekserer noder. Noder gemmer ikke eksplicit deres indeks, det beregnes ud fra nodernes vægt. Hvis de gemte deres indeks, ville det være nødvendigt O (n) tid til at opdatere indekserne for alle noder efter hver ændring i træet.
Lad os gå videre til en visuel repræsentation. Vores træ er tomt, lad os tilføje den 1. node til det:

Indekseret binært træ

Den første node har et indeks 0, og nu er 2 tilfælde mulige. I det første ændres indekset for rodelementet, i det andet ændres det ikke.

Indekseret binært træ

Ved roden vejer det venstre undertræ 1.

Andet tilfælde:

Indekseret binært træ

Rodens indeks ændrede sig ikke, fordi vægten af ​​dens venstre undertræ forblev 0.

Indekset for en node beregnes som vægten af ​​dets venstre undertræ + det tal, der sendes fra forælderen. Hvad er dette tal?, Dette er indekstælleren, som oprindeligt er lig med 0, fordi roden har ingen forælder. Så kommer det helt an på, hvor vi går ned til venstre barn eller højre. Hvis til venstre, tilføjes intet til tælleren. Hvis vi tilføjer indekset for den aktuelle node til den højre.

Indekseret binært træ

For eksempel hvordan indekset for et element med nøgle 8 (det højre underordnede af roden) beregnes. Dette er "Root Index" + "vægt af venstre undertræ af node med tast 8" + "1" == 3 + 2 + 1 == 6
Indekset for elementet med tast 6 vil være "Root Index" + 1 == 3 + 1 == 4

Derfor tager det tid at hente og slette et element efter indeks O (log n), for for at få det ønskede element skal vi først finde det (gå ned fra roden til dette element).

dybde

Ud fra vægten kan du også beregne træets dybde. Nødvendig for balancering.
For at gøre dette skal vægten af ​​den aktuelle node afrundes til det første tal til 2 potens, som er større end eller lig med den givne vægt og tage den binære logaritme fra den. Dette vil give os dybden af ​​træet, forudsat at det er afbalanceret. Træet er afbalanceret efter indsættelse af et nyt element. Jeg vil ikke give en teori om, hvordan man balancerer træer. Kildekoderne giver en balancerende funktion.

Kode til konvertering af vægt til dybde.

/*
 * Возвращает первое число в степени 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));
}

Resultaterne af

  • indsættelse af et nyt element sker i O (log n)
  • sletning af et element efter serienummer sker i O (log n)
  • opnåelse af et element ved serienummer sker i O (log n)

Fart O (log n) Vi betaler for, at al data opbevares i sorteret form.

Jeg ved ikke, hvor en sådan struktur kan være nyttig. Bare et puslespil for igen at forstå, hvordan træer fungerer. Tak for din opmærksomhed.

RЎSЃS <P "RєRё

Projektet indeholder testdata for at kontrollere driftshastigheden. Træet er ved at blive fyldt op 1000000 elementer. Og der er en sekventiel sletning, indsættelse og genfinding af elementer 1000000 enkelt gang. Det er 3000000 operationer. Resultatet viste sig at være ganske godt ~ 8 sekunder.

Kilde: www.habr.com

Tilføj en kommentar