Phương Pháp Sinh Dãy Nhị Phân Độ Dài N

by | Oct 13, 2025 | Cấu trúc dữ liệu và giải thuật, Đệ quy - Quay lui, Kỹ thuật lập trình, Tài liệu | 0 comments

Kỹ thuật sinh dãy nhị phân độ dài n là một chủ đề cơ bản trong lập trình tổ hợp và thường được dùng để duyệt qua tất cả các khả năng có thể của dãy gồm các ký tự 01. Dưới đây là phần trình bày chi tiết:

1. Mô tả bài toán cơ sở

Yêu cầu:
Sinh tất cả các dãy nhị phân có độ dài $n$, mỗi dãy gồm các ký tự 0 hoặc 1.

Ví dụ:
Với $n = 3$, ta có 8 dãy nhị phân tương ứng:

000
001
010
011
100
101
110
111

2. Các phương pháp sinh dãy nhị phân

Phương pháp 1: Duyệt toàn bộ bằng đệ quy (Backtracking)

Ý tưởng:

  • Tại mỗi vị trí trong dãy, ta chọn 0 hoặc 1.
  • Khi đã chọn đủ $n$ phần tử, in ra kết quả.

Code C++ :

#include <bits/stdc++.h>
using namespace std;

int n;
vector<int> a;

void Try(int i) {
    if (i == n) {
        for (int x : a) cout << x;
        cout << "\n";
        return;
    }
    for (int bit = 0; bit <= 1; bit++) {
        a[i] = bit;
        Try(i + 1);
    }
}

int main() {
    cin >> n;
    a.resize(n);
    Try(0);
    return 0;
}

Phân tích:

  • Số dãy sinh ra: $2^n$
  • Độ phức tạp: $O(2^n * n)$

Phương pháp 2: Sinh theo thứ tự từ điển (Iterative)

Ý tưởng:

  • Bắt đầu từ dãy gồm N phần tử toàn 0.
  • Mỗi lần, tìm bit 0 phải nhất → đổi nó thành 1 → đổi tất cả bit bên phải nó thành 0.

Code C++:

#include <bits/stdc++.h>
using namespace std;

void sinh(int n) {
    vector<int> a(n, 0);
    while (true) {
        for (int x : a) cout << x;
        cout << "\n";

        int i = n - 1;
        while (i >= 0 && a[i] == 1) i--;
        if (i < 0) break; // kết thúc
        a[i] = 1;
        for (int j = i + 1; j < n; j++) a[j] = 0;
    }
}

int main() {
    int n;
    cin >> n;
    sinh(n);
    return 0;
}

Phương pháp 3: Sử dụng biểu diễn nhị phân của số tự nhiên

Ý tưởng:

  • Duyệt các số từ $02^n - 1$.
  • In ra dạng nhị phân có độ dài đúng n.

Code C++ ngắn gọn:

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n;
    cin >> n;
    for (int i = 0; i < (1 << n); i++) {
        string s = "";
        for (int j = n - 1; j >= 0; j--)
            s += ((i >> j) & 1) + '0';
        cout << s << "\n";
    }
}

3. So sánh các phương pháp

Phương phápÝ tưởng chínhƯu điểmNhược điểm
Đệ quy (Try)Sinh nhánh theo chiều sâuDễ hiểu, dễ mở rộng (áp dụng cho hoán vị, tổ hợp)Dùng ngăn xếp đệ quy, tốn bộ nhớ khi n lớn
Lặp (Iterative)Tăng dần theo từ điểnKhông cần đệ quy, sinh nhanhKhó mở rộng cho bài phức tạp
Duyệt số nguyênDùng bitmaskCực ngắn gọn, hiệu quả caoKhó hiểu với người mới bắt đầu

4. Ứng dụng

  • Duyệt tất cả các tập con của một tập $n$ phần tử.
  • Sinh dữ liệu kiểm thử trong bài toán tổ hợp.
  • Sinh nghiệm trong bài toán tối ưu (ví dụ: bài toán balo nhị phân).
  • Là nền tảng cho kỹ thuật Bitmask Dynamic Programming.