Bài giảng hay về thuật toán sinh và quay lui.
Trang 1ÔN TẬP KTLT 2
Thuật toán sinh và quay lui
Trang 2Dàn bài
1. Thuật toán sinh và bài tập
2. Thuật toán quay lui và bài tập
Trang 3Bài toán sinh
1. Định nghĩa: Tạo ra dữ liệu.
2. Phương pháp sinh: Từ dữ liệu ban đầu, sinh ra dữ
liệu kế tiếp cho đến khi kết thúc.
3. Điều kiện của thuật toán sinh:
(1) Có thể xác định 1 thứ tự tập các cấu hình của tổ hợp
(thứ tự của các phép gán trị, thường dùng thứ tự từ điển)
Ví dụ: S1=“123 4 589”, S2=“123 5 789”
S1 < S2 nếu có 1 vị trí i tại đó S1[ i ] < S2[ i ]
(2) Có một cấu hình cuối (điều kiện kết thúc của thuật
toán).
(3) Có một cách để suy ra được cấu hình kế tiếp.
4 Áp dụng: giải bài toán liệt kê
Trang 4Ví dụ
Bài toán:Tìm số chuỗi có
độ dài 3 ký tự xyz với
Cấu hình cuối: trị cuối cùng
của mỗi miền trị
Cách sinh:Lấy trị kết tiếp của
mỗi miền trị theo cơ chế
vòng tròn
Dùng thứ
tự từ điển
để so sánh các phép gán trị
Ví dụ:
adm < adn
Trang 5Thuật toán sinh tổng quát.
Generate()
{
c = InitialConfigure(); //cấu hình ban đầu
Process (c); // xử lý cấu hình đang có
if (c=LastConfigure()) Stop=true
else stop = false;
while (not stop)
{
//Sinh cấu hình kế tiếp từ cấu hình đang có
c=getNextConfigure(c);
Process (c); // xử lý cấu hình này
if c= LastConfigure then stop = true;
}
End;
Trang 6Bài toán liệt kê các tập con của 1 tập
Với tập cha là 4 phần tử X={ a, b, c, d }, có thể dùng mảng “0111” mô tả cho tập con { b,c,d }
Mỗi tập con được biểu diễn là một chuỗi (xâu) nhị phân.
Trạng thái khởi tạo: “0000” mang ý nghĩa tập trống.
Trạng thái kết thúc: “1111” mang ý nghĩa là tập cha.
Trang 7Ví dụ : liệt kê các tập con
Với tập cha gồm 4 phần tử, có 24 tập con b
với các biểu diễn:
Trang 8Đặt tả thuật toán sinh
Cách cộng thêm 1 vào chuỗi nhị phân:
Trang 9Viết chương trình liệt kê
void InitConfigure(char bits[], int n) { for (int i=0; i<n; i++) bits[i]=0;
}
bits p(b)0123 _
{ for (int i=0; i<n; i++) if (bits[i]==0) return 0;
return 1;
}
bits p(b)0123 _
0000 0
bits p(b)0123 _
1111 15
Trang 10Viết chương trình liệt kê
void Generate(char bits[], int n)
{ int stop= FALSE;
InitialConfigure(bits, n); //cấu hình ban đầu
void Output(char bits[], int n, int count)
{ printf( ‘’\nTap con thu %d\t’’,count);
for (int i=0; i<n; i++) printf(‘’%d’’,bits[i]);
}
Trang 11Cải tiến thuật toán liệt kê các tập con
Nhận xét: Có thể tối ưu lại chương trình.
void NextConfigure(char bits[], int n,
if (!stop) { count++;
Output(bits,n,count); //xuat cấu hình này }
}}
Trang 12Bài toán tập con k-phần tử : Tìm hiều
Liệt kê các tập con k phần tử của tập n phần tử.
Ví dụ: Các tập con 3 phần tử của tập 5 phần tử { 1,2,3,4,5 } là:
Tổ hợp n chập k
Trang 13Bài toán tập con k-phần tử: Đặc tả
Trang 14Thuật toán sinh tập con kế tiếp từ tập con đã có
a1 a2 a3 a4 ak , chỉ số ở đây đi từ 1
(1) Tìm vị trí đầu tiên từ bên
phải 1 vị trí i sao cho a[i]
≠ n-k+i
i=k;
while (a[i]==n-k+i) i ;
(2) Thay a[i] bằng a[i] +1
Trang 15Viết chương trình bài toán tập con k- phần tử (1)
void Init (int result[], int k)
{ for (int i=1; i<=k; i++) result[i]=i;
{ 3,4,5 }
Trang 16void Generate(int result[], int n, int k)
{ int stop= FALSE;
Init(result, k); //cấu hình ban đầu
Process(result,k) // xuat cấu hình khoi dau
Trang 17Bài tập
Tạo tập tin văn bản có tên Tapcon.in chứa nội dung : 10 4
Ý nghĩa: số đầu: số phần tử của tập, số kế tiếp là số phần tử của tập con.
Dùng kỹ thuật sinh, viết chương trình ghi các tập con của tập này lên file Tapcon.out
Trang 18Bài toán hoán vị tập n phần tử
Cho tập X = { 1,2,3, , n} Hãy liệt kê tất cả các hoán vị của tập này.
Một hoán vị của X là một bộ A = (a1, a2, , an ) với ai ≠
aj nếu i ≠ j
Định nghĩa 1 thứ tự:
A = (a1, a2, ,ak-1, ak, an ) là hoán vị trước của
A’= (a’1, a’2, ,a’k-1, a’k, a’n ) nếu tìm được vị trí k sao cho ak < a’k
Ví dụ : 1234 5 67 là hoán vị trước của
1234 6 57
Đây chính là thứ tự từ điển.
Độ phức tạp n!
Trang 19Thuật toán sinh hoán vị kế tiếp
Trạng thái đầu {1,2,3,4} trạng thái cuối: {4,3,2,1}
Trạng thái trước {1,3,4,2} trạng thái sau: {1,4,2,3}
Trang 20hoán vị a[j], a[k]
Đảo mảng con từ a[j+1] đến a[n]
Trang 21Viết chương trình hoán vị (1)
void Init (int result[], int k)
{ for (int i=1; i<=k; i++) result[i]=i;
}
int LastConfigure(char result[], int n)
{ for (int i=1; i<n; i++) if (result[i] < result[i+1])
return 0;
return 1;
}
void Output(char result[], int n, int count)
{ printf( ‘’\n Hoan vi thu %d\t’’,count);
for (int i=0; i<n; i++) printf(‘’%d’’,result[i]);
}
Trang 22Viết chương trình hoán vị (2)
void NextConfigure (int result[],int n)
{ int j=n-1;
// Tìm chỉ số lớn nhất j mà aj<aj+1 từ phía phải, đây là phần tử sẽ bị hoán vị.
while(j>0 && result[j] > result[j+1]) j ;
int k=n;
//Tìm vị trí đầu tiên k đi ngược từ cuối tập trị với a[k] > a[j]
while(k>0 && result[j] > result[k]) k ;
// Hoán vị a[j], a[k]
int t= result[j];
result[j]= result[k];
result[k]= t;
//Đảo ngược nhóm trị a[j+1], a[n]
int left=j+1, right=n;
while (left < right)
{ t = result[left];
result[left]= result[right];
result[right]=t;
Trang 23Viết chương trình hoán vị (3)
void Generate(char result[], int n)
{ Init(result, n); //cấu hình ban đầu
Trang 25Thuật toán quay lui
Backtracking
Trang 26Vì sao cần thuật toán backtracking?
Không phải cấu hình nào cũng được sinh ra từ cấu hình trước đó một cách dễ dàng.
Phương pháp sinh kế tiếp chỉ giải quyết được
các bài toán đơn giản.
Không phải cấu hình ban đầu và cấu hình kế tiếp được nhận diện một cách dễ dàng, nhiều khi
phải chứng minh là tồn tại chúng.
Với bài toán liệt kê phức tạp, thuật toán
backtracking được áp dụng
Trang 27Backtracking: Ý tưởng
Tập biến x1 x2 x3 xn có thứ tự.
Mỗi biến có thể có 1 miền trị riêng.
Tại mỗi thời điểm, biến xi được tìm trị phù hợp.
Nếu tìm được trị phù hợp thì tiếp tục sang biến xi+1
Ngược lại, tìm trị khác cho biến xi-1
Trang 29Backtracking: thuật toán tổng quát
if (i==n) Process (CấuHìnhCủa X)
else Try (i+1, X, n)
}
}
}
Trang 30Thuật toán tổng quát
Trang 31Bài toán chuỗi bit
Tập biến: char result[], int n bit
Nhận xét: Miền trị chung D={‘0’, ‘1’}
Bit nào cũng được chấp nhận Không cần kiểm tra acceptable(xi ,j)
Trang 32Bài toán chuỗi 4 bit
Process (result, n, count);
} else Try (i+1, result, n, count);
}
}
Trang 33Viết chương trình chuỗi bit bằng backtracking
void Try ( int i, int result[], int n, int &count)
void Process (int result[], int n, int count)
{ printf(‘’\n Chuoi thu %d: \t ’’,count);
for (int i=1; i<=n; i++) printf(‘’%d,’’, result[i]);
}
Lời gọi hàm : Try(1, result, n, count); với count= 0;
Trang 34Bài toán liệt kê tập con 3 phần tử
if (i==k) Process (result, k);
else Try (i+1, result, n, k);
}
}
Trang 35Viết chương trình liệt kê tập con k-phần
void Try ( int i, int result[], int n, int k)
{ for (int j=result[i-1]+1; j <= n-k+i; j++ )
{ result[i]=j;
if (i==k) Process (result, k);
else Try (i+1, result, n, k);
}
}
Lời gọi hàm : Try(1, result, n, k) và result[0]=0
Trang 36Bài toán hoán vị
Liệt kê các hoán vị của một tập n phần tử
Một hoán vị được biểu diễn dạng p1p2p3 pnvới pi ≠ pj với i ≠ j, mỗi pi sẽ nhận trị từ 1 n.
Một trị j được gán cho pi nếu trị j này chưa được dùng Cần quản lý trị j này đã dùng hay chưa mảng B, n phần tử B[j] mang trị
TRUE mô tả rằng trị j chưa được dùng
Ban đầu mọi B[j]= TRUE.
Trang 37Bài toán hoán vị
hết tập biến) phải trả lại B[j]= TRUE để dùng
cho lần sau.
Trang 38Ví dụ về bài toán hoán vị với n= 3
101 001
101 100 111
B[j]=FALSE;
if (i==n) { count++;
Process (result, n, count); }
else Try (i+1, count);
B[j]=TRUE;
}
}
Trang 39Viết chương trình hoán vị bằng thuật toán backtracking
void Init (int *B, int n)
{ for (int i=1; i<=n; i++) B[i]=TRUE;
}
void Process (int result[], int n, int count)
{ printf(‘’\n Hoan vi thu %d: \t ’’,count);
for (int i=1; i<=n; i++) printf(‘’%d,’’, result[i]);
B[j]=FALSE;
if (i==n) { count++;
Process (result, n, count); }
else Try (i+1, count);
B[j]=TRUE;
}
}
Trang 40Bài tập
1. Cho dãy gồm n số tự nhiên phân biệt a1, a2, , an (n ≤ 1000);
và hai số tự nhiên k (k<n) và B Hãy liệt kê tất cả các dãy con k phần tử của dãy số {an} sao cho tổng các phần tử của dãy con
đó đúng bằng B.
2. Cho dãy gồm n số tự nhiên phân biệt a1, a2, , an (n ≤ 1000)
và số tự nhiên B Hãy liệt kê tất cả các dãy con của dãy số
{an} sao cho tổng các phần tử của dãy con đó đúng bằng B
Trang 411. Cho dãy gồm n số tự nhiên phân biệt a1, a2, , an (n ≤ 1000);
và hai số tự nhiên k (k<n) và B Hãy liệt kê tất cả các dãy con k phần tử của dãy số {an} sao cho tổng các phần tử của dãy con
void Try ( int i, int result[], int n, int k)
{ for (int j=result[i-1]+1; j <= n-k+i; j++ )
Trang 42Cho dãy gồm n số tự nhiên phân biệt a1, a2, , an (n ≤ 1000) và số tự
nhiên B Hãy liệt kê tất cả các dãy con của dãy số {an} sao cho tổng các phần tử của dãy con đó đúng bằng B
void Try ( int i, int result[], int n, int &count)
else Try (i+1, result, n, count);
void Process (int result[], int n, int count)
{ printf(‘’\n Chuoi thu %d: \t ’’,count);
for (int i=1; i<=n; i++) printf(‘’%d,’’, result[i]);
}
Lời gọi hàm : Try(1, result,
n, count); với count= 0;