Skip to content

Commit 6985391

Browse files
authored
Update README.md
1 parent 1651124 commit 6985391

File tree

1 file changed

+97
-16
lines changed
  • src/backtracking/python/all_permutations

1 file changed

+97
-16
lines changed
Lines changed: 97 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
## Generating all permutations of a list of elements
1+
## Generating All Permutations of a List
22

3-
Given a list of elements, the problem is to generate all possible permutations of these elements.
4-
5-
For example, if the input list is `[1, 2, 3]`, the possible permutations are:
3+
Given a list of *n* distinct elements, the goal is to produce every possible ordering (permutation) of those elements. For example, if the input is `[1, 2, 3]`, the six permutations are:
64

75
```
86
[1, 2, 3]
@@ -13,22 +11,105 @@ For example, if the input list is `[1, 2, 3]`, the possible permutations are:
1311
[3, 2, 1]
1412
```
1513

16-
## Approach
14+
Because there are *n!* permutations of *n* items, any algorithm will inevitably take at least *O(n!)* time just to enumerate them. In practice, two common ways to generate all permutations in Python are:
15+
16+
1. **Using a built-in library function (e.g., `itertools.permutations`)**
17+
2. **Writing a pure-Python backtracking routine**
18+
19+
Below is a conceptual overview of each approach, along with its time complexity and a brief discussion of advantages and trade-offs.
20+
21+
---
22+
23+
### 1. Built-in Permutations (Conceptual)
24+
25+
Most languages have a library routine that directly yields all permutations of a sequence in an efficient, low-level implementation. In Python, for instance, `itertools.permutations` returns every ordering as a tuple. Converting those tuples to lists (if needed) simply costs an extra *O(n)* per permutation.
26+
27+
* **Core idea**
28+
29+
* Defer the heavy lifting to a built-in routine that is usually implemented in C (or another compiled language).
30+
* Each call to the library function produces one permutation in constant (amortized) time.
31+
* If you need the result as lists instead of tuples, you convert each tuple to a list before collecting it.
32+
33+
* **Time Complexity**
34+
35+
* There are *n!* total permutations.
36+
* Generating each tuple is effectively *O(1)* (amortized), but converting to a list of length *n* costs *O(n)*.
37+
* Overall: **O(n! · n)**.
38+
39+
* **Pros**
40+
41+
* Very concise—just a single function call.
42+
* Relies on a battle-tested standard library implementation.
43+
* Highly optimized in C under the hood.
44+
45+
* **Cons**
46+
47+
* Still incurs the *O(n)* conversion for each permutation if you need lists.
48+
* Less educational (you don’t see how the algorithm actually works).
49+
50+
---
51+
52+
### 2. In-Place Backtracking (Conceptual)
53+
54+
A backtracking algorithm builds each permutation by swapping elements in the original list in place, one position at a time. Once every position is “fixed,” you record a copy of the list in that order. Then you swap back and proceed to the next possibility.
55+
56+
* **Core idea (pseudocode)**
57+
58+
1. Let the input list be `A` of length *n*.
59+
2. Define a recursive routine `backtrack(pos)` that means “choose which element goes into index `pos`.”
60+
3. If `pos == n`, then all indices are filled—append a copy of `A` to the results.
61+
4. Otherwise, for each index `i` from `pos` to `n−1`:
62+
63+
* Swap `A[pos]` and `A[i]`.
64+
* Recursively call `backtrack(pos + 1)`.
65+
* Swap back to restore the original order before trying the next `i`.
66+
67+
* **Time Complexity**
68+
69+
* Exactly *n!* permutations will be generated.
70+
* Each time you reach `pos == n`, you copy the current list (cost *O(n)*).
71+
* Swapping elements is *O(1)*, and there are lower-order operations for looping.
72+
* Overall: **O(n! · n)**.
73+
74+
* **Pros**
75+
76+
* Pure-Python and fairly straightforward to implement once you understand swapping + recursion.
77+
* Does not require constructing new intermediate lists at every recursive call—only one final copy per permutation.
78+
* In-place swapping keeps overhead minimal (aside from the final copy).
79+
80+
* **Cons**
81+
82+
* A bit more code and recursion overhead.
83+
* Uses recursion up to depth *n* (though that is usually acceptable unless *n* is quite large).
84+
* Easier to make an off-by-one mistake if you forget to swap back.
85+
86+
---
87+
88+
## Time Complexity (Both Approaches)
89+
90+
No matter which method you choose, you must generate all *n!* permutations and produce them as lists/arrays. Since each complete permutation takes at least *O(n)* time to output or copy, the combined runtime is always:
91+
92+
```
93+
O(n! · n)
94+
```
95+
96+
* *n!* choices of permutation
97+
* Copying or formatting each permutation (length n) costs an extra n
1798

18-
One approach to solve this problem is to use a backtracking algorithm.
99+
---
19100

20-
A backtracking algorithm works by starting with an empty list and adding elements to it one at a time, then exploring the resulting permutations, and backtracking (removing the last added element) when it is no longer possible to generate new permutations.
101+
## When to Use Each Approach
21102

22-
To implement the backtracking algorithm, we can use a recursive function that takes two arguments:
103+
* **Built-in library function**
23104

24-
* `input_list`: the list of elements from which the permutations are generated.
25-
* `output_list`: the current permutation being generated.
105+
* Ideal if you want minimal code and maximum reliability.
106+
* Use whenever your language provides a standard “permutations” routine.
107+
* Particularly helpful if you only need to iterate lazily over permutations (you can yield one tuple at a time).
26108

27-
The function can follow these steps:
109+
* **In-place backtracking**
28110

29-
* If the length of `output_list` is equal to the length of `input_list`, it means that a permutation of all the elements has been found. In this case, the function can append the permutation to a list of results and return.
30-
* If the length of `output_list` is not equal to the length of `input_list`, the function can enter a loop that enumerates the elements of the input list.
31-
* For each element, the function can check if it is present in the `output_list`. If the element is not present, the function can call itself recursively with `input_list and output_list + [element]` (to add the element to the permutation).
32-
* After the loop, the function can return the list of results.
111+
* Educational: you see how swapping and recursion produce every ordering.
112+
* Useful if you need to integrate custom pruning or constraints (e.g., skip certain permutations early).
113+
* Allows you to avoid tuple-to-list conversions if you directly output lists, although you still pay an *O(n)* copy cost per permutation.
33114

34-
The time complexity of this approach is $O(n! * n)$, where n is the length of the input list.
115+
In most practical scenarios—unless you have a specialized constraint or want to illustrate the algorithm—using the built-in routine is recommended for brevity and performance. However, if you need to customize the traversal (e.g., skip certain branches), an in-place backtracking solution makes it easy to check conditions before fully expanding each branch.

0 commit comments

Comments
 (0)