Bài giảng Cấu trúc dữ liệu và giải thuật – Bài 4: Kỹ thuật quay lui (Backtracking) với những kiến thức khái niệm về kỹ thuật quay lui, bài toán 8 con hậu - eight queen problem, bài toán mã đi tuần - knight tour problem, bài toán chiếc ba lô - knapsack problem.
Trang 1Lecturer: PhD Ngo Huu Phuc
Tel: 0438 326 077 Mob: 098 5696 580 Email: ngohuuphuc76@gmail.com
Trang 2Bài 4 K ỹ thuật quay lui
Backtracking
4.1 Khái ni ệm về kỹ thuật quay lui (6)
4.2 Bài toán 8 con h ậu - Eight Queen Problem (7)
4.3 Bài toán mã đi tuần - Knight Tour Problem (5)
4.4 Bài toán chi ếc ba lô - Knapsack Problem (6)
1 Lecture 11 – Backtracking.htm
Trang 34.1 Khái ni ệm kỹ thuật quay lui (1/6)
K ỹ thuật quay lui là quá trình phân tích từ trên xuống trong không gian tìm ki ếm
Trong trường hợp tổng quát, giả sử lời giải là một
vector:
a = (a[1], a[2], …, a[n])
trong đó, mỗi phần tử a[i] chọn từ tập hữu hạn S[i] (các kh ả năng của a[i]).
Trang 44.1 Khái ni ệm kỹ thuật quay lui (2/6)
Ta s ẽ giải quyết bài toán với kích thước k, có dạng:
a = (a[1], a[2], …, a[k])
và c ố gắng mở rộng bằng việc thêm phần tử tiếp theo vào trong vector
Sau khi thêm ph ần tử, kiểm tra xem có thể thực hiện
ti ếp được không.
N ếu vẫn có khả năng mở rộng, tiếp tục thực hiện;
N ếu không, xóa phần tử a[k] và làm lại bài toán từ tập S[k].
Trang 54.1 Khái ni ệm kỹ thuật quay lui (3/6)
G ọi S[1], tập các khả năng của a tại bước đầu tiên
k = k - 1 (*backtrack*)
Trang 64.1 Khái ni ệm kỹ thuật quay lui (4/6)
Sub Backtrack(a, k)
If a is a solution, get it
Else
k = k +1 compute S[k]
While S[k]<>[] do a[k] = an element in S[k]
S[k] = S[k] – a[k]
Backtrack(a, k) End Sub
Trang 74.1 Khái ni ệm kỹ thuật quay lui (5/6)
K ỹ thuật đệ quy có thể được dùng trong kỹ thuật
quay lui (đơn giản trong ứng dụng).
K ỹ thuật quay lui chắc chắn đúng khi liệt kê các khả
năng có thể.
Để tăng hiệu quả của kỹ thuật, có thể cắt bớt
không gian tìm ki ếm.
Trang 84.1 Khái ni ệm kỹ thuật quay lui (6/6)
Trong ph ần này, giải quyết một số bài toán, có sử dụng
k ỹ thuật quay lui:
Bài toán 8 h ậu - Eight Queen Problem.
Bài toán mã đi tuần - Knight Tour Problem.
Bài toán chi ếc ba lô - Knapsack Problem.
Trang 94.2 Eight Queen Problem (1/7)
Bài toán : đặt 8 con hậu trên bàn cờ sao cho không có 2 con
h ậu nằm trên cùng một hàng, cột và hàng ngang.
Một lời giải Không phải lời giải
Trang 104.2 Eight Queen Problem (2/7)
Th ực hiện:
• Tr ạng thái: Sắp đặt từ 0 đến 8 con h ậu lên bàn cờ.
• Tr ạng thái khởi tạo: không có con h ậu nào đặt lên bàn cờ.
• Đặt từng con hậu lên bàn cờ.
• Ki ểm tra kết quả: 8 con hậu đã đặt lên bàn cờ, không có con
h ậu nào ăn được nhau.
Trang 114.2 Eight Queen Problem (3/7)
M ột vài cách đặt 8 con hậu lên bàn cờ (trong số 92 lời giải)
Trang 124.2 Eight Queen Problem (4/7)
N ếu tại một cột, không đặt được con hậu mới, con hậu ở
c ột (hàng) đó được bỏ ra khỏi bàn cờ và chuyển xuống
c ột (hàng) trước đó.
Trang 134.2 Eight Queen Problem (5/7)
N ếu xét theo cột, tại một cột, tất cả các hàng đã xét, quay tr ở lại bước trước đó (xét cột trước).
N ếu con hậu không đặt được lên cột i, khi đó
không th ử trên cột i+1, quay lại cột i-1, bỏ con
h ậu đã đi sai.
V ới cách tiếp cận này, có thể giảm bớt phép thử.
Trang 144.2 Eight Queen Problem (6/7)
typedef enum {FALSE, TRUE} bools;
void printMatrix(inta[][N], int n);
int getMarkedCol(inta[][N], intn, int row);
bools feasible(inta[][N], int n, int row, int col);
void N_Queens(inta[][N], int n, int row);
// In ma tran chua cac con hau
void printMatrix(inta[][N], int n) {
int i, j;
for(i=0; i<n; i++) {
for(j=0; j<n; j++)printf("%d ", a[i][j]);
printf("\n"); }printf("\n"); }
// Lay cac cot da danh dau trong hang row
int getMarkedCol(inta[][N], intn, int row) {
int j;
for(j=0; j<n; j++)
if(a[row][j] == TRUE) return j;
printf("Loi: Khong co cot danh dau trong hang
%d.\n", row);
return-1;
Trang 154.2 Eight Queen Problem (6/7)
// Kiem tra xem con hau tiep theo co the dat o hang
// row, cot col khong
bools feasible(int a[][N], int n, int row, int col)
markedCol = getMarkedCol (a,n,i);
if(markedCol == col || abs(row-i) ==
abs(col-markedCol))
returnFALSE ;}
returnTRUE;
}
// Bai toan N hau
void N_Queens(inta[][N], int n, int row) {
int j;
if(row < n) {
for(j=0; j<n; ++j)
if(feasible(a, n, row, j)) {a[row][j] = TRUE;
N_Queens (a, n, row+1);a[row][j] = FALSE; }}
else {k++;
printf("Trang thai thu %d\n",k);printMatrix(a, n);
}
Trang 164.3 Knight Tour Problem (1/5)
Trang 174.3 Knight Tour Problem (2/5)
Bài toán:
Trên bàn c ờ 8 × 8 có một con mã (cách đi được định nghĩa thông thường)
B ắt đầu từ một góc của bàn cờ và thăm tất cả các
ô c ờ khác (63 ô cờ còn lại), sao cho mỗi ô cờ chỉ đến 1 lần.
Bài toán này được gọi là “mã đi tuần”.
Trang 184.3 Knight Tour Problem (3/5)
Cách ti ếp cận bài toán:
Có nhi ều cách tiếp cận để giải bài
toán này.
M ột trong số đó là cách giải của J.C
Warnsdorff được đưa ra năm 1823
Theo cách c ủa J.C Warnsdorff, con
mã đi theo theo thứ tự được mã hóa
(quy ước cách đi theo hình vẽ).
T ừ một vị trí, con mã có thể đi tối đa
đến 8 vị trí khác Tùy theo vị trí trên
bàn c ờ, các vị trí có thể này được mã
hóa theo th ứ tự từ 1 đến 8.
Trang 194.3 Knight Tour Problem (4/5)
void printVariant(inta[][M], int n);
void knight(int a[][M], intn, int row, int col, int num);
//dinh nghia cach di cho con ma
knight(a, M, i, j, 2);}
printf("\n");
Trang 204.3 Knight Tour Problem (5/5)
void knight(int a[][M], intn, int row, int col, int num)
{
// tim vi tri ke tiep
// vi tri ke tiep co gia nho nhat
// vi tri hien tai la (row,col)
int i;
for(i=0; i<SizeStep; i++)
{
int newrow = row+rowchange[i];
int newcol = col+colchange[i];
if(newrow>=0 && newrow<n && newcol>=0
&& newcol<n && a[newrow][newcol]==0)
{
a[newrow][newcol]=num;
if(num==n*n){
printVariant(a, M);
printf("\nNhan Enter de chon ket qua khac, nhan ESC de thoat!\n");
if(charc=getch()==27) exit(1);
Trang 214.4 Knapsack Problem (1/9)
Bài toán:
Cho N v ật, mỗi vật có trọng lượng w(i) và giá trị v(i).
M ột chiếc ba lô có khả năng đựng tối đa (theo trọng
lượng) là K.
Ch ọn các vật như thế nào để tổng giá trị của chúng là
l ớn nhất và tổng trọng lượng không vượt quá K?
Ý t ưởng:
Ta ch ọn các vật có giá trị cao nhất vào ba lô.
Tuy nhiên, các v ật có trọng lượng khác nhau, do đó cần
ọn một cách cẩn thận!!!
Trang 234.4 Knapsack Problem (3/9)
Xem xét ví d ụ sau với K = 9.
V ới ví dụ trên, vật 4 có tỷ lệ cao nhất.
N ếu ta lấy vật 4 này, khi đó, chỉ có thể đưa vật 1 thêm vào
Trang 24// K: trong luong toi da cua ba lo
// used[]: cac vat co trong ba lo,
// 0: chua lay, 1: da lay
// weight[]: trong luong cua tung vat
// value[]: gia tri cua tung vat
// current[0]: trong luong hien tai
// current[1]: gia tri hien tai
// current[2]: gia tri max
// pack[]: cac vat da lay o trang thai toi uu
int nCalls=0;
void knapSack(intK, bools used[], int item, int
weight[], int value[], bools pack[], int current[], int
n);
void main() {bools used[M];
Trang 25void knapSack(intK, bools used[], int item, int
weight[], int value[], bools pack[], int current[], int n){ nCalls++; // So loi goi
// Kiem tra da xem xet den vat cuoi
if(item == n){
// Kiem tra lai truong hop da lay vat
if(current[1] > current[2]){
printf("Tong gia tri hien tai: %d\n",current[1]);
// Luu thong tin hien tai
Trang 26// Khong lay duoc vat nay, tiep tuc thu vat khac.
knapSack(K, used, item+1, weight, value, pack, current, n);
}}
Trang 27// K: trong luong toi da cua ba lo
// used[]: cac vat co trong ba lo,
// 0: chua lay, 1: da lay
// weight[]: trong luong cua tung vat
// value[]: gia tri cua tung vat
// current[0]: trong luong hien tai
// current[1]: gia tri hien tai
// current[2]: gia tri max
// pack[]: cac vat da lay o trang thai toi uu
int nCalls=0;
void knapSack(intK, intused[], int item, int weight[],
int value[], int pack[], intcurrent[], int n);
Trang 28void knapSack(intK, intused[], int item, int weight[],
int value[], int pack[], int current[], int n){
nCalls++; // So loi goi
// Kiem tra da xem xet den vat cuoi
if(item == n){
// Kiem tra lai truong hop da lay vat
if(current[1] > current[2]){
printf("Tong gia tri hien tai: %d\n",current[1]);
// Luu thong tin hien tai
Trang 294.4 Knapsack Problem (9/9)
for(inti=0;i<n;i++)
if(pack[i]) printf("Vat %d, so lan chon %d
// Thu xem co the lay bao nhieu vat
used[item] = (K - current[0]) / weight[item];
current[0] += used[item] * weight[item];
current[1] += used[item] * value[item];
// Neu khong lay vat do lan nao ca
knapSack(K, used, item+1, weight, value, pack, current, n);
}}