Greedy-Algorithmus mit Beispiel: Was ist, Methode und Ansatz

โšก Intelligente Zusammenfassung

Der Greedy-Algorithmus baut eine optimale Lรถsung auf, indem er in jedem Schritt die beste lokale Wahl trifft. Dabei werden Rekursion, geordnete Ressourcen und eine Abbruchbedingung verwendet, um Scheduling-, Spannbaum-, Kรผrzeste-Wege- und Netzwerkoptimierungsprobleme effizient zu lรถsen.

  • ๐Ÿ“˜ Definition: Ein Greedy-Algorithmus wรคhlt rekursiv in jedem Schritt die lokal optimale Lรถsung aus, mit dem Ziel, eine global akzeptable Lรถsung zu finden.
  • ๐Ÿ“œ Geschichte: Dijkstra, Prim und Kruskal prรคgten das Paradigma in den 1950er Jahren, und CLRS formalisierte es spรคter als eigenstรคndige Entwurfstechnik.
  • ๐Ÿงญ Zwei Bedingungen: Jeder Schritt muss das Problem in Richtung der bestmรถglichen Lรถsung lenken, und der Prozess muss nach einer endlichen Anzahl von gierigen Schritten zum Stillstand kommen.
  • ๐Ÿ“… Aktivitรคtsauswahl: Klassisches Beispiel fรผr die Nichtรผberschneidung von Zeitplรคnen.ping Aktivitรคten durch Vergleich der berรผcksichtigten und verbleibenden Start- und Endzeiten.
  • โš ๏ธ Einschrรคnkungen: Der Greedy-Algorithmus versagt, wenn lokale Entscheidungen kein globales Optimum garantieren kรถnnen, wie beispielsweise beim Sortieren oder beim allgemeinen Problem des Handlungsreisenden.
  • ๐ŸŒ Hรคufige Beispiele: Dijkstra, Prim, Kruskal, Huffman-Codierung, fraktioniertes Rucksackproblem und Jobsequenzierung mit Fristen verwenden alle eine Greedy-Strategie.

Greedy-Algorithmus mit Beispiel: Was ist, Methode und Ansatz

Was ist ein Greedy-Algorithmus?

A Gieriger Algorithmus Teilt eine Menge von Ressourcen rekursiv auf der Grundlage der maximalen unmittelbaren Verfรผgbarkeit dieser Ressource in jeder Phase der Ausfรผhrung auf.

Die Lรถsung eines Problems mit dem Greedy-Ansatz umfasst zwei Phasen:

  1. Scannen der Artikelliste
  2. Optimierung

Beide Phasen laufen parallel ab, wรคhrend das Eingabe-Array schrittweise unterteilt wird.

Um dem Greedy-Ansatz zu folgen, sind Grundkenntnisse in Rekursion und Kontextwechsel hilfreich. tracBetrachten wir den Code. Das Greedy-Paradigma lรคsst sich mit einem Paar notwendiger und hinreichender Aussagen beschreiben.

Zwei Bedingungen definieren das Greedy-Paradigma.

  • Jeder einzelne Entscheidungsschritt muss das Problem in Richtung der bestmรถglichen Lรถsung lenken.
  • Die Problemstruktur muss nach einer endlichen Anzahl von gierigen Schritten zum Stillstand kommen.

Nachdem wir die Theorie geklรคrt haben, wollen wir uns nun die Geschichte des Greedy-Search-Ansatzes ansehen.

Geschichte von Greedy Algorithms

Hier sind die wichtigsten Meilensteine โ€‹โ€‹in der Geschichte der Greedy-Algorithmen:

  • Greedy-Algorithmen wurden erstmals in den 1950er Jahren fรผr Graph-Walk-Algorithmen konzipiert.
  • Edsger Dijkstra entwickelte seinen Algorithmus zur Berechnung des kรผrzesten Weges, um Routen durch die niederlรคndische Hauptstadt Amsterdam zu verkรผrzen.
  • Im selben Jahrzehnt entwickelten Prim und Kruskal Optimierungsstrategien, die die Pfadkosten entlang gewichteter Routen minimieren, um minimale Spannbรคume zu konstruieren.
  • In den 70er Jahren beschrieben die amerikanischen Forscher Cormen, Leiserson, Rivest und Stein in ihrem klassischen Werk die rekursive Substrukturierung von Greedy-Lรถsungen. Introduction to Algorithms Lehrbuch.
  • Das gierige Suchparadigma wurde 2005 in den NIST-Aufzeichnungen als eigenstรคndige Optimierungsstrategie katalogisiert.
  • Bis heute verwenden Webprotokolle wie Open Shortest Path First (OSPF) und viele Paketvermittlungsprotokolle die Greedy-Strategie, um die Transitzeit in einem Netzwerk zu minimieren.

Gierige Strategien und Entscheidungen

Die Logik reduziert sich in jeder Phase auf eine binรคre Entscheidung โ€“ โ€žgierigโ€œ oder โ€žnicht gierigโ€œ โ€“ basierend auf der Richtung, die der Algorithmus zum Fortschreiten einschlรคgt.

Der Dijkstra-Algorithmus identifiziert beispielsweise Hosts im Internet, indem er in jedem Schritt eine Kostenfunktion auswertet. Der von der Kostenfunktion zurรผckgegebene Wert entscheidet darรผber, ob der nรคchste Pfad โ€žgierigโ€œ oder โ€žnicht-gierigโ€œ ist.

Kurz gesagt, ein Algorithmus hรถrt auf, gierig zu sein, sobald er einen Schritt unternimmt, der nicht lokal optimal ist, und gierige Probleme enden, wenn kein weiterer gieriger Schritt mehr mรถglich ist.

Eigenschaften des Greedy-Algorithmus

Die wichtigen Merkmale eines Greedy-Algorithmus sind:

  • Eine geordnete Liste von Ressourcen enthรคlt Kosten- oder Wertzuweisungen, die die Beschrรคnkungen des Systems quantifizieren.
  • Der Algorithmus nutzt die maximale Menge an Ressourcen innerhalb der vorgegebenen Zeit.
  • Bei einem Aktivitรคtsplanungsproblem werden beispielsweise die Ressourcenkosten in Stunden gemessen und die Aktivitรคten mรผssen der Reihe nach ausgefรผhrt werden.

Eigenschaften des Greedy-Algorithmus

Warum den gierigen Ansatz wรคhlen?

Hier sind die Grรผnde fรผr die Verwendung des Greedy-Ansatzes:

  • Der gierige Ansatz birgt Kompromisse, die ihn fรผr die Optimierung gut geeignet machen.
  • Der offensichtlichste Grund ist, umgehend eine realisierbare Lรถsung zu finden. Im unten beschriebenen Problem der Aktivitรคtsauswahl kรถnnen, falls weitere Aktivitรคten vor Abschluss der aktuellen Aktivitรคt mรถglich sind, diese im selben Zeitfenster eingeplant werden.
  • Ein weiterer Grund ist, dass es ein Problem rekursiv anhand einer Bedingung aufteilt, ohne dass Teillรถsungen zusammengefรผhrt werden mรผssen.
  • Bei dem Problem der Aktivitรคtsauswahl wird der rekursive Teilungsschritt dadurch erreicht, dass die Liste einmal durchsucht und nur die in Frage kommenden Aktivitรคten berรผcksichtigt werden.

Wie man das Problem der Aktivitรคtsauswahl lรถst

Im Beispiel der Aktivitรคtsplanung hat jede Aktivitรคt eine Start- und Endzeit und ist zur besseren รœbersicht mit einer Nummer gekennzeichnet. Es gibt zwei Aktivitรคtskategorien:

  1. Berรผcksichtigung der Aktivitรคt: Die Referenzaktivitรคt, anhand derer die Fรคhigkeit gemessen wird, weitere verbleibende Aktivitรคten einzufรผgen.
  2. Verbleibende Aktivitรคten: Aktivitรคten an einem oder mehreren Indizes vor der betrachteten Aktivitรคt.

Die Kosten fรผr die Durchfรผhrung einer Aktivitรคt entsprechen ihrer Dauer, berechnet als (Ende โ€“ Anfang).

Der gierige Umfang ist einfach die Anzahl der verbleibenden Aktivitรคten, die innerhalb der Zeit einer betrachteten Aktivitรคt durchgefรผhrt werden kรถnnen.

ArchiStruktur des Greedy Approach

Schritt 1) Scannen Sie die Liste der Aktivitรคtskosten, beginnend mit dem Index 0 als Ausgangspunkt.

Schritt 2) Wenn bis zum Ende der betrachteten Aktivitรคt weitere Aktivitรคten abgeschlossen werden kรถnnen, suchen Sie nach diesen verbleibenden Aktivitรคten.

Schritt 3) Wenn keine weiteren Aktivitรคten geplant werden kรถnnen, wird die aktuell verbleibende Aktivitรคt als nรคchste berรผcksichtigte Aktivitรคt herangezogen. Wiederholen Sie Schritt 1 und Schritt 2 mit der neuen Aktivitรคt. Falls keine Aktivitรคten mehr verfรผgbar sind, fahren Sie mit Schritt 4 fort.

Schritt 4) Gib die Vereinigung der betrachteten Indizes zurรผck โ€“ dies sind die Aktivitรคtsindizes, die den Durchsatz maximieren.

ArchiStruktur des Greedy Approach

ArchiStruktur des Greedy Approach

Code Erlรคuterung

#include<iostream>
#include<stdio.h>
#include<stdlib.h>

#define MAX_ACTIVITIES 12

ArchiStruktur des Greedy Approach

Erklรคrung des Codes:

  1. Enthaltene Header-Dateien/Klassen
  2. Die maximale Anzahl der vom Benutzer konfigurierbaren Aktivitรคten.
using namespace std;

class TIME
{
    public:
    int hours;

    public: TIME()
    {
   	 hours = 0;
    }
};

ArchiStruktur des Greedy Approach

Erklรคrung des Codes:

  1. Deklariert den Standard-Namensraum fรผr Streaming-Operationen.
  2. Eine Klassendefinition fรผr TIME
  3. Ein Stunden-Zeitstempel.
  4. Ein TIME-Standardkonstruktor
  5. Die Stundenvariable.
class Activity
{
    public:
    int index;
    TIME start;
    TIME finish;

    public: Activity()
    {
   	 start = finish = TIME();
    }
};

ArchiStruktur des Greedy Approach

Erklรคrung des Codes:

  1. Eine Klassendefinition fรผr Activity.
  2. Zeitstempel, die zusammen eine Dauer definieren.
  3. Im Standardkonstruktor werden alle Zeitstempel auf 0 initialisiert.
class Scheduler
{
    public:
    int considered_index,init_index;
    Activity *current_activities = new    Activity[MAX_ACTIVITIES];
    Activity *scheduled;

ArchiStruktur des Greedy Approach

Erklรคrung des Codes:

  1. Teil 1 der Definition der Scheduler-Klasse.
  2. considered_index ist der Startpunkt fรผr das Scannen des Arrays.
  3. init_index wird verwendet, um wรคhrend der Einrichtung zufรคllige Zeitstempel zuzuweisen.
  4. Mit dem new-Operator wird dynamisch ein Array von Activity-Objekten erstellt.
  5. Der geplante Zeiger enthรคlt das aktuelle Ergebnis der Greedy-Operation.
Scheduler()
{
   	 considered_index = 0;
   	 scheduled = NULL;
...
...

ArchiStruktur des Greedy Approach

Erklรคrung des Codes:

  1. Der Scheduler-Konstruktor โ€” Teil 2 der Klassendefinition.
  2. considered_index markiert den Beginn des aktuellen Scans.
  3. Das AusmaรŸ der Gier ist zu Beginn undefiniert.
for(init_index = 0; init_index < MAX_ACTIVITIES; init_index++)
 {
   		 current_activities[init_index].start.hours =
   			 rand() % 12;

   		 current_activities[init_index].finish.hours =
   			 current_activities[init_index].start.hours +
   				 (rand() % 2);

   		 printf("\nSTART:%d END %d\n",
   		 current_activities[init_index].start.hours
   		 ,current_activities[init_index].finish.hours);
 }
&#8230;
&#8230;

ArchiStruktur des Greedy Approach

Erklรคrung des Codes:

  1. Eine For-Schleife initialisiert die Start- und Endzeiten jeder geplanten Aktivitรคt.
  2. Legt die Startzeit fest.
  3. Legt als Endzeitpunkt fest, dass er mindestens zur Startzeit liegt.
  4. Eine Debug-Anweisung gibt die zugewiesenen Zeitdauern aus.
	public:
   		 Activity * activity_select(int);
};

ArchiStruktur des Greedy Approach

Erklรคrung des Codes:

  1. Teil 4 โ€” der letzte Teil der Scheduler-Klassendefinition.
  2. activity_select() nimmt einen Startindex als Basis und teilt die gierige Suche in Teilprobleme auf.
Activity * Scheduler :: activity_select(int considered_index)
{
    this->considered_index = considered_index;
    int greedy_extent = this->considered_index + 1;
&#8230;
&#8230;

ArchiStruktur des Greedy Approach

  1. Der Bereichsauflรถsungsoperator (::) verknรผpft die Funktionsdefinition mit der Klasse Scheduler.
  2. considered_index wird als Wert รผbergeben, und greedy_extent wird auf den unmittelbar darauf folgenden Index initialisiert.
Activity * Scheduler :: activity_select(int considered_index)
{
    	while( (greedy_extent < MAX_ACTIVITIES ) &&
   	 ((this->current_activities[greedy_extent]).start.hours <
   		 (this->current_activities[considered_index]).finish.hours ))
    	{
   	 printf("\nSchedule start:%d \nfinish%d\n activity:%d\n",
   	 (this->current_activities[greedy_extent]).start.hours,
   	 (this->current_activities[greedy_extent]).finish.hours,
   	 greedy_extent + 1);
   	 greedy_extent++;
    	}
&#8230;
...

ArchiStruktur des Greedy Approach

Erklรคrung des Codes:

  1. Die Kernlogik โ€“ die gierige Ausdehnung ist auf MAX_ACTIVITIES begrenzt.
  2. Die Startzeit der aktuellen Aktivitรคt wird mit der Endzeit der betrachteten Aktivitรคt verglichen.
  3. Solange die Bedingung erfรผllt ist, wird optional eine Debug-Meldung ausgegeben.
  4. Der gierige Extent geht dann zum nรคchsten Index im Aktivitรคtsarray รผber.
...
if ( greedy_extent <= MAX_ACTIVITIES )
    {

   	 return activity_select(greedy_extent);
    }
    else
    {
   	 return NULL;
    }
}

ArchiStruktur des Greedy Approach

Erklรคrung des Codes:

  1. Die Bedingungsprรผfung stellt sicher, dass alle Aktivitรคten abgedeckt sind.
  2. Andernfalls startet der Algorithmus die gierige Suche vom aktuellen Index aus neu โ€“ ein rekursiver Schritt, der das Problem gierig aufteilt.
  3. Falls ja, wird die Kontrolle an den Aufrufer zurรผckgegeben, ohne dass die Mรถglichkeit besteht, Greed zu erweitern.
int main()
{
    Scheduler *activity_sched = new Scheduler();
    activity_sched->scheduled = activity_sched->activity_select(
   				activity_sched->considered_index);
    return 0;
}

ArchiStruktur des Greedy Approach

Erklรคrung des Codes:

  1. Die Hauptfunktion ruft den Scheduler auf.
  2. Ein neues Scheduler-Objekt wird instanziiert.
  3. Die Funktion activity_select() gibt nach Beendigung der gierigen Quest einen Activity-Zeiger an den Aufrufer zurรผck.

Ausgang:

START:7 END 7

START:9 END 10

START:5 END 6

START:10 END 10

START:9 END 10

Schedule start:5
finish6
 activity:3

Schedule start:9
finish10
 activity:5

Einschrรคnkungen der Greedy-Technik

Der Greedy-Ansatz eignet sich nicht fรผr Probleme, die fรผr jedes Teilproblem eine optimale Lรถsung erfordern, wie beispielsweise das Sortieren.

In solchen Fรคllen kann die Greedy-Methode zu Fehlern fรผhren โ€“ im schlimmsten Fall liefert sie eine nicht optimale Lรถsung.

Der grรถรŸte Nachteil gieriger Algorithmen besteht darin, dass sie Entscheidungen treffen, ohne zu wissen, was nach dem aktuellen gierigen Zustand kommt.

Das folgende Diagramm veranschaulicht diesen Nachteil der Greedy-Methode.

Einschrรคnkungen der Greedy-Technik

Bei dem hier als Baum dargestellten Greedy-Scan (hรถherer Wert bedeutet hรถhere Gier) wรผrde ein Algorithmus mit dem Wert 40 als nรคchstes 29 auswรคhlen, dann bei 12 enden, was insgesamt 41 ergibt.

Im Gegensatz dazu wรผrde eine Divide-and-Conquer-Strategie 25 mit 40 kombinieren, was insgesamt 65 ergibt und 24 Punkte hรถher ist als bei der lokal gierigen Wahl.

Beispiele fรผr Gierig Algorithms

Die meisten Netzwerkalgorithmen basieren auf einem Greedy-Ansatz. Gรคngige Beispiele fรผr Greedy-Algorithmen sind:

  • Prims Algorithmus fรผr minimale Spannbรคume
  • Problem des Handlungsreisenden (ungefรคhr)
  • Graphische Kartenfรคrbung
  • Kruskals Algorithmus fรผr minimale Spannbรคume
  • Dijkstras Algorithmus fรผr den kรผrzesten Pfad
  • Knotenรผberdeckung des Graphen
  • Rucksackproblem
  • Jobsequenzierung mit Fristen

Hรคufig gestellte Fragen

Greedy-Algorithmen bilden die Grundlage fรผr Entscheidungsbaumaufteilungen, Feature-Selection-Wrapper und Beam Search in Transformer-Decodern. KI-Systeme nutzen zudem Greedy-Layerwise-Pretraining und Greedy-Policy-Iteration im Reinforcement Learning, um schneller zu starken lokalen Optima zu gelangen.

Copilot und GPT unterstรผtzen Dijkstra-, Kruskal-, Huffman-Codierungs- und Aktivitรคtsauswahlroutinen in Python, C++den JavaDie Entwickler รผberprรผfen weiterhin die Greedy-Choice-Eigenschaft und die optimale Unterstruktur vor der Auslieferung.pingda KI-Code Randfรคlle รผbersehen kann.

Greedy-Algorithmen treffen in jedem Schritt eine lokal optimale Entscheidung und รผberprรผfen diese nicht erneut. Dynamische Programmierung untersucht รœberschneidungen.ping Teilprobleme werden gelรถst und die Ergebnisse in einer Tabelle gespeichert, um ein globales Optimum zu gewรคhrleisten. Greedy-Algorithmen sind schneller, funktionieren aber nur, wenn die Greedy-Choice-Eigenschaft erfรผllt ist.

Die Greedy-Choice-Eigenschaft bedeutet, dass ein globales Optimum durch lokal optimale Entscheidungen erreicht werden kann. Optimale Teilstruktur bedeutet, dass die optimale Lรถsung des Problems optimale Lรถsungen fรผr seine Teilprobleme enthรคlt. Beide Bedingungen mรผssen erfรผllt sein, damit ein Greedy-Algorithmus nachweislich korrekt ist.

Die Aktivitรคtsauswahl hat nach Sortierung nach Endzeit eine Laufzeit von O(n log n). Der Dijkstra-Algorithmus mit einem binรคren Heap hat eine Laufzeit von O((V + E) log V). Der Kruskal-Algorithmus hat mit Union-Find eine Laufzeit von O(E log E). Die Huffman-Codierung hat eine Laufzeit von O(n log n). Die Sortierung ist รผblicherweise der Hauptfaktor fรผr die Komplexitรคt.

Gierige Algorithmen sind die Grundlage fรผr GPS-Routing (Dijkstra), Netzwerkdesign (Prim, Kruskal), Dateikomprimierung (Huffman), CPU- und Festplattenplanung, Lastverteilung, Mรผnzwechsel in Registrierkassen und Paketrouting-Protokolle wie OSPF und BGP.

Greedy-Algorithmen versagen, wenn lokal optimale Entscheidungen zu einem global schlechteren Ergebnis fรผhren. Das allgemeine Problem des Handlungsreisenden, das 0/1-Rucksackproblem und das Mรผnzwechselproblem mit nicht-kanonischen Werten sind klassische Beispiele, bei denen Greedy-Algorithmen suboptimal sind und dynamische Programmierung erforderlich ist.

Die beiden Standardtechniken sind das Austauschargument und die Greedy-Stays-Ahead-Methode. Beim Austauschargument wird jede nicht-gierige Option gegen die gierige ausgetauscht, ohne die Lรถsung zu verschlechtern. Die Greedy-Stays-Ahead-Methode vergleicht partiell gierige und optimale Lรถsungen schrittweise.

Fassen Sie diesen Beitrag mit folgenden Worten zusammen: