Bagaimana cara efisien menghasilkan semua urutan biner dengan jumlah yang sama dari 0 dan 1?


10

Sebuah urutan biner dengan panjang hanya memerintahkan urutan sehingga setiap adalah baik atau . Untuk menghasilkan semua urutan biner seperti itu, seseorang dapat menggunakan struktur pohon biner yang jelas dengan cara berikut: root adalah "kosong", tetapi setiap anak kiri terkait dengan penambahan ke string yang ada dan setiap anak kanan ke . Sekarang, setiap urutan biner hanyalah jalur dengan panjang mulai dari akar dan berakhir pada daun.x 1 , , x n x j 0 1 0 1 n + 1nx1,,xnxj0101n+1

Inilah pertanyaan saya:

Bisakah kita melakukan lebih baik jika kita hanya ingin menghasilkan semua string biner dengan panjang yang memiliki nol dan ?n n2nnn

Dengan "bisakah kita melakukan yang lebih baik", maksud saya kita harus memiliki kompleksitas yang lebih rendah daripada algoritma konyol yang pertama membangun seluruh pohon di atas dan kemudian mencoba untuk menemukan jalur tersebut dengan jumlah yang sama dari tepi "kiri" dan "kanan".


Dapatkah Anda menemukan cara untuk secara efisien menghasilkan semua urutan angka yang meningkat secara ketat dalam kisaran dari 1 hingga ? 2 nn2n
Cornelius Brand

Saya tidak dapat mengomentari kompleksitas, tetapi algoritma naif saya akan menghasilkan jalan di sepanjang tepi kotak persegi dari satu sudut ke sudut diagonal, menggunakan semacam skema jalur mundur. Itu berarti bahwa 01 dan 10 berakhir di posisi yang sama (tidak seperti pohon Anda) tetapi dengan mundur kita tahu sejarah ini.
Hendrik Jan

Pada catatan yang mungkin berbeda, berikut adalah implementasi Java dari select -iterator . (nk)
Pål GD

Jawaban:


6

Jelas ada string biner dengan panjang . Untuk melintasi biner suatu algoritma harus mengunjungi setiap simpul sekali, yaitu harus dilakukan langkah-langkah. 2 n 2 n i = 0 2 i = 2 2 n + 1 - 1 = O ( 4 n )4n2n

i=02n2i=22n+11=O(4n)

Mari kita pertimbangkan algoritma rekursif yang melintasi pohon yang Anda jelaskan, tetapi menghitung jumlah satu dan nol pada jalannya, yaitu hanya akan melintasi bagian yang baik dari pohon.
Tetapi berapa banyak string biner dengan 0 dan 1 yang ada? Kami memilih 1 untuk string panjang kami dan menggunakan rumus Stirling di langkah 2: n n 2 n ( 2 nnnn2n

(2nn)=(2n)!(n!)2=4nπn(1+O(1/n))

EDIT
Berkat komentar Peter Shor kami juga dapat menganalisis jumlah langkah yang dibutuhkan oleh algoritma kedua, yang menghitung 1 dan 0. Saya mengutip komentarnya dari bawah:

Kami ingin menemukan semua urutan biner dengan tepat 0 dan 1. Kami melintasi pohon biner di mana setiap node adalah urutan paling banyak 0's dan 1's. Kami tidak perlu mengunjungi simpul dengan lebih dari 0 atau lebih dari 1. berapa banyak node yang perlu kita kunjungi? Ada string dengan 0 dan 1. Dengan menjumlahkan ini semua memberi . Sekarang, kita perlu mengunjungi masing-masing node dengan biaya rata-rata konstan per node. Kita dapat melakukan ini dengan mengunjungi setiap anak kiri terlebih dahulu, dan setiap anak kanan kedua.nn2nnn(i+ji)iji,jni=0nj=0n(i+ji)=(2n+2n+1)1

Menggunakan rumus Stirling lagi kita mendapatkan sebagai waktu berjalan dari algoritma baru.

(2n+2n+1)1=4n+11n+1(1+O(1/n))1=O(4nn)

Anda harus sedikit lebih berhati-hati. Agaknya setelah menghasilkan setiap string, kami memprosesnya dalam waktu . Jadi hanya memproses semua string seimbang membutuhkan waktu . Jika algoritma generasi "konyol" yang dioptimalkan memang , maka tidak ada banyak yang bisa diperoleh dengan beralih ke algoritma yang lebih cerdas, selain peluang untuk bug. Ω(n)Ω(4nn)O(4n)
Yuval Filmus

@Yuval Filmus: Apa sebenarnya yang Anda maksud dengan "pemrosesan string"? Jika Anda maksud waktu yang dihabiskan untuk output, yang tentunya , maka Anda harus mempertimbangkan faktor itu juga dalam waktu berjalan dari algoritma "konyol", yang kemudian adalah . Θ(n)O(4nn)
tranisstor

2
Maksud saya adalah jika Anda khawatir dengan perbedaan antara dan , maka sebagai minimum Anda harus menyatakan waktu berjalan yang benar; tidak cukup untuk mengungkapkan perbedaan potensial antara kedua algoritma. Selain itu, Anda harus hati-hati menganalisis algoritma baru yang Anda usulkan, untuk melihat bahwa faktor-faktor kecil yang "diabaikan" ini tidak membuatnya lebih lambat daripada algoritma sepele. 4n4n/nO~(4n)
Yuval Filmus

2
Bagaimana Anda hanya membangun "bagian yang baik" dari pohon tanpa harus memasukkan "bagian yang buruk" juga? Anda harus memasukkan semua simpul pohon yang tidak memiliki lebih dari anak kiri atau anak kanan di jalur dari root ke mereka. Ini berfungsi, tetapi Anda memerlukan argumen tambahan untuk menunjukkan bahwa itu berfungsi. Khususnya, Anda perlu menggunakan rumus . n n i = 0n j = 0 ( i + jnni=0nj=0n(i+ji)=(2n+2n+1)1
Peter Shor

2
Kami ingin menemukan semua urutan biner dengan tepat 0 dan 1. Kami melintasi pohon biner di mana setiap node adalah urutan paling banyak 0's dan 1's. Kami tidak perlu mengunjungi simpul dengan lebih dari 0 atau lebih dari 1. berapa banyak node yang perlu kita kunjungi? Ada string dengan 0 dan 1. Dengan menjumlahkan ini semua memberi . Sekarang, kita perlu mengunjungi masing-masing node dengan biaya rata-rata konstan per node. Kita dapat melakukan ini dengan mengunjungi setiap anak kiri terlebih dahulu, dan setiap anak kanan kedua.n 2 n n n ( i + jnn2nnn iji,jn n i = 0 n j = 0 ( i + j(i+ji)iji,jni=0nj=0n(i+ji)=(2n+2n+1)1
Peter Shor

2

Mungkin saya sedang tebal, tetapi pertanyaan awal meminta cara untuk menghasilkan semua "biner" urutan 2n panjang yang lebih efisien daripada melintasi pohon dari semua urutan biner panjang 2n dan hanya menghasilkan yang seimbang. Jadi mengapa menggunakan pohon?

Inilah pseudocode untuk algoritma rekursif yang menghasilkan semua urutan seperti itu (kata kunci "hasil" mengirimkan urutan ke output):

function all-balanced(n) {
  all-specified( "", n, n );
};

function all-specified( currentString, zeroes, ones ) {

  if (zeroes == 0) {
    for i = 0 to ones {
      currentString += "1";
    };
    yield currentString;
    return;
  };

  if (ones == 0) {
    for i = 0 to zeroes {
      currentString += "0";
    };
    yield currentString;
    return;
  };

  all-specified( currentString+"0", zeroes-1, ones );
  all-specified( currentString+"1", zeroes, ones-1 );
  return;
};

Jika saya salah paham tentang sesuatu, tolong beritahu saya, tetapi menurut saya ini adalah jawaban paling efisien untuk masalah yang diajukan, yang tidak pernah menyebutkan penggunaan pohon.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.