Zrozumienie rekurencji w celu generowania permutacji

Uważam, że rekurencja, poza bardzo prostymi takimi jak factorial, jest bardzo trudna do zrozumienia. Poniższy fragment wyświetla wszystkie permutacje ciągu znaków. Czy ktoś może mi pomóc to zrozumieć? Jak właściwie zrozumieć rekurencję?

void permute(char a[], int i, int n)
{
   int j;
   if (i == n)
     cout << a << endl;
   else
   {
       for (j = i; j <= n; j++)
       {
          swap(a[i], a[j]);          
          permute(a, i+1, n);
          swap(a[i], a[j]);
       }
   }
} 

int main()
{
   char a[] = "ABCD";
   permute(a, 0, 3);
   getchar();
   return 0;
}
Author: Nemo, 2011-09-24

6 answers

PaulR ma słuszną sugestię. Musisz uruchomić kod "ręcznie" (używając dowolnych narzędzi - debuggerów, paper, wywołań funkcji rejestrujących i zmiennych w określonych punktach), dopóki go nie zrozumiesz. Aby uzyskać wyjaśnienie kodu, odsyłam Cię do doskonałej odpowiedzi quasiverse.

Być może ta wizualizacja wykresu wywołania z nieco mniejszym ciągiem sprawia, że bardziej oczywiste jest, jak to działa: Wykres wywołania

Wykres został wykonany za pomocą graphviz .

// x.dot
// dot x.dot -Tpng -o x.png
digraph x {
rankdir=LR
size="16,10"

node [label="permute(\"ABC\", 0, 2)"] n0;
 node [label="permute(\"ABC\", 1, 2)"] n1;
  node [label="permute(\"ABC\", 2, 2)"] n2;
  node [label="permute(\"ACB\", 2, 2)"] n3;
 node [label="permute(\"BAC\", 1, 2)"] n4;
  node [label="permute(\"BAC\", 2, 2)"] n5;
  node [label="permute(\"BCA\", 2, 2)"] n6;
 node [label="permute(\"CBA\", 1, 2)"] n7;
  node [label="permute(\"CBA\", 2, 2)"] n8;
  node [label="permute(\"CAB\", 2, 2)"] n9;

n0 -> n1 [label="swap(0, 0)"];
n0 -> n4 [label="swap(0, 1)"];
n0 -> n7 [label="swap(0, 2)"];

n1 -> n2 [label="swap(1, 1)"];
n1 -> n3 [label="swap(1, 2)"];

n4 -> n5 [label="swap(1, 1)"];
n4 -> n6 [label="swap(1, 2)"];

n7 -> n8 [label="swap(1, 1)"];
n7 -> n9 [label="swap(1, 2)"];
}
 48
Author: user786653,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-24 09:40:19

Wybiera każdy znak ze wszystkich możliwych znaków:

void permute(char a[], int i, int n)
{
    int j;
    if (i == n)                  // If we've chosen all the characters then:
       cout << a << endl;        // we're done, so output it
    else
    {
        for (j = i; j <= n; j++) // Otherwise, we've chosen characters a[0] to a[j-1]
        {                        // so let's try all possible characters for a[j]
            swap(a[i], a[j]);    // Choose which one out of a[j] to a[n] you will choose
            permute(a, i+1, n);  // Choose the remaining letters
            swap(a[i], a[j]);    // Undo the previous swap so we can choose the next possibility for a[j]
        }
    }
} 
 21
Author: quasiverse,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-24 08:09:05

Aby efektywnie wykorzystać rekurencję w projektowaniu, rozwiązujesz problem zakładając, że już go rozwiązałeś . Mentalną trampoliną dla obecnego problemu jest "gdybym mógł obliczyć permutacje znaków n-1, to mógłbym obliczyć permutacje znaków N, wybierając każdy z nich po kolei i dołączając permutacje pozostałych znaków n-1, Co udaję, że już wiem, jak to zrobić".

Wtedy potrzebujesz sposobu na to, co nazywa się" bottoming out " the rekurencja. Ponieważ każdy nowy sub-problem jest mniejszy niż ostatni, być może w końcu dojdziesz do sub-problem, który naprawdę wiesz, jak rozwiązać.

W tym przypadku, znasz już wszystkie permutacje jednego znaku - to tylko znak. Więc wiesz, jak rozwiązać to dla n=1 i dla każdej liczby, która jest o jeden więcej niż liczba, którą możesz rozwiązać i gotowe. Jest to bardzo ściśle związane z czymś, co nazywa się indukcją matematyczną.

 17
Author: phunctor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-24 16:42:26

Chociaż jest to trochę stare pytanie i już odpowiedział myślałem o dodaniu moich wejść, aby pomóc nowym odwiedzającym. Planuje również wyjaśnienie czasu pracy bez skupiania się na rekurencyjnym pojednaniu.

Sample napisałem w C# , ale łatwe do zrozumienia dla większości programistów.

static int noOfFunctionCalls = 0;
static int noOfCharDisplayCalls = 0;
static int noOfBaseCaseCalls = 0;
static int noOfRecursiveCaseCalls = 0; 
static int noOfSwapCalls = 0;
static int noOfForLoopCalls = 0;

static string Permute(char[] elementsList, int currentIndex)
{
    ++noOfFunctionCalls;

    if (currentIndex == elementsList.Length)
    {
        ++noOfBaseCaseCalls;        
        foreach (char element in elementsList)
        {
            ++noOfCharDisplayCalls;

            strBldr.Append(" " + element);
        }
        strBldr.AppendLine("");
    }
    else
    {
        ++noOfRecursiveCaseCalls;

        for (int lpIndex = currentIndex; lpIndex < elementsList.Length; lpIndex++)
        {
            ++noOfForLoopCalls;

            if (lpIndex != currentIndex)
            {
                ++noOfSwapCalls;
                Swap(ref elementsList[currentIndex], ref elementsList[lpIndex]);
            }

            Permute(elementsList, (currentIndex + 1));

            if (lpIndex != currentIndex)
            {
                Swap(ref elementsList[currentIndex], ref elementsList[lpIndex]);
            }
        }
    }
    return strBldr.ToString();
}

static void Swap(ref char Char1, ref char Char2)
{
    char tempElement = Char1;
    Char1 = Char2;
    Char2 = tempElement;
}      

public static void StringPermutationsTest()
{
    strBldr = new StringBuilder();
    Debug.Flush();

    noOfFunctionCalls = 0;
    noOfCharDisplayCalls = 0;
    noOfBaseCaseCalls = 0;
    noOfRecursiveCaseCalls = 0;
    noOfSwapCalls = 0;
    noOfForLoopCalls = 0;

    //string resultString = Permute("A".ToCharArray(), 0);
    //string resultString = Permute("AB".ToCharArray(), 0);
    string resultString = Permute("ABC".ToCharArray(), 0);
    //string resultString = Permute("ABCD".ToCharArray(), 0);
    //string resultString = Permute("ABCDE".ToCharArray(), 0);

    resultString += "\nNo of Function Calls : " + noOfFunctionCalls;
    resultString += "\nNo of Base Case Calls : " + noOfBaseCaseCalls;
    resultString += "\nNo of General Case Calls : " + noOfRecursiveCaseCalls;
    resultString += "\nNo of For Loop Calls : " + noOfForLoopCalls;
    resultString += "\nNo of Char Display Calls : " + noOfCharDisplayCalls;
    resultString += "\nNo of Swap Calls : " + noOfSwapCalls;

    Debug.WriteLine(resultString);
    MessageBox.Show(resultString);       
}

Kroki: Dla np. gdy przekazujemy dane wejściowe jako "ABC".

  1. metoda permutacji wywołana z Main po raz pierwszy. Więc wywołanie z indeksem 0 i to jest pierwsze sprawdzam.
  2. w części else w pętli for powtarzamy od 0 do 2 wykonując 1 wywołanie za każdym razem.
  3. pod każdą pętlą wywołujemy rekurencyjnie lpcnt + 1. 4.1 gdy indeks wynosi 1, to 2 wywołania rekurencyjne. 4.2 gdy indeks wynosi 2, to 1 wywołania rekurencyjne.

Tak więc z punktu 2 do 4.2 w sumie połączeń jest 5 dla każdej pętli, a w sumie to 15 połączeń + wejście główne = 16. Za każdym razem loopCnt wynosi 3, wtedy jeśli warunek zostanie wykonany.

Na wykresie widzimy liczbę pętli, która staje się 3 łącznie 6 razy tj. wartość czynnikowa 3 tj. Długość wejściowa "ABC".

If polecenie 's for loop powtarza' N 'razy, aby wyświetlić znaki z przykładu" ABC " tj. 3. Łącznie 6 razy (czasy czynnikowe) wchodzimy w if, aby wyświetlić permutacje. Więc całkowity czas pracy = n X n!.

Podałem statyczne zmienne CallCnt i tabelę, aby szczegółowo zrozumieć wykonanie każdej linii.

Eksperci, zapraszam do edycji mojej odpowiedzi lub komentarza, jeśli któraś z moich danych nie jest jasna lub niepoprawne, cieszę się, że je poprawiłem.

Tutaj wpisz opis obrazkaTutaj wpisz opis obrazka

Pobierz przykładowy kod i inne próbki stąd

 3
Author: Sai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-12 11:22:24

Tutaj wpisz opis obrazka

Ten kod i odniesienie mogą pomóc ci go zrozumieć.

// C program to print all permutations with duplicates allowed
#include <stdio.h>
#include <string.h>

/* Function to swap values at two pointers */
void swap(char *x, char *y)
{
    char temp;
    temp = *x;
    *x = *y;
    *y = temp;
}

/* Function to print permutations of string
   This function takes three parameters:
   1. String
   2. Starting index of the string
   3. Ending index of the string. */
void permute(char *a, int l, int r)
{
   int i;
   if (l == r)
     printf("%s\n", a);
   else
   {
       for (i = l; i <= r; i++)
       {
          swap((a+l), (a+i));
          permute(a, l+1, r);
          swap((a+l), (a+i)); //backtrack
       }
   }
}

/* Driver program to test above functions */
int main()
{
    char str[] = "ABC";
    int n = strlen(str);
    permute(str, 0, n-1);
    return 0;
}

Odniesienie: Geeksforgeeks.org

 2
Author: Sazzad Hissain Khan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-10 09:13:06

Chciałbym dodać tutaj Moje 2 centy z implementacją JavaScript. Możesz przeglądać dzienniki konsoli, a także usuwać mylące linie console.log;

let str = "ABC";

permute(str, 0, str.length - 1, 0);

function permute(str, left, right, depth) {
  console.log(
    "called by depth:",
    depth,
    "str:",
    str,
    "left:",
    left,
    "right:",
    right
  );
  if (left === right) {
    console.log("PRINT:", str + "\n");
  } else {
    for (let i = left; i <= right; i++) {
      str = swap(str, left, i);
      console.log(
        "swap:",
        str,
        "depth:",
        depth,
        "left:",
        left,
        "inner counter:",
        i
      );
      console.log(
        "call permute",
        "str:",
        str,
        "depth:",
        depth + 1,
        "start left:",
        str[left + 1],
        "until right:",
        str[right]
      );
      permute(str, left + 1, right, depth + 1);
      str = swap(str, left, i);
      console.log(
        "backtrack swap",
        "depth:",
        depth,
        "str:",
        str,
        "left:",
        left,
        "right:",
        i
      );
    }
  }
}

function swap(str, left, right) {
  let charArr = str.split("");
  let leftTemp = charArr[left];
  charArr[left] = charArr[right];
  charArr[right] = leftTemp;
  return charArr.join("");
}

Wyjście:

Tutaj wpisz opis obrazka

Obraz drzewa pochodzi zhttp://www.geeksforgeeks.org/write-a-c-program-to-print-all-permutations-of-a-given-string/

Również jako strona dla programów rekurencyjnych:

Tutaj wpisz opis obrazka

 0
Author: Teoman shipahi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-17 03:34:13