Indeksējamais binārais koks

Indeksējamais binārais koks

Man bija uzdevums Ŕāda veida. NepiecieŔams izstrādāt datu glabāŔanas konteineru, kas nodroŔina Ŕādu funkcionalitāti:

  • ievietot jaunu elementu
  • izņemt elementu pēc secÄ«bas numura
  • iegÅ«t elementu pēc secÄ«bas numura
  • dati tiek glabāti sakārtotā veidā

Dati tiek nepārtraukti pievienoti un izņemti, tādēļ struktÅ«rai jānodroÅ”ina ātra darbÄ«bas ātrums. Sākumā mēģināju Å”o ideju Ä«stenot, izmantojot standarta konteinerus no std. Å is ceļŔ neizdevās un radās sapratne, ka ir jāizstrādā kaut kas paÅ”am. VienÄ«gais, kas ienāca prātā, bija izmantot bināro mežu meklēŔanai. Tā kā tas atbilst ātras ievietoÅ”anas, izņemÅ”anas un datu glabāŔanas sakārtotā veidā prasÄ«bām. Palika tikai izdomāt, kā indeksēt visus elementus un pārskaitÄ«t indeksus, kad mežs mainās.

struct node_s {    
    data_t data;

    uint64_t weight; // mezgla svars

    node_t *left;
    node_t *right;

    node_t *parent;
};

Šajā rakstā būs vairāk attēlu un teorijas nekā koda. Kodu varēs apskatīt saitei lejā.

Svars

Tāpēc koks tika nedaudz modificēts, pievienojot papildu informāciju par svaru mezglam. Mezgls svars ir pēcteču skaits Å”im mezglam + 1 (viena elementa svars).

Funkcija, kas iegūst mezgla svaru:

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

    return 0;
}

Lapu svaram attiecīgi ir 0.

Tālāk pāriesim pie konkrēta Ŕāda koka piemēra vizuālā attēla. Melns krāsā tiks attēlots mezgla atslēga (vērtÄ«ba netiks rādÄ«ta, jo tam nav vajadzÄ«bas), sarkans — mezgla svars, zaļŔ — mezgla indekss.

Kad koks ir tukŔs, tad tā svars ir 0. Pievienosim tam saknes elementu:

Indeksējamais binārais koks

Koka svars kļūst 1, saknes elementa svars ir 1. Saknes elements ir koka svars.

Pievienojam vēl dažus elementus:

Indeksējamais binārais koks
Indeksējamais binārais koks
Indeksējamais binārais koks
Indeksējamais binārais koks

Katru reizi, kad pievieno jaunu elementu, mēs nokāpjam pa mezgliem uz leju un palielinām katra izietā mezgla svara skaitÄ«tāju. Izveidojot jaunu mezglu, tam tiek pieŔķirts svars 1. Ja mezglam ar Ŕādu atslēgu jau pastāv, tad pārrakstām vērtÄ«bu un dodamies atpakaļ uz sakni, atcēlot svaru izmaiņas visos mezglos, kurus mēs esam izgājuÅ”i.
Ja notiek mezgla dzēŔana, tad mēs nokāpjam uz leju un dekremetējam izgājuÅ”o mezglu svarus.

Indeksi

Tagad pāriesim pie tā, kā indeksēt mezglus. Mezgli acīmredzami nesaglabā savu indeksu, tas tiek aprēķināts, pamatojoties uz mezglu svaru. Ja tie saglabātu savu indeksu, tas prasītu O(n) laiku, lai atjauninātu visu mezglu indeksus pēc katras koka izmaiņas.
Pāriesim pie vizuāla attēlojuma. MÅ«su koks ir tukÅ”s, pievienosim tam pirmo mezglu:

Indeksējamais binārais koks

Pirmajam mezglam ir indekss 0, un tagad iespējami ir 2 gadījumi. Pirmajā gadījumā saknes indekss mainīsies, otrajā nemainīsies.

Indeksējamais binārais koks

Saknes kreisajam apakŔkokam ir svars 1.

Otrais gadījums:

Indeksējamais binārais koks

Saknes indekss nemainījās, jo tās kreisā apakŔkoka svars palika 0.

Kā tiek aprēķināts mezgla indekss, tas ir tā kreisā apakÅ”koka svars + skaitlis, ko nodod vecāks. Kas tas ir par skaitli?, Tas ir indeksu skaitÄ«tājs, sākumā tas ir 0, jo saknei nav vecāka. Tālāk viss ir atkarÄ«gs no tā, uz kurieni mēs virzāmies, uz kreiso bērnu vai labo. Ja uz kreiso, tad skaitÄ«tājam nekas netiek pievienots. Ja uz labo, tad pievienojam paÅ”reizējā mezgla indeksu.

Indeksējamais binārais koks

Piemēram, kā tiek aprēķināts elementa ar atslēgu 8 indekss (labais bērns saknei). Tas ir "Saknes indekss" + "mezgla ar atslēgu 8 kreisā apakÅ”koka svars" + "1" == 3 + 2 + 1 == 6
Elementa ar atslēgu 6 indekss būs "Saknes indekss" + 1 == 3 + 1 == 4

Tādējādi, lai iegÅ«tu vai dzēstu elementu pēc indeksa, nepiecieÅ”ams laiks O(log n), jo, lai iegÅ«tu vajadzÄ«go elementu, mums vispirms ir jāatrod tas (jānonāk no saknes lÄ«dz Å”im elementam).

Dziļums

Pamatojoties uz svaru, var arÄ« aprēķināt koka dziļumu. NepiecieÅ”ams, lai lÄ«dzsvarotu.
Lai to izdarÄ«tu, paÅ”reizējā mezgla svars jānoapaļo lÄ«dz pirmajam skaitlim, kas ir 2 pakāpē un ir lielāks vai vienāds ar dotā svara, un jāņem tam binārais logaritms. Tādējādi mēs iegÅ«sim koka dziļumu, pieņemot, ka tas ir lÄ«dzsvarots. Koks tiek lÄ«dzsvarots pēc jauna elementa pievienoÅ”anas. Teoriju par to, kā lÄ«dzsvarot kokus, neaprakstīŔu. Avota kodā ir pieejama lÄ«dzsvaroÅ”ana funkcija.

Kods, lai pārveidotu svaru par dziļumu.

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

Secinājumi

  • jaunā elementa pievienoÅ”ana notiek O(log n)
  • elementa dzēŔana pēc secÄ«bas notiek O(log n)
  • elementa iegūŔana pēc secÄ«bas notiek O(log n)

Ātrums O(log n) mēs maksājam par to, ka visi dati tiek uzglabāti sakārtotā formā.

Nezinu, kur Ŕāda struktÅ«ra var noderēt. Tas ir vienkārÅ”i uzdevums, lai vēlreiz saprastu, kā darbojas koki. Paldies par uzmanÄ«bu.

Saites

Projektā ir ietverti testēŔanas dati ātruma pārbaudei. Koks tiek aizpildÄ«ts 1000000 elementi. Un notiek secÄ«ga dzēŔana, ievietoÅ”ana un elementu iegūŔana 1000000 reizes. Tas ir 3000000 operācijas. Rezultāts izrādÄ«jās diezgan labs ~ 8 sekundes.

Avots: habr.com

Iegādājieties uzticamu tÄ«mekļa hostingu ar DDoS aizsardzÄ«bu, VPS VDS serveriem šŸ”„ Iegādājieties uzticamu tÄ«mekļa hostingu ar DDoS aizsardzÄ«bu, VPS VDS serveriem | ProHoster