• Keine Ergebnisse gefunden

Algorithmen und Datenstrukturen

N/A
N/A
Protected

Academic year: 2022

Aktie "Algorithmen und Datenstrukturen"

Copied!
63
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

Algorithmen und Datenstrukturen

Werner Struckmann

Wintersemester 2005/06

(2)

7. Mengen, Verzeichnisse und Hash-Verfahren

7.1 Mengen

7.2 Verzeichnisse 7.3 Hashverfahren

(3)

Mengen

Unter einer Menge verstehen wir jede Zusammenfassung M von bestimmten, wohlunterschiedenen Objekten m unserer

Anschauung oder unseres Denkens (welche die Elemente von M genannt werden) zu einem Ganzen.

Es kommt es nicht darauf an, wie oft und in welcher Reihenfolge die Elemente einer Menge aufgeführt werden. Es gilt

{

2, 4,6, 8

} = {

6, 8, 2,4, 4,6,8

} = {

8, 6,2,4

}

.

Für Mengen sind unter anderem die folgenden Operationen definiert:

Elementbeziehung m

M

(4)

ADT Menge

type

: Set

(

T

) import

: Bool

operators

:

: →

Set

empty

? :

Set

Bool is_in

:

Set

×

T

Bool insert

:

Set

×

T

Set delete

:

Set

×

T

Set union

:

Set

×

Set

Set

intersection

:

Set

×

Set

Set difference

:

Set

×

Set

Set

· · ·

axioms ∀

s

Set,

x,y

T

· · ·

7.1 Mengen 7-2

(5)

Geordnete Mengen

Die Elemente einer Menge können durch eine Ordnungsrelation angeordnet sein.

Eine Ordnungsrelation ist eine reflexive, transitive und antisymmetrische Relation.

Wenn je zwei Elemente vergleichbar sind, heißt die Ordnungsrelation linear oder total.

(6)

ADT Geordnete Menge

type

: OrderedSet

(

T

) import

: Bool

operators

:

: →

OrderedSet

empty

? :

OrderedSet

Bool is_in

:

OrderedSet

×

T

Bool

insert

:

OrderedSet

×

T

OrderedSet delete

:

OrderedSet

×

T

OrderedSet

union

:

OrderedSet

×

OrderedSet

OrderedSet

intersection

:

OrderedSet

×

OrderedSet

OrderedSet difference

:

OrderedSet

×

OrderedSet

OrderedSet min

:

OrderedSet

T

max

:

OrderedSet

T

· · ·

axioms ∀

s

OrderedSet,

x,y

T

· · ·

7.1 Mengen 7-4

(7)

Mengenimplementierungen

Mengen können auf verschiedene Weisen realisiert werden. Einige davon besprechen wir in diesem Kapitel. Wir befassen uns nur mit endlichen Mengen.

Ungeordnete Mengen:

1. Bitfelder 2. Listen 3. Felder

Geordnete Mengen:

(8)

Kopieren von Objekten

Häufig besteht der Bedarf, ein Objekt zu kopieren, d. h. ein zweites Objekt zur Verfügung zu haben, das dem ersten vollständig gleicht.

In objektorientierten Programmiersprachen ist eine Anweisung der Form a

b hierfür oft nicht geeignet.

Es wird der Variablen a lediglich eine Referenz auf das

ursprüngliche Mengenobjekt zugewiesen. Manipulationen des durch a referenzierten Objekts verändern so zugleich das

durch b bestimmte Objekt.

Man unterscheidet zwischen so genannten flachen und tiefen Kopien.

Programmtechnische Details sind nicht Gegenstand dieser Vorlesung.

7.1 Mengen 7-6

(9)

Implementierung durch Bitfelder

Dieses Verfahren ist geeignet, Mengen A zu realisieren, die

Teilmenge einer kleinen endlichen Grundmenge G

= {

g1, . . . , gn

}

sind.

Es wird ein Bitfeld A mit

|

G

| =

n Bits vereinbart.

Es gilt gi

A

A

[

i

] =

1. Wir vereinbaren: 0 , false, 1 , true.

g1 1 g1 A g2 1 g2 A g3 0 g3 6∈ A

(10)

Implementierung durch Bitfelder

proc empty() begin var A: BitSet;

var i: int;

for i ← 1 to n do A[i] ← false;

od;

return A;

end

proc isIn(A: BitSet, i: int): bool begin return A[i];

end

7.1 Mengen 7-8

(11)

Implementierung durch Bitfelder

func intersection(B, C: BitSet): Bitset begin var A: BitSet;

var i: int;

for i ← 1 to n do

A[i] ← B[i] && C[i];

od;

return A;

end

func union(B, C: BitSet): Bitset begin var A: BitSet;

var i: int;

for i ← 1 to n do

A[i] ← B[i] || C[i];

(12)

Implementierung durch Bitfelder

func difference(B, C: BitSet): BitSet begin var A: BitSet;

var i: int;

for i ← 1 to n do

A[i] ← B[i] && ! C[i];

od;

return A;

end

7.1 Mengen 7-10

(13)

Implementierung durch Bitfelder

Die Laufzeit von

isIn

,

insert

und

delete

liegt in O

(

1

)

. Die Laufzeiten der Operationen

union

,

intersection

und

difference

sind abhängig von der Kardinalität der Grundmenge und liegen in O

(

n

)

.

Vorteile:

Für kleine Grundmengen ist die Implementierung sehr effizient.

Die Operationen besitzen teilweise eine konstante Laufzeit.

Nachteile:

(14)

Implementierung durch Listen

Die Elemente der Menge werden in einer verketteten Liste gespeichert.

Die Reihenfolge der Elemente spielt keine Rolle.

Elemente dürfen nicht mehrfach vorkommen.

Die Anzahl der Elemente der Menge braucht nicht beschränkt zu sein.

Der Platzbedarf richtet sich nach der Größe der Menge, nicht nach der Größe der Grundmenge.

Die Sortierung innerhalb der Liste kann zusätzliche Informationen enthalten (Beispiel: Reihenfolge des Einfügens).

7.1 Mengen 7-12

(15)

Implementierung durch Listen

Varianten: einfach und mehrfach verkettete Listen (siehe Abschnitt über Listen).

Pro Element wird mehr Speicherplatz verbraucht als bei der Bitfeldimplementierung.

Operationen sind aufwändiger als bei der Bitfeldimplementierung.

Im Folgenden: ausgewählte Algorithmen (diesmal aus objektorientierter Sicht).

(16)

Implementierung durch Listen

func copy(): ListSet begin var result: ListSet;

var tmp: T;

foreach tmp ← L.elements() do result.insert(tmp);

od;

return result;

end

7.1 Mengen 7-14

(17)

Implementierung durch Listen

func equals(other: Set): bool begin var tmp: T;

if size

,

other.size then return false;

foreach tmp ← L.elements() do if! other.isIn(tmp) then

return false;

od;

return true;

end

(18)

Implementierung durch Listen

func union(other: Set): ListSet begin var result: ListSet;

var tmp: T;

result ← other.copy();

foreach tmp ← L.elements() do result.insert(tmp);

od;

return result;

end

7.1 Mengen 7-16

(19)

Implementierung durch Listen

func intersection(other: Set): ListSet begin var result: ListSet;

var tmp: T;

foreach tmp ← L.elements() do if other.isIn(tmp) then

result.insert(tmp);

od;

return result;

end

(20)

Implementierung durch Listen

func difference(other: Set): ListSet begin var result: ListSet;

var tmp: T;

foreach tmp ← L.elements() do if! other.isIn(tmp) then

result.insert(tmp);

od;

return result;

end

7.1 Mengen 7-18

(21)

Vergleich

Operation Liste Bitfeld isIn O

( |

s

| )

O

(

1

)

insert O

( |

s

| )

O

(

1

)

delete O

( |

s

| )

O

(

1

)

copy O

( |

s

| )

O

( |

G

| )

union O

( |

t

| + |

t

| · |

s

| )

O

( |

G

| )

difference O

( |

s

| · |

t

| )

O

( |

G

| )

intersection O

( |

s

| · |

t

| )

O

( |

G

| )

equal O

( |

s

| · |

t

| )

O

( |

G

| )

(22)

Implementierung durch Felder

Die Elemente der Menge s werden in einem Feld A fester Länge gespeichert. Dabei belegen die Mengenelemente die Felder 1 bis

|

s

|

.

Der Index des höchsten belegten Feldes wird in einer Variablen size

= |

s

|

gespeichert.

Die Größe der Menge ist (in vielen Sprachen) durch die Feldgröße beschränkt.

Es ist keine dynamische Speicherverwaltung notwendig.

Die Sortierung innerhalb des Felds kann zusätzliche

Informationen enthalten (zum Beispiel die Reihenfolge des Einfügens).

7.1 Mengen 7-20

(23)

Implementierung durch Felder

func isIn(t: T): bool begin var i: int;

for i ← 1 to size do if A[i] = t then

return true;

od;

return false;

end

(24)

Implementierung durch Felder

proc insert(t: T) begin if! isIn(t) then

if size = A.size then

error(“maximale Größe erreicht”);

A[size] ← t;

size ← size + 1;

fi;

end

func copy(): ArraySet begin var result: ArraySet;

var i: int;

for i ← 1 to size do result.A[i] ← A[i];

od;

result.size ← size;

return result;

end

7.1 Mengen 7-22

(25)

Implementierung durch Felder

Die Laufzeit fast aller Operationen ist denen der

Listenimplementierung ähnlich. Bei der Operation delete müssen evtl. Bereiche des Felds verschoben werden.

Pro Element ist der Speicherverbrauch geringer als bei Implementation durch verkettete Listen.

Allerdings verbrauchen auch nicht belegte Felder Speicherplatz.

Der Speicherplatzbedarf ist unabhängig von der tatsächlichen Größe der Menge.

Der Speicherplatzbedarf ist bei geringem Füllstand unnötig groß.

(26)

Implementierung geordneter Mengen durch Listen

Die Grundidee folgt der Implementierung von (ungeordneten) Mengen durch Listen.

Allerdings sind die Elemente in der Liste jetzt entsprechend der Ordnungsrelation angeordnet.

Einige Operationen lassen sich dadurch beschleunigen.

7.1 Mengen 7-24

(27)

Implementierung geordneter Mengen durch Listen

Als Beispiel betrachten wir die Durchschnittsbildung. Jede Liste wird mit einem Zeiger durchlaufen.

1. Setze zv auf den Anfang der ersten und zw auf den Anfang der zweiten Liste.

2. Schreite mit zw solange fort, bis sein Wert w größer oder gleich dem Wert v von zv ist.

3. Fallunterscheidung:

3.1 w = v: Nimm v in die Zielmenge auf und setze zv weiter.

3.2 w > v: Setze zv weiter.

4. Solange keine Zeiger am Ende angekommen ist, fahre mit 2 fort.

(28)

Implementierung geordneter Mengen durch Listen

func intersection(other: SortListSet): SortListSet begin

var result: SortListSet;

var i, j: <Referenz auf ListElement>;

i ← L.head;

j ← other.head;

while i <gültige Referenz> && j <gült. Ref.> do if i.wert = j.wert then

result.insert(j.wert);

if i.wert <= j.wert then i ← i.next;

else

j ← j.next;

fi;

od;

return result;

end

7.1 Mengen 7-26

(29)

Implementierung geordneter Mengen durch Listen

Die Operation insert muss die Ordnung der Elemente beachten.

Die Operationen isIn und delete müssen im Misserfolgsfall nicht bis zum Ende der Liste suchen. Dies führt zwar nicht zu einer Verbesserung der Komplexitätsklasse, dennoch

bedeutet es eine Effizienzsteigerung.

Bei einigen Operationen (zum Beispiel union, intersection, difference) kann durch die Ordnung der Liste eine

Laufzeitverbesserung erreicht werden, die zu einer anderen Laufzeitklasse führt.

(30)

Implementierung geordneter Mengen durch Bäume

Anstatt die Elemente in einer Liste abzulegen, kann auch ein binärer Suchbaum verwendet werden.

Für die Operationen intersection, union, difference kann analog zur Verwendung von Listen bei geordneten Mengen effizienzsteigernd das gleichzeitige Durchwandern beider Bäume durchgeführt werden.

Insbesondere verbessern sich die Laufzeiten von isIn, insert und delete. Dies hängt vom verwendeten Baumtyp ab.

7.1 Mengen 7-28

(31)

Verzeichnisse

Es seien zwei (endliche) Mengen A und B gegeben.

Ein Verzeichnis (Dictionary) ist eine Relation D

A

×

B mit

a

A,b1, b2

B

: (

a,b1

) ∈

D

∧ (

a, b2

) ∈

D

b1

=

b2.

Mathematisch gesehen ist ein Verzeichnis eine partielle Abbildung von A nach B.

Bezeichnungen:

A Schlüsselmenge, Definitionsmenge a

A Schlüssel

(32)

Verzeichnisse

Verzeichnisse werden verwendet, um Schlüssel auf Datenobjekte abzubilden.

Beispiele:

Ergebnisliste einer Klausur: (Matrikelnummer, Note)

Dateisystem: (Dateiname, Datenblockmenge)

Da ein Verzeichnis als Menge von Paaren definiert ist, können die besprochenen Mengenimplementierungen verwendet

werden.

7.2 Verzeichnisse 7-30

(33)

Einige Operationen

keys

: P (

A

×

B

) → P (

A

)

keys

(

D

) = {

a

A

| ∃

b

B.

(

a, b

) ∈

D

}

delete

:

A

× P (

A

×

B

) → P (

A

×

B

)

delete

(

a, D

) =

D

\{ (

a,b

) | ∃

b

B.

(

a, b

) ∈

D

}

associate

: (

A

×

B

) × P (

A

×

B

) → P (

A

×

B

)

associate

((

a,b

)

, D

) =

delete

(

a, D

) ∪ { (

a, b

) }

(34)

Einige Operationen

value

:

A

× P (

A

×

B

) →

B

value

(

a,D

) =

b falls

b

B.

(

a,b

) ∈

D, sonst undefiniert values

: P (

A

×

B

) → P (

B

)

values

(

D

) = {

b

B

| ∃

a

A.

(

a,b

) ∈

D

}

size

: P (

A

×

B

) →

N0

size

(

D

) = |

D

|

7.2 Verzeichnisse 7-32

(35)

Verzeichnisse

Verzeichnis von Namen und Telefonnummern A Zeichenketten, B Zeichenketten, D = ∅

associate("Linus", "05313250", D) associate("Lena", "05315148", D)

associate("Johannes", "01727512", D)

Führt zu D = {("Linus", "05313250"), ("Lena", "05315148"), ("Johannes", "01727512")}.

keys(D) = {"Linus", "Lena", "Johannes"}

values(D) = {"05313250", "05315148", "01727512"}

value(Linus, D) = "05313250"

value(Peter, D) =

(36)

Hashverfahren - Einführung

Bei der Listenimplementierung einer Menge erfolgt der Zugriff auf ein Element in der Zeit O

(

n

)

.

Bitfeldimplementierungen benötigen nur die Zeit O

(

1

)

, können jedoch sinnvoll nur kleine Mengen behandeln.

Idee: Die beiden Möglichkeiten werden kombiniert.

Hashverfahren basieren auf Verzeichnissen D

A

×

B

für deren Schlüsselmenge A = {0, . . . ,n1} ⊆ N0 gilt

und deren Schlüssel berechnet werden mithilfe einer so genannten Hashfunktion h : BA .

Das Verzeichnis heißt Hashtabelle.

Für die zu speichernde Menge M ist M

B.

Die Berechnung der Werte der Hashfunktion erfolgt in O

(

1

)

Schritten.

7.3 Hashverfahren 7-34

(37)

Einführung

Beispiel:

A

= {

0, . . . , 9

}

, B

=

N0,

h

:

B

A, h

(

j

) =

j mod 10 Die zu speichernde Menge sei M

= {

35,67

} ⊆

N0.

Das Einfügen der Zahlen 35 und 67 führt wegen h

(

35

) =

5 und h

(

67

) =

7 zu folgender Hashtabelle:

Schl¨ussel Werte 0

1 2 3 4

5 35

6

7 67

8 9

(38)

Einführung

Gegeben sei eine Nummerierung der Buchstaben (A=1, B=2, usw.)

Hashfunktion für Vor- und Nachnamen:

h1: Addiere Nummer des ersten Buchstabens des Vornamens zur Nummer des ersten Buchstabens des Nachnamens.

h2: Addiere Nummer des zweiten Buchstabens des

Vornamens zur Nummer des zweiten Buchstabens des Nachnamens.

Der Hashwert ergibt sich aus diesem Wert durch Restbildung modulo Feldgröße, zum Beispiel mod 10.

Beispiel:

h1

(

„Andreas Rot“

) = (

1

+

18

)

mod 10

=

9 h2

(

„Andreas Rot“

) = (

14

+

15

)

mod 10

=

9

7.3 Hashverfahren 7-36

(39)

Einführung

Ausgangsstruktur ist ein Feld, die Hashtabelle,

dessen Indexmenge der Schlüsselmenge A entspricht und

deren Felder, genannt Buckets, die Elemente der zu speichernden Menge aufnehmen.

Ein Element der Wertemenge wird mithilfe der Hashfunktion auf einen Index des Felds abgebildet und in diesem Feld gespeichert.

Ein Bucket kann im Allgemeinen mehrere Elemente aufnehmen.

Da die Wertemenge üblicherweise sehr groß ist, wird die Hashfunktion nicht injektiv sein.

(40)

Einführung

Zusammenfassung des Prinzips:

Es ist eine Menge M

B zu speichern. B ist die Wertemenge.

Die Schlüsselmenge sei A

= {

0, . . . ,n

1

} ⊆

N0.

h

:

B

A ist eine Hashfunktion.

Das Feld zur Speicherung der Elemente von Menge M ist die Hashtabelle R. A ist der Indexbereich von R.

i

A.R

[

i

] = {

b

M

|

h

(

b

) =

i

} ⊆

B

i

A.Bi

= {

b

B

|

h

(

b

) =

i

} =

h1

(

i

) ⊆

B B

=

n

[

i=0

Bi disjunkte Zerlegung von B

M

=

[n

i=0

b

B

|

b

M,h

(

b

) =

i

7.3 Hashverfahren 7-38

(41)

Einführung

Menge M

Wertemenge B

Bi Bj Bk

Bucket 0: M B0 Bucket 1: M B1 Bucket 2: M B2

· · ·

Bucket n: M Bn h

h

h

isIn

(

M, b

)

für b

B:

(42)

Kollisionen

Je mehr Elemente die Menge enthält, desto wahrscheinlicher ist, dass es zu einer Kollision kommt:

b,b

M.b , b

h

(

b

) =

h

(

b

)

. Das heißt, h ist in diesem Fall nicht injektiv.

Open Hashing: Größe der Buckets erhöhen (d. h. mehrere Elemente aufnehmen).

|

M

|

ist dann unbegrenzt.

Closed Hashing: Auf einen anderen Bucket ausweichen (d. h.

maximal ein Element pro Bucket). Dann ist

|

M

| ≤ |

R

|

.

7.3 Hashverfahren 7-40

(43)

Hashfunktionen

Hashfunktionen sollten surjektiv sein. Für B

N0 sind zwei Beispiele für Hashfunktionen wie folgt definiert.

Divisionsmethode: h

:

B

A mit

h

(

j

) =

j mod

|

A

|

h(j) ist schnell zu berechnen.

Vermieden werden sollte |A| = 2k, k ∈ N.

Besser: |A| ist Primzahl nicht zu nahe an 2k, k ∈ N.

Multiplikationsmethode: h

:

B

A mit

h

(

j

) = ⌊|

A

| · (

j

·

c mod 1

) ⌋

, 0 < c < 1

(44)

Open Hashing (with Chaining)

Feld R von Listen mit Indexmenge A

= {

0, 1, . . . ,n

1

}

. Bei

Kollisionen wird das neue Element b der Liste R

[

h

(

b

)]

am Kopf hinzugefügt.

Beispiel:

M

= {

0,1, 10,11,30, 41, 49,51, 59

}

,n

=

10, h

(

b

) =

b mod n

9

· · · 1 0

59 49

51 41 11 1

30 10 0

h(9) M = {59,49}

7.3 Hashverfahren 7-42

(45)

Open Hashing (with Chaining)

Die Liste kann einfach oder doppelt verkettet sein.

Einfügen, Löschen und Suchen eines Elements b erfordern

das Berechnen des Hash-Wertes h(b) sowie

das Ausführen der entsprechenden Listenfunktion (isIn(b), insert(b), delete(b)) für die Liste R[h(b)].

Da die Hashfunktion in O

(

1

)

ausgeführt wird, dominiert somit die jeweilige Listenfunktion die Laufzeit der Operation.

Die Operationen union, intersection und difference können sukzessive für jede Liste durchgeführt werden.

Ist auf B eine Ordnung definiert, bieten sich neben

(46)

Open Hashing (with Chaining)

Die Laufzeit der Operation isIn(b)

beträgt im ungünstigsten Fall O

( |

M

| )

. Dieser Fall tritt ein, wenn alle Elemente von M in dem Bucket R

[

h

(

b

)]

liegen.

beträgt im günstigsten Fall O

(

1

)

. Dieser Fall tritt für

|

R

[

h

(

b

)] | =

0 ein.

Aussagekräftig ist daher nur der mittlere Fall. Es gilt:

Satz: Die Menge M sei durch „zufälliges Ziehen“ von Elementen

b1, . . . , bm aus B entstanden. Für m

= |

M

|

ist die mittlere Laufzeit

der Operation isIn()

T

(

m

) =

O 1

+

m

|

A

|

! .

7.3 Hashverfahren 7-44

(47)

Hashfunktionen

Bespiel 1: h(Name) = Anfangsbuchstabe des Namens

Bei deutschen Nachnamen sind die Buckets ’S’ und ’E’ sehr voll, die Buckets ’C’, ’Y’, ’X’, ’Q’ dagegen sehr leer.

Besser ist es, bei Namen bzw. Zeichenketten y

=

y1y2y3 . . .yz die folgende Hashfunktion zu verwenden.

h

(

y

) =









z

X

j=1

OrdnungCodeX

(

yi

)









mod

( |

A

| )

(48)

Hashfunktionen

Beispiel 2: Ein Compiler trägt die Bezeichner eines Programms in eine Hashtabelle ein.

M sei die Menge der Bezeichner,

|

M

| =

m.

Die Laufzeit für den Eintrag eines Bezeichers x ergibt sich durch:

Auswerten von h

(

x

)

: 1

Überprüfen von R

[

h

(

x

)]

:

|

R

[

h

(

x

)] | +

1

evtl. Anfügen von x: 1

|

R

[

h

(

x

)] | +

3

7.3 Hashverfahren 7-46

(49)

Hashfunktionen

Unter idealen Verhältnissen, also wenn nach dem Einfügen der Elemente b1,b2, . . . , bi1

|

R

[

h

(

bi

)] | =

i

1

|

A

|

ist, dann gilt für die Laufzeit zum Einfügen von m Elementen:

T

(

m

) =

Xm

i=1

( |

R

[

h

(

bi

)] | +

3

) =

Xm

i=1

i

1

|

A

| +

3

!

=

m m

+

1

2

|

A

| −

1

|

A

| +

3

!

(50)

Hashfunktionen

Wenn der Programmierer nur Bezeichner der Art „A00“ bis „A99“

verwendet werden, also M

= {

A00, A01, . . . ,A99

}

ist, und außerdem

h

(

y

) =









z

X

j=1

OrdnungASCII

(

yi

)









mod

( |

A

| )

und A

= {

0, . . . ,99

}

gelten, dann ist zwar

|

A

| = |

M

|

, aber es

werden nur 19 der 100 Buckets benutzt.

7.3 Hashverfahren 7-48

(51)

Hashfunktionen

j0 j0

+

1 j0

+

2

· · ·

j0

+

9 j0

+

10

· · ·

j0

+

18 A00 A01 A02

· · ·

A09

A10 A11

· · ·

A18 A19

A20

· · ·

A27 A28

· · ·

· · · · · · · · · · · ·

A90 A91

· · ·

A99 Es gibt also einen Bucket mit 10 Elementen und je zwei mit

9, 8, 7, . . . ,1 Elementen. Die Laufzeit für das Auffüllen eines

Buckets mit m Elementen beträgt

l

(

m

) =

3

+

4

+

5

+ · · · + (

m

+

3

)

, die Gesamtlaufzeit also

l

(

10

) +

2 X9

l

(

i

) =

742 349 5

=

T

(

100

)

(52)

Open Hashing (with Chaining)

Zusammenfassung:

Das Problem der Kollisionen wird durch Bucketlisten gelöst.

Die Menge M bzw. die Buckets können (im Prinzip) beliebig groß werden.

Falls die Listen lang werden, steigt die Zugriffszeit.

7.3 Hashverfahren 7-50

(53)

Closed Hashing

Jedes Element von R fasst maximal ein Element aus B. Es wird bei einer Kollision eine Folge von Hashfunktionen verwendet:

h0

:

B

A, h1

:

B

A, . . . , hn

:

B

A.

Einfügen von b

B: 1. i

0

2. Fallunterscheidung bezüglich des Felds R

[

hi

(

b

)]

:

Feld ist frei: Füge b ein und beende den Algorithmus.

Feld enthält bereits b: Beende den Algorithmus.

Feld ist bereits durch ein Element b , b belegt (Kollision):

ii + 1.

(54)

Closed Hashing

Einfügen von 1,6, 5,3, die Indexmenge sei A

= {

0,1, 2,3

}

und h0

:

B

A mit h0

(

j

) =

j mod

|

A

| =

j mod 4

hi

:

B

A mit hi

(

j

) =

h0

(

j

) +

i mod

|

A

| ;

i > 0

3 2 1 0

+1 h0(1) = 1

1 3 2 1 0

+6

h0(6) = 2 6 1 3 2 1 0

+5 h0(5) = 1

5 6 1 3 2 1 0

+3 h0(3) = 3

5 6 1 3

3 2 1 0

h1(5) = 2 h2(5) = 3

h1(3) = 0

Das Feld R ist nun voll, 5 und 3 konnten nicht am „eigentlich“

vorgesehenen Platz eingefügt werden.

7.3 Hashverfahren 7-52

(55)

Closed Hashing

Suchen von b

B 1. i

0

2. Fallunterscheidung bezüglich des Felds R

[

hi

(

b

)]

:

Feld ist frei: b ist nicht enthalten, beende den Algorithmus.

Feld enthält b: b ist gefunden, beende den Algorithmus.

Feld ist durch ein Element b , b belegt: ii + 1

3. Falls i < n wiederhole Schritt 2, andernfalls beende den Algorithmus (Das Element ist im vollständig gefüllten Feld nicht enthalten).

3 0

(56)

Closed Hashing

Löschen eines Elements:

Problem: Das gelöschte Feld muss von einem leeren Feld

unterscheidbar sein, da es auf dem Suchpfad eines Elements liegen kann.

Eine Lösung: Das Element bleibt gespeichert, das Feld wird dafür mit einem booleschen Flag versehen, dass angibt, ob es sich um ein aktives oder ein gelöschtes Element handelt. Notwendige

Änderungen:

Der Algorithmus zum Suchen muss über als gelöscht markierte Felder hinweggehen.

Der Algorithmus zum Einfügen kann Elemente auch auf als gelöscht markierten Feldern einfügen.

Folgerung: Die Laufzeit der Suche/des Löschens hängt nicht länger vom tatsächlichen Füllgrads des Felds ab.

7.3 Hashverfahren 7-54

(57)

Lineare Kollisionsbehandlung

Bei der linearen Kollisionsbehandlung werden Hashfunktionen der folgenden Form verwendet:

h0

:

B

A Hashfunktion,

hi

:

B

A mit hi

(

j

) = (

h0

(

j

) +

i

)

mod

|

A

|

für i > 0.

Nach Bestimmung des ersten Hashwertes mit h0 berechnet h1 den nächsten Index durch Addition von 1. Bei Erreichen des letzten

Indexes wird 0 als nächster Index bestimmt.

(58)

Lineare Kollisionsbehandlung

Die lineare Kollisionsbehandlung kann zu einer Clusterbildung (primary clustering) führen. Je größer ein Cluster, desto höher die

Wahrscheinlichkeit, dass der Hashwert h0

(

z

)

eines einzufügenden Elements z in dem Cluster liegt, wodurch dieser verlängert wird. Dies hat negative Auswirkungen auf die Laufzeit der Suche nach freien Feldern.

h0(j) h1(j) h2(j) h3(j)

Cluster belegter Felder

7.3 Hashverfahren 7-56

(59)

Quadratische Kollisionsbehandlung

Bei der quadratischen Kollisionsbehandlung werden Hashfunktionen der folgenden Form verwendet:

h0

:

B

A Hashfunktion,

hi

:

B

A mit hi

(

j

) = (

h0

(

j

) +

c1

·

i

+

c2

·

i2

)

mod

|

A

|

für i > 0 und Konstanten c1,c2 , 0.

Bezüglich des Clusterbildung ist die quadratische Kollisionsbehandlung günstiger als die lineare.

Um die Hashtabelle vollständig auszunutzen, sind c1 und c2 geeignet zu wählen.

Da sich auch hier die Hashwerte von hi zweier Elemente b,b

B mit h0

(

b

) =

h0

(

b

)

im weiteren Verlauf gleichen,

(60)

Doppeltes Hashing

Beim doppelten Hashing werden Hashfunktionen der folgenden Form verwendet:

ha

:

B

A,hb

:

B

A

hi

:

B

A mit hi

(

j

) = (

ha

(

j

) +

i

·

hb

(

j

))

für i

0

Eine der besten Methoden für Closed Hashing, da die Folgen von Hashwerten nahezu zufällig erscheinen.

Der erste Hashwert eines Elements hängt von nur einer Hashfunktion ha ab.

Der zweite dann auch von der zweiten Funktion hb.

Dass beide Hashfunktionen für zwei unterschiedliche Elemente aus B die gleichen Resultate erzeugen, ist

(abhängig von der Wahl von ha und hb) unwahrscheinlich.

7.3 Hashverfahren 7-58

(61)

Doppeltes Hashing

hb

(

j

)

und

|

A

|

sollten teilerfremd sein, damit die gesamte Indexmenge durchsucht wird.

Möglichkeiten:

|

A

| =

2k und hb

(

j

)

produziert nur ungerade Zahlen.

|

A

|

ist eine Primzahl und

j

B

:

0 < hb

(

j

)

<

|

A

|

Beispiel (Cormen): ha

:

B

A mit ha

(

j

) =

j mod

|

A

|

und

hb

:

B

A mit hb

(

j

) =

1

+ (

j mod k

)

mit k etwas kleiner als

|

A

|

. j

=

123456,

|

A

| =

701, k

=

700 führt zu ha

(

j

) =

80,hb

(

j

) =

257 Der erste untersuchte Feld hat den Index 80, danach wird jedes

(62)

Closed Hashing

Ist die Hashfunktion bzgl. B identisch gleichverteilt, so sind bei einem Füllfaktor α

=

|An| < 1 höchstens 11

α Versuche notwendig, um zu entscheiden, dass ein Element nicht in M enthalten ist.

Ist die Hashfunktion bzgl. B identisch gleichverteilt, so sind bei einem Füllfaktor α

=

|An| < 1 höchstens 11

α Versuche notwendig, um ein Element einzufügen.

Ist die Hashfunktion bzgl. B identisch gleichverteilt, so sind bei einem Füllfaktor α

=

n

|A| < 1 höchstens α1 ln 11

α Versuche notwendig, um ein Element zu finden. (Falls die

Wahrscheinlichkeit, mit der nach einem Element gesucht wird, für alle Elemente gleich ist.)

Die Beweise dieser Aussagen findet man beispielsweise bei Cormen et al., S. 242 ff.

7.3 Hashverfahren 7-60

(63)

Zusammenfassung

Operation isIn

(

x

)

, n

= |

M

|

Anzahl der Elemente der Menge Implementierung Worst Case Average Case

Bitfeld O

(

1

)

O

(

1

)

Liste unsortiert O

(

n

)

O

(

n

)

Liste sortiert O

(

n

)

O

(

n

)

Rot-Schwarz-Baum O

(

log

(

n

))

O

(

logn

)

Open Hashing O

(

n

)

O

(

1

+

mn

)

Closed Hashing O

( |

A

| )

O

(

11α

)

Referenzen

ÄHNLICHE DOKUMENTE

C2.1 Erreichbarkeit C2.2 K¨ urzeste Pfade C2.3 Azyklische Graphen C2.4 Zusammenhang C2.5 Zusammenfassung.. R¨ oger (Universit¨ at Basel) Algorithmen und Datenstrukturen 2

I Entscheidungsprobleme: Ja/Nein-Antwort gesucht Gegeben gewichteter Graph, Knoten s, t und Zahl K.. Gibt es einen Pfad von s nach t mit Kosten h¨

I In der Praxis ist Union-Find meist schneller, da der Graph f¨ ur viele Zwecke nicht vollst¨ andig aufgebaut werden muss. I Ist der Graph schon aufgebaut, kann

Sie sind jetzt aber an einer Hochschule, und dort gibt es nicht f ¨ur jeden Arbeitsschritt eine separate Einladung und Anleitung,. denn das Studienziel ist, daß Sie

• wer mittels Bibliotheksfunktion sortiert, macht es sehr wahrscheinlich auch falsch — denn er benutzt eine Liste, meint aber eine Menge, f¨ur die gibt es deutlich

• (2 P) Wie unterscheidet sich asymptotisch die Komplexit¨at einer Implementierung des Algorithmus von Dijkstra mit fast vollst¨andig balancierten bin¨aren Heaps von einer

Definition: Ein binärer Suchbaum ist ein AVL-Baum, wenn für jeden Knoten p des Baums gilt, dass sich die Höhe des linken Teilbaums von der Höhe des rechten Teilbaums von p höchstens

Definition einer Menge von Fakten und Regeln sowie einer Anfrage in Form einer zu prüfenden Aussage; z. Prüfen und Anwenden der Regeln, bis keine neuen Fakten mehr erzeugt