640 likes | 819 Views
Søgning I. Plan. Sekventiel søgning Binær søgning Binære søgetræer ------------------------------------------- Balancerede binære søgetræer - 2-3-4-træer - rød - sort -træer.
E N D
Plan • Sekventiel søgning • Binær søgning • Binære søgetræer • ------------------------------------------- • Balancerede binære søgetræer • - 2-3-4-træer • - rød-sort-træer
Søgning er genfinding af lagret information. Informationen har form af poster, der hver har en nøgle. Målet med en søgning er at finde den eller de poster, der har en nøgle, der matcher en given søgenøgle. Søgning • Søgning er det problem at afgøre, om en mængde af objekter indeholder et objekt, der opfylder visse specificerede krav, og i givet fald finde det. Søgning er den mest tidsforbrugende aktivitet i mange programmer. At erstatte en dårlig metode med en god fører derfor ofte til en væsentlig effektivitetsforøgelse.
Klassifikation af søgemetoder • intern/ekstern søgning • statisk/dynamisk søgning • søgning baseret på nøglesammen-ligninger/digitale egenskaber ved nøglerne • søgning baseret på de aktuelle nøgler/ transformerede nøgler
class Dictionary { void insert(keyType v, infoType info); infoType search(keyType v); } Ordbog (symboltabel)en abstrakt datatype til søgning • Mængden af poster med tilhørende operationer beskrives ved en abstrakt datatype, Dictionary. Metoden insert indsætter en ny post med nøgle v og tilknyttet information, info, i ordbogen. Metoden search leder efter en post, der har v som nøgle. Hvis søgningen lykkes, returneres postens tilknyttede information. Ellers returneres en værdi, der indikerer “ingen information” (infoNil).
Andre operationer • Initialisering (ved hjælp af en eller flere konstruktører) • Genfinding af alle poster med en given nøgle: Enumeration searchAll(keyType v) • Sletning af poster med en given nøgle: void remove(keyType v) • Sortering af en ordbogs poster: Vector sort() • Sammenlægning af to ordbøger: Dictionary join(Dictionary other)
class Dictionary { private Node a[]; private int N; Dictionary(int max) { a = new Node[max+1]; N = 0; for (int i = 0; i <= max; i++) a[i] = new Node(); } void insert(int v, String info) { a[++N].key = v; a[N].info = info; } String search(int v) { ...} } Repræsentation ved usorteret array af poster • ( keyType = int,infoType = String,infoNil = null ) • class Node { int key; String info; }
Sekventiel (lineær) søgning • String search(int v) { a[0].key = v; a[0].info = null; int i = N+1; while (v != a[--i].key) ; return a[i].info; } Sekventiel søgning, implementeret med usorteret array, bruger altid N+1 sammenligninger for en mislykket søgning og gennemsnitligt (N+1)/2 sammenligninger for en succesfuld søgning. Kompleksitet af søgning er O(N). Kompleksitet af indsættelse er O(1).
class Dictionary { private Node a[], head, z; private int N; Dictionary(int max) { z = new Node(Integer.MAX_VALUE, null, null); head = new Node(0, null, z); } void insert(int v, String info) { ... } String search(int v) { ... } } Repræsentation ved usorteret liste af poster • class Node { int key; String info; Node next; Node(int k, String i, Node n) { key = k; info = i; next = n; } }
String search(int v) { Node t = head; while ((t = t.next) != z) if (t.key == v) return t.info; return null; } Indsættelse og søgning i usorteret liste • void insert(int v, String info) { head.next = new Node(v, info, head.next); } Kompleksiteten er den samme som for et usorteret array.
String search(int v) { Node t = head; while (v > t.next.key) t = t.next; return v == t.key ? t.info : null; } Indsættelse og søgning isorteret liste • void insert(int v, String info) { Node t = head; while (v > t.next.key) t = t.next; t.next = new Node(v, info, t.next); } Sekventiel søgning, implementeret med sorteret liste, bruger gennemsnitligt cirka N/2 sammenlig-ninger for både succesfuld og mislykket søgning. Kompleksiteten af såvel indsættelse som søgning er O(N).
Repræsentation vedsorteret array • void insert(int v, String info) { int i = ++N; while (a[i-1].key > v) { a[i] = a[i-1]; i--; } • a[i] = new Node(v, info); } Kompleksiteten af insert er O(N). Metoden search kan implementeres som for usorteret array (med kompleksitet O(N)). Mere effektivt er det dog at benytte binær søgning.
Eksempel: Søgning efter M. A A A C E E E G H I L M N P R S X I L M N P R S X I L M M Binær søgning • Metode: • Opdel arrayet i to (næsten) lige store dele. • Afgør i hvilken af de to dele, nøglen skal findes. • Fortsæt søgningen i denne del på samme måde.
Implementation afbinær søgning • Rekursiv udgave: • String search(int v, int l, int r) { • if (l <= r) { • int x = (l+r)/2; • if (v == a[x].key) return a[x].info; • if (v < a[x].key) return search(v, l, x-1); • return search(v, x+1, r); } • return null; } Kald: info = search(v, 1, N);
Implementation afbinær søgning • Iterativ udgave: • String search(int v) { • int l = 1, r = N; • while (l <= r) { • int x = (l+r)/2; • if (v == a[x].key) return a[x].info; • if (v < a[x].key) r = x-1; • else l = x+1; } • return null; }
String search(int v) { • int l = 1, r = N; • while (l < r) { • int x = (l+r+1)/2; • if (v < a[x].key) r = x-1; • else l = x; } • return v == a[l].key ? a[l].info : null; } Alternativ implementation afbinær søgning Binær søgning blev første gang beskrevet i 1946. Den første fejlfri udgave blev publiceret i 1960. I 1986 fandt Bentley, at 90% af alle ”computer professionals” ikke kunne skrive en fejlfri udgave på to timer.
H C N A E L R S E A M I A P E G X Kompleksiteten af binær søgning • Binær søgning bruger aldrig mere end log2N + 1 sammenligninger - for såvel succesfuld som mislykket søgning. • Binær søgning kan beskrives ved et sammenlignings-træ:
Er ternær søgning bedre end binær søgning? • Antal sammenligninger: Binær søgning: • CB(N) = 1 + CB(N/2) for N ≥ 2, CB(1) = 1 • som har løsningen CB(N) = log2(N) + 1, for N ≥ 1. • Ternær søgning: CT(N) = 1/3*1 + 2/3*2 + CT(N/3) for N ≥ 2, CT(1) = 1 • som har løsningen CT(N) = 5/3*log3(N) + 1 for N ≥ 1. • Idet 5/3*log3(N) + 1 = 5/3*log2(N)/log2(3) + 1 og 5/3 > log2(3), får vi, at CT(N) > CB(N) for N ≥ 1. • Svaret er altså: nej.
a[r] v a[l] l x r Interpolationssøgning • Indeksintervallet opdeles efter et gæt på nøglens placering. Ved lineær interpolation sammenholdes søgenøglen med nøglerne i de to intervalendepunkter. • I binær søgning erstattes x = (l + r)/2med x = l + (v-a[l])*(r-l)/(a[r]-a[l]).
Kompleksiteten af interpolationssøgning • Interpolationssøgning bruger cirka log2log2N + 1 sammenligninger for både succesfuld og mislykket søgning på tilfældige filer. • Færre end 5 forsøg i praksis (225 ≈ 4*1010). • Svagheder: (1) filerne er ikke “tilfældige”, (2) beregningerne af x kan koste mere end de sparede sammenligninger.
head H C N A L R S A I P M X Binære søgetræer • Ved et binærtsøgetræ forstås et binært træ bestående af poster med nøgler, hvor der for enhver knude gælder, at alle poster i venstre undertræ er mindre endeller lig med knudens nøgle, mens alle knuder i højre undertræ er størreend knudens nøgle.
class Node { int key; String info; Node l, r; Node(int k, String i, Node ll, Node rr) { key = k; info = i; l = ll; r = rr; } } Class Dictionaryimplementeret ved binære søgetræer
class Dictionary { private Node head, z; private int N; Dictionary() { z = new Node(0 ,null, null, null); head = new Node(Integer.MIN_VALUE, null, null, z); } void insert(int v, String info) { ... } String search(int v) { ... } void remove(int v) { ... } }
A D M X Søgning i binære søgetræer • String search(int v) { Node x = head.r; z.key = v; while (v != x.key) x = v < x.key ? x.l : x.r; return x.info; } Antallet af sammenligninger afhænger af søgetræets udseende. I bedste fald, nemlig når træet er fuldt, udføres cirka log2N sammenligninger. I værste fald, nemlig når træet er en lineær liste, udføres N+1 sammenligninger ved mislykket søgning.
Indsættelse i binære søgetræer • Der foretages en mislykket søgning, og den nye knude indsættes på den eksterne knudes plads. For at knuden kan indsættes i træet, bestemmes dens farknude, p. • void insert(int v, String info) { • Node p = head, x = head.r; • while (x != z) { • p = x; • x = v < x.key ? x.l : x.r; } • x = new Node(v, info, z, z); • if (v < p.key) p.l = x; else p.r = x; }
Rekursiv udgave af insert Node insertR(Node n, int v, String info) { if (n == z) return new Node(v, info, z, z); if (v < n.key) n.l = insertR(n.l, v, info); else n.r = insertR(n.r, v, info); return n; } void insert(int v, String info) { insertR(head, v, info); }
Rekursiv udgave af search • String searchR(Node n, int v) { • if (n == z) return null; • if (v == n.key) return n.info; • if (v < n.key) return searchR(n.l, v); • return searchR(n.r, v); } public String search(int v) { return searchR(head.r, v); }
Kompleksiteten af søgning og indsættelse • En søgning eller en indsættelse i et binært søgetræ kræver i gennemsnit cirka 2 lnN sammenligninger i et træ, der er opbygget ud fra N tilfældige nøgler. 2 lnN ≈ 1.39 log2N • Eksempel: N = 106 2 ln N ≈ 27.6 log2N≈ 19.9
H C N A L R S M I A P X K I C N A L R K S M A P X Sletning i binære søgetræer • Sletning af roden i et binært søgetræ (H): • Erstat roden med den næste højere post (I ).
p p.l eller p.r t H C N c R A L x S M P I A X K Sletning af H c.l = x.r; x.l = t.l; x.r = t.r; if (v < p.key) p.l = x; else p.r = x;
Metoden remove void remove(int v) { Node c, p, x, t; z.key = v; p = head; t = head.r; while (v != t.key) { p = t; t = v < t.key ? t.l : t.r; } if (t == z) return; if (v < p.key) p.l = x; else p.r = x; } if (t.r == z) x = t.l; else if (t.r.l == z) { x = t.r; x.l = t.l; } else { c = t.r; while (c.l.l != z) c = c.l; x = c.l; c.l = x.r; x.l = t.l; x.r = t.r; }
Rekursiv udgave af remove • Node removeR(Node t, int v) { • if (t == z) return z; • if (v < t.key) { t.l = removeR(t.l, v); return t; } • if (v > t.key) { t.r = removeR(t.r, v); return t; } • if (t.r == z) return t.l; • t.r = removeMin(t.r); • min.l = t.l; min.r = t.r; • return min; } • Node min; • void remove(int v) { removeR(head, v); }
RemoveMin(rekursiv udgave) Node removeMin(Node t) { if (t.l == z) { min = t; return t.r; } t.l = removeMin(t.l); return t; }
Sortering forbinære søgetræer Vector sort() { Vector V = new Vector(); sort(head.r, V); return V; } void sort(Node n, Vector V) { if (n == z) return; sort(n.l, V); V.addElement(n.info); sort(n.r, V); } Inorder-gennemgang af det binære træ
Balancerede søgetræer • Balancering er en teknik, der garanterer, at de værste tilfælde ved søgning ikke forekommer. • Ideen er at omorganisere træet under indsættelse, så det bliver fuldt (eller næsten fuldt). • I det følgende præsenteres et rød-sort-træ, en datastruktur, der garanterer O(logN) kompleksi-tet både for indsættelse og søgning. • Princippet i algoritmerne forklares dog bedst ved hjælp af datastrukturen et 2-3-4-træ.
En 2-knude: ≤ R > R R En 3-knude: ≤ H (> H, ≤ R) > R H R En 4-knude: ≤ E (> E, ≤ H) (> H ≤ R) > R E H R 2-3-4-træer • Et 2-3-4-træ er et søgetræ, hvor hver knude kan • have 2, 3 eller 4 udgående hægter (sønner).
E R S H I N A C Eksempel på et 2-3-4-træ Søgning i et 2-3-4-træ er simpel. (1) Sammenlign søgenøglen med nøglerne i roden. (2) Find intervallet, der indeholder søgenøglen. (3) Følg den tilsvarende hægte (rekursivt).
E R A C H I N S Indsættelse i et 2-3-4-træ • Foretag en mislykket søgning (søgning til bunden af træet). • Hvis 2-knude på bunden: konverter til 3-knude. • Hvis 3-knude på bunden: konverter til 4-knude. • Hvis 4-knude på bunden: ?
(1) Faderen er en 2-knude: Eksempel: D D G E G H E H • Løsning: Sørg for at der aldrig opstår en 4-knude i bunden! • Transformere træet undervejs nedad. Enhver 4-knude, der mødes, “splittes” ved hjælp af én af følgende 3 transformationer:
Eksempel: D G D G K M H K M H • (2) Faderen er en 3-knude:
Eksempel: Invariant: Den aktuelle knude er ikke en 4-knude. Derfor er indsættelse i bunden let. G D G K D K • (3, særtilfælde) Roden er en 4-knude:
D I R A B C S N G H I R D E G H A B C N S Eksempel på konstruktion af et 2-3-4-træ Indsættelse af E (roden splittes)
I D R E G H A B C N S Indsættelse af X (simpel) I D R E G H A B C N S X Indsættelse af F (transformation 1) I D G R N S X A B C H E F
Kompleksitet • Søgning i 2-3-4 træer med N knuder besøger aldrig mere end log2N + 1 knuder. Afstanden fra roden til ethvert blad er den samme. Kun i tilfælde 3 (roden er en 4-knude) øges træets højde. Afstanden til ethvert blad øges med 1. • Indsættelse i et 2-3-4 træ med N knuder kræver færre end log2N + 1 splitninger, og synes at kræve færre end 1 splitning i gennemsnit.
Skitse til implementation afindsættelse i 2-3-4-træer • void insert(int v) { Node p = head, x = head.r; while (x != z) { p = x; x = theRightLink(p, v); if (isFourNode(x)) x = split(x); } if (p == head) head.r = new TwoNode(v); else if (isTwoNode(p)) makeThree(p, v); else if (isThreeNode(p)) makeFour(p, v); } • Direkte implementation er kompliceret på grund af • håndtering af forskellige knudetyper• splitskal kunne håndtere mange tilfælde
eller Rød-sort-træer • Ide: Repræsenter 2-3-4-træer som binære træer • med “indre” kanter for 3- og 4-knuder. Gamle kanter kaldes sorte. Nye kanter kaldesrøde.
K N R C F I S X P A D E G H L M J K R F N C S I A E G M P X J D H L Invarianter (rød-sort-egenskaberne): (1) Der er aldrig to konsekutive røde kanter på en vej. (2) Enhver vej fra roden til en ekstern knude indeholder det samme antal sorte kanter. Transformation af 2-3-4-træ til rød-sort-træ
Søgning og indsættelsei rød-sort-træer Søgning er sædvanlig søgning i binære søgetræer. En søgning i et rød-sort-træ med N knuder kræver færre end 2log2N + 2 sammenligninger. En søgning i et rød-sort-træ med N knuder, der er bygget ud fra tilfældige nøgler bruger i gennemsnit cirka 1.002log2N sammenligninger. Indsættelse er simplere end for 2-3-4 træer, fordi antallet af splitningstilfælde reduceres. Sletning er også simplere (men behandles ikke her).
Skift farve på 3 kanter Splitningstilfælde • (1)