[Thuật toán] Liệt kê hoán vị
Ta sẽ lập chương trình liệt kê các hoán vị của {1, 2, …, n} theo thứ tự từ điển.
Ví dụ với n = 3, ta phải liệt kê đủ 6 hoán vị:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
Như vậy hoán vị đầu tiên sẽ là 〈1, 2, …, n〉. Hoán vị cuối cùng là 〈n, n-1, …, 1〉.
Hoán vị sẽ sinh ra phải lớn hơn hoán vị hiện tại, hơn thế nữa phải là hoán vị vừa đủ lớn hơn hoán vị hiện tại theo nghĩa không thể có một hoán vị nào khác chen giữa chúng khi sắp thứ tự. Giả sử hoán vị hiện tại là a = 〈3, 2, 6, 5, 4, 1〉, xét 4 phần tử cuối cùng, ta thấy chúng được xếp giảm dần, điều đó có nghĩa là cho dù ta có hoán vị 4 phần tử này thế nào, ta cũng được một hoán vị bé hơn hoán vị hiện tại. Như vậy ta phải xét đến a[2] = 2, thay nó bằng một giá trị khác. Ta sẽ thay bằng giá trị nào?, không thể là 1 bởi nếu vậy sẽ được hoán vị nhỏ hơn, không thể là 3 vì đã có a[1] = 3 rồi (phần tử sau không được chọn vào những giá trị mà phần tử trước đã chọn). Còn lại các giá trị 4, 5, 6. Vì cần một hoán vị vừa đủ lớn hơn hiện tại nên ta chọn a[2] = 4. Còn các giá trị (a[3], a[4], a[5], a[6]) sẽ lấy trong tập {2, 6, 5, 1}. Cũng vì tính vừa đủ lớn nên ta sẽ tìm biểu diễn nhỏ nhất của 4 số này gán cho a[3], a[4], a[5], a[6] tức là 〈1, 2, 5, 6〉. Vậy hoán vị mới sẽ là 〈3, 4, 1, 2, 5, 6〉.
Ta có nhận xét gì qua ví dụ này: Đoạn cuối của hoán vị hiện tại được xếp giảm dần, số a[5] = 4 là số nhỏ nhất trong đoạn cuối giảm dần thoả mãn điều kiện lớn hơn a[2] = 2. Nếu đổi chỗ a[5] cho a[2] thì ta sẽ được a[2] = 4 và đoạn cuối vẫn được sắp xếp giảm dần. Khi đó muốn biểu diễn nhỏ nhất cho các giá trị trong đoạn cuối thì ta chỉ cần đảo ngược đoạn cuối. Trong trường hợp hoán vị hiện tại là 〈2, 1, 3, 4〉 thì hoán vị kế tiếp sẽ là 〈2, 1, 4, 3〉. Ta cũng có thể coi hoán vị 〈2, 1, 3, 4〉 có đoạn cuối giảm dần, đoạn cuối này chỉ gồm 1 phần tử (4) Vậy kỹ thuật sinh hoán vị kế tiếp từ hoán vị hiện tại có thể xây dựng như sau:
- Xác định đoạn cuối giảm dần dài nhất, tìm chỉ số i của phần tử a[i] bắt đầu đoạn cuối đó. Điều này đồng nghĩa với việc tìm từ vị trí cuối dãy lên đến phần tử thứ 2 (vì nếu tìm được ở phần tử đầu tiên tức đó là hoán vị cuối cùng rồi nên không cần), gặp chỉ số i đầu tiên thỏa mãn a[i-1] < a[i].
- Nếu tìm thấy chỉ số i như trên
– Trong đoạn cuối giảm dần, tìm phần tử a[k] nhỏ nhất thoả mãn điều kiện a[k] > a[i-1]. Do đoạn cuối giảm dần, điều này thực hiện bằng cách tìm từ cuối dãy lên đầu gặp chỉ số k đầu tiên thoả mãn a[k] > a[i-1] (có thể dùng tìm kiếm nhị phân).
– Đảo giá trị a[k] và a[i-1]
– Lật ngược thứ tự đoạn cuối giảm dần (từ a[i] đến a[k]) trở thành tăng dần. - Nếu không tìm thấy tức là toàn dãy đã sắp giảm dần, đây là cấu hình cuối cùng.
code minh họa C++.
/* code by nguyenvanquan7826 */ #include <cstdio> #include <cstdlib> #include <iostream> #include <algorithm> // thu vien chua ham swap (hoan vi 2 so) using namespace std; int main(int argc, char **argv) { int n, *a; scanf("%d", &n); a = new int[n]; for (int i=0; i<n; i++) // khoi tao va in ra cau hinh ban dau { a[i] = i+1; printf("%d ", a[i]); } printf("n"); int i = n-1; // i la vi tri ma doan cuoi giam dan while (i>0) // trong khi doan cuoi khong phai bat dau tu vi tri dau tien { i = n-1; while (a[i] < a[i-1]) // tim vi tri dau tien ma doan cuoi giam dan { i--; } if (i>0) // Neu tim duoc i { int k; // tim so a[k] nho nhat trong doan giam dan ma a[k] > a[i-1] for (k = n-1; k >= i; k--) { if (a[k] > a[i-1]) { break; } } swap(a[k], a[i-1]); // hoan vi a[k] va a[i-1] // dao nguoc doan cuoi giam dan thanh tang dan for (int k = i; k < i + (n - i) / 2; k++) { swap(a[k], a[n - 1 - k + i]); } for (int k = 0; k < n; k++) // in hoan vi ra { printf("%d ", a[k]); } printf("n"); } } return 0; }
Bài viết trích dẫn từ Giáo trình cấu trúc dữ liệu và giải thuật của thầy Lê Minh Hoàng. Các bạn có thể xem code Pascal như trong giáo trình thầy đã viết.
Chào bạn, các bài viết của bạn rất hay, mình muốn hỏi chút về một thuật toán bạn chưa viết, có thể không liên quan tới hoán vị lắm đó là bài toán tìm tất cả các tập con của một tập hợp n phần tử như là {1,2,3,4,…n} thì thuật toán có thể theo bạn có thể dùng là như thế nào được không .và nếu dùng thuật toán đệ qui cho bài toán này thì như thế nào.theo mình tìm hiểu thì đa phần các bạn khác đều dùng Binary counting
Cảm ơn bạn đã quan tâm tới các bài viết của blog.
Đối với bài này mình chưa có thời gian viết lên blog nhưng bạn có thể tham khảo cách ” liệt kê các tập con k phần tử của tập {1, 2, …, n} theo thứ tự từ điền” như trong sách của thầy Hoàng tại trang số 20 (link tài liệu ở bên trên). đó là 1 cách rất hay. Mình sẽ cố gắng viết trong thời gian sớm.
quên mình đọc nhầm đề.mình có làm rồi mà k nhớ,để tìm lại code xem,nếu còn mình update lại sau :d
đây là code C của mình,bạn có thể tham khảo xem sao:
Chào bạn! Cảm ơn bài viết của bạn. Bạn có thể viết code bài toán hoán vị bằng pascal mà ko sử dụng chương trình con ko?
Code Pascal trong giáo trình của thấy Lê Minh hoàng đã có rồi nhé. Trong bài code của thầy chỉ có 1 đoạn chuơng trình con là swap – hoán vị 2 số, bạn có thể thay trực tiếp mà không cần dùng chương trình con.
chào bạn, mình dang cần làm bài này nhưng mình không giỏi môn lập tr2inh cho lắm bạn có thể giúp mình không, tìm tất cả các tập hợp của chuỗi số nhập vào ví dụ nhập 012 cho ra 012,021,102,120,201,210 bạn giúp mình ghi chi tiết nhé cám ơn bạn
Cái này hoàn toàn giống mà bạn. Chỉ là thay các số nhập vào bằng các ký tự trong chuỗi của bạn thôi. 😀
mình dùng dev=c++ nhé bạn
cho minh xin mau ben pascal duoc khong
Bạn xem trong giáo trình nhé
cho e hỏi độ phức tạp của thuật toán này là gì có phải là O(n.n!) k ạ
– Lật ngược thứ tự đoạn cuối giảm dần (từ a[i] đến a[k]) trở thành tăng dần.
cái dòng này của anh em thấy nó kì kì, đúng ra phải thay chữ k thành chữ n mới đúng
Ah đúng rồi bạn. Mình lộn, tài a[i] đến a[n].