Betrachten Sie die Konvertierung von Python rekursiv in nicht rekursiv

Einführung

Rekursive Funktionen sind zwar praktisch, haben aber auch Nachteile. Aus diesem Grund wurden rekursive Funktionen seit langem in nicht rekursive Funktionen konvertiert. Die Konvertierung selbst wurde durchgeführt, war aber zufällig und ich habe theoretisch nicht darüber nachgedacht, warum sie konvertiert werden kann. Ich wollte theoretisch über den Konvertierungsprozess nachdenken, konnte aber die Gelegenheit nicht nutzen. In der Zwischenzeit nahm der Artikel über rekursive Funktionen zu, und ich begann, Artikel zu schreiben. (Es ist lange her, seit ich es in Ruhe gelassen habe) Ein weiterer Faktor ist, dass ich ein anderes Programm geschrieben und die Bedeutung des Stapelns erkannt habe.

Es leugnet jedoch nicht die Nützlichkeit rekursiver Funktionen. Ich liebe rekursive Funktionen.

Angenommener Leser

Die Programmiersprache kann alles sein, also werde ich Python verwenden. Ich werde den Algorithmus selbst, der zum Erstellen des rekursiven Programms verwendet wird, nicht erklären. Daher ist es für diejenigen gedacht, die Python verstehen und Algorithmen verstehen (untersuchen).

Stack und Stack aufrufen

Der Stack, der die Ausführung des Programms steuert, heißt Call Statck. Es wird auch als Ausführungsstapel, Kontrollstapel, Funktionsstapel usw. bezeichnet. In diesem Artikel werden wir mit dem Aufrufstapel vereinheitlichen. Außerdem wird der Stapel als Datenstruktur einfach als Stapel bezeichnet.

Warum rekursive Funktionen vermieden werden können

Rekursive Funktionen verwenden den Aufrufstapel, indem sie die Funktion aufrufen. Es gibt eine Obergrenze für die Kapazität, die im Aufrufstapel gespeichert werden kann. Die Kapazität kann durch Ändern der Einstellungen des Compilers usw. geändert werden, ist jedoch noch begrenzt. (Ich habe es in letzter Zeit berührt, daher weiß ich nicht, wie flexibel es ist.) Wenn die Kapazität des Aufrufstapels überschritten wird, tritt ein Stapelüberlauf auf und die Programmausführung wird gestoppt. Im Prinzip verbrauchen rekursive Funktionen eine große Menge an Aufrufstapel und verursachen einen Stapelüberlauf. Es hängt von den Einstellungen ab, aber meiner Erfahrung nach, die nicht so viele sind, ist es einfach, den Aufrufstapel zu verbrauchen und einen Stapelüberlauf zu verursachen. (Vielleicht liegt es an deinem Mistprogramm) Daher wird es tendenziell vermieden.

Andererseits ist der vom Stapel verwendete Heap-Speicher relativ flexibler als der Aufrufstapel. (Ich denke, das hängt auch von den Einstellungen und dem zu erstellenden Programm ab.) Daher werden wir zur Verwendung des Stapels wechseln.

In letzter Zeit gibt es Compiler mit einer Funktion namens Tail-Receipt-Optimierung, die Tail-Receipt in eine Schleifenstruktur umwandelt. Es wird besser, ohne sich zu viele Sorgen machen zu müssen. Es wird bequemer.

Conversion-Richtlinie

In diesem Artikel halte ich es für eine typische Konvertierungsmethode, aber ich möchte in Betracht ziehen, eine rekursive Funktion mithilfe eines Stapels zu einer nicht rekursiven Funktion zu machen. Dies ist ein Bild des Umschaltens der Verwendung des Aufrufstapels auf die Verwendung des Stapels.

Bodenbelag

Betrachten Sie zunächst einen einfachen Boden als Beispiel.

Hierarchische rekursive Version

Die Implementierung der Hierarchie unter Verwendung von rekursiv ist wie folgt.

def  factorialR(n) :
    if n == 0 :
        return 1
    return n *  factorialR(n-1)

Hierarchische rekursive Aufrufstapelbewegung

Lassen Sie uns veranschaulichen, wie der Aufrufstapel aussieht, wenn diese Funktion mit n = 5 ausgeführt wird. Es ist keine strenge Operation, sondern eine Bildbewegung. Wenn die Funktion ausgeführt wird, schiebt sie zuerst den Aufruf selbst "factorialR (5)" an den Aufrufstapel. factorialR (5) wird bei Ausführung in5 * factorialR (4)umgewandelt.

Bottom Top
f(5)
Bottom Top
5 * f(4)

Rufen Sie dann factorialR (4) auf und schieben Sie es in den Aufrufstapel. factorialR (4) wird bei Ausführung in4 * factorialR (3)umgewandelt.

Bottom Top
5 * f(4) f(4)
Bottom Top
5 * f(4) 4 * f(3)

Im Folgenden funktioniert es auf die gleiche Weise.

Bottom Top
5 * f(4) 4 * f(3) f(3)
Bottom Top
5 * f(4) 4 * f(3) 3 * f(2)
Bottom Top
5 * f(4) 4 * f(3) 3 * f(2) f(2)
Bottom Top
5 * f(4) 4 * f(3) 3 * f(2) 2 * f(1)
Bottom Top
5 * f(4) 4 * f(3) 3 * f(2) 2 * f(1) f(1)
Bottom Top
5 * f(4) 4 * f(3) 3 * f(2) 2 * f(1) 1 * f(0)

Der rekursive Aufruf wurde fortgesetzt, bis n = 0 war.

Bottom Top
5 * f(4) 4 * f(3) 3 * f(2) 2 * f(1) 1 * f(0) f(0)

Wenn n = 0 ist, wird 1 zurückgegeben.

Bottom Top
5 * f(4) 4 * f(3) 3 * f(2) 2 * f(1) 1 * f(0) 1

Wenn factorialR (0) ausgeführt und 1 zurückgegeben wird, wird der Aufrufstapel um eins verringert. Dann kehrt es zu dem Zeitpunkt zurück, zu dem n = 1 aufgerufen wird, "1 * Fakultät R (0)" aufgelöst wird und "1 * 1 = 1" zurückgegeben wird.

Bottom Top
5 * f(4) 4 * f(3) 3 * f(2) 2 * f(1) 1

In ähnlicher Weise verringert die Rückgabe des vorherigen Ausführungsergebnisses den Aufrufstapel um eins. Es kehrt zu dem Punkt zurück, an dem n = 2 aufgerufen wird und "2 * Fakultät R (1)" aufgelöst wird und "2 * 1 = 2" zurückgegeben wird. Im Folgenden funktioniert es auf die gleiche Weise.

Bottom Top
5 * f(4) 4 * f(3) 3 * f(2) 2
Bottom Top
5 * f(4) 4 * f(3) 6
Bottom Top
5 * f(4) 24
Bottom Top
120

Schließlich wird "5 * Fakultät R (4) = 5 * 24 = 120" und 120 zurückgegeben.

In der rekursiven Version wird der Aufruf rekursiv ausgeführt, sodass der Aufruf auf dem Aufrufstapel gestapelt wird. Ich möchte die Struktur der Funktion beibehalten und nicht den Aufrufstapel verwenden. Die obige Funktion erforderte das Berechnungsergebnis in der Mitte und das Argument, das für den nächsten Funktionsaufruf zur Berechnung der Leistung verwendet wurde. Versuchen wir daher, das Berechnungsergebnis in der Mitte in eine Variable und den Wert für den nächsten Funktionsaufruf auf dem Stapel zu setzen. Im Fall von "5 * f (4)" wird 5 auf die Variable und 4 auf den Stapel gelegt. Basierend auf dieser Idee möchte ich eine nicht rekursive Version erstellen.

Nicht rekursive Version

Lassen Sie uns noch einmal das Verhalten des Stapels veranschaulichen, wenn n = 5 ist. Wenn die Funktion startet, drückt sie zuerst 5 auf den Stapel. Setzen Sie außerdem die Variable Ergebnis, mit der das Berechnungsergebnis in der Mitte gespeichert wird, auf 1.

Bottom Top Result
5 1

Der nächste Schritt besteht darin, den Stapel zu öffnen und den Wert 5 abzurufen. Dann multiplizieren Sie das Ergebnis mit dem abgerufenen Wert. Schieben Sie zum Schluss den Wert 4 von "5-1 = 4" auf den Stapel.

Bottom Top Result
4 5

Der nächste Schritt besteht darin, den Stapel zu öffnen, um den Wert 4 abzurufen. Dann multiplizieren Sie das Ergebnis mit dem abgerufenen Wert. Schieben Sie zum Schluss den Wert 3 von "5-1 = 3" auf den Stapel.

Bottom Top Result
3 20

Im Folgenden funktioniert es auf die gleiche Weise.

Bottom Top Result
2 60
Bottom Top Result
1 120
Bottom Top Result
0 120

Geben Sie den Wert 0 ein und multiplizieren Sie das Ergebnis mit 1. Wenn 0, gibt es keinen nächsten Wert, der auf den Stapel gedrückt werden kann.

Bottom Top Result
120

Nachdem der Stapel verschwunden ist, wird der Ergebniswert von 120 als Antwort auf die Potenz von n = 5 gesucht. Ich denke, Sie können die Bedeutung der Verwendung des Stapels finden, weil der Boden einfach ist. Ich mache dies jedoch zur Verallgemeinerung der Konvertierung. Machen wir es zu einer darauf basierenden Funktion. Um ehrlich zu sein, wäre es wie folgt?

def factorialL(n):
    stack = [n]
    result = 1
    current = 0
    while len(stack) > 0:
        current = stack.pop()
        if current <= 0:
            current = 1
        else :
            stack.append(current - 1)
        result *= current
    return result

Hierarchie nicht rekursive Version Teil 2

Stellen Sie sich eine Implementierung vor, die das Verhalten der rekursiven Version des Aufrufstapels auf dem Stapel reproduziert. Bewegen wir den Stapel wie folgt.

Bottom Top
5 4
Bottom Top
5 4 3
Bottom Top
5 4 3 2
Bottom Top
5 4 3 2 1

Wenn n = 0 ist, ist es "0! = 1", also ist 1 fest.

Bottom Top
5 4 3 2 1 1

Dann platzen Sie die bestätigte 1 und brechen Sie einen Stapel. Speichern Sie diese geknallte 1 als Ergebnis.

Bottom Top Result
5 4 3 2 1 1

Geben Sie dann den Top-Wert 1 ein und brechen Sie einen Stapel. Berechnen Sie "1 * 1" mit der zuvor gespeicherten 1 und der gerade gepoppten 1 und speichern Sie "1" als Ergebnis. Dies bestätigt den Wert von n = 1. Im Folgenden wird es auf die gleiche Weise ausgeführt.

Bottom Top Result
5 4 3 2 1

Pop den Top-Wert 2 und multiplizieren Sie ihn mit dem Ergebniswert 1, um das Ergebnis 2 im Ergebnis zu erhalten.

Bottom Top Result
5 4 3 2

Pop den Top-Wert 3 und multiplizieren Sie ihn mit dem Ergebniswert 2, um das Ergebnis 6 im Ergebnis zu erhalten.

Bottom Top Result
5 4 6

Pop den Top-Wert 4 und multiplizieren Sie ihn mit dem Ergebniswert 6, um das Ergebnis 24 im Ergebnis zu erhalten.

Bottom Result
5 24

Pop den Top-Wert 5 und multiplizieren Sie ihn mit dem Ergebniswert 24, um das Ergebnis 120 im Ergebnis zu erhalten.

Result
120

Nachdem der Stapel verschwunden ist, wird der Ergebniswert von 120 als Antwort auf die Potenz von n = 5 gesucht.

Wäre es in diesem Fall wie folgt?

def factorialL(n):
    stack = []
    for i in range(n, 0, -1) :
        stack.append(i)
    result = 1
    for i in range(len(stack)) :
        top = stack.pop()
        result *= top
    return result

Hierarchie nicht rekursive Versionsvereinfachung

Eine etwas sauberere Implementierung sieht folgendermaßen aus:

def factorial1(n):
    result = 1
    for i in range(1, n + 1) :
        result *= i
    return result
from functools import reduce
def factorial2(n):
    return reduce(lambda a, b: a * b, range(1, n + 1), 1)

Fibonacci-Folge

Als nächstes möchte ich die Fibonacci-Sequenz als Beispiel nehmen.

Rekursive Version der Fibonacci-Sequenz

Eine typische Implementierung mit Fibonacci-Sequenzwiederholung würde folgendermaßen aussehen: Der große Unterschied zum Bodenbelag besteht darin, dass Sie sich zweimal in einem Lauf anrufen.

def fibonacciR(n) :
    if n == 0 or n == 1 :
        return n
    return fibonacciR(n - 2) + fibonacciR(n - 1)

Rekursives Aufrufstapelverhalten der Fibonacci-Sequenz

Lassen Sie uns den Zustand des Aufrufstapels veranschaulichen, wenn n = 5 ist, wie im Fall von Bodenbelägen. Wenn die Funktion ausgeführt wird, wird "fibonacciR (5)" zuerst auf den Aufrufstapel verschoben. Dann wird es in "FibonacciR (3) + FibonacciR (4)" umgewandelt.

Bottom Top
f(5)
Bottom Top
f(3) + f(4)

Dann wird "fibonacciR (3)" aufgerufen und in den Aufrufstapel geschoben. Bei der Ausführung wird es in "fibonacciR (1) + fibonacciR (2)" konvertiert.

Bottom Top
f(3) + f(4) f(3)
Bottom Top
f(3) + f(4) f(1) + f(2)

Dann wird "fibonacciR (1)" aufgerufen und in den Aufrufstapel geschoben. fibonacciR (1) gibt bei Ausführung 1 zurück. Dies löst teilweise "FibonacciR (1) + FibonacciR (2)" auf, wenn "FibonacciR (3)" aufgerufen wird, um "1 + FibonacciR (2)" zu werden.

Bottom Top
f(3) + f(4) f(1) + f(2) f(1)
Bottom Top
f(3) + f(4) f(1) + f(2) 1
Bottom Top
f(3) + f(4) 1 + f(2)

Um "fibonacciR (2)" aufzulösen, wird es aufgerufen und in den Aufrufstapel verschoben. Bei der Ausführung wird es in "fibonacciR (0) + fibonacciR (1)" konvertiert.

Bottom Top
f(3) + f(4) 1 + f(2) f(2)
Bottom Top
f(3) + f(4) 1 + f(2) f(0) + f(1)

fibonacciR (0) wird aufgerufen und in den Aufrufstapel verschoben. fibonacciR (0) gibt bei Ausführung 0 zurück. Dies löst teilweise "fibonacciR (0) + fibonacciR (1)" auf, wenn "fibonacciR (2)" aufgerufen wird, um "0 + fibonacciR (1)" zu werden.

Bottom Top
f(3) + f(4) 1 + f(2) f(0) + f(1) f(0)
Bottom Top
f(3) + f(4) 1 + f(2) f(0) + f(1) 0
Bottom Top
f(3) + f(4) 1 + f(2) 0 + f(1)

In ähnlicher Weise wird "fibonacciR (1)" aufgerufen und in den Aufrufstapel verschoben. fibonacciR (1) gibt bei Ausführung 1 zurück. Dies löst "0 + fibonacciR (1)" auf, wenn "fibonacciR (2)" auf "0 + 1 = 1" aufgerufen wird.

Bottom Top
f(3) + f(4) 1 + f(2) 0 + f(1) f(1)
Bottom Top
f(3) + f(4) 1 + f(2) 0 + f(1) 1
Bottom Top
f(3) + f(4) 1 + f(2) 0 + 1

"1 + fibonacciR (1)" beim Aufrufen von "fibonacciR (3)" wird in "1 + 1 = 2" aufgelöst.

Bottom Top
f(3) + f(4) 1 + 1

Beim Aufruf von "fibonacciR (5)" wird ein Teil von "fibonacciR (3) + fibonacciR (4)" in "2 + fibonacciR (4)" aufgelöst.

Bottom Top
2 + f(4)

Es funktioniert genauso wie unten.

Bottom Top
2 + f(4) f(4)
Bottom Top
2 + f(4) f(2) + f(3)
Bottom Top
2 + f(4) f(2) + f(3) f(2)
Bottom Top
2 + f(4) f(2) + f(3) f(1) + f(0)
Bottom Top
2 + f(4) f(2) + f(3) f(1) + f(0) f(1)
Bottom Top
2 + f(4) f(2) + f(3) f(1) + f(0) 1
Bottom Top
2 + f(4) f(2) + f(3) 1 + f(0) f(0)
Bottom Top
2 + f(4) f(2) + f(3) 1 + f(0) 0
Bottom Top
2 + f(4) f(2) + f(3) 1
Bottom Top
2 + f(4) 1 + f(3)
Bottom Top
2 + f(4) 1 + f(3) f(3)
Bottom Top
2 + f(4) 1 + f(3) f(1) + f(2)
Bottom Top
2 + f(4) 1 + f(3) f(1) + f(2) f(1)
Bottom Top
2 + f(4) 1 + f(3) f(1) + f(2) 1
Bottom Top
2 + f(4) 1 + f(3) 1 + f(2)
Bottom Top
2 + f(4) 1 + f(3) 1 + f(2) f(2)
Bottom Top
2 + f(4) 1 + f(3) 1 + f(2) f(0) + f(1)
Bottom Top
2 + f(4) 1 + f(3) 1 + f(2) f(0) + f(1) f(0)
Bottom Top
2 + f(4) 1 + f(3) 1 + f(2) f(0) + f(1) 0
Bottom Top
2 + f(4) 1 + f(3) 1 + f(2) 0 + f(1)
Bottom Top
2 + f(4) 1 + f(3) 1 + f(2) 0 + f(1) f(1)
Bottom Top
2 + f(4) 1 + f(3) 1 + f(2) 0 + f(1) 1
Bottom Top
2 + f(4) 1 + f(3) 1 + f(2) 0 + 1
Bottom Top
2 + f(4) 1 + f(3) 1 + 1
Bottom Top
2 + f(4) 1 + 2
Bottom Top
2 + 3
Bottom Top
5

Es funktioniert wie oben und gibt den Wert 5 zurück, wenn n = 5 ist. Ich habe es lange geschrieben, aber das Verhalten des Aufrufstapels ist so. Dies allein ist schwer zu verstehen, daher möchte ich eine Halbierende verwenden, um die Bewegung dieses Aufrufstapels darzustellen.

Rekursive Version des Halbbaums der Fibonacci-Sequenz

Als Baum mit zwei Zweigen ausgedrückt, ist es wie folgt.

mermaid_非再帰.png

graph TB;
    0[5]---1[3]
    0---2[4]
    1---3[1]
    1---4[2]
    4---5[0]
    4---6[1]
    2---7[2]
    2---8[3]
    7---9[0]
    7---10[1]
    8---11[1]
    8---12[2]
    12---13[0]
    12---14[1]

Sie können sehen, dass die Tiefe des Aufrufstapels der Tiefe des Halbbaums entspricht. Sie können auch sehen, dass die Reihenfolge, in der die Werte aufgelöst werden, der Suchreihenfolge der Suche mit Tiefenpriorität des gegabelten Baums entspricht. Die Suche mit Tiefenpriorität nach gegabelten Bäumen wird häufig als mithilfe von Stapeln implementiert beschrieben. Hier wird es mit dem Call Stack implementiert.

Als nächstes möchte ich eine nicht rekursive Version erstellen.

Nicht rekursive Version der Fibonacci-Sequenz

Die rekursive Version der Fibonacci-Sequenz ist ebenfalls eine einfache Funktion, und in der Mitte befindet sich kein Berechnungsergebnis. Was also auf den Stapel gelegt werden soll, ist, die an die Funktion übergebenen Argumente auf den Stapel zu legen.

Lassen Sie uns die Stapelsituation veranschaulichen, wenn n = 5 wie zuvor ist. Wenn es zu laufen beginnt, schiebt es das erste gegebene Argument 5 auf den Stapel.

Bottom Top
5

Der nächste Schritt besteht darin, den Stapel zu öffnen und 5 abzurufen. 5 kann den Wert nicht aus der Graduierungsformel der Fibonacci-Sequenz bestimmen. Stattdessen werden wir nach "n-2" und "n-1" fragen. Mit anderen Worten, drücken Sie "5-2 = 3" bzw. "5-1 = 4" auf den Stapel. Ich werde von der größten Zahl zuerst pushen.

Bottom Top
4 3

Der nächste Schritt besteht darin, den Stapel zu öffnen und 3 abzurufen. 3 kann den Wert nicht bestimmen, drücken Sie stattdessen 1 bzw. 2.

Bottom Top
4 2 1

Der nächste Schritt besteht darin, den Stapel zu öffnen und die 1 abzurufen. 1 kann als Wert bestimmt werden, speichern Sie ihn also als Ergebnis.

Bottom Top Result
4 2 1

Der nächste Schritt besteht darin, den Stapel zu öffnen und 2 abzurufen. 2 kann den Wert nicht bestimmen, drücken Sie stattdessen 0 bzw. 1.

Bottom Top Result
4 1 0 1

Der nächste Schritt besteht darin, den Stapel zu öffnen und Nullen abzurufen. Da 0 als Wert bestimmt werden kann, fügen Sie ihn zum Ergebnis hinzu und speichern Sie ihn.

Bottom Top Result
4 1 1

Der nächste Schritt besteht darin, den Stapel zu öffnen und die 1 abzurufen. Da 1 als Wert bestimmt werden kann, fügen Sie ihn zum Ergebnis hinzu und speichern Sie ihn.

Bottom Top Result
4 2

Im Folgenden funktioniert es auf die gleiche Weise.

Bottom Top Result
3 2 2
Bottom Top Result
3 1 0 2
Bottom Top Result
3 1 2
Bottom Top Result
3 3
Bottom Top Result
2 1 3
Bottom Top Result
2 4
Bottom Top Result
1 0 4
Bottom Top Result
1 4
Result
5

Es funktioniert wie oben und gibt auch den Wert 5 zurück, wenn n = 5 ist. Ich würde das gerne wieder ehrlich umsetzen.

def fibonacciL(n) :
  result = 0
  current = 0
  stack = [n]
  while len(stack) > 0 :
    current = stack.pop()
    if current == 1 or current == 0 :
      result += current
    else :
      stack.append(current-1)
      stack.append(current-2)
  return result

Ich entschied mich für eine Implementierung ähnlich dem Bodenreiten. (Obwohl ich versuche, eine ähnliche Implementierung zu haben)

Vereinfachung der nicht rekursiven Version der Fibonacci-Sequenz

Wenn Sie nur die Antwort wünschen, ohne den Stapel zu kennen, können Sie auch Folgendes tun. Im Fall von Stapeln wurde es aus dem mit dem größeren Koeffizienten des allmählichen Ausdrucks berechnet, wie z. B. "n-> n-1-> n-2-> ...-> 0". Dies wird aus dem mit dem kleineren Koeffizienten berechnet, wie z. B. "0-> 1-> ...-> n".

def fibonacciL2(n):
  if n == 0:
    return 0
  x, y = 0, 1
  for _ in range(n - 1):
    x, y = y, x + y
  return y

Schnelle Sorte

Als nächstes betrachten Sie eine schnelle Sortierung.

Schnelle Sortierung rekursiver Version

Ich habe versucht, die schnelle Sortierung wie folgt rekursiv zu implementieren. Ich versuche, doppelte Werte zu erhalten. Ich denke, es gibt einige Unterschiede zur typischen Implementierung, aber ich denke, es ist eine schnelle Sortierung. Ich habe "print ()" eingefügt, um den Sortiervorgang zu zeigen. Zu diesem Zweck definieren und verwenden wir auch Hilfsfunktionen. isSorted () bestimmt, ob es sortiert ist.

def isSorted(lt):
  b = lt[0]
  for x in lt:
    if b > x:
      return False
    b = x
  return True

def getListIndeces(lt, s=0):
  if len(lt) == 0:
    return "><"
  maxLen = len(str(max(lt)))
  text = reduce(lambda x, y: x + y, [f"{i+s:{maxLen}}, " for i, x in enumerate(lt)], ">")
  return f"{text[:-2]}<"


def listToFormatedString(lt):
  if len(lt) == 0:
    return "[]"
  maxLen = len(str(max(lt)))
  text = reduce(lambda x, y: x + y, [f"{x:{maxLen}}, " for x in lt], "[")
  return f"{text[:-2]}]"

def getPivot(lt, l, r):
  return lt[l + int((r - l) / 2)]

def quickSort(lt):
  _quickSort(lt, 0, len(lt) - 1)

def _quickSort(lt, l, r):
  if l >= r:
    print(f"   {getListIndeces(lt[l:r+1], s=l)}, l: {l}, r: {r}\nr: {listToFormatedString(lt[l:r+1])}")
    return
  i = l
  j = r
  p = getPivot(lt, l, r)
  print(f"   {getListIndeces(lt[l:r+1], s=l)}, l: {l}, r: {r}, p: {p}\ns: {listToFormatedString(lt[l:r+1])}")
  while i < j:
    while lt[i] < p:
      i += 1
    while lt[j] > p:
      j -= 1
    if i < j:
      if lt[i] == lt[j]:
        j -= 1
      else:
        lt[i], lt[j] = lt[j], lt[i]
    print(f"p: {listToFormatedString(lt[l:r+1])}, {i}: {lt[j]}, {j}: {lt[i]}")
  _quickSort(lt, l, i - 1)
  _quickSort(lt, j + 1, r)

Geben Sie "[7, 2, 1, 4, 6, 0, 8, 5, 9, 3]" als Argument an und führen Sie es aus.

lt = [7, 2, 1, 4, 6, 0, 8, 5, 9, 3]
quickSort(lt)
print(lt, isSorted(lt))

Anschließend wird das folgende Ausführungsergebnis angezeigt.

   >0, 1, 2, 3, 4, 5, 6, 7, 8, 9<, l: 0, r: 9, p: 6
s: [7, 2, 1, 4, 6, 0, 8, 5, 9, 3]
p: [3, 2, 1, 4, 6, 0, 8, 5, 9, 7], 0: 7, 9: 3
p: [3, 2, 1, 4, 5, 0, 8, 6, 9, 7], 4: 6, 7: 5
p: [3, 2, 1, 4, 5, 0, 6, 8, 9, 7], 6: 8, 7: 6
p: [3, 2, 1, 4, 5, 0, 6, 8, 9, 7], 6: 6, 6: 6
   >0, 1, 2, 3, 4, 5<, l: 0, r: 5, p: 1
s: [3, 2, 1, 4, 5, 0]
p: [0, 2, 1, 4, 5, 3], 0: 3, 5: 0
p: [0, 1, 2, 4, 5, 3], 1: 2, 2: 1
p: [0, 1, 2, 4, 5, 3], 1: 1, 1: 1
r: >0<, l: 0, r: 0
r: [0]
   >2, 3, 4, 5<, l: 2, r: 5, p: 4
s: [2, 4, 5, 3]
p: [2, 3, 5, 4], 3: 4, 5: 3
p: [2, 3, 4, 5], 4: 5, 5: 4
p: [2, 3, 4, 5], 4: 4, 4: 4
   >2, 3<, l: 2, r: 3, p: 2
s: [2, 3]
p: [2, 3], 2: 2, 2: 2
r: ><, l: 2, r: 1
r: []
r: >3<, l: 3, r: 3
r: [3]
r: >5<, l: 5, r: 5
r: [5]
   >7, 8, 9<, l: 7, r: 9, p: 9
s: [8, 9, 7]
p: [8, 7, 9], 8: 9, 9: 7
p: [8, 7, 9], 9: 9, 9: 9
   >7, 8<, l: 7, r: 8, p: 8
s: [8, 7]
p: [7, 8], 7: 8, 8: 7
p: [7, 8], 8: 8, 8: 8
r: >7<, l: 7, r: 7
r: [7]
r: ><, l: 9, r: 8
r: []
r: ><, l: 10, r: 9
r: []
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] True

Schnelles Sortieren des Verhaltens des rekursiven Aufrufstapels

Die schnelle Sortierung zeigt auch die Bewegung des Aufrufstapels. Zuerst wird es als "f (lt)" in den Aufrufstapel verschoben.

Bottom Top
f(lt)

6 wird anfänglich als Drehpunkt ausgewählt, Werte unter 6 werden an den Anfang der Liste verschoben und Werte über 6 werden an den Anfang der Liste verschoben. Da der Grenzindex 6 ist, wird "quickSort ()" als Ergebnis der Sortierung bei "lt [0: 6]" bzw. "lt [7:10]" ausgeführt. Der Wert des Grenzindex ist der Drehpunkt. Wenn doppelte Werte vorhanden sind, befindet sich der als Drehpunkt ausgewählte Wert möglicherweise in der sortierten Liste. Der Grenzindex wird sortiert und die Position festgelegt. Das Element, dessen Position festgelegt ist, wird als "Ergebnis" angezeigt.

Bottom Top Result
f(lt[7:10]), f(lt[0:6]) lt[6]

Der Aufrufstapel wird dann mit "f (lt [0: 6])" zur Ausführung verschoben.

Bottom Top Result
f(lt[7:10]) f(lt[0:6]) lt[6]

Hier wird 1 als Drehpunkt gewählt, wobei Werte kleiner als 1 vorwärts und Werte größer als 1 rückwärts geteilt werden. Der Begrenzerindex ist 1, und "quickSort ()" wird auf "lt [0: 1]" bzw. "lt [2: 6]" ausgeführt.

Bottom Top Result
f(lt[7:10]) f(lt[2:6]), f(lt[0:1]) lt[1] + lt[6]

F (lt [0: 1]) wird auf den Aufrufstapel verschoben.

Bottom Top Result
f(lt[7:10]) f(lt[2:6]) f(lt[0:1]) lt[1] + lt[6]

lt [0: 1] hat eine Länge von 1, wird also positioniert und aus dem Aufrufstapel entfernt.

Bottom Top Result
f(lt[7:10]) f(lt[2:6]) lt[0:2] + lt[6]

Der Aufrufstapel kehrt zurück, dann wird der Bereich von "lt [2: 6]" sortiert. Hier wird 4 als Drehpunkt gewählt und in einen Wertebereich von weniger als 4 und Werte von mehr als 4 unterteilt. Der Begrenzerindex ist 4, und "quickSort ()" wird auf "lt [2: 4]" bzw. "lt [5: 6]" ausgeführt. Stellen Sie die Position von "lt [4]" fest.

Bottom Top Result
f(lt[7:10]) f(lt[5:6]), f(lt[2:4]), lt[0:2] + lt[4]+ lt[6]

F (lt [2: 4]) wird auf den Aufrufstapel verschoben.

Bottom Top Result
f(lt[7:10]) f(lt[5:6]) f(lt[2:4]) lt[0:2] + lt[4]+ lt[6]

2 wird als Drehpunkt gewählt und in weniger als 2 und mehr als 2 unterteilt. quickSort () wird bei lt [2: 2] bzw. lt [3: 4] ausgeführt, getrennt durch einen Begrenzerindex von 2. lt [2] wird bestätigt.

Bottom Top Result
f(lt[7:10]) f(lt[5:6]) f(lt[3:4]), f(lt[2:2]) lt[0:3] + lt[4] + lt[6]

Sowohl "lt [2: 2]" als auch "lt [3: 4]" werden ausgeführt, aber die Position ist fest, da die Länge des Bereichs 1 oder weniger beträgt. f (lt [2: 2]) wird gedrückt und gepoppt, weil es 0 lang ist. f (lt [3: 4]) wird ausgeführt, die Position von lt [3] ist festgelegt und es wird geknallt.

Bottom Top Result
f(lt[7:10]) f(lt[5:6]) lt[0:3] + lt[4] + lt[6]

Der Aufrufstapel kehrt zurück, dann wird der Bereich von "lt [5: 6]" sortiert. Da lt [5: 6] auch einen Bereich von 1 hat, ist die Position fest und knallt. Die erste Hälfte der Liste ist jetzt sortiert und bestätigt. Die zweite Hälfte wird auf die gleiche Weise verarbeitet.

Bottom Top Result
f(lt[7:10]) lt[0:7]

9 wird als Drehpunkt gewählt und in "lt [7: 9]" und "lt [9: 9]" unterteilt. lt [9] wird bestätigt.

Bottom Top Result
f(lt[9:9]), f(lt[7:9]) lt[0:7] + lt[9]

f (lt [7: 9]) wird gedrückt.

Bottom Top Result
f(lt[9:9]) f(lt[7:9]) lt[0:7] + lt[9]

8 wird als Drehpunkt gewählt und in "lt [7: 8]" und "lt [8: 8]" unterteilt. lt [8] wird bestätigt.

Bottom Top Result
f(lt[9:9]) f(lt[8:8]), f(lt[7:8]) lt[0:7] + lt[8:10]

f (lt [7: 8]) wird gedrückt.

Bottom Top Result
f(lt[9:9]) f(lt[8:8]) f(lt[7:8]) lt[0:7] + lt[8:10]

Die Position von "lt [7]" ist fest.

Bottom Top Result
f(lt[9:9]) f(lt[8:8]) lt

Sowohl lt [8: 8] als auch lt [9: 9] haben eine Länge von 0, sodass sie enden, ohne etwas zu tun, und der Prozess endet.

Result
lt

Sie können sehen, dass es jetzt dem Verhalten des Aufrufstapels in der Fibonacci-Sequenz ähnlich ist.

Schnelle Sortierung nicht rekursiver Version

Die schnelle Sortierung wird auch in nicht rekursiv konvertiert. Der Prozess des Stapelns auf dem Aufrufstapel wird auf die gleiche Weise wie die herkömmliche Denkweise auf dem Stapel gestapelt. Bei der schnellen Sortierung wird der Bereich, der als nächstes sortiert werden muss, auf dem Stapel gestapelt. Die Implementierung sieht folgendermaßen aus: Ich dachte, es wäre ein Schmerz, aber es ist fast das gleiche wie in der retrospektiven Version.

def quickSortL(lt):
  length = len(lt)
  stack = [(0, length - 1)]
  while len(stack) > 0:
    l, r = stack.pop()
    if l >= r:
      continue
    i = l
    j = r
    p = getPivot(lt, l, r)
    while i < j:
      while lt[i] < p:
        i += 1
      while lt[j] > p:
        j -= 1
      if i < j:
        if lt[i] == lt[j]:
          j -= 1
        else:
          lt[i], lt[j] = lt[j], lt[i]
    stack.append((j + 1, r))
    stack.append((l, i - 1))

2-minütige Suche

An dieser Stelle ist es ein Slapstick, aber ich würde gerne eine zweiminütige Suche in Betracht ziehen.

2-minütige rekursive Suchversion

Zunächst ist es eine rekursive Version, aber ich habe sie wie folgt implementiert. Dies setzt voraus, dass Sie eine sortierte Liste mit eindeutigen Werten übergeben. Sie können "uniqueSorted ()" verwenden, um eine Liste zu erstellen, die Ihren Kriterien entspricht. Dies beinhaltet auch "print ()" zur Erklärung.

def uniqueSorted(lt):
  return sorted(list(set(lt)))

def binarySearchR(lt, n):
  return _binarySearchR(lt, n, 0, len(lt) - 1)

def _binarySearchR(lt, n, l, r):
  if l > r:
    return -1
  middleIndex = int(l + (r - l) / 2)
  middleValue = lt[middleIndex]
  print(getListIndeces(lt[l:r + 1], l))
  print(listToFormatedString(lt[l:r + 1]), middleIndex, middleValue)
  if middleValue > n:
    return _binarySearchR(lt, n, l, middleIndex - 1)
  elif middleValue < n:
    return _binarySearchR(lt, n, middleIndex + 1, r)
  else:
    return middleIndex

Ich habe es wie folgt ausgeführt:

lt = [3, 6, 12, 13, 14, 16, 17, 22, 51, 56, 58, 62, 66, 70, 74, 75, 80, 92, 94, 95]
n = 70
print(binarySearchR(lt, n))

Dann wird das folgende Ausführungsergebnis erhalten.

> 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19<
[ 3,  6, 12, 13, 14, 16, 17, 22, 51, 56, 58, 62, 66, 70, 74, 75, 80, 92, 94, 95] 9 56
>10, 11, 12, 13, 14, 15, 16, 17, 18, 19<
[58, 62, 66, 70, 74, 75, 80, 92, 94, 95] 14 74
>10, 11, 12, 13<
[58, 62, 66, 70] 11 62
>12, 13<
[66, 70] 12 66
>13<
[70] 13 70
13

2-minütige Suche Rekursive Version Aufruf der Stapelbewegung

Veranschaulicht den Aufrufstapel. Zuerst wird es mit lt als Liste und 70 als der Nummer, die Sie finden möchten, zum Aufrufstapel verschoben. Als Medianindex wird 9 gewählt. Der Wert von "lt [9]" ist 56, was weniger als die Zahl 70 ist, die Sie finden möchten. Daher werden wir mit der Suche nach der zweiten Hälfte der Liste fortfahren.

Bottom Top
f(lt)

BinarySearch () wird als lt [10:20] ausgeführt, um die zweite Hälfte der Liste zu durchsuchen. f (lt [10:20]) wird gedrückt. Als Medianindex wird 14 gewählt. Der Wert von lt [14] ist 74, was größer ist als die Zahl 70, die Sie finden möchten. Daher werden wir mit der Suche nach der ersten Hälfte der Liste fortfahren.

Bottom Top
f(lt) f(lt[10:20])

BinarySearch () wird als lt [10:14] ausgeführt, um mit der Suche fortzufahren. f (lt [10:14]) wird gedrückt. Index 11 wird gewählt. Der Wert von "lt [11]" ist 62, was weniger als die Zahl 70 ist, die Sie finden möchten. Daher werden wir mit der Suche nach der zweiten Hälfte der Liste fortfahren.

Bottom Top
f(lt) f(lt[10:20]) f(lt[10:14])

BinarySearch () wird als lt [12:14] ausgeführt, um mit der Suche fortzufahren. f (lt [12:14]) wird gedrückt. Index 12 wird gewählt. Der Wert von "lt [12]" ist 66, was weniger als die Zahl 70 ist, die Sie finden möchten. Daher werden wir mit der Suche nach der zweiten Hälfte der Liste fortfahren.

Bottom Top
f(lt) f(lt[10:20]) f(lt[10:14]) f(lt[12:14])

BinarySearch () wird als lt [13:14] ausgeführt, um mit der Suche fortzufahren. f (lt [13:14]) wird gedrückt. Index 13 wird gewählt. Der Wert von lt [13] ist 70, was der Zahl 70 entspricht, die Sie suchen möchten. Da eine gleiche Zahl gefunden wurde, wird der Indexwert 13 zurückgegeben.

Bottom Top
f(lt) f(lt[10:20]) f(lt[10:14]) f(lt[12:14]) f(lt[13:14])

Danach wird es aus dem Aufrufstapel entfernt, während der Wert zurückgegeben wird.

Bottom Top
f(lt) f(lt[10:20]) f(lt[10:14]) f(lt[12:14]) 13
Bottom Top
f(lt) f(lt[10:20]) f(lt[10:14]) 13
Bottom Top
f(lt) f(lt[10:20]) 13
Bottom Top
f(lt) 13
Bottom Top
13

Die Bewegung des Aufrufstapels entspricht jetzt der des Fußbodens. Es kann gesagt werden, dass es fast die gleiche Struktur hat, mit Ausnahme des Unterschieds in der bedingten Verzweigung und der Frage, ob mit dem zurückgegebenen Wert verarbeitet werden soll.

2-minütige Suche nicht rekursive Version

Die nicht rekursive Version sieht jetzt so aus: Dies entspricht auch der schnellen Sortierung und hat fast die gleiche Struktur wie die rekursive Version.

def binarySearchL(lt, n):
  stack = [(0, len(lt) - 1)]
  while len(stack) > 0:
    l, r = stack.pop()
    if l > r:
      return -1
    middleIndex = int(l + (r - l) / 2)
    middleValue = lt[middleIndex]
    if middleValue > n:
      stack.append((l, middleIndex - 1))
    elif middleValue < n:
      stack.append((middleIndex + 1, r))
    else:
      return middleIndex

Bei einer 2-minütigen Suche ist der Aufrufstapel eine einfache Bewegung, die das Erhöhen und Verringern nicht wiederholt, sodass er ohne Verwendung des Stapels problemlos konvertiert werden kann. Das Folgende ist eine leicht vereinfachte Version.

def binarySearchL2(lt, n):
  l, r = 0, len(lt) - 1
  while l <= r:
    middleIndex = int(l + (r - l) / 2)
    middleValue = lt[middleIndex]
    if middleValue > n:
      l, r = l, middleIndex - 1
    elif middleValue < n:
      l, r = middleIndex + 1, r
    else:
      return middleIndex
  return -1

Schließlich

Einige rekursive Funktionen listen nur einfache Algorithmen auf, aber das Aufrufstapelverhalten ist für alle rekursiven Funktionen ähnlich. Mit anderen Worten kann gesagt werden, dass es mit einer ähnlichen Struktur implementiert werden kann. Schließlich dachte ich, dass es mir schwer fällt, in eine nicht rekursive Funktion zu konvertieren, weil ich die Struktur der rekursiven Funktion nicht vollständig verstanden habe. Der große Unterschied besteht darin, ob Sie den Aufrufstapel oder den Stapel verwenden. Dann erkannte ich die Bedeutung der grundlegenden Datenstruktur und der in der Schule erlernten Algorithmen. Die Grundlagen sind wichtig. Bedeutet das, dass die skrupellosen Studententage vorüber sind (´ ・ ω ・ \ `)


Blinddarm

Überprüfen / ändern Sie die Tiefe des Aufrufstapels.

Sie können die Tiefe des Python-Aufrufstapels mit sys.getrecursionlimit () überprüfen. Dies hängt von der Umgebung ab, aber Sie können die Tiefe mit "sys.setrecursionlimit (limit)" ändern.

2 Astbaum Meerjungfrau

graph TB;
    0[5]---1[3]
    0---2[4]
    1---3[1]
    1---4[2]
    4---5[0]
    4---6[1]
    2---7[2]
    2---8[3]
    7---9[0]
    7---10[1]
    8---11[1]
    8---12[2]
    12---13[0]
    12---14[1]

Recommended Posts

Betrachten Sie die Konvertierung von Python rekursiv in nicht rekursiv
[Python] So rufen Sie eine Funktion von c aus Python auf (ctypes edition)
So erstellen Sie eine rekursive Funktion
[Python 3.8 ~] Wie man rekursive Funktionen mit Lambda-Ausdrücken intelligent definiert
Senden Sie eine Nachricht von Slack an einen Python-Server
Bearbeiten Sie Excel in Python, um eine Pivot-Tabelle zu erstellen
So öffnen Sie einen Webbrowser über Python
So generieren Sie ein Python-Objekt aus JSON
Änderungen von Python 3.0 zu Python 3.5
[Python] Ich habe versucht, den Typnamen als Zeichenfolge aus der Typfunktion abzurufen
Schritte von der Installation von Python 3 bis zur Erstellung einer Django-App
Vom Kauf eines Computers bis zur Ausführung eines Programms auf Python
Führen Sie die Python-Funktion von Powershell aus (wie Sie Argumente übergeben).
Python-Skript, das eine JSON-Datei aus einer CSV-Datei erstellt
Post von Python nach Slack
Erstellen Sie eine Funktion in Python
Flirte von PHP nach Python
Ein Weg zum mittleren Python
So rufen Sie eine Funktion auf
Anaconda aktualisiert von 4.2.0 auf 4.3.0 (python3.5 aktualisiert auf python3.6)
Wechseln Sie von Python2.7 zu Python3.6 (centos7)
Stellen Sie von Python aus eine Verbindung zu SQLite her
So schneiden Sie ein Block-Multiple-Array aus einem Multiple-Array in Python
So führen Sie ein Python-Programm in einem Shell-Skript aus
Erstellen Sie einen Mastodon-Bot mit einer Funktion, die automatisch mit Python antwortet
Zusammenfassung vom Erstellen von Python 3.4. * Von der Quelle zum Erstellen einer wissenschaftlichen Computerumgebung
So starten Sie AWS Batch über die Python-Client-App
Ich möchte viele Prozesse von Python aus starten
So geben Sie char * in einer Rückruffunktion mit ctypes in Python zurück
Extrahieren Sie den Wert, der einem Wert am nächsten kommt, aus einem Listenelement in Python
Rufen Sie Rust von Python an, um schneller zu werden! PyO3-Tutorial: Umschließen einer einfachen Funktion Teil ➀
Rufen Sie Rust von Python an, um schneller zu werden! PyO3-Tutorial: Umschließen einer einfachen Funktion Teil ➁
Rufen Sie Matlab von Python zur Optimierung auf
[Python] Was ist eine Zip-Funktion?
Berühren Sie Python-Objekte in Elixir
[Road to Intermediate Python] Rufen Sie eine Klasseninstanz wie eine Funktion mit __call__ auf
Konvertierung von pdf nach txt 1 [pdfminer]
Post von Python auf Facebook Timeline
[Lambda] [Python] Von Lambda auf Twitter posten!
Senden Sie eine Nachricht von IBM Cloud Functions an Slack in Python
Vom Aufbau einer Python-Umgebung für unerfahrene Personen bis zur Hello-Welt
Versuchen Sie, mit Python3 eine Zeichenfolge aus einem Bild zu extrahieren
So erhalten Sie eine Zeichenfolge aus einem Befehlszeilenargument in Python
[Python] So erhalten und ändern Sie Zeilen / Spalten / Werte aus einer Tabelle.
MP3 → WAV-Konvertierung mit Python
Python / Machen Sie ein Diktat aus einer Liste.
Ich habe eine Funktion zum Laden des Git-Erweiterungsskripts in Python geschrieben
[Python] Machen Sie die Funktion zu einer Lambda-Funktion
Stellen Sie von Python aus eine Verbindung zur utf8mb4-Datenbank her
Von der Installation von Ansible bis zum Erstellen einer Python-Umgebung in der virtuellen Umgebung von Vagrant
Python (vom ersten Mal bis zur Ausführung)
Poste ein Bild von Python auf Tumblr
Stellen Sie eine Anfrage von der Gerätefarm (Appium Python) an das API-Gateway
[Python3] Definition eines Dekorators, der die Ausführungszeit einer Funktion misst