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) - Ngô Hữu Phước

4.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]). 4.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].

pdf29 trang | Chia sẻ: thanhle95 | Lượt xem: 447 | Lượt tải: 1download
Bạn đang xem trước 20 trang tài liệu 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) - Ngô Hữu Phước, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
Lecturer: PhD. Ngo Huu Phuc Tel: 0438 326 077 Mob: 098 5696 580 Email: ngohuuphuc76@gmail.com Cấu trúc dữ liệu và giải thuật Bài 4: Kỹ thuật quay lui (Backtracking) @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University1 Bài 4. Kỹ thuật quay lui Backtracking Nội dung: 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) Tham khảo: 1. Lecture 11 – Backtracking.htm @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University2 4.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]). @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University3 4.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]. @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University4 4.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 = 1 While k > 0 do While S[k][] do (*advance*) a[k] = an element in S[k] If (a[1], a[2],, a[k]) is solution, print it! k = k + 1 Tính S[k], tập khả năng của a tại bước k. k = k - 1 (*backtrack*) @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University5 4.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 @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University6 4.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. @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University7 4.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. @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University8 4.2. Eight Queen Problem (1/7) @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University9 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 4.2. Eight Queen Problem (2/7) @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University10 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.  648 cách đặt 8 con hậu 4.2. Eight Queen Problem (3/7) @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University11 Một vài cách đặt 8 con hậu lên bàn cờ (trong số 92 lời giải) 4.2. Eight Queen Problem (4/7) Ý tưởng:  Mỗi lần đệ quy, tìm cách đặt 1 con hậu lên một cột (hoặc hàng) riêng.  Với mỗi lần gọi, tình trạng bàn cờ đã biết (các con hậu đã đặt)  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 đó. @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University12 4.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ử. @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University13 4.2. Eight Queen Problem (6/7) #include "stdio.h" #include "conio.h" #include "math.h" #define N 8 int k=0; typedef enum {FALSE, TRUE} bools; void printMatrix(int a[][N], int n); int getMarkedCol(int a[][N], int n, int row); bools feasible(int a[][N], int n, int row, int col); void N_Queens(int a[][N], int n, int row); void main() { int a[N][N] ; int i=0, j=0; for(i=0; i<N; i++) for(j=0; j<N; j++) a[i][j]=0; N_Queens(a,N,0); getch(); } @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University14 // In ma tran chua cac con hau void printMatrix(int a[][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(int a[][N], int n, 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; } 4.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) { int i; int markedCol ; for(i=0; i<row; i++) { markedCol = getMarkedCol (a,n,i); if(markedCol == col || abs(row-i) == abs(col- markedCol)) return FALSE ; } return TRUE; } @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University15 // Bai toan N hau void N_Queens(int a[][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); } } 4.3. Knight Tour Problem (1/5) @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University16 4.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”. @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University17 4.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. @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University18 4.3. Knight Tour Problem (4/5) #include "stdio.h" #include "conio.h" #include "stdlib.h“ #define M 5 #define SizeStep 8 void printVariant(int a[][M], int n); void knight(int a[][M], int n, int row, int col, int num); //dinh nghia cach di cho con ma int rowchange[] = {-2, -1, 1, 2, 2, 1,-1,-2}; int colchange[] = {-1, -2, -2,-1, 1, 2, 2, 1}; void main() { int a[M][M] ; int i=0, j=0; int m,n; for(i=0; i<M; i++) for(j=0; j<M; j++) { @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University19 for(m=0;m<M;m++) for(n=0;n<M;n++) a[m][n]=0 ; a[i][j]=1; knight(a, M, i, j, 2); } getch(); } void printVariant(int a[][M], int n) { // in ket qua int i, j; for(i=0; i<n; ++i) { for(j=0; j<n; j++) printf("%2d ", a[i][j]); printf("\n"); } } 4.3. Knight Tour Problem (5/5) void knight(int a[][M], int n, 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=0 && newcol<n && a[newrow][newcol]==0) { a[newrow][newcol]=num; if(num==n*n) { printVariant(a, M); @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University20 printf("\nNhan Enter de chon ket qua khac, nhan ESC de thoat!\n"); if(char c=getch()==27) exit(1); } else knight(a, n, newrow, newcol, num+1); a[newrow][newcol]=0; } } } 4.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 chọn một cách cẩn thận!!! @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University21 4.4. Knapsack Problem (2/9)  Trường hợp đơn giản: Giả sử các vật có cùng trọng lượng. Rõ ràng, dễ đưa ra lời giải! Lấy các vật có giá trị lớn.  Có thể sử dụng tỷ số giữa giá trị và trọng lượng và sử dụng tỷ số này? @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University22 4.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 ba lô. Tổng giá trị là: 7 + 15 = 22.  Tuy nhiên, nếu chọn đúng, nên lấy vật 1 và vật 2, mỗi vật chọn 1 lần, tổng giá trị là: 7 + 16 = 23. @Copyright PhD. Ngo Huu Phuc, Le Quy Don Technical University23 Vật Trọng lượng Giá trị Tỷ lệ 1 3 7 7/3 2 6 16 8/3 3 7 19 19/7 4 5 15 3 4.4. Knapsack 01 - Problem (4/9) #include "conio.h" #include "stdio.h" #define M 10 typedef enum {FALSE, TRUE} bools; // Tham so // 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(int K, bools used[], int item, int weight[], int value[], bools pack[], int current[], int n); @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University24 void main() { bools used[M]; bools pack[M]; int weght[M]; int value[M]; int current[3]; int K; for(int i=0;i<M;i++) used[i]=pack[i]=FALSE; current[0]=current[1]=current[2]=0; int n; printf("Nhap so vat: "); scanf("%d",&n); printf("Nhap trong luong toi da cho ba lo:"); scanf("%d",&K); 4.4. Knapsack 01 - Problem (5/9) for(int i=0;i<n;i++) { printf("Nhap trong luong vat %d:",i); scanf("%d",&weght[i]); printf("Nhap gia tri vat %d:",i); scanf("%d",&value[i]); } knapSack(K,used,0,weght,value,pack,current,n); printf("Tong so lan goi ham: %d\n",nCalls); getch(); } @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University25 void knapSack(int K, 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 current[2] = current[1]; for (int k = 0; k < n; k++) pack[k] = used[k]; printf("Cac vat duoc chon: "); for(int i=0;i<n;i++) 4.4. Knapsack 01 - Problem (6/9) if(pack[i]) printf("%d, ",i); printf("\n"); } } else { // Khi chua kiem tra het cac vat if (current[0] + weight[item] <= K) { // Lay vat item nay used[item] = TRUE; current[0] += weight[item]; current[1] += value[item]; knapSack(K, used, item+1, weight, value, pack, current, n); // Khi khong lay vat nay used[item] = FALSE; current[0] -= weight[item]; @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University26 current[1] -= value[item]; } // Khong lay duoc vat nay, tiep tuc thu vat khac. knapSack(K, used, item+1, weight, value, pack, current, n); } } 4.4. Knapsack Problem (7/9) #include "conio.h" #include "stdio.h" #define M 10 // Tham so // 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(int K, int used[], int item, int weight[], int value[], int pack[], int current[], int n); @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University27 void main() { int used[M]; int pack[M]; int weght[M]; int value[M]; int current[3]; int K; for(int i=0;i<M;i++) used[i]=pack[i]=0; current[0]=current[1]=current[2]=0; int n; printf("Nhap so vat: "); scanf("%d",&n); printf("Nhap trong luong toi da cho ba lo:"); scanf("%d",&K); 4.4. Knapsack Problem (8/9) for(int i=0;i<n;i++) { printf("Nhap trong luong vat %d:",i); scanf("%d",&weght[i]); printf("Nhap gia tri vat %d:",i); scanf("%d",&value[i]); } knapSack(K,used,0,weght,value,pack,current,n); printf("Tong so lan goi ham: %d\n",nCalls); getch(); } @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University28 void knapSack(int K, int used[], 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 current[2] = current[1]; for (int k = 0; k < n; k++) pack[k] = used[k]; printf("Cac vat duoc chon: "); 4.4. Knapsack Problem (9/9) for(int i=0;i<n;i++) if(pack[i]) printf("Vat %d, so lan chon %d ",i,pack[i]); printf("\n"); } } else { // 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]; while ( used[item] > 0 ) { knapSack(K, used, item+1, weight, value, pack, current, n); // Bo mot vat cuoi ra, neu khong lay duoc used[item]--; @Copyright Dr. Ngo Huu Phuc, Le Quy Don Technical University29/29 current[0] -= weight[item]; current[1] -= value[item]; } // Neu khong lay vat do lan nao ca knapSack(K, used, item+1, weight, value, pack, current, n); } }