
Jag stötte pÄ en uppgift av följande slag. Det Àr nödvÀndigt att implementera en datalagringsbehÄllare som tillhandahÄller följande funktionalitet:
- lÀgg till ett nytt element
- ta bort ett element efter dess ordningsnummer
- hÀmta ett element efter dess ordningsnummer
- data lagras i sorterad ordning
Data lÀggs stÀndigt till och tas bort, strukturen mÄste sÀkerstÀlla snabb hastighet. I början försökte jag implementera nÄgot sÄdant med hjÀlp av standardbehÄllare frÄn std. Den vÀgen var inte framgÄngsrik, och jag kom till insikten att jag behövde implementera nÄgot sjÀlv. Det enda som kom i minnet var att anvÀnda ett binÀrt söktrÀd. Eftersom det uppfyller kraven för snabb inlÀggning, borttagning och lagring av data i sorterad ordning. Nu Äterstod bara att lista ut hur man ska indexera alla element och rÀkna om indexen nÀr trÀdet förÀndras.
struct node_s {
data_t data;
uint64_t weight; // nodens vikt
node_t *left;
node_t *right;
node_t *parent;
};I artikeln kommer det att vara fler bilder och teori Àn kod. Koden kan ses via lÀnken nedan.
Vikt
För detta utsattes trÀdet för en liten modifiering, ytterligare information om vikt noden. Vikten av en nod Àr antalet efterkommande av denna nod + 1 (vikten av en enda enhet).
Funktionen för att fÄ nodens vikt:
uint64_t bntree::get_child_weight(node_t *node) {
if (node) {
return node->weight;
}
return 0;
}För ett blad Àr vikten dÀrför lika med 0.
LĂ„t oss nu gĂ„ vidare till en visuell representation av ett exempel pĂ„ ett sĂ„dant trĂ€d. Med svart fĂ€rg kommer nyckeln pĂ„ noden att visas (vĂ€rdet visas inte, eftersom det inte Ă€r nödvĂ€ndigt), med röd â nodens vikt, med grön â nodens index.
NÀr trÀdet Àr tomt Àr dess vikt 0. LÄt oss lÀgga till rootelementet:

Vikten av trÀdet blir 1, vikten av rootelementet Àr 1. Vikten av rootelementet Àr vikt för trÀdet.
LÄt oss lÀgga till nÄgra fler element:




Varje gÄng ett nytt element lÀggs till, gÄr vi ner genom noderna och ökar viktvÀrdet för varje passerad nod. NÀr en ny nod skapas fÄr den vikten 1. Om en nod med en sÄdan nyckel redan finns, skriver vi om vÀrdet och gÄr tillbaka upp till roten, vilket ÄterstÀller viktvÀrdena för alla de noder vi passerat.
Om en nod tas bort, gÄr vi nerÄt och minskar vikten för de passerade noderna.
Index
LÄt oss nu gÄ vidare till hur man indexerar noder. Noderna lagrar uppenbarligen inte sitt index, utan det berÀknas utifrÄn nodernas vikt. Om de skulle lagra sitt index skulle det krÀva O(n) tid för att uppdatera indexen för alla noder efter varje Àndring av trÀdet.
Vi gÄr vidare till en visuell representation. VÄrt trÀd Àr tomt, vi lÀgger till den första noden:

Den första noden har index 0, och nu finns det tvÄ möjliga fall. I det första fallet kommer rotnodens index att Àndras, i det andra kommer det inte att förÀndras.

Roten har ett vÀnster deltrÀd med vikten 1.
Det andra fallet:

Indexet för roten har inte Àndrats, eftersom vikten av dess vÀnstra deltrÀd förblev 0.
Hur indexet för en nod berÀknas, Àr vikten av dess vÀnstra deltrÀd plus det tal som ges av förÀldern. Vad Àr det hÀr talet? Det Àr indexrÀknaren, som initialt Àr 0, eftersom roten inte har nÄgon förÀlder. Sedan beror allt pÄ om vi gÄr ner till vÀnster barn eller höger. Om vi gÄr till vÀnster, sÄ lÀggs inget till indexrÀknaren. Om vi gÄr till höger, dÄ lÀgger vi till indexet för den aktuella noden.

Till exempel hur indexet för elementet med nyckeln 8 (höger barn av roten) berÀknas. Det Àr "Index för roten" + "vikten av den vÀnstra deltrÀdet av noden med nyckeln 8" + "1" == 3 + 2 + 1 == 6
Indexet för elementet med nyckeln 6 kommer att vara "Index för roten" + 1 == 3 + 1 == 4
Följaktligen, för att fÄ, ta bort ett element efter index krÀvs tid O(log n), eftersom för att fÄ det önskade elementet mÄste vi först hitta det (gÄ ner frÄn roten till detta element).
Djup
UtifrÄn vikten kan man ocksÄ berÀkna djupet pÄ trÀdet. Detta behövs för balansering.
För detta mÄste vikten av den aktuella noden avrundas till det första talet som Àr en potens av 2 som Àr större Àn eller lika med den angivna vikten och ta den binÀra logaritmen av det. PÄ sÄ sÀtt fÄr vi djupet pÄ trÀdet, under förutsÀttning att det Àr balanserat. TrÀdet balanseras efter att en ny nod har lagts till. Jag kommer inte att ge teorin om hur man balanserar trÀd. I kÀllkoderna presenteras en balansfunktion.
Kod för att konvertera vikt till djup.
/*
* ĐĐŸĐ·ĐČŃаŃĐ°Đ”Ń ĐżĐ”ŃĐČĐŸĐ” ŃĐžŃĐ»ĐŸ ĐČ ŃŃĐ”ĐżĐ”ĐœĐž 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));
}Sammanfattning
- insÀttning av en ny nod sker pÄ O(log n)
- borttagning av ett element enligt ordningsnummer sker pÄ O(log n)
- hÀmtning av ett element enligt ordningsnummer sker pÄ O(log n)
Hastighet O(log n) vi betalar för att alla data lagras i sorterad ordning.
Jag vet inte var en sÄdan struktur kan vara anvÀndbar. Det Àr bara en uppgift för att förstÄ hur trÀd fungerar. Tack för uppmÀrksamheten.
LĂ€nkar
Projektet innehÄller testdata för att kontrollera hastigheten. TrÀdet fylls med 1000000 element. Det sker sekventiell borttagning, insÀttning och hÀmtning av element 1000000 gÄng. Det vill sÀga 3000000 operationer. Resultatet visade sig vara ganska bra ~ 8 sekunder.
KĂ€lla: habr.com
