Lektion
Die Theorie — Binäre Suche: vier Entscheidungen, sechzehn Schleifen
Die binäre Suche findet einen Wert x in einem sortierten Array, indem sie einen Indexbereich von lo bis hi verwaltet, in dem x liegen muss, sofern es überhaupt vorkommt. Jeder Durchlauf liest das mittlere Element a[mid] und behält die Hälfte auf der Seite von x. Der Bereich ist abgeschlossen, wenn hi der letzte noch enthaltene Index ist (lo <= hi, hi beginnt bei n − 1), und halboffen, wenn hi der erste Index dahinter ist (lo < hi, hi beginnt bei n).
Was die einzelnen Symbole bedeuten
lo, hi- die Grenzen des noch zu durchsuchenden Bereichs
mid- ⌊(lo + hi)/2⌋, der in diesem Durchlauf gelesene Index
n- die Anzahl der Elemente an den Indizes 0 bis n − 1
N- die Länge des größten geprüften Arrays: jedes n von 0 bis N und jeder Zielwert, insgesamt (N + 1)² Fälle
- Setzt voraus
- Verschiedene Werte im Array, eine Schleife, die sofort zurückkehrt, sobald a[mid] gleich x ist, sowie bei Ablauf und Prüfung ganze Zahlen ohne Obergrenze. Für die Überlauftabelle entfällt die letzte Annahme; dort wird wie in Java mit 32 Bit gerechnet.
- Versagt, wenn
- Auch bei mehrfach vorkommenden Werten geben die beiden richtigen Schleifen einen Index zurück, an dem x steht, aber nicht unbedingt den ersten. Für den ersten Treffer, wie ihn eine Bereichsabfrage auf einem sortierten Index benötigt, darf die Schleife bei Gleichheit nicht anhalten: Der Bereich ist halboffen, bei a[mid] ≥ x gilt stets
hi = mid, und nach dem Ende der Schleife ist lo die Antwort.
Aufgabe vollständig gelöst
-
Warum nur zwei Schleifen übrig bleiben 4 Schritte
Klick auf Hängt bei lo = mid. Die Prüfung kann Arrays mit höchstens 256 Elementen testen. Zeige für jedes n, dass die Schleife mit halboffenem Intervall und
lo = mid + 1immer endet, dass dieselbe Schleife mitlo = midhängen bleibt, sobald hi = lo + 1 und a[lo] < x gilt, und welche Bedingung sich daraus für die Schleife mit geschlossenem Intervall ergibt.-
Bei der Schleife mit halboffenem Intervall wird der Rumpf nur ausgeführt, solange lo < hi gilt. Der abgerundete Wert von mid ist dann mindestens lo und echt kleiner als hi.
-
Daher verringert
hi = midden Wert von hi um mindestens eins, undlo = mid + 1erhöht lo um mindestens eins. In beiden Fällen sinkt hi − lo, anfangs gleich n, bei jedem Durchlauf um mindestens eins. Unter null kann die Differenz nicht fallen, also endet die Schleife nach höchstens n Durchläufen. -
Betrachte nun
lo = mid. Für hi = lo + 1 gilt mid = ⌊(2lo + 1)/2⌋ = lo. Ist außerdem a[lo] < x, bleibt lo durch die Aktualisierung unverändert, und der nächste Durchlauf wiederholt lediglich den vorigen. Das vorgegebene Beispiel erreicht diesen Zustand bei lo = 6, hi = 7, mit a[6] = 13 und x = 14. -
Die Schleife mit geschlossenem Intervall läuft, solange lo <= hi gilt. Deshalb ist lo = hi zulässig; dann gilt mid = lo = hi. Eine Aktualisierung auf mid würde nichts verändern, also müssen beide Aktualisierungen über mid hinausgehen. Das ist möglich, weil a[mid] gerade gelesen wurde. Beim halboffenen Intervall ist hi bereits ausgeschlossen. Dort verwirft
hi = middas eben gelesene Element und alle Elemente darüber, die wegen a[mid] > x nicht infrage kommen.hi = mid − 1würde zusätzlich a[mid − 1] verwerfen, obwohl dort x stehen könnte.
Antwort
Jeder Durchlauf muss das Suchintervall verkleinern, darf nur Indizes ausschließen, an denen x nicht stehen kann, und das Anfangsintervall muss das gesamte Array umfassen. Für
lo <= hibedeutet das hi = n − 1,hi = mid − 1undlo = mid + 1; fürlo < higilt hi = n,hi = midundlo = mid + 1. Die Prüfung bestätigt die beiden verbleibenden Varianten für Arrays mit bis zu 256 Elementen. Das Argument gilt für jedes n und leistet damit, was kein Test leisten kann. Bei 32-Bit-Arithmetik kommt eine weitere Voraussetzung hinzu: lo + hi muss darstellbar sein. Die dritte Erkenntnis zeigt, was geschieht, wenn diese Voraussetzung verletzt ist. -
Lernpfad
Warum ein Algorithmus korrekt ist
Quellen (2)
- The overflow in the JDK's binary search, the nine years it went unnoticed, and Bentley's proved-correct version carrying the same line; also the source of the 1946 and 1962 dates the first insight quotes from Programming Pearls: Joshua Bloch, "Extra, Extra - Read All About It: Nearly All Binary Searches and Mergesorts are Broken." Google Research Blog, 2 June 2006.
- The bug report the page reproduces: java.util.Arrays.binarySearch on a 1,100,000,000-byte array, searching for its last element, throwing ArrayIndexOutOfBoundsException: -1064671149: OpenJDK bug JDK-5045582, "(coll) binarySearch() fails for size larger than 1<<30." Filed 11 May 2004 against Java 5.0, resolved (fixed in 6) 28 April 2006.