Saya ingin tahu bagaimana std:next_permutation
diterapkan, jadi saya mengekstrak gnu libstdc++ 4.7
versinya dan membersihkan pengenal dan pemformatan untuk menghasilkan demo berikut ...
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
template<typename It>
bool next_permutation(It begin, It end)
{
if (begin == end)
return false;
It i = begin;
++i;
if (i == end)
return false;
i = end;
--i;
while (true)
{
It j = i;
--i;
if (*i < *j)
{
It k = end;
while (!(*i < *--k))
/* pass */;
iter_swap(i, k);
reverse(j, end);
return true;
}
if (i == begin)
{
reverse(begin, end);
return false;
}
}
}
int main()
{
vector<int> v = { 1, 2, 3, 4 };
do
{
for (int i = 0; i < 4; i++)
{
cout << v[i] << " ";
}
cout << endl;
}
while (::next_permutation(v.begin(), v.end()));
}
Outputnya seperti yang diharapkan: http://ideone.com/4nZdx
Pertanyaan saya adalah: Bagaimana cara kerjanya? Apa artinya i
, j
dan k
? Nilai apa yang mereka pegang di berbagai bagian eksekusi? Apa sketsa bukti kebenarannya?
Jelas sebelum memasuki loop utama itu hanya memeriksa kasus daftar elemen 0 atau 1 sepele. Pada entri loop utama, saya menunjuk ke elemen terakhir (bukan satu ujung terakhir) dan daftarnya setidaknya terdiri dari 2 elemen.
Apa yang terjadi di badan loop utama?
c++
c++11
permutation
stl-algorithm
lexicographic
Andrew Tomazos
sumber
sumber
Jawaban:
Mari kita lihat beberapa permutasi:
Bagaimana kita beralih dari satu permutasi ke permutasi berikutnya? Pertama, mari kita lihat hal-hal dengan sedikit berbeda. Kita dapat melihat elemen sebagai digit dan permutasi sebagai angka . Melihat masalah dengan cara ini kami ingin mengurutkan permutasi / angka dalam urutan "naik" .
Ketika kita memesan nomor, kita ingin "meningkatkannya dengan jumlah terkecil". Misal saat menghitung kita tidak menghitung 1, 2, 3, 10, ... karena masih ada 4, 5, ... di antaranya dan walaupun 10 lebih besar dari 3, ada bilangan yang hilang yang bisa didapat meningkatkan 3 dengan jumlah yang lebih kecil. Dalam contoh di atas kita melihat bahwa
1
tetap sebagai angka pertama untuk waktu yang lama karena ada banyak pengurutan ulang dari 3 "digit" terakhir yang "meningkatkan" permutasi dengan jumlah yang lebih kecil.Jadi kapan kita akhirnya "menggunakan"
1
? Ketika tidak ada lagi permutasi dari 3 digit terakhir.Dan kapan tidak ada lagi permutasi dari 3 digit terakhir? Saat 3 digit terakhir berada dalam urutan menurun.
Aha! Ini adalah kunci untuk memahami algoritme. Kami hanya mengubah posisi "digit" ketika segala sesuatu di sebelah kanan dalam urutan menurun karena jika tidak dalam urutan menurun maka masih ada lebih banyak permutasi yang harus dilakukan (yaitu kita dapat "meningkatkan" permutasi dengan jumlah yang lebih kecil) .
Sekarang mari kembali ke kode:
Dari 2 baris pertama dalam loop,
j
merupakan elemen dani
merupakan elemen sebelumnya.Kemudian, jika elemen berada dalam urutan menaik, (
if (*i < *j)
) lakukan sesuatu.Sebaliknya, jika semuanya dalam urutan menurun, (
if (i == begin)
) maka ini adalah permutasi terakhir.Jika tidak, kami melanjutkan dan kami melihat bahwa j dan i pada dasarnya berkurang.
Kami sekarang memahami
if (i == begin)
bagian tersebut sehingga yang perlu kami pahami hanyalahif (*i < *j)
bagian tersebut.Perhatikan juga: "Maka jika elemen-elemennya dalam urutan menaik ..." yang mendukung pengamatan kita sebelumnya bahwa kita hanya perlu melakukan sesuatu pada satu digit "ketika segala sesuatu di sebelah kanan dalam urutan menurun".
if
Pernyataan urutan menaik pada dasarnya mencari tempat paling kiri di mana "segala sesuatu di sebelah kanan dalam urutan menurun".Mari kita lihat kembali beberapa contoh:
Kita melihat bahwa ketika semua yang ada di sebelah kanan digit berada dalam urutan menurun, kita menemukan digit terbesar berikutnya dan meletakkannya di depan dan kemudian meletakkan digit yang tersisa dalam urutan menaik .
Mari kita lihat kodenya:
Karena hal-hal di kanan berada dalam urutan menurun, untuk menemukan "digit terbesar berikutnya" kita hanya perlu mengulang dari akhir, yang kita lihat di 3 baris kode pertama.
Selanjutnya, kita menukar "digit terbesar berikutnya" ke depan dengan
iter_swap()
pernyataan dan kemudian karena kita tahu digit tersebut adalah digit terbesar berikutnya, kita tahu bahwa digit di sebelah kanan masih dalam urutan turun, jadi untuk mengurutkannya dalam urutan naik, kita hanya harus melakukannyareverse()
.sumber
Combinatorics
, tapi ini yang paling klasik.Implementasi gcc menghasilkan permutasi dalam urutan leksikografis. Wikipedia menjelaskannya sebagai berikut:
sumber
Knuth membahas lebih dalam tentang algoritma ini dan generalisasinya di bagian 7.2.1.2 dan 7.2.1.3 Seni Pemrograman Komputer . Dia menyebutnya "Algoritma L" - rupanya itu berasal dari abad ke-13.
sumber
Berikut implementasi lengkap menggunakan algoritme pustaka standar lainnya:
Demo
sumber
is_final_permutation
lebih informatif daripadabegin == end - 1
. Memanggilis_sorted_until
/upper_bound
memisahkan logika permutasi dari operasi tersebut, dan membuatnya jauh lebih bisa dimengerti. Selain itu upper_bound adalah pencarian biner, sedangkanwhile (!(*i < *--k));
linier, jadi ini lebih berkinerja.Ada kemungkinan implementasi yang jelas pada penggunaan cppreference
<algorithm>
.Ubah konten menjadi permutasi berikutnya secara leksikografis (di tempat) dan kembalikan true jika ada, jika tidak urutkan dan kembalikan false jika tidak ada.
sumber