Mindmap-Galerie Datenstruktur
Dies ist eine Mindmap über Datenstrukturen, einschließlich linearer Tabellen, Stapel und Warteschlangen, Zeichenfolgen, Bäume und Binärbäume, Suche, Sortierung usw. Sie ist sehr praktisch und es lohnt sich, sie zu sammeln.
Bearbeitet um 2021-09-25 14:52:15Welche Preismethoden gibt es für Projektunteraufträge im Rahmen des EPC-Generalvertragsmodells? EPC (Engineering, Procurement, Construction) bedeutet, dass der Generalunternehmer für den gesamten Prozess der Planung, Beschaffung, Konstruktion und Installation des Projekts verantwortlich ist und für die Testbetriebsdienste verantwortlich ist.
Die Wissenspunkte, die Java-Ingenieure in jeder Phase beherrschen müssen, werden ausführlich vorgestellt und das Wissen ist umfassend. Ich hoffe, es kann für alle hilfreich sein.
Das Software-Anforderungs-Engineering ist ein Schlüsselkapitel für Systemanalytiker. Zu den Kapiteln „Anforderungserhebung“ und „Anforderungsanalyse“ gehören häufig Veröffentlichungen.
Welche Preismethoden gibt es für Projektunteraufträge im Rahmen des EPC-Generalvertragsmodells? EPC (Engineering, Procurement, Construction) bedeutet, dass der Generalunternehmer für den gesamten Prozess der Planung, Beschaffung, Konstruktion und Installation des Projekts verantwortlich ist und für die Testbetriebsdienste verantwortlich ist.
Die Wissenspunkte, die Java-Ingenieure in jeder Phase beherrschen müssen, werden ausführlich vorgestellt und das Wissen ist umfassend. Ich hoffe, es kann für alle hilfreich sein.
Das Software-Anforderungs-Engineering ist ein Schlüsselkapitel für Systemanalytiker. Zu den Kapiteln „Anforderungserhebung“ und „Anforderungsanalyse“ gehören häufig Veröffentlichungen.
Datenstrukturen 8.26
1. Einleitung
1.1 Grundkonzepte der Datenstruktur
1.1.1 Grundkonzepte und Terminologie
1.Daten
Es ist ein Informationsträger, eine Sammlung von Symbolen, die in einen Computer eingegeben und von einem Computerprogramm erkannt und verarbeitet werden können.
2. Datenelemente
Es ist die Grundeinheit von Daten. Ein Datenelement kann aus mehreren Datenelementen bestehen.
Datenelement: Die kleinste unteilbare Einheit, die ein Datenelement darstellt
3. Datenobjekte
Eine Sammlung von Datenelementen gleicher Art, die eine Teilmenge von Daten darstellen (z. B. ist das Studenteninformationsobjekt eine Teilmenge der gesamten Studenteninformationen und Kursinformationen).
4.Datentyp
Es ist der allgemeine Name einer Menge von Werten und einer Reihe von Operationen, die für diese Menge definiert sind (z. B. eine Variable vom Typ int, deren Wertemenge in einem bestimmten Intervall eine Ganzzahl ist (die Größe des Intervalls variiert je nach Maschine). ), und die Operationen darauf sind definiert als Addition, Subtraktion, Multiplikation, Division sowie Modulo- und andere Operationen)
1. Atomtyp
Ein Datentyp, dessen Wert nicht unterteilt werden kann
2. Strukturtyp
Ein Datentyp, dessen Wert in mehrere (Komponenten) unterteilt werden kann
3. Abstrakte Datenstruktur ADT
Bezieht sich auf ein mathematisches Modell und eine Reihe von Operationen, die auf dem Modell definiert sind (nur die logischen Eigenschaften werden hervorgehoben)
Abbildung 1-4
1.1.2 Datenstruktur und drei Elemente
Datenstruktur
Ist eine Sammlung von Datenelementen, die in einer bestimmten Beziehung zueinander stehen
Drei Elemente
1. Logische Datenstruktur
lineare Struktur
Allgemeiner linearer Tisch
eingeschränkter linearer Tisch
Stapel, Warteschlange
Zeichenfolge
Lineare Tischwerbung
Array
nichtlineare Struktur
versammeln
Baumstruktur
Diagrammstruktur
2. Datenspeicherstruktur (Bild, physische Struktur)
sequentielle Speicherung
Vorteile: 1. Es kann ein wahlfreier Zugriff erreicht werden. 2. Jedes Element belegt den geringsten Speicherplatz. Nachteile: 1. Es kann nur ein ganzer benachbarter Block von Speichereinheiten verwendet werden, sodass leicht mehr externe Fragmente generiert werden können
Kettenspeicher
Vorteile: 1. Es ist nicht erforderlich, dass logisch benachbarte Elemente physisch benachbart sind, sodass keine Fragmentierung auftritt. Nachteile: 1. Zeiger verbrauchen zusätzlichen Platz. 2. Es kann kein wahlfreier Zugriff erreicht werden, sondern nur sequentieller Zugriff.
Indexspeicher
Beim Speichern von Elementinformationen wird eine zusätzliche Indextabelle erstellt, und jedes Element in der Indextabelle wird als Indexelement (Schlüssel, Adresse) bezeichnet. Vorteile: 1. Schnelles Abrufen. Nachteile: 1. Die zusätzliche Indextabelle beansprucht zusätzlichen Speicherplatz 2. Beim Hinzufügen oder Löschen von Daten müssen Sie auch die Indextabelle ändern, was viel Zeit in Anspruch nimmt.
Hash-Speicher
Berechnen Sie direkt die Speicheradresse des Elements (Hash-Speicher) basierend auf dem Elementschlüsselwort. Vorteile: 1. Der Vorgang zum Hinzufügen, Löschen und Überprüfen von Knoten ist sehr schnell. 1. Wenn die Hash-Funktion nicht gut ist, kann es zu Konflikten kommen Es treten Elementspeichereinheiten auf, und die Lösung des Konflikts erhöht den Zeit- und Platzaufwand.
3. Datenoperationen
Definition von Operationen
Weisen Sie angesichts der logischen Struktur auf die Betriebsfunktion hin
Durchführung von Operationen
Weisen Sie auf die spezifischen Arbeitsschritte für die Speicherstruktur hin
1.2 Algorithmen und Algorithmenbewertung
Es ist eine Beschreibung der Lösung eines bestimmten Problems und eine endliche Folge von Anweisungen.
Zeitkomplexität
Wenn die Problemgröße n ist, ist die Laufzeit proportional zu O(1<log2n<n<nlog2n<n^2<n3<2^n<n!<n^n)
Raumkomplexität
Die Problemgröße beträgt n, der Algorithmus arbeitet zusätzlich zu Eingabe und Programm: Der vom Algorithmus benötigte Hilfsraum ist konstant, d. h. O(1).
2Linearer Tisch
2.1 Definition der linearen Tabelle
Eine endliche Folge von n Datenelementen mit demselben Datentyp
2.2 Sequentielle Darstellung linearer Tabellen
Sequenztabelle
einfügen
Bool ListInsert(SqList &L,int i,ElemType e){ If(i < 1 || i > L.length 1) return false; If(L.length >= MaxSize) return false; For(int j = L.length;j>=i;j- -) //L.length ist die Position nach dem letzten Element im Array-Index L.data[j] = L.data[j-1]; L.data[i-1] = e; L.Länge; return true; }
Zeitkomplexität Bester Schlechtester Durchschnitt O(1) O(n) O(n)
löschen
Bool ListDelete(SqList &L,int i,ElemType &e){ If( i<1 || i>L.length) return false; e = L.data[i-1]; For(int j = i;j<L.length;j) L.data[j-1] = L.data[j]; L.Länge - -; Rückgabe true; }
O(1) O(n) O(n)
Suche nach Wert (sequentiell suchen)
Int LocateElem(SqList L,ElemType e){ For(int i=0;i<L.length;i) If(L.data[i] == e) return i 1; Rückgabe 0; }
O(1) O(n) O(n)
2.3 Verkettete Darstellung linearer Tabellen
Einzelliste typedef struct LNode{ ElemType-Daten; struct LNode *next; }LNode,*LinkList;
Erstellen Sie eine einfach verknüpfte Liste
Methode zum Einsetzen des Kopfes
LinkList List_HeadInsert(LinkList &L){ LNode *s;int x; L = (LNode*)malloc(sizeof(LNode)); L->next = NULL; scanf(“%d”,&x); while(x != 9999){ s = (LNode*)malloc(sizeof(LNode)); s->data = x;s->next = L->next; L->next = s; s->data = x; scanf(“%d”,&x); } return L;}
Methode zum Einführen des Schwanzes
LinkList List_TailInsert(LinkList &L){ int x; L = (LNode*)malloc(sizeof(LNode)); LNode *s,*r = L; scanf(“%d”,&x); while(x != 9999){ s = (LNode*)malloc(sizeof(LNode)); s->data = x; r->next = s; s = r; scanf(“%d”,&x); } r-next = NULL; Rückkehr L: }
Finden Sie Knoten anhand der Seriennummer
LNode *GetElem(LinkList L,int i){ int j = 1; LNode *p = L->next; if(i == 0) return L; if(i < 1) return NULL; while(p && j<i){ p = p ->next; J; } Rückkehr p; }
Knotentabellenknoten nach Wert suchen
LNode *LocateElem(LinkList L,ElemType e){ LNode *p = L->next; while(p != NULL && p->data != e) p = p->next; Rückkehr p; }
Knoten einfügen
p = GetElem(L,i-1); s->next = p->next; p->next = s; (i-1 ist p, eingefügt nach p)
Zeit läuft)
s->next = p->next; p->next = s; temp = p->data; p->data = s->data; s->data = temp; (i ist p, Daten nach p einfügen und austauschen und p-Vorwärtseinfügung implementieren)
Zeit O(1)
Knoten löschen
p = GetElem(L,i-1); q = p->next; p->next = p->next->next; frei(q);
Zeit läuft)
q = p->next; p->data = q->data; p->next = p->next->next; frei(q);
Zeit O(1)
Fragen Sie nach der Tischlänge
Die Tabellenlänge umfasst nicht den Kopfknoten
An)
Doppelt verknüpfte Liste typedef struct DNode{ ElemType-Daten; struct DNode *prior,*next; }DNode,*DLinkList;
Knoten einfügen
s->next = p->next; s->next->prior = s; s->prior = p; p->next = s;
O(1)
Knoten löschen
p->prior->next = p->next; p->next->prior = p->prior; frei(p);
O(1)
zirkuläre verknüpfte Liste Offensichtliche Merkmale: Der letzte Knotenzeiger ist nicht NULL. Die Nullbedingung ist, ob der Kopfknoten gleich dem Kopfzeiger ist, nicht NULL, das heißt, wenn (L->next == L)
Zirkuläre einfach verknüpfte Liste
Zirkuläre doppelt verkettete Liste
statische verknüpfte Liste Verwenden Sie Arrays, um die verknüpfte Speicherstruktur einer linearen Liste zu beschreiben. Merkmale: Der Zeiger ist die relative Adresse des Knotens (Array-Index), auch Cursor genannt. Wie die Sequenzliste muss auch die statische verknüpfte Liste vorab einen kontinuierlichen Speicherplatz zuweisen. #define MaxSize 50 typedef struct{ ElemType-Daten; int next; }SLinkList[MaxSize];
3 Stapel und Warteschlangen
Stapel Cattelan-Zahl: 1/n 1(C2n n)
Sequentielle Speicherung des Stapels (sequentieller Stapel) #define MaxSize 50 typedef struct{ ElemType-Daten[MaxSize]; int top; //Stack-Top-Zeiger }SqStack;
Initialisierung void InitStack(SqStack &S){ oben = -1; }
Der Stapel ist leer bool StackEmpty(SqStack S){ if(S.top == -1) return true; sonst gibt false zurück; }
in den Stapel schieben bool Push(SqStack &S,ElemType x){ if(S.top == MaxSize-1) return false; S.data[ S.top] = x; //top wird auf -1 initialisiert return true; }
Pop bool Pop(SqStack &S,ElemType &x){ if(S.top == -1) return false; x = S.data[S.top- -]; return true; }
Lesen Sie das oberste Element des Stapels bool GetTop(SqStack S,ElemType &x){ if(S.top == -1) return false; x = S.data[S.top]; return true; }
Gemeinsamer Stapel: Nutzen Sie die relativ unveränderte Position der Unterseite des Stapels und teilen Sie zwei aufeinanderfolgende Stapel einen eindimensionalen Array-Raum. Die Unterseiten der beiden Stapel werden an beiden Enden des gemeinsam genutzten Raums festgelegt zwei Stapel erstrecken sich in Richtung der Mitte des gemeinsam genutzten Raums; der gemeinsam genutzte Stapel kann den Speicherplatz effizienter nutzen. Die beiden Stapelräume passen sich aneinander an und ein Überlauf tritt nur auf, wenn der gesamte Speicherplatz voll ist.
Anfänglich: top0 = -1 top1 = MaxSize; Stapel voll: nur, wenn zwei Stapelzeiger benachbart sind top1 - top0 = 1
Kettenstapel typedef struct Linknode{ ElemType-Daten; struct Linknode *next; }*LinkStack;
Warteschlange
Speicherung der Warteschlangenreihenfolge #define MaxSize 50 typedef struct{ ElemType-Daten[MaxSize]; innen vorne, hinten; }SqQueue;
sequentielle Warteschlange
Bedingung für leeres Team: Q.front == Q.rear ==0; beachten Sie, dass Q.rear == MaxSize nicht als Bedingung für die Beurteilung verwendet werden kann, dass das Team voll ist, es kann falsch sein und überlaufen
kreisförmige Warteschlange Die Warteschlangenentfernungs-, Verbindungs- und Volloperationen der zirkulären Warteschlange müssen % haben; die leere Bedingung ist nur Q.rear == Q.front
Initialisierung void InitQueue(SqQueue &Q){ Q.hinten = Q.vorn = 0; }
Das Team ist leer bool isEmpty(SqQueue Q){ if(Q.rear == Q.front) return true; sonst gibt false zurück; }
Tritt dem Team bei bool EnQueue(SqQueue &Q,ElemType x){ if((Q.rear 1)%MaxSize == Q.front) return false; Q.data[Q.rear] = x; Q.rear = (Q.rear 1)%MaxSize; return true; }
Aus der Warteschlange entfernen bool DeQueue(SqQueue &Q,ElemType &x){ if(Q.rear == Q.front) return false; x = Q.data[Q.front]; Q.front = (Q.front 1)%MaxSize; return true; }
Kettenspeicherung von Warteschlangen (Kettenwarteschlange) typedef struct LNode{ ElemType-Daten; struct LNode* next; }LNode; typedef struct{ LNode *vorne,*hinten; }LinkQueue; Die Kettenwarteschlange eignet sich für Situationen, in denen sich Datenelemente relativ stark ändern und es keine Situation gibt, in der die Warteschlange voll ist und einen Überlauf verursacht.
Initialisierung void InitQueue(LinkQueue &Q){ Q.front = Q.rear = (LinkQueue*)malloc(sizeof(LinkQueue)); //Erstelle den Kopfknoten Q.front ->next = NULL; }
Das Team ist leer bool IsEmpty(LinkQueue&Q){ if(Q.front == Q.rear) return true;//Mit Kopfknoten sonst gibt false zurück; }
Tritt dem Team bei void EnQueue(LinkQueue &Q,ElemType x){ LinkNode *s = (LinkNode*)malloc(sizeof(LinkNode)); s->data = x; s->next = NULL;//Erstelle einen neuen Knoten und füge ihn am Ende der Kette ein Q.rear->next = s; Q.rear = s; }
Aus der Warteschlange entfernen bool DeQueue(LinkQueue &Q,ElemType &x){ if(Q.rear == Q.front) return false; LinkNode *p = Q.front->next; x = p->Daten; Q.front->next = p->next; if(Q.rear == p) Q.rear = Q.front;//Die ursprüngliche Warteschlange hat nur einen Knoten frei(p); return true; }
Doppelendige Warteschlange: Eine Warteschlange, die es beiden Enden ermöglicht, Enqueue- und Dequeue-Operationen durchzuführen, ist immer noch eine lineare Struktur.
Eingabebeschränkte Deque
Erlauben Sie Einfügungen und Löschungen an einem Ende, aber nur Löschungen am anderen Ende
Ausgabebeschränkte Deque
Erlauben Sie Einfügungen und Löschungen an einem Ende, aber nur Einfügungen am anderen Ende
Anwendungen von Stapeln und Warteschlangen
Anwendung des Stack-in-Bracket-Matchings
[]()([][]()) ist das richtige Format; (] [) ist das falsche Format
Legen Sie einen leeren Stapel fest und lesen Sie die Klammern nacheinander. Wenn es eine rechte Klammer ist, entfernen Sie es zur Überprüfung. Wenn es übereinstimmt, wird es aus dem Stapel entfernt. Andernfalls wird ein Fehler gemeldet Wenn Sie die Klammer verlassen, schieben Sie sie in den Stapel. Überprüfen Sie am Ende, ob der Stapel leer ist. Wenn er nicht leer ist, stimmt die Klammersequenz nicht überein.
Anwendung des Stapels bei der Ausdrucksauswertung
Infix-Ausdrücke: hängen von der Operatorpriorität ab, behandeln aber auch Klammern Postfix-Ausdrücke: Operatorpriorität bereits berücksichtigt, keine Klammern (Post-Order-Durchlauf des Ausdrucksbaums) Konvertieren Sie den Infix-Ausdruck in den Postfix-Ausdruck
Verarbeiten von Postfix-Ausdrücken: Scannen Sie jedes Element des Ausdrucks nacheinander. Wenn es sich um einen Operanden handelt, verschieben Sie ihn auf den Stapel. Wenn es sich um einen Operator handelt, verlassen Sie kontinuierlich die beiden Operanden Y und X vom Stapel, um X<OP>Y zu bilden. und die Berechnungsergebnisse werden zurück auf den Stapel gelegt
Anwendung des Stapels in der Rekursion
Anwendung der Warteschlange beim hierarchischen Durchlaufen
Der Wurzelknoten tritt in die Warteschlange ein. Wenn die Warteschlange leer ist (alle Knoten wurden verarbeitet), wird der nächste Schritt wiederholt. Wenn ein linker Knoten vorhanden ist, wird der nächste Schritt in der Warteschlange wiederholt. Das linke Kind tritt dem Team bei. Wenn es ein rechtes Kind hat, fügen Sie das rechte Kind dem Team hinzu und kehren Sie zum vorherigen Schritt zurück
Anwendung von Warteschlangen in Computersystemen
1. Lösen Sie das Problem der Geschwindigkeitsinkongruenz zwischen dem Host und externen Geräten. 2. Lösen Sie Ressourcenwettbewerbsprobleme, die durch mehrere Benutzer verursacht werden
1. Host und Drucker: Richten Sie einen Druckdatenpuffer ein. Wenn der Puffer voll ist, unterbricht der Host die Ausgabe der FCFs aus der Warteschlange und sendet nach dem Drucken eine Anfrage an den Host.
2.CPU-Ressourcenzuweisung: FCFS
Komprimierte Lagerung spezieller Matrizen Problemlösung: Zeichnen Sie entsprechend den entsprechenden Bedingungen eine Skizze und ziehen Sie nun eine Schlussfolgerung. Die Hauptsache ist, die Speicherlogik zu verstehen, anstatt sich Formeln zu merken.
Array Eine endliche Folge von n Datenelementen desselben Typs Dimensionsgrenze: der Wertebereich des Index Arrays sind eine Verallgemeinerung linearer Tabellen. Ein Ein-Bit-Array kann als lineare Tabelle betrachtet werden, und ein zweidimensionales Array kann als lineare Tabelle betrachtet werden, deren Elemente ebenfalls lineare Tabellen mit fester Länge sind Wenn das Array definiert ist, ändern sich seine Abmessungen und Grenzen nicht mehr. Daher verfügt das Array zusätzlich zur Initialisierung und Zerstörung nur noch über Operationen zum Zugreifen auf Elemente und zum Ändern von Elementen. Das zweidimensionale Array wird physisch so gespeichert, wie es einem Ein-Bit zugeordnet ist Array!
Array-Speicherstruktur Zeilen-Major; Spalten-Major
Komprimierte Speicherung von Matrizen Komprimierter Speicher: Für mehrere Elemente mit demselben Wert wird nur ein Speicherplatz zugewiesen, und für Nullelemente wird kein Speicherplatz zugewiesen. Spezielle Matrizen: haben viele identische Matrixelemente oder Nullelemente mit regelmäßiger Verteilung
Symmetrische Matrix
A[1 n][1 n] wird in B[n(n 1)/2] gespeichert Entwickeln Sie die Formel auf der einen Seite und vertauschen Sie i und j auf der anderen Seite
Dreiecksmatrix
A[1 n][1 n] wird in B[n(n 1)/2 1] gespeichert Nach der Ableitung der Formel auf der einen Seite speichert die andere Seite nur eine Zahl in n(n 1)/2. Bei der Analyse der spezifischen Situation liegt dies daran, dass der Index bei Null beginnt.
obere Dreiecksmatrix
untere Dreiecksmatrix
tridiagonale Matrix
k=2i j-3 Wenn der Index k eines Ein-Bit-Arrays B i=(k 1)/3 1 ist, ist j=k-2i 3 ! ! ! Nicht auswendig lernen! ! ! Skizzieren Sie zum Verständnis die entsprechenden Zeilen einer tridiagonalen Matrix! ! !
spärliche Matrix Die Anzahl der Nicht-Null-Elemente t in der Matrix ist im Verhältnis zur Anzahl der Matrixelemente s sehr klein, dh die Matrix mit s>>t wird als dünn besetzte Matrix bezeichnet. Beispielsweise hat eine Matrix 100x100 weniger als 100 Elemente ungleich Null.
Verwenden Sie die Triplett-Methode (Zeilenbeschriftung, Spaltenbeschriftung, Wert) oder die Methode der vernetzten Liste
4 Spieße
Definition und Implementierung von String
Definition von String: String wird als String bezeichnet. Die Objekte der nicht-numerischen Verarbeitung auf Computern sind grundsätzlich String-Daten. String (String) S; eine Teilsequenz, die aus mehreren aufeinanderfolgenden Zeichenfolgen in einer Zeichenfolge besteht, wird als Teilzeichenfolge der Zeichenfolge bezeichnet, und die entsprechende Zeichenfolge, die die Teilzeichenfolge enthält, wird als Hauptzeichenfolge bezeichnet. Die logische Struktur einer Zeichenfolge ist der einer linearen Tabelle sehr ähnlich. Der einzige Unterschied besteht darin, dass das Datenobjekt einer linearen Tabelle auf ein einzelnes Element beschränkt ist ist ein Teilstring!
String-Speicherstruktur
Sequentielle Speicherung mit fester Länge #define MaxLen 255 typedef struct{ char ch[MaxLen]; int Länge; }SString; Sie können die Länge auch nicht aufzeichnen und „\0“ verwenden, das nicht in der Zeichenfolgenlänge enthalten ist, um die Zeichenfolgenlänge anzugeben.
Heap-zugewiesene Speicherdarstellung (dynamische Zuweisung) typedef struct{ char*ch; int Länge; }HString; In der C-Sprache wird dynamisch zugewiesener Speicherplatz im Heap gespeichert und von malloc() free() verwaltet. Wenn die Zuweisung fehlschlägt, wird NULL zurückgegeben.
Darstellung der Blockkettenspeicherung: Jeder Knoten wird als Block bezeichnet, und ein Block kann ein Zeichen oder mehrere Zeichen speichern. Die gesamte verknüpfte Liste wird als Blockkettenstruktur bezeichnet. Wenn der letzte Knoten weniger als einen Block einnimmt, wird er normalerweise mit „#“ gefüllt.
Grundlegende String-Operationen Mindestsatz an Operationen: String-Zuweisung, String-Vergleich, Ermitteln der String-Länge, String-Verkettung und Ermitteln von Teilstrings; Das heißt, diese Operationen können nicht mit anderen String-Operationen implementiert werden.
String-Zuweisung StrAssign(&T,chars) weist chars den Wert von T zu
String-Vergleich StrCompare(S,T) wenn S>T,return>0;S=T,return 0;S<T,return<0
Ermitteln Sie die Stringlänge. StrLength(S) gibt die Anzahl der S-Elemente zurück
Reihenverkettung Concat(&T,S1,S2) verwendet T, um eine neue Zeichenfolge zurückzugeben, die durch die Verkettung von S1 und S2 gebildet wird
Suchen Sie die Teilzeichenfolge SubString(&Sub,S,pos,len). Verwenden Sie Sub, um die Teilzeichenfolge zurückzugeben, beginnend mit dem pos-Zeichen der S-Zeichenfolge und weiter mit der Länge von len
String-Muster-Abgleich Der Positionierungsvorgang von Teilzeichenfolgen wird üblicherweise als Mustervergleich von Zeichenfolgen bezeichnet. Gefunden wird die Position der Teilzeichenfolge (oft als Musterzeichenfolge bezeichnet) in der Hauptzeichenfolge.
Einfacher Mustervergleichsalgorithmus
int index(SString S,SString T){ int i=1,j=1; while(i<=S.length && j<=T.length){ if(S.ch[i] == T.ch[j]){ ich ;j ; } anders{ i = i-j 1 1;j = 1; } if(j > T.length) return i - T.length; sonst 0 zurückgeben; } Schlechteste Zeitkomplexität O(mn)
Verbesserter Mustervergleichsalgorithmus – KMP-Algorithmus Während des Matching-Vorgangs werden die bereits erhaltenen Matching-Informationen verwendet, sodass der Haupt-String-Zeiger i nicht mehr zurückläuft, sondern von der aktuellen Position aus weiter rückwärts abgleicht. Die Berechnung der Anzahl der Ziffern zum Zurückschieben des Modus hängt nur von der Struktur des Modus selbst ab! Hat nichts mit der Hauptsaite zu tun Obwohl der Normalmodus O(m n) ist, beträgt die erkannte Zeitkomplexität des KMP-Algorithmus O(n m).
Verwandte konzepte: Präfix: Alle Header-Teilzeichenfolgen der Zeichenfolge mit Ausnahme des letzten Zeichens; Suffix: Alle nachfolgenden Teilzeichenfolgen der Zeichenfolge außer dem ersten Zeichen; Teilweise Übereinstimmung (PM): Die längste gleiche Präfix- und Suffixlänge der Zeichenfolge. Beispiel: 'ababa' Achtung! ! Beginnen Sie mit dem ersten Zeichen der Hauptzeichenfolge, teilen Sie alle Teilzeichenfolgen auf und analysieren Sie dann die Länge des Präfixes und Suffixes bzw. des längsten gleichen Suffixes und Suffixes. Das Präfix und Suffix „a“ ist eine leere Menge, und die längste gleiche Präfix- und Suffixlänge ist 0 „ab“ Präfix „a“ Suffix „b“ Die längste gleiche Übereinstimmungslänge ist 0 'aba' Präfix 'a' 'ab' Suffix 'a' 'ba' {'a' 'ab'} ^ {'a' 'ba'} = 'a', die längste gleiche Suffixlänge ist 1 „abab“ Präfix „a“ „ab“ „aba“ Suffix „b“ „ab“ „bab“, die längste gleiche Präfix- und Suffixlänge beträgt 2 'ababa' Präfix 'a' 'ab' 'aba' 'abab' Suffix 'a' 'ba' 'aba' 'baba' ,{ } ^ { } = {'a' 'aba'}, die längste gleiche Suffixlänge 3 Der teilweise übereinstimmende Wert der endgültigen Zeichenfolge ist also 00123
Wenn Sie den KMP-Algorithmus verwenden, schreiben Sie zuerst die PM-Tabelle der Musterzeichenfolge (Teilzeichenfolge); wenn die Teilzeichenfolge nicht mit der Hauptzeichenfolge übereinstimmt, suchen Sie den PM-Wert des letzten übereinstimmenden Elements der Teilzeichenfolge in der PM-Tabelle und lassen Sie ihn dann Teilzeichenfolge Die zurückgelegte Distanz (Anzahl der übereinstimmenden Zeichen – PM-Wert des letzten übereinstimmenden Zeichens); wenn bei einer bestimmten Fahrt eine Nichtübereinstimmung auftritt und der Teilübereinstimmungswert des entsprechenden Teils 0 ist, bedeutet dies, dass es keine gleichen Suffixe gibt Suffixe in der übereinstimmenden Sequenz sind am größten (übereinstimmende Länge - 0) und werden für den nächsten Vergleich direkt an die Position verschoben, auf die die Hauptzeichenfolge zu diesem Zeitpunkt zeigt. Move = (j-1) - PM[j-1];
Generierung und Änderung des nächsten Arrays: Um die Nichtübereinstimmung zu erleichtern, überprüfen Sie, anstatt sich auf ein Zeichen zu freuen, direkt den PM-Wert der aktuellen nicht übereinstimmenden Zeichenposition, sodass alle Elemente in der PM um 1 Position nach rechts verschoben werden und die erste Position mit -1 gefüllt wird , und die letzte Position wird ignoriert; Wird als nächstes Array bezeichnet, Move = (j-1)-next[j] Zu diesem Zeitpunkt kehrt j zu j = j-Move = next[j] 1; Addiere dann 1 zu allen next und aktualisiere das nächste Array, sodass j = next[j] (Zu diesem Zeitpunkt ist die erste Position des nächsten Arrays 0) Die Bedeutung des letzten nächsten Arrays: Wenn das j-te Zeichen der Teilzeichenfolge nicht übereinstimmt, springen Sie zur nächsten [j]-Position der Teilzeichenfolge für die nächste Übereinstimmungsrunde! ! !
Code mit dem letzten nächsten Array: int index_KMP(String T,String S,int next[]){ int i=1,j=1; while(i<=S.length && j<=T.length){ if(S.ch[i] == T.ch[j]){i ;j ;} sonst j = next[j]; } if(j > T.length) return i-T.length; sonst 0 zurückgeben; }
Weitere Optimierung des KMP-Algorithmus Beispiel: Wenn die Hauptzeichenfolge „aaabaaaab“ und die Musterzeichenfolge „aaaab“ übereinstimmen, liegt an der vierten Position eine Nichtübereinstimmung vor. Wenn das Array next[j] oben verwendet wird, wird es jedes Mal um eins nach links verschoben dann wird verglichen; es erscheint jedoch Pj = Pnext[j], was zu einem redundanten Vergleich führt. Lösung: Stellen Sie sicher, dass Pj = Pnext[j] nicht angezeigt wird. Wenn dies der Fall ist, führen Sie die Rekursion erneut durch, korrigieren Sie next[j] in next[next[j]] und nennen Sie es nextval[j].
5 Bäume und binäre Bäume
Bäume und Wald
Konzept
Ein Baum ist eine endliche Menge von n (n>0) Knoten. Wenn n = 0 ist, wird er als leerer Baum bezeichnet. Er hat nur einen Wurzelknoten und die verbleibenden Knoten können in disjunkte endliche Mengen, sogenannte Teilbäume, unterteilt werden. 1. Der Wurzelknoten des Baums hat keinen Vorgänger und die Knoten außer dem Wurzelknoten haben nur einen Vorgänger. 2. Alle Knoten im Baum können null oder mehr Nachfolger haben. Der Baum ist für die Darstellung von Daten mit a geeignet hierarchische Struktur; 3. Terminologie: 1. Vorfahr-Nachkomme (kann mehrere Ebenen umfassen, solange eine Top-Down-Beziehung besteht (mit demselben Elternknoten); 2. Die Anzahl der Kinder eines Knotens im Baum wird als Grad des Knotens bezeichnet. Der maximale Grad eines Knotens im Baum ist der Grad des Baums. 3. Knoten mit einem Grad > 0 werden als Verzweigungsknoten (nicht terminale Knoten) bezeichnet; die Anzahl der Zweige eines Verzweigungsknotens ist der Grad des Verzweigungsknotens; 4. Die Knotenebene wird ausgehend von der Wurzel des Baums definiert, wobei der Wurzelknoten die Knoten der ersten Ebene ist, deren übergeordnete Knoten sich auf derselben Ebene befinden und Cousinen voneinander sind. Die Tiefe eines Knotens wird Schicht für Schicht akkumuliert, beginnend beim Wurzelknoten und von oben nach unten (stellen Sie sich vor, die Wurzeln des Baums werden nach unten tiefer). Die Höhe des Knotens wird Schicht für Schicht akkumuliert, beginnend am Blattknoten und von unten nach oben (von unten nach oben sieht es sehr hoch aus). Die Höhe oder Tiefe eines Baums ist die maximale Anzahl der Knotenebenen im Baum; 4. Geordnete Bäume und ungeordnete Bäume, geordnete Bäume sind nicht austauschbar 5. Pfad und Pfadlänge: Der Pfad besteht aus der Folge von Knoten, die zwischen den beiden Knoten durchlaufen werden, und die Pfadlänge ist die Anzahl der durchlaufenen Kanten! 6. Wald: eine Sammlung von m disjunkten Bäumen. Solange der Wurzelknoten gelöscht wird, wird der Baum zu einem Wald. Wenn umgekehrt ein Wurzelknoten zu m Bäumen hinzugefügt wird, wird der Wald zu einem Baum.
Natur
Natur: 1. Die Anzahl der Knoten des Baums ist gleich der Summe der Grade aller Knoten (Gesamtzahl der Zweige) 1 (Wurzelknoten) 2. Die i-te Ebene eines Baums vom Grad m hat höchstens m^i-1 Knoten (i>1) 3. Ein m-ärer Baum mit der Höhe h hat höchstens (m^i - 1)/(m-1) Knoten. 4. Die minimale Höhe eines m-ären Baums mit n Knoten beträgt
Speicherstruktur
1. Sequentielle Speicherung: Übergeordnete Notation: Eine große Struktur legt eine kleine Struktur fest, und die kleine Struktur besteht aus Daten und übergeordneten Zeigern. Die große Struktur ist ein Array, das aus mehreren kleinen Strukturen und der Anzahl der Knoten besteht. #define Max_Tree_SIze 100 typedef struct{ ElemType-Daten; int parent; }PTNode; typedef struct{ PTNode-Knoten[Max_Tree_Size]; int n; }PTree; Diese Struktur nutzt die Tatsache aus, dass jeder Knoten (mit Ausnahme des Wurzelknotens) nur einen übergeordneten Knoten hat und den übergeordneten Knoten jedes Knotens schnell erhalten kann, erfordert jedoch das Durchlaufen des gesamten Baums, um die untergeordneten Knoten des Knotens zu finden.
2. In der untergeordneten Darstellung: Verbinden Sie die untergeordneten Knoten jedes Knotens mit einer einzelnen verknüpften Liste, um eine lineare Struktur zu bilden. Es gibt n verknüpfte Listen für n Knoten (die untergeordnete verknüpfte Liste eines Blattknotens ist eine leere verknüpfte Liste). Diese Methode ist sehr einfach, um Kinder zu finden, aber um Eltern zu finden, müssen die n untergeordneten verknüpften Listen durchlaufen werden, auf die die Zeiger der untergeordneten verknüpften Listen in n Knoten zeigen. Ähnlich wie eine kritische Tabelle
3. Darstellung des Kindesbruders: Auch als binäre Baumdarstellung bekannt. typedef struct CSNode{ ElemType-Daten; struct CSNode *firstchild,*nextsibling; }CSNode,*CSTree; Implementieren Sie den Vorgang der Konvertierung eines Baums in einen Binärbaum bequem Das linke Kind hat einen Bruder
Operationen: Umwandlung von Bäumen, Wäldern und Binärbäumen
Konvertierung zwischen Bäumen und Binärbäumen; sowohl Bäume als auch Binärbäume können durch binär verknüpfte Listen dargestellt werden. Die physikalische Struktur ist dieselbe, aber die Regeln für die Konvertierung eines Baums in einen Binärbaum sind unterschiedlich. Da der Wurzelknoten keine Brüder hat, gibt es für den Wurzelknoten keine rechten Handschriftkonvertierungsschritte: 1. Verbinden Sie eine Linie zwischen Geschwisterknoten. 2. Behalten Sie nur die Verbindung zwischen jedem Knoten und seinem ersten untergeordneten Knoten bei und verwenden Sie andere. Löschen Sie alle Verbindungen der untergeordneten Knoten 3. Nehmen Sie die Wurzel des Baumes als Achse und drehen Sie sie um 45 Grad im Uhrzeigersinn
Konvertierung von Wald in Binärbaum 1. Konvertieren Sie zunächst jeden Baum im Wald in einen Binärbaum 2. Die Wurzel jedes Baums wird als Geschwisterbeziehung betrachtet, und die nachfolgenden Bäume werden nacheinander als rechte Knoten des vorherigen Baums betrachtet. 3. Drehen Sie die Wurzel des ersten Baumes um 45 Grad im Uhrzeigersinn
Konvertieren Sie einen Binärbaum in eine Gesamtstruktur: 1. Trennen Sie nacheinander den rechten Teilbaum des Wurzelknotens, bis nur noch ein Wurzelknoten ohne rechten Teilbaum vorhanden ist. 2. Konvertieren Sie alle getrennten Binärbäume in Bäume, um den ursprünglichen Wald zu erhalten Das Konvertieren eines Binärbaums in einen Baum oder eine Gesamtstruktur ist einzigartig
Konvertieren Sie einen Binärbaum in einen Baum 1. Entfernen Sie alle rechten Knoten, rechten Knoten und rechten Knoten des linken Teilbaums des Wurzelknotens. . . alle als Kinder der Wurzel 2. Nacheinander anpassen
Traverse
Baumdurchquerung
Zuerst Root-Traversierung Entspricht der Durchquerung des entsprechenden Binärbaums vor der Bestellung
Back-Root-Traversal Entspricht dem Durchlaufen des entsprechenden Binärbaums in der richtigen Reihenfolge! ! ! ! !
Leveldurchquerung
Unterwegs durch den Wald
Durchquerung des Waldes vorbestellen Jeder Baum wird von vorne nach hinten mit der Wurzel zuerst durchquert.
Der Reihe nach den Wald durchqueren Durchlaufen Sie die Wurzel jedes Baums von vorne nach hinten (im Wald heißt es, dass die Durchquerung in der richtigen Reihenfolge relativ zum entsprechenden Binärbaum ist).
Anwendung von Bäumen – Gewerkschaftssuche
Union-Find ist eine einfache Mengendarstellung, die drei Operationen unterstützt Die Strukturdefinition des Union-Suchsatzes: #define GRÖSSE 100 int UFSets[SIZE]; Eine Menge in der Union-Suche ist ein Baum, und mehrere Mengen bilden einen Wald.
Initialisierungsoperation (S ist ein Vereinigungssuchsatz), initialisiert jedes Element im Satz S zu einer Teilmenge mit nur einem einzigen Element, S ist der gesamte Satz, der eine Gesamtstruktur ist und im übergeordneten Darstellungsarray gespeichert ist (beide sind - 1 zu diesem Zeitpunkt) void Initial(int S[]){ for(int i=0;i<size;i) S[i] = -1; }
Die Suchoperation findet die Untersammlung, in der sich das einzelne Element x in der Menge S befindet, und gibt den Namen der Untersammlung zurück. int Find(int S[],int x){ while(S[x]>=0)//Schleife, um die Wurzel von x zu finden x = S[x];//Ordne x der Wurzel von x zu x zurückgeben; }
Bei der Vereinigungsoperation wird die Teilmenge root2 in der S-Menge mit root1 zusammengeführt. Dabei dürfen sich root1 und root2 nicht überschneiden, da sie sonst nicht zusammengeführt werden. void Union(int S[],int Root1,int Root2){ //Erfordert, dass Root1 und Root2 unterschiedlich sind S[Root2] = Root1; //Root2 mit einem anderen Root1 verbinden } Der Wert des Wurzelknotens jedes Baums in der Gesamtstruktur im übergeordneten Darstellungsarray ist negativ und der absolute Wert ist die Gesamtzahl der Knoten unter der Wurzel.
Binärbaum
Konzept: Jeder Knoten hat höchstens zwei Teilbäume und die Reihenfolge kann nicht umgekehrt werden. Die Reihenfolge der Knoten in einem Binärbaum ist nicht relativ zu einem anderen Knoten, sondern wird bestimmt. (Unterschiedlich zu einem Baum der Stufe 2); Mehrere spezielle Binärbäume: 1. Vollständiger Binärbaum: Jede Ebene enthält die meisten Knoten 2. Vollständiger Binärbaum: 1-n Sequenznummern entsprechen vollständig den Knoten im vollständigen Binärbaum 3. Die Schlüssel aller Knoten im linken Teilbaum des binären Sortierbaums sind kleiner als die Schlüssel des Wurzelknotens und die Schlüssel rechts sind größer als die Wurzel. 4. Ausgeglichener Binärbaum: Der Tiefenunterschied zwischen dem linken Teilbaum und dem rechten Teilbaum eines Knotens im Baum überschreitet nicht 1 5. Hinweis auf den Binärbaum
Hinweis-Binärbaum (unbekannt, daher speziell erweitert) typedef struct TreadNode{ ElemType-Daten; struct TreadNode *lchild,*rchild; int ltag,rtag; }ThreadNode,*ThreadTree; Eine binär verknüpfte Liste, die aus einer Knotenstruktur als Speicherstruktur des Binärbaums besteht, wird als verknüpfte Hinweisliste bezeichnet. Die Zeiger, die auf den Vorgänger und Nachfolger des Knotens zeigen, werden als Hinweise bezeichnet. Ein Binärbaum mit Hinweisen wird als Hinweis-Binärbaum bezeichnet
Merkmale und Definition: Verwenden Sie den Nullzeiger des Binärbaums, um den Vorgänger- oder Nachfolgerzeiger zu speichern, sodass er genauso einfach durchlaufen werden kann wie das Durchlaufen einer verknüpften Liste. Der Clue-Binärbaum beschleunigt das Finden des Vorgängers und Nachfolgers eines Knotens! Regelung: Wenn es keinen linken Teilbaum gibt, soll lchild auf seinen Vorgängerknoten zeigen; wenn es keinen rechten Teilbaum gibt, soll rchild auf seinen Nachfolgerknoten zeigen; (lchild,ltag,data,rtag,rchild) ltag = 0,lchild bezieht sich auf das linke Kind, ltag = 1,lchild bezieht sich auf den Knotenvorgänger rtag = 0, rchild bezieht sich auf das rechte Kind, rtag = 1, rchild bezieht sich auf den Nachfolger des Knotens
Konstruktion eines geordneten Hinweis-Binärbaums Das Wesentliche beim Erkennen eines Binärbaums besteht darin, den Binärbaum einmal zu durchlaufen void InTread(ThreadTree &p,ThreadTree &pre){ if(p!=NULL){ InThread(p->lchild,pre); if(p->lchild == NULL){ p->lchild = pre; p->ltag = 1; } if(pre!=NULL && pre->next->rchild == NULL){ pre->rchild = p; pre->rtag = 1; } pre = p; InThread(p->rchild,pre); } } void CreateInThread(ThreadTree T){ ThreadTree pre = NULL; if(T!=NULL){ InThread(T,pre); pre->rchild = NULL; pre->rtag = 1; } } Sie können der Hinweisliste des Binärbaums auch einen Kopfknoten hinzufügen, lchild auf den Wurzelknoten des Binärbaums verweisen lassen und rchild auf den zuletzt besuchten Knoten des Binärbaums verweisen; Darüber hinaus verweisen sowohl das lchild des zuerst besuchten Knotens als auch das rchild des zuletzt besuchten Knotens im Binärbaum auf den Kopfknoten. Es wird zu einer bidirektional verknüpften Hinweisliste, die das Durchlaufen des Hinweis-Binärbaums von vorne nach hinten und von hinten nach vorne erleichtert.
Durchquerung binärer Bäume mit Inorder-Hinweisen Die Knoten des In-Order-Clue-Binärbaums verbergen die Vorgänger- und Nachfolgerinformationen des Clue-Binärbaums. 1. Suchen Sie den ersten Knoten unter der In-Order-Sequenz im In-Order-Hinweis-Binärbaum: ThreadNode *Firstnode(ThreadNode *p){ while(p->ltag == 0) p = p->lchild; Rückkehr p; }//Suche den unteren linken Knoten (nicht unbedingt ein Blattknoten) 2. Suchen Sie den Nachfolger von Knoten p im In-Order-Clue-Binärbaum unter der In-Order-Sequenz ThreadNode *Nextnode(ThreadNode *p){ if(p->rtag == 0) return Firstnode(p->rchild); sonst return p->rchild; } 3. Hauptfunktion void Inorder(ThreadNode *T){ for(ThreadNode *p = Firstnode(T);p!=NULL;p-Nextnode(p)) Besuch(p); }
Hinweis-Binärbaum vorbestellen und Hinweis-Binärbaum nachbestellen Sie müssen lediglich das Codesegment der Thread-Transformation und die Position ändern, an der die rekursiven Funktionen des linken und rechten Thread-Teilbaums aufgerufen werden. Die Richtung, die auf NULL zeigt, kann unterschiedlich sein. Ein Vorgänger bezieht sich auf NULL und ein Nachfolger bezieht sich auf NULL. Der spezifische Vorgänger und Nachfolger muss entsprechend der Durchlaufreihenfolge im Detail analysiert werden; Besonderheit: Um den Nachfolger im Post-Order-Clue-Binärbaum zu finden, müssen Sie die Knoteneltern kennen, das heißt, Sie müssen eine verknüpfte Dreizackliste mit einem Flag-Feld als Speicherstruktur verwenden (die verknüpfte Dreizackliste hat drei Felder). , und ein Zeiger auf den übergeordneten Knoten wird hinzugefügt)
Natur: 1. Die Anzahl der Blattknoten eines nicht leeren Binärbaums ist gleich der Anzahl der Knoten mit dem Grad 2 1, d. h. n0 = n2 1 2. Die k-te Ebene eines nicht leeren Binärbaums hat höchstens 2^(k-1) Knoten. 3. Ein nicht leerer Binärbaum mit der Höhe h hat höchstens 2^h-1 Knoten. 4. Vollständiger Binärbaum: i>1 Der übergeordnete Knoten ist durch i/2 begrenzt. i ist eine gerade Zahl, i/2; i ist eine ungerade Zahl, (i-1)/2 2i<=n Das linke Kind von i ist 2i, andernfalls gibt es kein linkes Kind 2i 1<=n Das rechte Kind von i ist 2i 1, sonst gibt es kein richtiges Kind 5. Zwei Formeln: 2^(h-1)-1<n<=2^h-1;2^(h-1) <=n <2^h
Lagerstruktur: 1. Sequentielle Speicherstruktur: Für sequentielle Speicherstrukturen eignen sich vollständige Binärbäume und allgemeine Binärbäume. Es wird empfohlen, die Speicherung mit dem Array-Index 1 zu beginnen, was den Eigenschaften von entspricht ein vollständiger Binärbaum i 2. Kettenspeicherstruktur: Die Speicherplatznutzung ist höher als die sequentielle Speicherstruktur (allgemeiner Binärbaum). typedef struct BiTNode{ ElemType-Daten; struct BiTNode *lchild,*rchild; }BiTNode,*BiTree;
arbeiten
Traverse
DFS Jeder Knoten wird einmal und nur einmal besucht. Die Zeitkomplexität und die Raumkomplexität sind beide O(n). Die Tiefe des rekursiven Arbeitsstapels ist die Tiefe des Baums.
Durchquerung vorbestellen void PreOrder(BiTree T){ if(T!=NULL){ Besuch(T); Vorbestellung(T->lchild); PreOreder(T->rchild); } } Nicht rekursiv: void PreOrder2(BiTree T){ InitStack(S);BiTree p = T; while(p || !isEmpty(S)){ wenn P){ visit(p);Push(S,p); p = p->lchild; } anders{ Pop(S,p); p = p->rchild; } } }
Inorder-Durchquerung void InOrder(BiTree T){ if(T!=NULL){ InOrder(T->lchild); Besuch(T); InOrder(T->rchild); } } Nicht rekursiv: void InOrder2(BiTree T){ InitStack(S);BiTree p = T; while(p || !isEmpty(S)){//p ist nicht leer oder der Stapel ist keine leere Schleife if(p){ //Ganz nach links Push(S,p); p = p->lchild; } anders{ Pop(S,p);visit(p); p = p->rchild; //p geht zum rechten Teilbaum } } }
Postorder-Durchquerung void PostOrder(BiTree T){ if(T!=NULL){ PostOrder(T->lchild); PostOrder(T->rchild); Besuch(T); } }
BFS
Leveldurchquerung void LevelOrder(BiTree T){ InitQueue(Q); BiTree p = T; Enqueue(Q,p); while(!isEmpty(Q)){ Aus der Warteschlange entfernen(Q,p); Besuch(p); if(p->lchild != NULL) Enqueue(Q,p->lchild); if(p->rchild != NULL) Enqueue(Q,p->rchild); } }
Anwendung
Binärer Sortierbaum BST (binärer Suchbaum)
BST-Suche BSTNode *BST_Search(BiTree T,ElemType-Schlüssel){ while(T!=NULL&&key!=T->data){ if(key<T.data) T = T->lchild; sonst T=T->rchild; } Rückkehr T; }
BST-Einfügung: Der eingefügte Knoten muss ein neu hinzugefügter Blattknoten sein int BST_Insert(BiTree &T,KeyType k){ if(T==NULL){ T = (BiTree)malloc(sizeof(BSTNode)); T->key = k; T->lchild = T->rchild = NULL; Rückgabe 1; } sonst if(k==T->key) return 0; else if(k<T->key) return BST_Insert(T->lchild,k); sonst return BST_Insert(T->rchild,k); }
Struktur von BST void Create_BST(BiTree &T,KeyType str[],int n){ T = NULL; int i =0; while(i<n){ BST_Insert(T,str[i]); ich; } }
Löschung von BST
Der gelöschte Knoten ist ein Blattknoten und wird direkt gelöscht.
Wenn der gelöschte Knoten z nur einen linken Teilbaum oder rechten Teilbaum hat, soll sein linker Teilbaum oder rechter Teilbaum die von z ersetzen
Wenn der gelöschte Knoten z sowohl linke als auch rechte Teilbäume hat, ersetzen Sie z durch den direkten Nachfolger (oder direkten Vorgänger) von z und löschen Sie dann diesen direkten Nachfolger (oder direkten Vorgänger) aus dem BST, wodurch zu den ersten beiden Situationen gewechselt wird.
BST-Sucheffizienzanalyse
Die Sucheffizienz von BST hängt hauptsächlich von der Höhe des Baums ab Wenn der Höhenunterschied zwischen dem linken und dem rechten Teilbaum des BST niemals 1 überschreitet, wird ein solcher binärer Sortierbaum als ausgeglichener Binärbaum bezeichnet und die durchschnittliche Suchlänge beträgt O (log2n). Wenn der BST ein einzelner Baum ist, beträgt die durchschnittliche Suchlänge O(n)
Die durchschnittliche Suchlänge ASL=∑PiCi (i=1,2,3,…,n)Pi ist die Wahrscheinlichkeit des i-ten Datenelements in der Nachschlagetabelle und Ci ist die Anzahl der Vergleiche, die beim Finden durchgeführt wurden das i-te Datenelement.
Vergleich zwischen BST und binärer Suche: Der Entscheidungsbaum der binären Suche ist eindeutig, die Suche nach BST ist jedoch nicht eindeutig (die Einfügereihenfolge beeinflusst die Baumform). Einfügen und Löschen: Das Objekt der binären Suche ist eine geordnete Sequenzliste, für die BST den Knoten nicht verschieben muss, sondern nur den Zeiger ändern muss, um ihn abzuschließen, nämlich O (log2n). Zusammenfassung: Wenn es sich bei der geordneten Tabelle um eine statische Nachschlagetabelle handelt, eignet sie sich für die sequentielle Tabellenspeicherung und verwendet die binäre Suche. Wenn es sich bei der geordneten Tabelle um eine dynamische Nachschlagetabelle handelt, sollte ein binärer Sortierbaum als logische Struktur ausgewählt werden.
ausgeglichener Binärbaum
Entwickelt, um eine Leistungseinbuße binärer Sortierbäume zu vermeiden Definition: Der absolute Wert des Höhenunterschieds zwischen dem linken und dem rechten Wort eines Knotens überschreitet nicht 1, was als ausgeglichener Baum bezeichnet wird. Der Höhenunterschied zwischen dem linken Teilbaum und dem rechten Teilbaum eines Knotens ist der Gleichgewichtsfaktor des Knotens. Der Gleichgewichtsfaktor eines ausgeglichenen Binärbaumknotens kann daher nur -1 0 1 sein leerer Baum.
Einfügung in einen ausgeglichenen Binärbaum: Das Ziel jeder Anpassung ist der minimale unausgeglichene Teilbaum, dh der Teilbaum mit dem Knoten, dessen Absolutwert des Ausgleichsfaktors, der dem eingefügten Knoten auf dem Einfügepfad am nächsten liegt, größer als 1 ist, als Wurzel! Die erste Hälfte des Einfügevorgangs eines ausgeglichenen Binärbaums ist der gleiche wie der eines binär sortierten Baums. Wenn jedoch nach dem Einfügen ein Ungleichgewicht auftritt, wird er in vier Situationen unterteilt. (Verbesserte BST); Hier bezieht sich LL RR LR RL auf die Position des eingefügten Knotens relativ zum Wurzelknoten des minimal unausgeglichenen Teilbaums (die Position eines bestimmten Teilbaums eines untergeordneten Baums).
LL-ausgeglichene Rotation (rechte Einzelrotation) Der Knoten wird unten links (nicht unbedingt direkt angrenzend) des linken Teilbaums von A eingefügt (muss direkt daneben liegen), und der B-Knoten befindet sich am ersten L (das linke Kind des minimal unausgeglichenen Teilbaum-Wurzelknotens). nach rechts gedreht, um A zu ersetzen, und A ist Da das rechte Kind von B, hatte B ursprünglich Kinder als linken Teilbaum von A.
RR-Balanced-Spin (linker Single-Spin) Der Knoten wird unten rechts vom rechten Kind von A eingefügt. Der B-Knoten am ersten R wird nach links gedreht, um A zu ersetzen. A wird als linker Teilbaum von B verwendet, und der ursprüngliche linke Teilbaum von B wird als verwendet rechter Teilbaum von A. ps: Sowohl der LL- als auch der RR-Modus helfen dem linken/rechten Kind B des unausgeglichenen Teilbaumknotens A, „heraufzukommen“.
LR-ausgeglichene Drehung (doppelte Drehung zuerst nach links und dann nach rechts) Ein Knoten wird in den rechten Teilbaum des linken untergeordneten Knotens A eingefügt. Drehen Sie zunächst den Wurzelknoten C des rechten Teilbaums von A's linkem Kind B nach links oben zur Position von B und dann den C-Knoten nach rechts oben zur Position des A-Knotens.
RL-ausgeglichene Rotation (rechts und dann links doppelte Rotation) Ein Knoten wird in den linken Teilbaum des rechten untergeordneten Knotens A eingefügt. Drehen Sie zuerst den linken Teilbaum-Wurzelknoten C des rechten untergeordneten Knotens B B von Knoten A an die Position von B und dann C an die Position von Knoten A. ps: Wenn LR und RL rotiert werden, hat es keinen Einfluss auf den Rotationsprozess, ob der neue Knoten in den linken Teilbaum von C oder den rechten Teilbaum von C eingefügt wird. Die LR- und RL-Modi sollen dabei helfen, den Wurzelknoten C des rechten/linken Teilbaums des linken/rechten Kinds B des unausgeglichenen Teilbaums „oben“ zu halten.
Finden Sie einen ausgeglichenen Binärbaum Identisch mit der binär sortierten Baumsuche; Die maximale Tiefe eines ausgeglichenen Binärbaums mit n Knoten beträgt O(log2n), daher beträgt die maximale Suchlänge auch O(log2n); Die maximale Anzahl an Vergleichen, die erforderlich sind, um einen ausgeglichenen Binärbaum mit einer bestimmten Anzahl von Knoten zu finden.
Ein ausgeglichener Binärbaum der Höhe h Es gibt höchstens 2^h - 1 Knoten; Es gibt mindestens h(n) Knoten, h(n)=h(n-1) h(n-1) 1,h(0)=0,h(1)=1,h(2)=2c
roter schwarzer Baum
Rot-schwarzes Baumkonzept
Grundlegende Operationen rot-schwarzer Bäume
Rot-Schwarz-Baum-Eigenschaften
Anwendungen von rot-schwarzen Bäumen
Leistungsanalyse des Rot-Schwarz-Baums
Huffman-Bäume und Huffman-Codierung
Definition des Huffman-Baums Einem Knoten im Baum wird ein Wert zugewiesen, der eine bestimmte Bedeutung darstellt, die als Gewicht des Knotens bezeichnet wird. Das Produkt aus der Pfadlänge von der Wurzel des Baums zu einem beliebigen Knoten (Anzahl der durchlaufenen Kanten) und der Gewichtung des Knotens wird als gewichtete Pfadlänge des Knotens bezeichnet. Die Summe der gewichteten Pfadlängen aller Blattknoten im Baum wird als gewichtete Pfadlänge des Baums bezeichnet und als WPL (Weighted Path Length of Tree) aufgezeichnet. In einem Binärbaum mit n gewichteten Blattknoten wird der Binärbaum mit der kleinsten gewichteten Pfadlänge WPL als Huffman-Baum bezeichnet! Wird auch optimaler Binärbaum genannt
Die Struktur des Huffman-Baums Gegeben sind n Knoten mit den Gewichten w1,w2,..,wn bzw. Erstellen Sie einen neuen Wurzelknoten und wählen Sie jedes Mal die beiden Knoten mit dem kleinsten Gewicht aus, um einen Binärbaum zu bilden, und behandeln Sie sie als neue Wurzelknotengewichte in der ursprünglichen Reihe Der Wurzelknoten wird gelöscht und ein neuer Wurzelknoten mit Gewicht wird hinzugefügt. Wiederholen Sie diesen Vorgang, bis keine unabhängigen Knoten mehr vorhanden sind
Eigenschaften von Huffman-Bäumen 1. Jeder Anfangsknoten wird schließlich zu einem Blattknoten. Je kleiner das Gewicht, desto größer die Pfadlänge vom Knoten zum Wurzelknoten. 2. Während des Konstruktionsprozesses werden insgesamt n-1 neue Knoten (Doppelzweigknoten) erstellt, sodass die Gesamtzahl der Knoten des Huffman-Baums 2n-1 beträgt 3. Jede Konstruktion wählt 2 Bäume als Kinder des neuen Knotens aus, sodass es im Huffman-Baum keinen Knoten mit Grad 1 gibt.
Huffman-Codierung
Mehrere verwandte Konzepte: Codierung mit fester Länge: binäre Darstellung jedes Zeichens gleicher Länge Codierung mit variabler Länge: Verschiedene Zeichen werden durch Binärbits unterschiedlicher Länge dargestellt Die Codierung mit variabler Länge ist viel besser als die Codierung mit fester Länge, da Zeichen mit hoher Häufigkeit kurze Codes und Codes mit niedriger Häufigkeit lange Codes zugewiesen werden können, wodurch die durchschnittliche Codierungslänge von Zeichen verkürzt und Daten komprimiert werden Die Huffman-Kodierung ist eine weit verbreitete und sehr effektive Datenkomprimierungskodierung Keiner der Codes ist der Code des Präfixes, es ist der Code des Präfixes; dekodieren Sie ihn: Identifizieren Sie ihn von vorne nach hinten und übersetzen Sie ihn in den Originalcode
Der Huffman-Baum geht von der Wurzel nach unten und weist einer Seite 0 und der anderen Seite 1 zu (entweder 0 oder 1 links und rechts, solange sie immer einheitlich sind); Die WPL des Huffman-Baums kann als die durch die endgültige Codierung erhaltene Binärcodelänge betrachtet werden. Huffman-Bäume sind nicht eindeutig, WPL muss optimal sein
6 Bilder
Grundlegende Konzepte von Graphen
G=(V,E), Scheitelpunktmenge V, Kantenmenge E; V(G), stellt die endliche nichtleere Menge von Scheitelpunkten im Diagramm dar. E(G) stellt die Menge von Beziehungen (Kanten) zwischen den Scheitelpunkten des Diagramms dar G; |V| stellt die Anzahl der Eckpunkte dar, |E| stellt die Anzahl der Kanten dar Eine lineare Tabelle kann eine leere Tabelle sein, und ein Baum kann ein leerer Baum sein, aber ein Diagramm kann kein leeres Diagramm sein. Die Scheitelpunktmenge V im Diagramm darf nicht leer sein und E kann leer sein.
gerichteter Graph, ungerichteter Graph Gerichteter Graph: E ist eine gerichtete Kante (Bogen) und ein Bogen ist ein geordnetes Eckpunktpaar, bezeichnet als <v,w> Ungerichteter Graph: E ist eine ungerichtete Kante (Kante) und eine Kante ist ein ungeordnetes Scheitelpunktpaar, aufgezeichnet als (v, w)
Einfaches Diagramm, mehrere Diagramme Einfaches Diagramm: 1. Es gibt keine doppelten Kanten (Kanten, die zwischen zwei benachbarten Knoten in einem gerichteten Diagramm aufeinander zeigen, werden nicht als doppelte Kanten gezählt) 2. Es gibt keine Kante vom Scheitelpunkt zu sich selbst. Mehrere Grundstücke: 1. Die Anzahl der Kanten zwischen zwei Eckpunkten ist größer als 1 2. Erlauben Sie, dass Scheitelpunkte durch eine Kante mit sich selbst in Beziehung gesetzt werden Die Definitionen mehrerer Diagramme und einfacher Diagramme sind relativ. In der Datenstruktur werden nur einfache Diagramme erläutert.
Vollständiges Diagramm (einfaches vollständiges Diagramm) Für ungerichtete Graphen beträgt der Wertebereich von |E| 0-n(n-1)/2, Ein ungerichteter Graph mit n(n-1)/2 Kanten wird als vollständiger Graph bezeichnet, und es gibt eine Kante zwischen zwei beliebigen Eckpunkten; Für gerichtete Graphen beträgt der Wertebereich von |E| 0-n(n-1), Ein gerichteter Graph mit n(n-1) Bögen wird als gerichteter vollständiger Graph bezeichnet, und zwischen zwei beliebigen Eckpunkten gibt es einen entgegengesetzten Bogen.
Nebenhandlung G=(V,E),G'=(V',E'); Wenn V‘ eine Teilmenge von V und E‘ eine Teilmenge von E ist, dann heißt G‘ ein Teilgraph von G. Wenn V(G')=V(G) erfüllt ist, dann wird G' der erzeugte Teilgraph von G genannt; Nicht jede Teilmenge von V und E kann einen G-Teilgraphen bilden, da die Eckpunkte, die einigen Kanten in der Teilmenge von E zugeordnet sind, möglicherweise nicht in dieser Teilmenge von V enthalten sind.
Konnektivität, verbundene Diagramme und verbundene Komponenten Wenn es einen Weg vom Scheitelpunkt v zum Scheitelpunkt w gibt, dann werden v und w als verbunden bezeichnet. Wenn zwei beliebige Knoten in G verbunden sind, handelt es sich um einen verbundenen Graphen, andernfalls handelt es sich um einen nicht zusammenhängenden Graphen. Der maximal zusammenhängende Teilgraph in einem ungerichteten Graphen wird als Zusammenhangskomponente bezeichnet. Ein zusammenhängender Graph hat mindestens n-1 Kanten. Wenn die Anzahl der Kanten kleiner als n-1 ist, muss es sich um einen nicht zusammenhängenden Graphen handeln. Wie viele Kanten kann ein unzusammenhängender Graph maximal haben? : Der kritische Zustand von n-1 Eckpunkten, die einen vollständigen Graphen bilden (es kann jederzeit eine Kante hinzugefügt werden, um einen verbundenen Graphen zu bilden)
Maximal verbundener Untergraph: Ein verbundener Untergraph in Diagramm G, der nicht in anderen verbundenen Untergraphen enthalten ist (Maximum ist hier nicht das Maximum einer bestimmten Menge, sondern eine nicht enthaltene Beziehung, bei der es sich tatsächlich um alle Scheitelpunkte im verbundenen Untergraphen und in der Kante handelt existieren) Minimal zusammenhängender Teilgraph: Der Spannbaum des Graphen G ist ein minimal zusammenhängender Teilgraph
Stark zusammenhängender Graph, stark zusammenhängende Komponenten Wenn in einem gerichteten Graphen ein Paar von Eckpunkten v nach w und w nach v Pfade haben, dann nennt man die beiden Eckpunkte stark verbunden. Wenn ein Knotenpaar im Graphen stark verbunden ist, wird der Graph als stark verbundener Graph bezeichnet. Der maximal stark verbundene Teilgraph in einem gerichteten Graphen wird als stark verbundene Komponente des gerichteten Graphen bezeichnet; Die Situation mit den wenigsten Kanten, wenn ein gerichteter Graph stark zusammenhängend ist: Es werden mindestens n Kanten benötigt, um einen Kreis zu bilden
Spannender Baum, Spannender Wald Der Spannbaum eines verbundenen Graphen ist ein minimal verbundener Teilgraph, der alle Eckpunkte im Diagramm enthält. Wenn die Anzahl der festen Eckpunkte n ist, hat der Spannbaum n-1 Kanten. Wenn eine Kante vom aufspannenden Baum abgeschnitten wird, wird er zu einem nicht zusammenhängenden Teilgraphen. Wenn eine Kante hinzugefügt wird, wird ein Zyklus gebildet. In einem nicht verbundenen Diagramm bildet der Spanning Tree verbundener Komponenten den Spanning Forest des nicht verbundenen Diagramms.
Scheitelpunktgrad, In-Grad und Out-Grad Ungerichteter Graph: TD(v), die Summe der Grade aller Eckpunkte eines ungerichteten Graphen ist gleich dem Zweifachen der Anzahl der Kanten Gerichteter Graph: Der Grad des Scheitelpunkts v wird in In-Grad-ID(v) und Out-Grad-OD(v) unterteilt. Der Grad des Scheitelpunkts v ist gleich der Summe aus In-Grad und Out-Grad: TD(v). =ID(v) OD(v) , der In-Grad aller Eckpunkte eines gerichteten Graphen ist gleich dem Out-Grad, der der Anzahl der Kanten entspricht
Bians Quanhe.com Jede Kante kann mit einem numerischen Wert markiert werden, der eine bestimmte Bedeutung hat, die als Gewicht der Kante bezeichnet wird. Der Graph mit gewichteten Kanten wird als gewichteter Graph bezeichnet, der auch als Netzwerk bezeichnet wird.
Dichtes Diagramm, spärliches Diagramm Ein Graph mit wenigen Kanten wird als dünn besetzter Graph bezeichnet, das Gegenteil als dichter Graph. Wenn G |E|<|V|log|V| erfüllt, wird G im Allgemeinen als dünn besetzter Graph betrachtet.
Wege, Weglängen und Schleifen Pfad: Ein Pfad vom Scheitelpunkt vp nach vq bezieht sich auf die Scheitelpunktsequenz vp,vi1...vim,vq, Die Anzahl der Kanten auf einem Pfad wird Pfadlänge genannt Ein Pfad, dessen erster und letzter Scheitelpunkt gleich sind, wird Zyklus oder Kreis genannt Wenn ein Graph n Eckpunkte und mehr als n-1 Kanten hat, muss der Graph einen Kreis haben.
Einfacher Weg, einfache Schleife In einer Pfadsequenz wird ein Pfad, dessen Scheitelpunkte nicht wiederholt vorkommen, als einfacher Pfad bezeichnet. Mit Ausnahme des ersten Scheitelpunkts und des letzten Scheitelpunkts wird der Zyklus, in dem die verbleibenden Scheitelpunkte nicht in diesem Diagramm enden, als einfacher Zyklus bezeichnet.
Distanz Wenn der kürzeste Weg vom Scheitelpunkt u zum Scheitelpunkt v existiert, dann ist die Länge dieses Weges der Abstand von u nach v; Gibt es überhaupt keinen Weg von u nach v, dann wird die Entfernung als ∞ aufgezeichnet
gerichteter Baum Ein gerichteter Graph, in dem der In-Grad eines Knotens 0 und der Out-Grad der übrigen Knoten 1 ist, wird als gerichteter Baum bezeichnet.
Diagrammspeicher
Adjazenzmatrix-Methode
Es bezieht sich auf die Verwendung eines eindimensionalen Arrays zum Speichern der Beziehung zwischen Scheitelpunkten im Diagramm und auf die Verwendung eines zweidimensionalen Arrays zum Speichern der Kanteninformationen im Diagramm (die Adjazenzbeziehung zwischen Scheitelpunkten). Die Adjazenzbeziehung zwischen Eckpunkten wird als Adjazenzmatrix bezeichnet. Die Speicherstruktur des gesamten Graphen G: #define MaxVertexNum 100 typedef char VertexType; typedef int EdgeType; typedef struct{ VertexType Vex[MaxVertexNum]; //Vertex-Tabelle EdgeType Edge[MaxVertexNum][MaxVertexNum];//Adjazenzmatrix, Kantentabelle int vexnum,arcnum; }MGraph;
Beachten: 1. In einfachen Anwendungen kann ein zweidimensionales Array direkt als Adjazenzmatrix des Diagramms verwendet werden. 2. In einem ungerichteten Diagramm ist die Adjazenzmatrix eine symmetrische Matrix (eindeutig) und kann komprimiert und gespeichert werden. 3. Wenn die Adjazenzmatrix nur angibt, ob die Kante vorhanden ist, kann ElemType die Aufzählungstypen 0 und 1 verwenden. 4. Die räumliche Komplexität der Adjazenzmatrixdarstellung beträgt O(n^2), n ist die Anzahl der Scheitelpunkte |V|
Merkmale der Adjazenzmatrixdarstellung: 1. Die Anzahl der Nicht-Null-Elemente (oder Nicht-∞) in Zeile i (oder Spalte j) eines ungerichteten Graphen repräsentiert den Grad TD(v) des Knotens Die Anzahl der Nicht-Null-Elemente (oder Nicht-∞) in der i-ten Zeile und j-ten Spalte des gerichteten Graphen stellt die Out-Grad-OD(v) und In-Grad-ID(v) des Knotens dar. 2. Wenn Sie eine Adjazenzmatrix zum Speichern eines Diagramms verwenden, können Sie leicht feststellen, ob zwischen zwei beliebigen Eckpunkten im Diagramm eine Kantenverbindung besteht. Um jedoch festzustellen, wie viele Kanten es im Diagramm gibt, müssen Sie jedes Element zeilenweise erkennen und Spalte, was sehr zeitaufwändig ist. 3. Dichte Graphen eignen sich zur Speicherdarstellung mithilfe von Adjazenzmatrizen. 4. Nehmen Sie an, dass die Adjazenzmatrix des Graphen G A ist und das Element A^n[i][j] von A^n gleich der Anzahl der Pfade der Länge n vom Scheitelpunkt i zum Scheitelpunkt j ist.
Adjazenzlistenmethode
Wenn es sich bei einem Diagramm um ein Diagramm mit geringer Dichte handelt, spart die Verwendung der Adjazenzlistenmethode viel Platz. Die Adjazenzlistenmethode des Diagramms kombiniert sequentielle Speicher- und Kettenspeichermethoden Erstellen Sie für jeden Scheitelpunkt im Diagramm eine einfach verknüpfte Liste. Der Knoten in der i-ten einfach verknüpften Liste ist an die Kante vi des Scheitelpunkts gebunden ausgehende Kantenliste) (Die Kantenliste ist „Edge“ ist wirklich erstaunlich) Jeder Knoten in der Kantentabelle stellt eine Kante dar, speichert jedoch eine Scheitelpunktnummer und lässt eine andere Scheitelpunktnummer (in der Scheitelpunkttabelle) weg. Der Kopfzeiger der Kantentabelle und die Scheitelpunktdateninformationen des Diagramms G werden nacheinander gespeichert (sogenannte Scheitelpunkttabelle). #define MaxVertexNum 100 typedef struct ArcNode{//edge Tabellenknoten int adjvex;//Die Position des Scheitelpunkts, auf den der Bogen zeigt struct ArcNode *next; //Info Typeinfo; //Das Kantengewicht des Netzwerks }ArcNode; typedef struct VNode{ VertexType-Daten; // Vertex-Informationen struct VNode *first;//Zeiger auf den ersten Bogen, der an den Scheitelpunkt angehängt ist }VNode, AdjList[MaxVertexNum]; typedef struct{ AdjList vertices;//Adjazenzliste int vexnum, arcnum;//Die Anzahl der Eckpunkte und Bögen des Diagramms }ALGraph
Merkmale: 1. Für einen ungerichteten Graphen beträgt der erforderliche Speicherplatz O(|V| 2|E|); (jede Kante erscheint zweimal in der Adjazenzliste) Für einen gerichteten Graphen beträgt der erforderliche Speicherplatz O(|V| |E|); 2. Bei Diagrammen mit geringer Dichte kann die Verwendung von Adjazenzlisten erheblich Speicherplatz sparen. 3. In der Adjazenzliste ist es bei gegebenem Scheitelpunkt einfach, alle seine Kanten in der Adjazenzmatrix zu finden. Eine Zeile muss gescannt werden, O(n). Wenn Sie feststellen möchten, ob es eine Kante zwischen zwei gegebenen Scheitelpunkten gibt, können Sie diese schnell in der Adjazenzmatrix finden (ein bisschen Zufallszugriff), während die Adjazenzmatrix einen anderen Knoten in der Kantentabelle finden muss, der dem entsprechenden Knoten entspricht , was effizienter ist. 4. In einem gerichteten Graphen erfordert das Ermitteln des Out-Grades eines Scheitelpunkts nur die Berechnung der Anzahl der Knoten in der angrenzenden Adjazenzliste, das Ermitteln des Out-Grades erfordert jedoch das Durchlaufen aller Adjazenzlisten. Daher kann die inverse Adjazenzliste verwendet werden Beschleunigen Sie den In-Grad einer Scheitelpunktlösung. 5. Die Adjazenzliste des Diagramms ist nicht eindeutig und die Verbindungsreihenfolge der Kantenknoten ist willkürlich.
Kreuzlistenmethode
Eine vernetzte Liste ist eine verknüpfte Speicherstruktur eines gerichteten Graphen. Jeder Bogen im Graphen hat einen Knoten. Der Bogenknoten hat 5 Felder: (tailvex, headvex, hlink, tlink, info) tailvex und headvex sind jeweils die Ursprungs- und Einfügescheitelpunkte der Kante; hlink und tlink verbinden jeweils die Kantenknoten derselben Ursprungs- und Einfügungsscheitelpunkte. Der Scheitelpunktknoten hat 3 Felder: (Daten, Firstin, Firstout) Die vernetzte Liste ist nicht eindeutig, aber eine vernetzte Liste repräsentiert ein bestimmtes Diagramm In der vernetzten Liste ist es einfach, den In-Grad von V und den Out-Grad von V zu finden.
Methode mit mehreren Adjazenzlisten
Adjazenz-Mehrfachliste ist eine Kettenspeicherstruktur eines ungerichteten Graphen Kantenknoten (mark,ivex,ilink,jvex,jlink,info) mark ist ein Flag-Feld, das markieren kann, ob diese Kante durchsucht wurde; ivex und jvex sind die Positionen der beiden an der Kante angehängten Eckpunkte im Diagramm; An den Scheitelpunkt JVEX Edge ist ein Zeigerfeld angehängt, das auf verschiedene Informationen im Zusammenhang mit der Kante verweist vertex(data,firstedge)
Grundlegende Operationen an Diagrammen
Adjacent(G,x,y);Neighbors(G,x);InsertVertex(G,x);DeleteVertex(G,x); AddEdge(G,x);RemoveEdge(G,x,y);FirstNeighbor(G,x);NextNeighbor(G,x); Get_edge_value(G,x,y);Set_edge_value(G,x,y,v);
Graphdurchquerung Ausgehend von einem bestimmten Scheitelpunkt ist der einmalige Besuch aller Scheitelpunkte die Grundlage für die Lösung von Konnektivitätsproblemen, topologischer Sortierung und Problemen mit dem Algorithmus für kritische Pfade. Um zu vermeiden, dass derselbe Scheitelpunkt mehrmals besucht wird, muss er im Gegensatz zum Baum nach dem Durchlaufen eines Scheitelpunkts aufgezeichnet und im Hilfsarray besucht werden[]; Die Graphdurchquerung umfasst BFS und DFS Bei Verwendung der Adjazenzmatrix beträgt die Zeitkomplexität O(n^2); bei Verwendung der Adjazenzliste beträgt die Zeitkomplexität O(|V| |E|); BFS\DFS benötigt Speicherplatz O(n) (Die zeitliche und räumliche Komplexität hat nichts mit der Methodenauswahl von BFS\DFS zu tun, sondern hängt hauptsächlich von der Speichermethode ab.)
Breite zuerst suchen Baumartige hierarchische Durchquerung Greifen Sie Schicht für Schicht zu, daher ist eine Hilfswarteschlange erforderlich bool besucht[MAV_VERTEX_NUM]; void BFSTraverse(Graph G){ for(int i=0;i<G.vexnum;i) besuchte[i] = FALSCH; InitQueue(Q); for(int i=0;i<G.vexnum;i) if(!visited[i]) BFS(G,i); } void BFS(Graph G,int v){ Besuch(v); besuchte[v] = TRUE; Enqueue(Q,v); while(!isEmpty(Q)){ for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)) if(!visit[w]){ Besuch(w); besuchte[w] = TRUE; Enqueue(Q,w); } } }
BFS löst das Single-Source-Shortest-Path-Problem void BFS_MIN_Distance(Graph G,int u){ for(i=0;i<G.vexnum;i) //d[i] stellt den kürzesten Weg von u nach i dar d[i]=∞;//Pfadlänge initialisieren besuchte[u]=TRUE; d[u]=0; EnQueue(Q,u); while(!isEmpty(Q)){ DeQueue(Q,u); for(w=FirstNeighbor(G,u);w>=0;w=NextN eighbor(G,u,w)) if(visited[w]){ besuchte[w]=TRUE; d[w]=d[u] 1;//Pfadlänge 1 EnQueue(Q,w); } } }
Breitenspannender Baum Die Adjazenzmatrix wird eindeutig gespeichert – ihr Spannbaum in der Breitenrichtung ist eindeutig Der Adjazenzlistenspeicher ist nicht eindeutig – sein Breiten-Spannungsbaum ist nicht eindeutig
Tiefensuche
Ähnlich wie bei der Vorbestellungsdurchquerung eines Baums bool besucht[MAX_VERTEX_NUM]; void DFSTraverse(Graph G){ for(v=0;v<G.vexnum;v) besucht[v] = FALSCH; for(v=0,v<G.vexnum;v) if(!visited[v]) DFS(G,v); } void DFS(Graph G,int v){ Besuch(v); besuchte[v] = TRUE; for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)) if(!visited[w]){ DFS(G,w); } }
Die Tiefe erstreckt sich über Bäume und Wälder Verbundene Diagramme können tiefgreifende Bäume erzeugen, und nicht verbundene Diagramme können Wälder erzeugen.
Graphkonnektivität
Graph-Traversal-Algorithmen können verwendet werden, um die Konnektivität des Graphen zu bestimmen. Wenn ein ungerichteter Graph ausgehend von einem beliebigen Scheitelpunkt verbunden ist, kann auf alle Scheitelpunkte im Graphen mit nur einem Durchlauf zugegriffen werden; Wenn ein gerichteter Graph verbunden ist und es einen Pfad vom Anfangspunkt zu jedem Scheitelpunkt im Diagramm gibt, kann auf alle Scheitelpunkte im Diagramm zugegriffen werden.
Anwendung von Diagrammen
minimaler Spannbaum Wenn eine Kante abgeschnitten wird, wird der Spannbaum zu einem nicht zusammenhängenden Graphen; Wenn eine Kante hinzugefügt wird, wird auf dem Weg eine Schleife gebildet; Der minimale Spannbaum ist etwas Besonderes. Er muss der Spannbaum mit der kleinsten Summe der Kantengewichte im Spannbaumsatz sein. Wenn die Gewichte jeder Kante unterschiedlich sind, ist der minimale Spannbaum eindeutig; im Allgemeinen ist der minimale Spannbaum nicht unbedingt eindeutig. Die Summe der Gewichte der Kanten des minimalen Spannbaums ist immer eindeutig und minimal. GENERIC_MST(G){ T=NULL; während T keinen Spannbaum bildet; Finden Sie eine Kante mit minimalen Kosten (u, v). Nach dem Hinzufügen von T wird keine Schleife generiert. T=T∪(u,v); }
Prim-Algorithmus (BFS) Wählen Sie ähnlich wie bei Dijkstra zunächst einen Scheitelpunkt aus dem Diagramm aus und fügen Sie ihn dem Baum T hinzu. Wählen Sie dann einen Scheitelpunkt aus, der dem aktuellen Satz von Scheitelpunkten in T am nächsten liegt, und fügen Sie den Scheitelpunkt und die Kante zum Baum T hinzu Nach jeder Operation wird die Anzahl der Kanten um 1 erhöht, bis alle Scheitelpunkte im Diagramm zum Baum T hinzugefügt werden. T ist der minimale Spannbaum. Es müssen n-1 Kanten in T vorhanden sein voidPrim(G,T){ T=leere Menge; U={W}; while((V-U)!=empty set){//Wenn der Baum nicht alle Eckpunkte enthält Sei (u, v) die Kante, die u∈U und v∈(V-U) ergibt und das kleinste Gewicht hat; T=T∪{(u,v)};//Kanten werden in Bäume klassifiziert U=U∪{v};//Scheitelpunkte werden in Bäume klassifiziert } } Die zeitliche Komplexität des Prim-Algorithmus beträgt O(|V^2|) und hängt nicht von |E| ab. Daher eignet er sich zum Lösen des minimalen Spannbaums von Graphen mit dichten Kanten.
Kruskal-Algorithmus Im Gegensatz zum Prim-Algorithmus, der von den Eckpunkten aus expandiert, um einen minimalen Spannbaum zu erzeugen, wählt der Kruskal-Algorithmus geeignete Kanten in aufsteigender Reihenfolge der Gewichte aus, um einen minimalen Spannbaum zu erstellen. Zunächst bildet jeder Scheitelpunkt eine verbundene Komponente. Anschließend wird in der Reihenfolge der Kantengewichte von klein nach groß die Kante ausgewählt, die noch nicht ausgewählt wurde und das kleinste Gewicht aufweist, wenn der an der Kante befestigte Scheitelpunkt auf eine andere verbundene Komponente fällt Komponente der T-Komponente, fügen Sie diese Kante zu T hinzu, andernfalls verwerfen Sie diese Kante, bis alle Eckpunkte in T auf einer verbundenen Komponente liegen void Kruskal(V,T){ T=V; // T initialisieren, nur Scheitelpunkte numS=n;//Anzahl der verbundenen Komponenten while(numS>1){//Wenn die Anzahl der verbundenen Komponenten größer als 1 ist Nehmen Sie die Kante (v, u) mit dem kleinsten Gewicht aus E if (v und u gehören zu verschiedenen Zusammenhangskomponenten in T) T=T∪{(v,u)}; numS--; } } } Kruskals Algorithmus verwendet einen Heap zum Speichern der Kantenmenge, sodass jedes Mal nur O(log|E|) Zeit benötigt wird, um die Kante mit dem kleinsten Gewicht auszuwählen. Das Hinzufügen einer neuen Kante ähnelt dem Prozess der Lösung einer Äquivalenz Klasse und die Union-Suchmethode können verwendet werden. Datenstruktur zur Beschreibung von T, die Zeitkomplexität beträgt O(|E|log|E|), sodass Kruskal für Diagramme mit spärlichen Kanten und vielen Scheitelpunkten geeignet ist.
kürzester Weg Die BFS-Durchquerung in der vorherigen Abbildung zur Lösung des kürzesten Pfads gilt für ungewichtete Diagramme. Hier diskutieren wir den kürzesten Pfad mit der kürzesten gewichteten Pfadlänge. Die Algorithmen zur Lösung des kürzesten Pfads basieren alle auf einer Eigenschaft: Der kürzeste Pfad zwischen zwei Punkten umfasst auch die kürzesten Pfade zwischen anderen Eckpunkten auf dem Pfad.
Dijkstra-Algorithmus (BFS) Kürzester Weg aus einer Hand; Der Dijkstra-Algorithmus legt eine Menge S fest, um die Eckpunkte des kürzesten Pfades aufzuzeichnen, der erhalten wurde. Zunächst wird der Quellpunkt v0 in S platziert. Jedes Mal, wenn ein neuer Eckpunkt vi in die Menge S aufgenommen wird, muss der Quellpunkt v0 geändert werden zum aktuell kürzesten Scheitelpunkt im eingestellten V-S-Längenwert; negative Gewichtswerte an Kanten sind nicht zulässig Richten Sie zwei Hilfsarrays ein: dist[]: Zeichnen Sie die aktuell kürzeste Pfadlänge vom Quellpunkt v0 zu anderen Eckpunkten auf. Ausgangszustand: Wenn es einen Bogen von v0 nach vi gibt, ist dist[i] das Gewicht des Bogens, andernfalls wird dist[i] festgelegt ] zu ∞. (dist[] geht jeweils nur einen Schritt zurück – BFS-Idee) Pfad[]: Pfad[i] repräsentiert den Vorgängerknoten des kürzesten Pfades vom Quellpunkt zum Scheitelpunkt i. Am Ende des Algorithmus kann der kürzeste Weg vom Quellpunkt v0 zum Scheitelpunkt vi verfolgt werden. Zeichnen Sie beim manuellen Lösen ein Diktat-Array. Das Wichtigste ist, den Scheitelpunkt bei jeder Auswahl zum Satz S hinzuzufügen, den Pfadabstand jedes Punkts zu aktualisieren und dann den nächsten Schritt ausgehend vom letzten Scheitelpunkt des Satzes auszuführen S. Zeitkomplexität O(|V|^2)
Floyd-Algorithmus Der kürzeste Weg zwischen jedem Scheitelpunktpaar Die Rekursion erzeugt eine quadratische Matrixfolge n-ter Ordnung: A^-1,A^0,..A^N; wobei A^k[i][j] die Länge vom Scheitelpunkt i bis j darstellt und der k-te Scheitelpunkt verwendet wird als Entspannen Sie die mittleren Eckpunkte Die Zeitkomplexität beträgt O(|V|^3) (entspricht der einmaligen Ausführung des Dijkstra-Algorithmus für jeden Scheitelpunkt O(|V^2|)*|V|), Kanten mit negativen Gewichten sind nicht zulässig und können auf gerichtete Graphen/ungerichtete Graphen wirken (umgewandelt in gerichtete Graphen mit denselben bilateralen Seiten).
Gezielter azyklischer Graphbeschreibungsausdruck Gerichteter azyklischer Graph DAG (Directed acycle graph): In einem gerichteten Graphen gibt es keinen Zyklus. Es ist ein effektives Werkzeug zur Beschreibung von Ausdrücken, die häufig vorkommende Unterausdrücke enthalten. Beispielsweise kann eine Zeichenfolge von Infix-Ausdrücken durch einen Binärbaum dargestellt werden (der Wurzelknoten speichert Operatoren und der Unterbaum speichert Operanden. Durch die Verwendung eines gerichteten azyklischen Diagramms können dieselben Unterausdrücke gemeinsam genutzt werden.
topologische Folge Eine Folge, die aus Scheitelpunkten eines gerichteten azyklischen Graphen besteht. Jeder Scheitelpunkt kommt nur einmal vor. Wenn Scheitelpunkt A in der Folge vor Scheitelpunkt B steht, gibt es im Graphen keinen Pfad von B nach A. Es kann auch so verstanden werden: Eine topologische Sequenz ist eine Reihenfolge der Eckpunkte eines gerichteten azyklischen Graphen. Jedes AOV-Netz verfügt über eine oder mehrere topologisch sortierte Sequenzen.
AOV-Netzwerk (Aktivität im Vertex-Netzwerk) Wenn ein DAG zur Darstellung eines Projekts verwendet wird, seine Scheitelpunkte Aktivitäten darstellen und die gerichtete Kante <Vi, Vj> eine Beziehung darstellt, in der Vi vor Vj stehen muss, wird diese Art von Diagramm als Netzwerk mit Scheitelpunkten bezeichnet, die Aktivitäten darstellen. Es gibt viele Algorithmen zur topologischen Sortierung von AOV-Netzwerken. Einer der am häufigsten verwendeten Algorithmen ist: 1. Wählen Sie einen Scheitelpunkt ohne Vorgänger aus dem AOV-Netzwerk aus und geben Sie ihn aus 2. Löschen Sie den Scheitelpunkt und alle von ihm ausgehenden gerichteten Kanten aus dem Netzwerk 3. Wiederholen Sie Schritt 1.2, bis das AOV-Netzwerk leer ist oder im aktuellen Netzwerk keine Scheitelpunkte ohne Vorgänger vorhanden sind (was anzeigt, dass im Diagramm ein Zyklus vorhanden sein muss). Da die Ausgabe jedes Scheitelpunkts das Löschen seiner Startkante erfordert, beträgt die zeitliche Komplexität der topologischen Sortierung O(|V| |E|) Darüber hinaus kann die topologische Sortierung auch mit DFS implementiert werden (beginnend mit den Scheitelpunkten mit einem Eingangsgrad von 0 und einem Ausgangsgrad von ungleich 0 nacheinander), und sie schlägt fehl, solange eine Rückverfolgung erfolgt, bis ein Strich abgeschlossen ist ) umgekehrte topologische Sortierung Beginnen Sie am Scheitelpunkt mit Out-Grad 0 Das AOV-Netzwerk verwendet Adjazenzmatrixspeicher. Wenn es sich um eine Dreiecksmatrix handelt, muss eine topologische Sequenz vorhanden sein und umgekehrt.
bool TopologicalSort(Graph G){ InitStack(S); for(int i=0;i<G.vexnum;i) if(indegree[i]==0) Push(S,i);//Alle Scheitelpunkte mit In-Grad 0 auf den Stapel schieben int count=0; while(!isEmpty(S)){ Pop(S,i); print[count]=i;//Eckpunkte ausgeben for(p=G.vertices[i].firstarc;p;p=p->nextarc){ // Verringere den In-Grad aller Scheitelpunkte, auf die i zeigt, um 1 und schiebe die Scheitelpunkte, deren In-Grad auf 0 reduziert ist, auf den Stapel v = p->adjvex; if(!(--indegree[v])) Push(S,v); } if(count<G.vexnum) falsch zurückgeben; anders return true; }
Kritischer Pfad
AOE-Netzwerk (Aktivität im Edge-Netzwerk) Ereignisse werden durch Scheitelpunkte dargestellt, Aktivitäten werden durch gerichtete Kanten dargestellt und die Kosten für den Abschluss der Aktivität werden durch das Gewicht auf der Kante dargestellt (z. B. die für den Abschluss der Aktivität erforderliche Zeit). Zwei Eigenschaften des AOE-Netzwerks: 1. Erst nachdem das durch einen Scheitelpunkt dargestellte Ereignis eintritt, können die Aktivitäten beginnen, die durch die gerichteten Kanten ausgehend vom Scheitelpunkt dargestellt werden. 2. Das Ereignis an einem Scheitelpunkt kann erst auftreten, nachdem die durch die eingehenden Kanten eines Scheitelpunkts dargestellten Aktivitäten abgeschlossen sind. Es gibt nur einen Scheitelpunkt mit einem In-Grad von 0 im AOE-Netzwerk, der als Start-Scheitelpunkt (Quellpunkt) bezeichnet wird und den Anfang des gesamten Projekts darstellt. Das AOE-Netzwerk hat auch nur einen Scheitelpunkt mit einem Out-Grad von 0 , genannt Endscheitelpunkt (Senke). Stellt das Ende des gesamten Projekts dar. Einige Aktivitäten im AOE-Netzwerk können parallel ausgeführt werden, aber alle Aktivitäten auf allen Pfaden können nicht enden, bis sie abgeschlossen sind. Daher wird unter allen Pfaden von der Quelle bis zur Senke der Pfad mit der größten Pfadlänge als kritischer Pfad bezeichnet Die Aktivitäten auf dem Pfad werden als kritische Aktivitäten bezeichnet (das Finden der kritischen Aktivitäten ist gleichbedeutend mit dem Finden des kritischen Pfads). Die kürzeste Zeit zum Abschluss des gesamten Projekts ist die Länge des kritischen Pfads.
Finden Sie wichtige Aktivitäten 1. Der früheste Zeitpunkt des Auftretens des Ereignisses vk ve(k) Es bezieht sich auf die längste Pfadlänge vom Quellpunkt v1 zum Scheitelpunkt vk. Der früheste Auftrittszeitpunkt des Ereignisses vk ist der früheste Zeitpunkt, zu dem alle Aktivitäten beginnend mit vk beginnen können. Sei vk ein beliebiger Nachfolger von vj if(ve[j] Weight(vj,vk) > ve(k)) then ve[k]=ve[j] Weight(vj,vk) „Spannungsoperation“ Gehen Sie bei der Berechnung des ve()-Werts in der Reihenfolge von vorne nach hinten vor und können Sie ihn anhand der topologischen Sortierung berechnen. 2. Der späteste Auftrittszeitpunkt vl(k) des Ereignisses vk Es bezieht sich auf den spätesten Zeitpunkt vl(k), zu dem k eintreten kann, ohne den Abschluss des gesamten Projekts zu verzögern. Gehen Sie bei der Berechnung des vl()-Werts von hinten nach vorne vor und können Sie ihn auf der Grundlage der inversen topologischen Reihenfolge berechnen. 3. Der früheste Startzeitpunkt e(i) der Aktivität ai 4. Die späteste Startzeit der Aktivität ai ist l(i) 5. Die Differenz zwischen der spätesten Startzeit l(i) einer Aktivität ai und ihrer frühesten Startzeit e(i) d(i)=l(i)-e(i) Es bezieht sich auf die Zeitspanne für die Fertigstellungszeit der Aktivität. Vereinfacht ausgedrückt bedeutet es, wie lange die KI verzögern kann. Wenn d(i)=0, dann ist i die Schlüsselaktivität Der kritische Pfad im Netzwerk ist nicht eindeutig, und bei Netzwerken mit mehreren kritischen Pfaden kann die Erhöhung der Geschwindigkeit wichtiger Aktivitäten auf einem kritischen Pfad nicht die Dauer des gesamten Projekts verkürzen. Dies kann nur durch die Beschleunigung der wichtigsten Interaktionen auf allen erreicht werden Kritische Pfade. Ziel ist es, die Bauzeit zu verkürzen
Das Verständnis der Lösung vl(k): ve(k) ist die sorgfältigste Bewegung vom Quellpunkt zum Senkenpunkt; vl(k) ist die Bewegung vom Senkenpunkt zum Quellpunkt und die Bewegung, die dem am nächsten kommt Sinkpunkt ist ausgewählt (auch „Gehen Sie einfach geradeaus“, was der bequemste Weg ist, ziehen Sie ihn einfach, wenn Sie können)
7Finden
Grundbegriffe der Suche
Suche: Der Prozess der Suche nach Datenelementen, die bestimmte Bedingungen in einem Datensatz erfüllen Nachschlagetabelle (Nachschlagestruktur): Eine Sammlung von Daten, die zum Nachschlagen verwendet werden. Es gibt vier häufig verwendete Operationen für Nachschlagetabellen 1. Fragen Sie ab, ob sich ein bestimmtes Datenelement in der Nachschlagetabelle befindet 2. Rufen Sie ein bestimmtes Datenelement ab, das die Bedingungen erfüllt 3. Fügen Sie ein Datenelement in die Nachschlagetabelle ein 4. Löschen Sie ein Datenelement aus der Nachschlagetabelle Statische Nachschlagetabelle: Eine Nachschlagetabelle, die nur die oben genannten ein und zwei Schritte umfasst. Es ist nicht erforderlich, die Nachschlagetabelle dynamisch zu ändern (sequentielle Suche, halbe Suche, Hash-Suche usw.). , usw.) Schlüsselwort: Der Wert eines Datenelements in einem Datenelement, der das Element eindeutig identifiziert Durchschnittliche Suchlänge: Während des Suchvorgangs bezieht sich die Länge einer Suche auf die Anzahl der zu vergleichenden Schlüsselwörter. Die durchschnittliche Suchlänge ist die durchschnittliche Anzahl der Schlüsselwortvergleiche in allen Suchvorgängen.
Üben Sie ASL bei der Suche nach Erfolg und Misserfolg
Sequentielle Suche und binäre Suche
Sequentielle Suche Sie wird auch als lineare Suche bezeichnet und eignet sich sowohl für sequentielle Listen als auch für verknüpfte Listen. Vorteile: Es gibt keine Anforderungen an die Speicherung und Reihenfolge der Datenelemente. Nachteile: Wenn n groß ist, ist die Effizienz gering
Sequentielle Suche nach allgemeinen linearen Tabellen typedef struct{//Datenstruktur der Nachschlagetabelle ElemType *elem; //Basisadresse des Elementspeicherplatzes int TableLen;//Die Länge der Tabelle }SSTable; int Search_Seq(SSTable ST,ElemType key){ ST.elem[0] = key;//Sentinel for(i=ST.TableLen;ST.elem[i]!=key;--i);//Blick von hinten nach vorne gib i zurück; } Im obigen Algorithmus wird ST.elem[0] als Sentinel bezeichnet, sodass die interne Schleife von Search_Seq nicht beurteilen muss, ob das Array außerhalb der Grenzen liegt, da die Schleife definitiv herausspringt, wenn i==0 ist befriedigt. Durch die Einführung von Sentinels können viele unnötige Beurteilungen vermieden und dadurch die Programmeffizienz verbessert werden.
Sequentielle Suche in geordneter Liste Wenn vor der Suche bekannt ist, dass die Schlüsselwörter in der Tabelle in Ordnung sind, ist nach einem fehlgeschlagenen Vergleich kein Vergleich mit dem anderen Ende der Tabelle erforderlich, um den Suchfehler zurückzugeben, wodurch sich die durchschnittliche Suchlänge verringert Suchfehler. Ein Entscheidungsbaum kann verwendet werden, um den Suchprozess einer geordneten linearen Liste zu beschreiben. Die kreisförmigen Knoten stellen die Elemente dar, die in der geordneten linearen Liste vorhanden sind. Die rechteckigen Knoten sind Ihre eigenen fiktiven leeren Knoten , dann Entsprechend gibt es n 1 Knoten, bei denen die Suche fehlgeschlagen ist. Die durchschnittliche Suchlänge (ASL) einer fehlgeschlagenen Suche beträgt (1 2 .. n n)/(n 1); die Wahrscheinlichkeit, jeden „fehlgeschlagenen Knoten“ zu finden, beträgt 1/n 1
Der Unterschied zwischen einer ungeordneten und einer geordneten sequentiellen Suche besteht nur dann, wenn die Suche fehlschlägt.
ASL-Erfolg=(n 1)/2
Halbsuche (binäre Suche) Es eignet sich nur für sequentielle Speicherstrukturen, nicht für Kettenspeicherstrukturen und erfordert die Reihenfolge der Elemente nach Schlüsselwörtern. Die Zeit ist O(log2n)
int Binary_Search(SeqList L,ElemType key){ int low=0,high=L.TableLen-1,mid; while(low<=high){ mittel = (niedrig hoch)/2; if(L.elem[mid]==key) return mid; sonst if(L.elem[mid]<key){ niedrig = mittel 1; // mid = (low high)/2; } anders{ hoch = mittel-1; // mid = (low high)/2; } } return -1; }
Blocksuche (Suche nach Indexreihenfolge) Es nutzt die jeweiligen Vorteile der sequentiellen Suche und der binären Suche. Es hat eine dynamische Struktur und ist für die schnelle Suche geeignet. Die Grundidee der Blocksuche besteht darin, die Nachschlagetabelle in mehrere Unterblöcke zu unterteilen. Die Elemente innerhalb eines Blocks können ungeordnet sein, aber die Blöcke sind geordnet (das größte Schlüsselwort in der ersten Gruppe ist kleiner als alle Schlüsselwörter im zweiten Block usw.). Erstellen Sie dann eine Indextabelle, um jedes Element in der Tabelle zu indizieren Enthält den maximalen Schlüssel jedes Blocks und die Adresse des ersten Elements in jedem Block, geordnet nach Schlüssel. Der Prozess der Blocksuche: 1. Bestimmen Sie den Block, in dem sich der zu durchsuchende Datensatz in der Indextabelle befindet (sequentielle oder halbe Suche). 2. Sequentielle Suche innerhalb des Blocks Durchschnittliche Suchlänge der blockierten Suche: Summe der durchschnittlichen Länge der Indexsuche und der Intra-Block-Suche
B-Baum und B-Baum
B-Baum (Mehrweg-Balanced-Suchbaum) (verbesserte Version von BST) Die maximale Anzahl von Kindern aller Knoten im B-Baum wird als Ordnung des B-Baums bezeichnet und normalerweise durch m dargestellt. Ein B-Baum der Ordnung m ist entweder ein leerer Baum oder erfüllt die folgenden Eigenschaften: 1. Jeder Knoten im Baum hat höchstens m Teilbäume, das heißt, er enthält höchstens m-1 Schlüsselwörter. 2. Wenn der Wurzelknoten kein Endknoten ist, gibt es mindestens zwei Teilbäume 3. Alle Nicht-Blattknoten mit Ausnahme des Wurzelknotens haben mindestens m/2 Obergrenze-Teilbäume, das heißt, sie enthalten mindestens m/2 Obergrenze-1-Schlüsselwörter. 4. Die Struktur aller Nicht-Blattknoten ist wie folgt (n, P0, K1, P1, K2, P2, ..., Kn, Pn) Ki ist ein Schlüsselwort und erfüllt die Bedingungen K1<K2<..<Kn; Pi ist ein Zeiger auf den Wurzelknoten des Teilbaums, auf den der Pi-Zeiger zeigt, der größer als Ki und kleiner als Ki ist das Schlüsselwort im Knoten number of 5. Alle Blattknoten erscheinen auf derselben Ebene und enthalten keine Informationen. Sie sind tatsächlich nicht vorhanden. Die Zeiger auf diese Knoten sind leer.
B Baumhöhe Die Anzahl der Festplattenzugriffe, die für die meisten Vorgänge in einem B-Baum erforderlich sind, ist proportional zur Höhe des B-Baums. Jeder Knoten im Baum hat höchstens m Teilbäume und m-1 Schlüsselwörter, und die Anzahl der Schlüsselwörter sollte n≤m^h-1 erfüllen; Die h1-te Schicht hat mindestens 2(m/2 Obergrenze)^(h-1) Knoten, und die h1-te Schicht ist ein Blattknoten, der keine Informationen enthält; Für einen B-Baum mit n Schlüsselwörtern ist der Knoten, an dem die Blattknotensuche fehlschlägt, n 1, also ist n 1>=2 (m/2 übernimmt die Obergrenze)^(h-1), d. h. h≤log ( m/2 übernimmt die Obergrenze)((n 1)/2 1)
B-Tree-Suche 1. Knoten im B-Baum finden 2. Suchen Sie nach Schlüsselwörtern innerhalb des Knotens Der Suchvorgang von 1. wird auf der Festplatte ausgeführt, und der Suchvorgang von 2. wird im Speicher ausgeführt; Nachdem Sie den Zielknoten gefunden haben, lesen Sie ihn in den Speicher ein und verwenden Sie dann die sequentielle Suchmethode oder die binäre Suchmethode innerhalb des Knotens.
Einfügen in den B-Baum Wenn Sie den Standort direkt einfügen, nachdem Sie ihn nicht gefunden haben, werden die Anforderungen in der B-Baum-Definition zerstört. 1. Positionierung: Suchen Sie den Nicht-Blattknoten in der untersten Ebene (finden Sie den Blattknoten und nehmen Sie den Nicht-Blattknoten der vorherigen Ebene). 2. Einfügung: Die Anzahl der Schlüsselwörter in jedem nicht fehlgeschlagenen Knoten liegt innerhalb des Intervalls [m/2 nimmt die Obergrenze -1, m-1] an und kann direkt eingefügt werden; Wenn die Anzahl der eingefügten Knotenschlüsselwörter größer als m-1 ist, wird der Knoten geteilt. Aufteilungsmethode: Nehmen Sie die Obergrenze von der mittleren Position m/2 und teilen Sie das Schlüsselwort in zwei Teile. Der linke Teil wird im ursprünglichen Knoten enthalten und der rechte Teil wird im neuen Knoten an der mittleren Position platziert. m/2 nimmt den oberen Grenzpunkt, um den übergeordneten Knoten des ursprünglichen Knotens einzufügen;
Löschung des B-Baums Wenn sich das gelöschte Schlüsselwort k nicht im Endknoten (dem niedrigsten Nicht-Blattknoten) befindet, kann k durch k's Vorgänger (Nachfolger) k' ersetzt werden, und dann wird k' im entsprechenden Knoten gelöscht und in ein Schlüsselwort umgewandelt im Endknoten; Wenn sich das gelöschte Schlüsselwort am Endknoten (dem niedrigsten Nicht-Blattknoten) befindet, gibt es drei Situationen: 1. Schlüsselwörter direkt löschen: Die Anzahl der Schlüsselwörter ≥ m/2 hat die Obergrenze -1 2. Brüder sind genug: Die Anzahl der Schlüsselwörter = m/2, die Obergrenze beträgt -1, und die Anzahl der linken und rechten (benachbarten) Geschwisterschlüsselwörter ist größer oder gleich m/2, die Obergrenze beträgt. Dann verwenden seine Brüder, Eltern und er selbst die Vater-Sohn-Transpositionsmethode, um ein Gleichgewicht zu erreichen 3. Es sind nicht genügend Brüder vorhanden: Die Anzahl der gelöschten Schlüsselwörter ist = m/2 und die Obergrenze beträgt -1. Zu diesem Zeitpunkt sind die Knoten seiner linken und rechten (benachbarten) Brüder ebenfalls = m/2 und die Obergrenze ist 1. Dann werden die Schlüsselwörter nach dem Löschen mit den Schlüsselwörtern im linken (rechten) Geschwisterknoten zusammengeführt und die Anzahl der Schlüsselwörter im übergeordneten Knoten wird um eins reduziert Der Wurzelknoten kann direkt auf 0 reduziert werden (Wurzelknoten löschen). Der neue Knoten wird als Wurzel bezeichnet.
B-Baum-bezogene Eigenschaften Es ist notwendig, die Beziehung zwischen der Anzahl der Schlüsselwörter und der Anzahl der Teilbäume zu verstehen: Die Anzahl der Schlüsselwörter ist 1 kleiner als die Anzahl der Teilbäume. Alle nichtterminalen Knoten außer dem Wurzelknoten haben mindestens m/2 Obergrenze-Teilbäume (m/2 Obergrenze – 1 Schlüsselwort) Schlüsselwörter in Knoten werden in aufsteigender Reihenfolge von links nach rechts sortiert. Das heißt, m/2 nimmt die Obergrenze -1 ≤ n ≤ m-1 an, das heißt, [m/2 nimmt die Obergrenze -1, m-1] an.
B-Baum
B-Baum ist ein deformierter B-Baum, der als Reaktion auf die Anforderungen der Datenbank erscheint. Ein B-Baum m-Ordnung erfüllt die folgenden Bedingungen: 1. Jeder Zweigknoten hat höchstens m Teilbäume 2. Der Nicht-Blatt-Wurzelknoten hat mindestens zwei Teilbäume 3. Die Anzahl der Teilbäume eines Knotens entspricht der Anzahl der Schlüsselwörter 4. Alle Blattknoten enthalten alle Schlüsselwörter und Zeiger auf entsprechende Datensätze. Die Schlüsselwörter sind in der Reihenfolge ihrer Größe in den Blattknoten angeordnet, und benachbarte Blattknoten sind in der Reihenfolge ihrer Größe miteinander verbunden.
Hauptunterschiede zu B-Bäumen: 1. Ein Knoten mit n Schlüsselwörtern im B-Baum hat n Teilbäume, und jedes Schlüsselwort entspricht einem Teilbaum; n Schlüsselwortknoten im B-Baum enthalten n-1 Teilbäume. 2. Der Bereich der Anzahl n von Schlüsselwörtern für jeden Knoten im B-Baum beträgt [m/2 nimmt die Obergrenze, m] (relativ zur Ober- und Untergrenze der Anzahl n von Knotenschlüsselwörtern im B-Baum plus 1). ); Wurzelknoten B: [1,m],B:[1,m-1] 3. In Baum B enthalten Blattknoten Informationen (alle Schlüsselwörter), und Nicht-Blattknoten dienen nur als Indizes. 4. Jede Suche im B-Baum, ob erfolgreich oder nicht, ist ein Pfad vom Wurzelknoten zum Blattknoten.
Hash-tabelle
Grundkonzepte von Hash-Tabellen
Aufgrund der bisherigen linearen Tabellen- und Baumsuche gibt es keinen eindeutigen Zusammenhang zwischen der Position des Datensatzes in der Tabelle und dem Schlüssel des Datensatzes. Daher muss bei der Suche nach Datensätzen in diesen Tabellen eine Reihe von Schlüsselwörtern verglichen werden. Diese Suchmethode basiert auf Vergleichen, und die Effizienz der Suche hängt von der Anzahl der Vergleiche ab. Hash-Funktion: Die Funktion Hash(key)=Addr, die die Schlüsselwörter in der Nachschlagetabelle der Adresse zuordnet, die dem Schlüsselwort entspricht, d. h. unter Verwendung der Eigenschaften des Schlüsselworts selbst und mit möglichst wenig „Vergleichs“-Suchen. Die Hash-Funktion kann mehrere verschiedene Schlüsselwörter derselben Adresse zuordnen, was als „Kollision“ bezeichnet wird. Diese verschiedenen Schlüsselwörter, die kollidieren, werden Synonyme genannt. Einerseits minimiert eine gut gestaltete Hash-Funktion Konflikte; andererseits sind Konflikte unvermeidlich und es müssen Methoden für den Umgang mit ihnen entwickelt werden. Hash-Tabelle: Eine Datenstruktur, auf die basierend auf Schlüsselwörtern direkt zugegriffen wird. Mit anderen Worten: Die Hash-Tabelle stellt eine direkte Zuordnungsbeziehung zwischen Schlüsselwörtern und Speicheradressen her. Im Idealfall beträgt die zeitliche Komplexität der Suche in einer Hash-Tabelle O(1), was bedeutet, dass sie nichts mit der Anzahl der Elemente zu tun hat.
So erstellen Sie eine Hash-Funktion
1. Der Definitionsbereich der Hash-Funktion muss alle Speicherschlüssel umfassen, und der Wertebereich hängt von der Größe oder dem Adressbereich der Hash-Tabelle ab. 2. Die von der Hash-Funktion berechneten Adressen sollten mit gleicher Wahrscheinlichkeit gleichmäßig im gesamten Adressraum verteilt sein, wodurch das Auftreten von Konflikten verringert wird. 3. Die Hash-Funktion sollte so einfach wie möglich sein und in kurzer Zeit den Hash für jedes Schlüsselwort berechnen können.
1. Direktadressierungsmethode Nehmen Sie den Wert einer linearen Funktion des Schlüsselworts direkt als Hash-Adresse. H(Taste)=Taste oder H(Taste)=Axt-Taste b Dies steht im Einklang mit der Situation, dass die Schlüsselwortverteilung grundsätzlich kontinuierlich ist. Wenn die Schlüsselwortverteilung diskontinuierlich ist und viele Leerstellen vorhanden sind, wird Speicherplatz verschwendet.
Methoden zur Konfliktbearbeitung
Hash-Suche und Leistungsanalyse
8 sort
Grundbegriffe des Sortierens
Stabilität des Algorithmus: Es gibt zwei Elemente Ri und Rj in der zu sortierenden Liste, und ihre entsprechenden Schlüsselwörter sind dieselben keyi=keyj. Wenn die relativen Positionen von Ri und Rj nach Verwendung eines bestimmten Sortieralgorithmus unverändert bleiben, erfolgt die Sortierung Der Algorithmus ist stabil, sonst ist er es nicht. Stabilität spiegelt nicht die Qualität des Algorithmus wider, sondern beschreibt nur die Natur des Algorithmus. Sortieralgorithmen lassen sich in zwei Kategorien einteilen, je nachdem, ob die Datenelemente während des Sortiervorgangs vollständig im Speicher sind: 1. Interne Sortierung: Beim Sortieren werden alle Elemente zum Sortieren im Speicher abgelegt. 2. Externe Sortierung: Beim Sortieren können nicht alle Elemente gleichzeitig im Speicher abgelegt werden. Sie müssen während des Sortiervorgangs kontinuierlich zwischen internem und externem Speicher verschoben werden. Im Allgemeinen erfordert die interne Sortierung einen Vergleich und eine Bewegung, aber nicht jede interne Sortierung erfordert einen Vergleich, wie z. B. die Basissortierung
Sortieren durch Einfügen Jedes Mal wird ein zu sortierender Datensatz entsprechend der Größe des Schlüsselworts in die zuvor aufgezeichnete Teilsequenz eingefügt.
direkte Einfügungssortierung
Geordnete Folge L[1..i-1]L(i) Ungeordnete Folge L[i 1…n] void InsertSort(ELemType A[],int n){ int i,j; for(i=2;i<=n;i){//Fügen Sie die folgenden n-1 Zahlen vorne ein if(A[i]<A[i-1]){ A[0] = A[i];//Als Sentinel kopieren, A[0] speichert keine Elemente for(j=i-1;A[0]<A[j];j--){//Finden Sie die Einfügeposition von hinten nach vorne A[j 1] = A[j]; A[j 1] = A[0]; } } Raumkomplexität O(1), Raumkomplexität O(n^2) Bester Fall: Die Elemente in der Tabelle sind bereits sortiert, Zeit O(n) Schlimmster Fall: Die Reihenfolge der Elemente in der Tabelle ist umgekehrt, Zeit O(n^2) Da jeder Vergleich von hinten nach vorne erfolgt, ist er stabil. Geeignet für sequentielle Listen und verknüpfte Listen (Sie können die angegebene Elementposition von vorne nach hinten finden) Geeignet für grundsätzlich geordnete Sortiertabellen mit kleinen Datenmengen
Halbeinfügungssortierung
void InsertSort(ElemType A[],int n){ int i,j,low,high,mid; for(i=2;i<=n;i ){ A[0] =A[i]; niedrig=1;hoch=i-1; while(low<=high){ mittel = (niedrig hoch)/2; if(A[mid]>A[0])high=mid-1; sonst niedrig=mittel 1; } for(j=i-1;j>=high 1;j--) A[j 1] = A[j]; A[hoch 1]=A[0]; } } Zeitkomplexität O(n^2) Die Anzahl der Vergleiche hat nichts mit dem Ausgangszustand der zu sortierenden Liste zu tun, sondern hängt nur von der Anzahl n der Elemente in der Liste ab; Die Anzahl der Verschiebungen hängt vom Ausgangszustand der zu sortierenden Liste ab
Hill-Sorte
Teilen Sie die Sortiertabelle zunächst in mehrere Untertabellen auf (Datensätze mit demselben Inkrement bilden eine Untertabelle di 1 = di / 2, und das letzte Inkrement ist gleich 1) und führen Sie eine direkte Einfügungssortierung für jede Untertabelle durch die gesamte Tabelle. Wenn die Elemente grundsätzlich in der richtigen Reihenfolge sind, wird für alle Datensätze eine Direkteinfügungssortierung durchgeführt. void ShellSort(ELemType A[],int n){ //A[0] ist eine temporäre Speichereinheit, kein Wächter. Wenn j<=0, ist die Einfügeposition erreicht for(dk=n/2;dk>=1;dk/=2)//Schrittgrößenänderung for(i=di 1;i<=n;i) if(A[i]<A[i-dk]){//A[i] muss in die geordnete inkrementelle Untertabelle eingefügt werden A[0]=A[j];//vorübergehend in A[0] gespeichert for(j=i-dk;j>0&&A[0]<A[j];j-=dk) A[j dk]=A[j]; } } Es gibt keine eindeutige Schlussfolgerung zur Zeitkomplexität, es wird jedoch angenommen, dass sie O(n^2) ist. Sortierinstabilität Geeignet für Sequenztabelle
Sortierung tauschen Vertauschen Sie die Positionen der beiden Datensätze in der Sequenz basierend auf den Vergleichsergebnissen der Schlüsselwörter
Blasensortierung
Vergleichen Sie die Werte benachbarter Elemente von hinten nach vorne (von vorne nach hinten) und tauschen Sie sie aus, wenn sie in umgekehrter Reihenfolge vorliegen. Schlüsselwörter schweben nach und nach wie Blasen an der Oberfläche void BubbleSort(ELemType A[],int n){ for(int i=0;i<n-1;i){//n-1 mal Flag = false; for(j=n-1;j>i;j--) if(A[j-1]>A[j]){//Wenn die Reihenfolge umgekehrt ist swap(A[j-1],A[j]); Flag = wahr; } if(flag==false)//Die Flagge des Endes der Blasensortierung: kein einziger Austausch zurückkehren; } } Raum O(1), Zeit O(n^2) Stabilität: Elemente werden nicht ausgetauscht, wenn sie gleich und stabil sind Die durch die Blasensortierung erzeugte Reihenfolge ist global geordnet, was nicht der lokalen Reihenfolge der Einfügungssortierung entspricht.
Schnelle Sorte Basierend auf dem „Teile-und-herrsche“-Denken
Wählen Sie ein beliebiges Element in der Liste aus, das als Pivot sortiert werden soll (normalerweise das erste Element), und teilen Sie die sortierte Sequenz in zwei unabhängige Teile L[1..k-1] L(k) L[k 1, ...n] , sodass die erste Hälfte kleiner als der Pivot und die zweite Hälfte größer als der Pivot ist und der Pivot an der Endposition L(k) platziert wird. Dieser Vorgang wird als One-Pass-Schnellsortierung (One-Pass-Division) bezeichnet. void QuickSort(ELemType A[],int low,int high){ if(low<high){//Bedingung für rekursives Herausspringen int Pivotpos = Partition(A,low,high);//Partition QuickSort(A,low,pivotpos-1); QuickSort(A,pivotpos 1,high); } } Die Leistung und der Schlüssel des Schnellsortierungsalgorithmus ergeben sich aus der Partitionsoperation. Es gibt viele Versionen der Partitionsoperation. Hier wird das erste Element als Pivot-Pivot für die Partitionierung verwendet. int Partition(ElemType A[],int low,int high){ ElemType-Pivot=A[niedrig]; while(low<high){//Schleifenunterbrechungsbedingung while(low<high && A[high]>=pivot) --high; A[niedrig]=A[hoch]; whilie(low<high && A[low]<=pivot) low; A[hoch] = A[niedrig]; } A[low] = Pivot; Rückkehr niedrig; } Platz: Erfordert einen rekursiven Arbeitsstapel, vorzugsweise O(log2n), durchschnittlich O(log2n) (so weit wie möglich in zwei Hälften geteilt, wie ein vollständiger Binärbaum), Im schlimmsten Fall ist O(n) (die Anzahl der Elemente in den beiden geteilten Bereichen beträgt n-1 und 0, es sind n-1 rekursive Aufrufe erforderlich und die Stapeltiefe beträgt O(n)). Zeit: Die Laufzeit der Schnellsortierung hängt davon ab, ob die Division symmetrisch ist. Der schlimmste Fall ist halb n-1 und halb 0. Die maximale Asymmetrie tritt auf jeder Rekursionsebene auf, die der anfänglichen geordneten Sequenz entspricht (sequentiell oder umgekehrt). Ordnung), ist O(n^2) Stabilität: Instabil, die gleichen Elemente werden auf verschiedene Teile übertragen und die relativen Positionen werden geändert. Hinweis: Die schnelle Sortierung erzeugt keine geordnete Teilsequenz, aber das Pivot-Element wird in jedem Durchgang an der endgültigen Position platziert. Verbessern Sie die Effizienz: 1. Wählen Sie Pivot-Elemente aus, die die Sequenz so weit wie möglich unterteilen können. 2. Wählen Sie Pivot-Elemente zufällig aus Die idealste Unterteilung: Beide Textaufgaben sind kleiner als n/2, die Zeit beträgt O(nlog2n) und die Laufzeit der schnellen Sortierung liegt im Durchschnitt sehr nahe am besten Fall. Quicksort ist der Sortieralgorithmus mit der besten Durchschnittsleistung unter allen internen Sortieralgorithmen.
Auswahl sortieren Bei jedem Durchlauf wird das kleinste Element des Schlüsselworts in der Originalsequenz als i-tes Element der geordneten Teilsequenz ausgewählt, bis n-1 Durchläufe erfolgt
Einfache Auswahlsortierung
void SelectSort(ElemType A[],int n){ for(i=0;i<n-1;i)//Insgesamt n-1 Mal min=i;//Notieren Sie die minimale Elementposition for(j=i 1;j<n;j) if(A[j]<A[min]) min = j; if(min!=i) swap(A[i],A[min]); } } Raum O(1), Zeit O(n^2) Stabilität: Es kann dazu führen, dass sich die relative Position von Schlüsselwörtern, die dieselben Elemente enthalten, ändert, was instabil ist.
Heap-Sortierung
Der Heap ist ein Array-Objekt eines Baums. Ein eindimensionales Array kann als vollständiger Binärbaum betrachtet werden, der die Eigenschaften erfüllt 1.L(i)>=L(2i) und L(i)>=L(2i 1) oder 2.L(i)<=L(2i) und L(i)<=L(2i 1) ( 1<=i<=n/2 (nimm die Grenze); Diejenigen, die 1. erfüllen, werden als große Root-Heaps (große Top-Heaps) betrachtet, und diejenigen, die 2. erfüllen, werden als kleine Root-Heaps (kleine Top-Heaps) betrachtet. Heap-Sortierung: Zuerst werden n Elemente im Array in einen anfänglichen Heap eingebaut; nach der Ausgabe des oberen Elements des Heaps wird das untere Element des Heaps zu diesem Zeitpunkt nicht mehr an die Spitze des Heaps gesendet erfüllt die Eigenschaften eines großen Wurzelhaufens und der Heap wird an die Spitze des Heaps verschoben, sodass er weiterhin die Natur eines großen oberen Heaps beibehalten kann. Geben Sie dann das oberste Element des Turms aus und wiederholen Sie den Vorgang, bis nur noch ein Element im Heap übrig ist. Heap-Sortieralgorithmus: void HeapSort(ElemType A[],int len){ BuildMaxHeap(A,len); for(i=len;i>1;i--){ Swap(A[i],A[1]);//Gib das obere Element des Heaps aus und tausche es mit dem unteren Element des Heaps aus HeadAdjust(A,1,i-1);//Ordnen Sie die verbleibenden i-1-Elemente in einem Stapel an } } Die Heap-Sortierung eignet sich für Situationen, in denen es viele Schlüsselwörter gibt (in der Größenordnung von Hunderten von Millionen). Beispiel: Um die 100 höchsten Maximalwerte unter 100 Millionen Zahlen auszuwählen, verwenden Sie zunächst ein Array von 100, lesen Sie die ersten 100 Zahlen, erstellen Sie einen kleinen oberen Heap und lesen Sie dann die verbleibenden Zahlen nacheinander den oberen Teil des Heaps, verwerfen Sie sie andernfalls ersetzen Sie den oberen Teil des Heaps durch diese Zahl und ändern Sie die Größe des Heaps. Nachdem die Daten gelesen wurden, sind die 100 Zahlen im Heap die erforderlichen. Raumeffizienz O(1), Zeiteffizienz O(nlog2n) instabil
1. So konstruieren Sie eine ungeordnete Sequenz in einen anfänglichen Heap Filtern Sie den Teilbaum mit dem (n/2 begrenzten)-ten Knoten als Wurzel (wenn es sich um einen großen Wurzelheap handelt, vergleichen Sie das Schlüsselwort des Wurzelknotens und die Größe seiner Schlüsselwörter für den linken und rechten Teilbaum und tauschen Sie sie aus, wenn sie nicht übereinstimmen die großen Root-Heap-Regeln), Machen Sie diesen Teilbaum zu einem Heap. Anschließend werden die an jedem Knoten verwurzelten Teilbäume (n/2 minus der Grenze -1,1) der Reihe nach vorwärts gefiltert. Bei der Betrachtung als vollständiger Binärbaum wird der Wurzelknoten schrittweise von unten nach oben angepasst (Schlüssel werden zwischen dem Wurzelknoten und den untergeordneten Knoten ausgetauscht); im Array wird der Wurzelknoten von rechts nach links angepasst (Schlüsselwörter werden ausgetauscht). void BuildMaxHeap(ElemType A[],int len){ for(int i=len/2;i>0;i--)//Von i=n/2 bis 1 den Heap wiederholt anpassen HeadAdjust(A,i,len); } void HeadAdjust(ElemType A[],int k,int len){ //HeadAdjust passt den Teilbaum mit Element k als Wurzel an A[0]=A[k]; for(i=2*k;i<=len;i*=2){//Filtern Sie nach unten entlang der untergeordneten Knoten mit größerem Schlüssel if(i<len && A[i]<A[i 1]) i ;//Erhalten Sie den Index des untergeordneten Knotens mit einem größeren Schlüssel if(A[0]>=A[i]) break;//Ende der Filterung anders{ A[k] = A[i];//A[i] an den übergeordneten Knoten anpassen k=i;//Ändern Sie den k-Wert, um weiterhin nach unten zu filtern } } A[k]=A[0]; } Die Anpassungszeit hängt von der Baumhöhe O(h) ab und die Zeitkomplexität beträgt O(n). Eine ungeordnete Zahl kann in linearer Zeit in einen Heap eingebaut werden.
2. Wie werden nach der Ausgabe des obersten Elements des Heaps die verbleibenden Elemente in einen neuen Heap angepasst? Nachdem das oberste Element des Heaps ausgegeben wurde, wird das letzte Element des Heaps mit dem obersten Element des Heaps ausgetauscht. Zu diesem Zeitpunkt werden die Eigenschaften des Heaps zerstört und eine Abwärtsfilterung ist erforderlich. Von oben nach unten filtern (Wurzelknotenschlüsselwörter und Teilbaumschlüsselwörter austauschen)
Heap-Einfügung Den Schwanz einführen und von unten nach oben anpassen
Sortierung und Basissortierung zusammenführen
Zusammenführen, sortieren Die Bedeutung des Zusammenführens besteht darin, zwei oder mehr geordnete Listen zu einer neuen geordneten Liste zusammenzufassen
Unter der Annahme, dass die zu sortierende Tabelle n Datensätze enthält, kann sie als n geordnete Untertabellen mit einer Länge von jeweils 1 betrachtet und dann paarweise zusammengeführt werden, um n/2 nach oben begrenzte Datensätze mit einer Länge von 2 oder 1 zu erhalten. Sequenzliste; weiterhin zwei nacheinander zusammenführen, bis sie zu einer geordneten Liste der Länge n zusammengeführt wird. Diese Methode wird als 2-Wege-Zusammenführungssortierung bezeichnet. ElemType *B=(ElemType*)malloc((n 1)*sizeof(ElemType));//Hilfsarray B void Merge(ElemType A[],int low,int mid,int high){ //Tabelle A[low..mid]A[mid 1..high] werden jeweils geordnet, füge sie zu einer geordneten Liste zusammen for(int k=low;k<=high;k) B[k]=A[k]; for(i=low;j=mid 1;k=i,i<=mid&&j<=high;k ){ if(B[i]<=B[j]) A[k]=B[i ]; sonst A[k]=B[j]; } while(i<=mid) A[k ]=B[i ]; while(j<=high) A[k ]=B[j ]; } void MergeSort(ELemType A[],int low,int high){ if(low<high){ int mid=(low high)/2; MergeSort(A,low,mid); MergeSort(A,mid 1,high); Merge(A,low,mid,high); } } Raumeffizienz: n Einheiten Hilfsraum, Raumkomplexität O(n) Zeiteffizienz: Jede Zusammenführung ist O(n), und die Zusammenführung der oberen Log2n-Grenze ist erforderlich. Die Zeitkomplexität des Algorithmus beträgt O(nlog2n). Stabilität: Die Operation Merge() ändert nicht die relative Reihenfolge von Datensätzen mit denselben Schlüsselwörtern, stabil
Im Allgemeinen erfüllt für die k-Wege-Zusammenführung von N Elementen die Anzahl der Sortiervorgänge m k^m=N, also m=logkN, und da m eine ganze Zahl ist, nimmt m=logkN die Obergrenze an
Radix-Sortierung Nutzen Sie die Idee der Sortierung nach mehreren Schlüsselwörtern, um einzelne logische Schlüsselwörter zu sortieren
Sortieren Sie nach der Keyword-Größe. Das Schlüsselwort jedes Knotens aj besteht aus d Tupeln, kd-1j ist das primäre Schlüsselwort und kj0 ist das sekundäre Schlüsselwort. Um eine Sortierung nach mehreren Schlüsselwörtern zu erreichen, gibt es normalerweise zwei Methoden: 1. Das höchstwertige Bit zuerst (MSD), mehrere kleinere Teilsequenzen Schicht für Schicht entsprechend der abnehmenden Gewichtung der Schlüsselwörter aufteilen und schließlich alle Teilsequenzen zu einer geordneten Sequenz verbinden. 2. Niedrigste Ziffer zuerst (LSD), sortieren Sie in aufsteigender Reihenfolge der Schlüsselwortgewichtung, um eine geordnete Reihenfolge zu bilden. arbeiten: 1. Verteilung: Leeren Sie die Warteschlange Q0 ... Qr-1 und fügen Sie sie dann entsprechend dem entsprechenden Bit jedes Knotens der entsprechenden Warteschlange hinzu. 2. Sammlung: Verbinden Sie die Warteschlangenknoten von Q0..Qr-1 Ende an Ende, um eine neue Knotensequenz zu erhalten und eine neue lineare Tabelle zu bilden. Beispiel: Um Sequenzen unter 1000 zu sortieren, bestimmen Sie zunächst die Basis r (kann als r-Basis betrachtet werden). Die Basis von 1000 ist 10, daher sind während des Sortiervorgangs 10 Kettenwarteschlangen erforderlich. Zahlen unter 1000 sind dreistellig, daher sind drei „Verteilungs“- und „Sammel“-Vorgänge erforderlich. Alle Datensätze mit demselben Schlüsselwort mit der niedrigsten Ziffer (Einerziffer) werden einer Warteschlange zugewiesen, und dann wird der „Sammel“-Vorgang ausgeführt. Platzeffizienz: Der für eine Sortierfahrt erforderliche Hilfsspeicherplatz beträgt r (r Warteschlangen: r Warteschlangenkopfzeiger, r Warteschlangenendzeiger), O(r) Zeiteffizienz: d Durchläufe der Zuweisung und Sammlung, ein Durchgang der Zuweisung erfordert O(n), ein Durchgang der Sammlung erfordert O(r), die zeitliche Komplexität beträgt O(d(n r)), unabhängig vom Anfangszustand der Sequenz . Stabilität: Die Radix-Sortierung selbst erfordert, dass die bitweise Sortierung stabil sein muss, also ist sie stabil!
Vergleich und Anwendung verschiedener interner Sortieralgorithmen
Vergleich interner Sortieralgorithmen
Anwendung des internen Sortieralgorithmus
1. Wenn n klein ist, kann eine direkte Einfügungssortierung oder eine einfache Auswahlsortierung verwendet werden. Da die direkte Einfügungssortierung mehr Datensatzverschiebungen erfordert als die einfache Auswahlsortierung, ist die einfache Auswahlsortierung besser, wenn der Datensatz selbst eine große Menge an Informationen enthält. 2. Wenn der Anfangszustand der Datei grundsätzlich nach Schlüsselwörtern geordnet ist, empfiehlt es sich, Direkteinfügung oder Blasensortierung zu verwenden. 3. Wenn n groß ist, verwenden Sie die Sortiermethode O(nlog2n): Schnellsortierung, Heap-Sortierung oder Zusammenführungssortierung. Die Schnellsortierung gilt derzeit als die beste Methode der vergleichsbasierten internen Sortierung. Wenn die zu sortierenden Schlüsselwörter zufällig verteilt sind, erfordert die Schnellsortierung im Durchschnitt weniger Zeit als die Schnellsortierung nicht eintreten. Aber beide Sortierungen sind instabil. Wenn ein stabiler O(nlog2n)-Algorithmus erforderlich ist, wird die Zusammenführungssortierung verwendet, das Zusammenführen von Paaren aus einem einzelnen Datensatz wird jedoch nicht empfohlen. Sie können normalerweise in Kombination mit der direkten Einfügungssortierung verwendet werden: Verwenden Sie zunächst die direkte Einfügungssortierung, um längere geordnete Unterteilungen zu erhalten. Dateien und fügen Sie sie dann einzeln zusammen. Die Direkteinfügungssortierung ist stabil, und die verbesserte Zusammenführungssortierung ist ebenfalls stabil. 4. Bei der vergleichsbasierten Sortiermethode treten nach jedem Vergleich zweier Schlüsselwortgrößen nur zwei mögliche Übergänge auf. Daher kann ein Binärbaum verwendet werden, um den Vergleichsentscheidungsprozess zu beschreiben. Es kann bewiesen werden, dass: wenn es n Dateien gibt Wenn Schlüsselwörter zufällig verteilt sind, benötigt jeder Sortieralgorithmus, der auf „Vergleich“ basiert, mindestens O(nlog2n) Zeit. 5. Wenn n sehr groß ist und die Anzahl der aufgezeichneten Schlüsselwörter klein ist und zerlegt werden kann, ist es besser, die Basissortierung zu verwenden. 6. Wenn der Datensatz selbst eine große Menge an Informationen enthält, kann eine verknüpfte Liste als Speicherstruktur verwendet werden, um zu vermeiden, dass viel Zeit mit dem Verschieben des Datensatzes verschwendet wird.
externe Sortierung
Grundkonzepte externer Sortieralgorithmen
Die im Speicher durchgeführte Sortierung wird als interne Sortierung bezeichnet. In vielen Anwendungen müssen große Dateien sortiert werden, und die gesamte Datei kann zum Sortieren nicht in den Speicher kopiert werden. Daher müssen die zu sortierenden Datensätze im externen Speicher abgelegt werden. Beim Sortieren werden die Daten Teil für Teil zum Sortieren übertragen. Während des Sortiervorgangs ist ein mehrfacher Austausch zwischen Speicher und externem Speicher erforderlich. Diese Sortierung wird als externe Sortierung bezeichnet
externe Sortiermethode
Der Zeitaufwand während des externen Sortiervorgangs berücksichtigt hauptsächlich die Anzahl der Festplattenzugriffe, also die Anzahl der IOs Bei der externen Sortierung wird normalerweise die Zusammenführungssortierung verwendet: 1. Teilen Sie die Dateien im externen Speicher entsprechend der Größe des Speicherpuffers in Unterdateien der Länge l auf, lesen Sie sie nacheinander in den Speicher ein, sortieren Sie sie mithilfe der internen Sortiermethode und schreiben Sie die geordneten Unterdateien Diese geordneten Unterdateien werden nach dem Zurücksortieren in den externen Speicher als zusammengeführte Segmente oder sequentielle Zeichenfolgen bezeichnet. 2. Führen Sie diese zusammengeführten Segmente nacheinander zusammen, sodass die zusammengeführten Segmente allmählich von klein nach groß zunehmen, bis die gesamte geordnete Datei erhalten wird. Beim Zusammenführen in einem Durchgang werden alle Segmente derselben aktuellen Ebene zu einem zusammengeführt Gesamte externe Sortierzeit = Zeit, die für die interne Sortierung benötigt wird, Zeit für das Lesen und Schreiben externer Informationen, Zeit, die für die interne Zusammenführung benötigt wird Die Zeit zum Lesen und Schreiben externer Speicherinformationen ist viel länger als die Zeit zum internen Sortieren und internen Zusammenführen. Das Lesen und Schreiben externer Speicherinformationen basiert auf „Festplattenblöcken“. Daher beträgt die Gesamtzahl der erforderlichen Lese- und Schreibvorgänge: 2*Gesamtzahl der Datensätze/Anzahl der Datensätze in einem Festplattenblock*n (Anzahl der Male) Gesamtzahl der Datensätze/Anzahl der Datensätze in einem Plattenblock (interne Sortierung erfordert auch ein vollständiges Lesen und Schreiben) Führen Sie für r anfängliche Zusammenführungssegmente eine k-Wege-balancierte Zusammenführung durch. Die Höhe des Baums (invertierter k-ary-Baum) = logkr und die Obergrenze = die Anzahl der Zusammenführungsdurchgänge S Es ist ersichtlich, dass durch Erhöhen der Anzahl der Zusammenführungspfade k oder durch Verringern der Anzahl der anfänglichen Zusammenführungssegmente r die Anzahl der Zusammenführungsdurchgänge S verringert werden kann, wodurch die Anzahl der Festplatten-E/As verringert und die Geschwindigkeit der externen Sortierung verbessert wird.
Mehrweg ausgewogener Merging- und Loser-Baum
Eine Erhöhung der Anzahl der Zusammenführungspfade k kann die Anzahl der Zusammenführungsdurchgänge S verringern, erhöht jedoch die interne Zusammenführungszeit. Bei der internen Zusammenführung sind für die Auswahl des kleinsten Schlüsselworts unter k Elementen k-1 Vergleiche erforderlich, und für jede Zusammenführung von n Elementen sind (n-1)(k-1) Vergleiche erforderlich Anzahl der für die Zusammenführung erforderlichen Vergleiche beträgt S(n-1)(k-1)=log2r nimmt die Obergrenze an (n-1)(k-1)/log2k nimmt die Obergrenze an, wobei (k-1)/log2k die Obergrenze annimmt und mit wächst Wachstum von k. Daher kann der gewöhnliche interne Zusammenführungssortierungsalgorithmus nicht verwendet werden.
Um zu verhindern, dass die interne Zusammenführung durch die Erhöhung von k beeinträchtigt wird, wird der Verliererbaum eingeführt. Der Verliererbaum ist eine Variante der Baumsortierung und kann als vollständiger Binärbaum betrachtet werden. Die k Blattknoten speichern jeweils die Datensätze der k Zusammenführungssegmente, die derzeit am Vergleich während des Zusammenführungsprozesses teilnehmen. Die internen Knoten werden verwendet, um die „Verlierer“ der linken und rechten Knoten aufzuzeichnen und die Gewinner mit dem Vergleich fortzusetzen der Wurzelknoten. Der Wurzelknoten ist der Gewinner. S(n-1) log2k übernimmt die Obergrenze = log2k übernimmt die Obergrenze (n-1) log2k übernimmt die Obergrenze = (n-1) log2r übernimmt die Obergrenze Nach der Verwendung des Verliererbaums hat die Vergleichsreihenfolge der internen Zusammenführungssortierung nichts mit k zu tun, aber die Anzahl der Zusammenführungspfade k ist durch den Speicherplatz begrenzt, was die Pufferkapazität innerhalb eines bestimmten Raums verringern kann. Wenn der k-Wert zu groß ist, wird zwar die Anzahl der Zusammenführungsdurchgänge verringert, die Anzahl der E/As steigt jedoch dennoch. ps: Im Vergleich zum Gewinnerbaum müssen Sie bei der Rekonstruktion des Gewinnerbaums einen Vergleich mit den Geschwisterknoten durchführen und dann die übergeordneten Knoten ändern. Bei der Rekonstruktion des Verliererbaums müssen Sie nur ganz nach oben gehen, um die übergeordneten Knoten zu vergleichen.
Ersetzungsauswahlsortierung (erzeugt erste Zusammenführungssegmente)
Um r zu reduzieren, benötigt r=n/l eine Obergrenze, daher müssen wir einen Weg finden, ein längeres anfängliches Zusammenführungssegment l zu erzeugen. Angenommen, die zu sortierende Anfangsdatei ist FI, die anfängliche Zusammenführungssegment-Ausgabedatei ist FO, der Speicherarbeitsbereich ist WA, FO und WA sind anfänglich leer und WA kann w Datensätze aufnehmen. Die Schritte des Ersetzungsauswahlalgorithmus sind wie folgt: 1. Geben Sie w Datensätze von FI in den Arbeitsbereich WA ein 2. Wählen Sie den Datensatz mit dem minimalen Schlüsselwortwert aus WA aus und zeichnen Sie ihn als MINIMAX auf 3. MINIMAX auf FO aufzeichnen 4. Wenn FI nicht leer ist, geben Sie den nächsten Datensatz von FI an WA aus 5. Wählen Sie aus allen Datensätzen in WA den kleinsten Schlüsselwortdatensatz aus, dessen Schlüsselwörter größer als die Schlüsselwörter des MINIMAX-Datensatzes sind, als neuen MINIMAX-Datensatz. 6. Wiederholen Sie die Schritte 3–5, bis kein neues MINIMAX in WA ausgewählt werden kann, erhalten Sie ein anfängliches Zusammenführungssegment und geben Sie ein Endflag des Zusammenführungssegments an FO aus. 7. Wiederholen Sie die Schritte 2–6, bis WA leer ist und alle anfänglichen zusammengeführten Segmente erhalten sind. Der Prozess der Auswahl der MINIMAX-Aufzeichnung in WA erfordert die Verwendung von Verliererbäumen.
bester Zusammenführungsbaum
Nachdem die Datei durch Substitutionsauswahl sortiert wurde, werden zunächst zusammengeführte Segmente unterschiedlicher Länge erhalten. Wie kann die Zusammenführungsreihenfolge der anfänglichen Zusammenführungssegmente mit unterschiedlichen Längen organisiert werden, um die Anzahl der E/As zu minimieren? Zeichnen Sie den entsprechenden Zusammenführungsbaum. Die gewichtete Pfadlänge WPL des Baums ist die Gesamtzahl der während des rekursiven Prozesses gelesenen Datensätze. Erweitern Sie die Idee des Huffman-Baums auf den Fall des k-ary-Baums. Lassen Sie im Zusammenführungsbaum zuerst das anfängliche Zusammenführungssegment mit einer kleinen Anzahl von Datensätzen und das anfängliche Zusammenführungssegment mit einer großen Anzahl von Datensätzen zusammenführen zuletzt zusammengeführt Die gesamte IO kann mit der geringsten Anzahl von Malen erstellt werden Der beste Zusammenführungsbaum. Methode zum Erstellen des besten Zusammenführungsbaums: Wenn das anfängliche Zusammenführungssegment nicht ausreicht, um einen strengen k-ary-Baum zu bilden, muss ein „virtuelles Segment“ mit einer Länge von 0 hinzugefügt werden. Wie bestimme ich die Anzahl der hinzuzufügenden virtuellen Segmente? Ein strenger k-ary-Baum hat n0=(k-1)nk 1. Wenn (n0-1)%(k-1)=0, dann kann ein k-Fork-Mergebuch mit nk internen Knoten erstellt werden. Wenn (n0-1)%(k-1)=u!=0, können k-u-1 leere Zusammenführungssegmente hinzugefügt werden, um einen Zusammenführungsbaum zu erstellen
Das AOV-Netzwerk und das AOE-Netzwerk sind beide DAGs, und die Kanten und Scheitelpunkte der beiden haben unterschiedliche Bedeutungen. Die Kanten im AOV-Netzwerk haben keine Gewichtungen und stellen nur den Kontext zwischen den Scheitelpunkten dar; die Kanten im AOE-Netzwerk haben Gewichte und stellen die Kosten für den Abschluss der Aktivität dar.
Der Unterschied zwischen Dijkstra und Prim: Die Summe aller Kanten im minimalen Spannbaum von Prim muss am kleinsten sein, nur der Abstand zwischen zwei Punkten im MST (Unit Point Shortest Path Tree) ist nicht unbedingt kleiner als der Der ursprüngliche Graph AB ist kürzer, da der Abstand zum Quellpunkt addiert wird. Der Unterschied zwischen beiden liegt in der Relaxationsoperation. Darüber hinaus kann Prim nur für ungerichtete Graphen verwendet werden, und Dijkstra kann \ ungerichtete Graphen haben (ungerichtete Graphen werden in gerichtete Graphen umgewandelt, ij bilateral)
Das Durchlaufen der Adjazenzmatrix ist eindeutig, das Durchlaufen der Adjazenzliste jedoch nicht eindeutig.
Konnektivität wird in ungerichteten Graphen diskutiert, und starke Konnektivität wird in gerichteten Graphen diskutiert.