Bài tập môn học kỹ thuật lập trình
TRƯỜNG ĐẠI HỌC BÁCH KHOA HÀ NỘI VIỆN CÔNG NGHỆ THÔNG TIN VÀ TRUYỀN THÔNG ——————– * ——————— BÀI TẬP MƠN HỌC KỸ THUẬT LẬP TRÌNH Nhóm 40 : Nguyễn Duy Thành-CNTT1 20102737 Bạch Văn Hải-CNTT1 20101464 Vũ Văn Hiệp-CNTT2 20101545 Nguyễn Xuân Hòa-CNTT2 20101559 Giáo viên : Lương Mạnh Bá HÀ NỘI Ngày 25 tháng năm 2012 Mục lục Bài toán xếp hậu 1.1 Cách giải đệ quy 1.1.1 Phân tích toán 1.1.2 Thuật toán 1.1.3 Listing chương trình 1.1.4 Kết 1.2 Cách giải không đệ quy 1.2.1 Phân tích 1.2.2 Listing chương trình 1.2.3 Kết 3 4 13 13 14 17 Bài toán tháp Hà Nội 2.1 Cách giải đệ quy 2.1.1 Phân tích 2.1.2 Thuật toán 2.1.3 Listing chương trình 2.1.4 Kết 2.2 Cách giải khơng đệ quy 2.2.1 Phân tích thuật toán 2.2.2 Thuật toán 2.2.3 List chương trình 2.2.4 Kết 19 19 19 19 19 21 22 22 23 23 24 Hình 1: Quân Hậu ô (3,2) chiếm cột thứ 2, đường chéo loại I thứ đường chéo loại II thứ 1 Bài toán xếp hậu Đề bài: Liệt kê tất cách xếp N quân hậu bàn cờ N x N cho chúng không ăn 1.1 Cách giải đệ quy 1.1.1 Phân tích tốn Đánh số cột số dịng bàn cờ từ đến N − Rõ ràng N quân Hậu xếp N hàng khác Do ta cần tìm xem qn Hậu xếp cột Gọi colsi số cột quân Hậu hàng thứ i (i = 0− > N − 1), colsi lấy giá trị từ đến N − Ta tìm colsi cho i từ đến N − Giá trị colsi = j coi thoả mãn ô cờ (i, j) chưa bị quân Hậu số qn Hậu tìm trước ăn Một qn Hậu ăn theo chiều ngang, dọc theo hai đường chéo Một qn Hậu trước ăn (i, j) theo chiều ngang có nghĩa quân Hậu hàng i, điều khơng thể xảy cách làm ta hàng xếp quân Hậu Quân Hậu ăn theo chiều dọc có nghĩa quân Hậu có số cột j Đối với hai đường chéo, ta thấy đường có i + j = const, (0 ≤ i + j ≤ 2N − 2), đường có phương trình i − j = const, (1 − N ≤ i − j ≤ N − 1) Như có tất 2N − đường chéo loại I (i + j = const) 2N − đường chéo loại II (i − j = const) Khi xếp qn Hậu vào (i, j) có nghĩa cột j; đường chéo loại I thứ (i + j) đường chéo loại II thứ (i − j) bị chiếm, quân Hậu sau không phép đặt vào vị trí có cột, đường chéo với Để biểu diễn cột, đường chéo bị chiếm ta sử dụng mảng bool , bi , ci thể việc cột thứ i, đường chéo loại I thứ i, đường chéo loại II i bị chiếm hay chưa Do ngôn ngữ C khơng hỗ trợ mảng có số âm nên ta gán: bool raw_c[2*N]; bool* c = raw_c + N; Khi đó, c[i] == raw_c[i + N ] với −N ≤ i ≤ N − 1.1.2 Thuật toán Ta dùng thuật toán đệ quy quay lui với toán Cụ thể gồm bước sau: Khởi tạo a, b, c true, có nghĩa tất cột vào đường chéo lúc đầu chưa bị chiếm i = thứ tự quân Hậu xét, số hàng Thử giá trị j, ≤ j ≤ N − số cột quân Hậu (hàng i) Nếu ô (i, j) chưa bị chiếm, tức aj == bi+j == ci−j == true chấp nhận giá trị j (gán colsi = j) Đánh dấu cột, đưòng chéo tương ứng với ô (i, j) bị chiếm (aj = bi+j = ci−j = f alse) Nếu i == N − 1, tức tìm kết in Cịn lại ta tìm colsi+1 Sau trả lại (i, j) (tức gán aj = bi+j = ci−j = true) Nếu ô (i, j) bị chiếm lại tiếp tục thử giá trị j Nếu khơng có khả thoả mãn quay lại bước trước để tìm giá trị colsi−1 Có thể biểu diễn mã sau: def find(i): for j in range(0, N): if a[j] and b[i+j] and c[i-j]: cols[i] = j a[j] = b[i+j] = c[i-j] = False if i < N - 1: find(i+1) else: PrintResult() a[j] = b[i+j] = c[i-j] = True 10 1.1.3 Listing chương trình Listing chương trình chứa file queens_recursive.c, chương trình viết ngơn ngữ C theo chuẩn C99 nên số trình biên dịch cũ khơng biên dịch 10 11 /* * ============================================================== * * Filename: queens_recursive.c * * Description: Bai toan xep hau, cach giai de quy * * Version: 1.0 * Created: 03/12/2012 07:12:42 PM * Revision: none * Compiler: gcc 12 13 14 * * ============================================================== */ 15 16 17 18 19 #include #include #include #include 20 21 22 23 24 25 int *cols; bool *a, *b, *raw_c, *c; // Cột thứ i // Đường chéo I, i + j = const // Đường chéo II, i - j = const 26 27 28 void init_state(int); void free_state(int); 29 30 void find(int, int); 31 32 33 34 void print_result(int const*, int); void print_result_board(int const*, int); void (*print_func)(int const*, int) = print_result; 35 36 37 int main(int argc, char *argv[]) { int n = 8; 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 // Xử lý tham số dòng lệnh if(argc >= 2) { n = atoi(argv[1]); if(n 3) { fprintf(stderr, "Usage: %s [NSIZE] [-b | board] \n " , argv[0]); return EXIT_FAILURE; } 60 61 // Thực init_state(n); find(0, n); free_state(n); 62 63 64 65 66 return EXIT_SUCCESS; 67 68 } 69 70 71 72 73 74 75 76 77 78 79 80 81 /* * === FUNCTION ============================================== * Name: init_state * Description: Khởi tạo mảng lưu trạng thái cột, * đường chéo * ============================================================= */ void init_state(int n) { a = (bool*) malloc(sizeof(bool)*n); b = (bool*) malloc(sizeof(bool)*2*n); raw_c = (bool*) malloc(sizeof(bool)*2*n), c = raw_c + n; 82 83 cols = (int*) malloc(sizeof(int)*n); 84 85 86 87 88 89 90 91 92 /* Ban đầu, tất cột, đường chéo gán * chưa bị chiếm */ for(int i = 0; i < n; ++i) a[i] = b[i] = raw_c[i] = true; for(int i = n; i < 2*n; ++i) b[i] = raw_c[i] = true; } /* - end of function init_state - */ 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 /* * === FUNCTION ============================================== * Name: free_state * Description: Giải phóng nhớ * ============================================================= */ void free_state(int n) { free(a); free(b); free(raw_c); free(cols); } /* - end of function free_state - */ 108 109 110 111 112 113 114 115 /* * === FUNCTION ============================================== * Name: print_result * Description: In kết hình * ============================================================= */ 116 117 118 void print_result(int const* cols, int n) { static int count = 0; 119 120 printf( "Kết thứ %d: \n " , ++count); 121 122 123 /* giá trị thứ i kết số cột * quân hậu có số hàng i */ 124 125 126 127 128 129 130 for(int i = 0; i < n; ++i) { printf( "%2d " , cols[i]); } printf( " \n\n " ); } /* - end of function print_result - */ 131 132 133 134 135 136 137 138 139 140 141 /* * === FUNCTION ============================================== * Name: print_result_board * Description: In bàn cờ dạng ASCII biểu diễn kết thay * in số vị trí qn Hậu * ============================================================= */ void print_result_board(int const* cols, int n) { static int count = 0; 142 143 printf( "Kết thứ %d: \n " , ++count); 144 145 146 147 for(int i = 0; i < n; ++i) printf( " _" ); putchar( ’\n’ ); 148 149 150 151 152 153 154 for(int i = n-1; i >= 0; i) { for(int j = 0; j < cols[i]; ++j) printf( "|_" ); printf( "|*" ); for(int j = cols[i]+1; j < n; ++j) printf( "|_" ); 155 printf( "| \n " ); 156 157 } 158 159 160 161 printf( " \n\n " ); } /* - end of function print_result_board - */ 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 /* * === FUNCTION ============================================== * Name: find * Description: Tìm vị trị đặt quân hậu hàng thứ curr_row * thoả mãn yêu cầu * ============================================================= */ void find(int curr_row, int n) { for(int curr_col = 0; curr_col < n; ++curr_col) { if(a[curr_col] /* cột curr_col chưa bị chiếm */ /* đường chéo loại I curr_row + curr_col chưa bị chiếm */ && b[curr_row + curr_col] /* đường chéo loại II curr_row - curr_col chưa bị chiếm */ && c[curr_row - curr_col]) { cols[curr_row] = curr_col; 180 // Cập nhật trạng thái a[curr_col] = b[curr_row + curr_col] = c[curr_row - curr_col] = false; 181 182 183 184 if(curr_row < n - 1) { // Tìm cách xếp hậu hàng find(curr_row + 1, n); } else { print_func(cols, n); } 185 186 187 188 189 190 191 // trả lại trạng thái cũ a[curr_col] = b[curr_row + curr_col] = c[curr_row - curr_col] = true; 192 193 194 } 195 196 197 198 } } /* - end of function find - */ 1.1.4 Kết Với N = 8, có tất 92 kết khác (46 tính kết đối xứng 1) 10 Có thể in kết dạng bàn cờ trực quan hơn, thay số vị trí: /queen_recursive board Nếu kết q dài, lưu vào file Ví dụ: với N = 12 (có 14200 kết quả) /queen_recursive 12 board > qr12.txt 12 1.2 1.2.1 Cách giải khơng đệ quy Phân tích Theo thuật tốn đệ quy: 10 def find(i): for j in range(0, N): if a[j] and b[i+j] and c[i-j]: cols[i] = j a[j] = b[i+j] = c[i-j] = False if i < N - 1: find(i+1) else: PrintResult() a[j] = b[i+j] = c[i-j] = True Giả sử cấu hình cols = (j0 , j1 , , ji−1 ) Ta thấy: ❼ Việc lựa chọn giá trị ji khoảng từ đến N-1 dòng (2) bắt đầu sau tìm ji−1 trước Và tiếp tục sau kết thúc việc tìm kết ứng với cấu hình cols = (j0 , j1 , , ji−1 , ji − 1), ❼ Dòng (5) thực sau chấp nhận ji , ❼ Dòng (10) thực in kết quả, việc chấp nhận j dịng (4) khơng dẫn kết Do ta khử đệ quy phép lặp, dùng biến i để lưu giá trị hàng Cho ji thử giá trị từ đến N-1, thoả mãn cập nhật trạng thái, tăng i lên tiếp tục với i + Cho đến i == N − in kết trả lại trạng thái Nếu khơng cịn ji thoả mãn giảm i trả lại trạng thái cũ Quá trình kết thúc i == khơng cịn giá trị j0 thoả mãn (đã hết cấu hình để thử) Ta có mã sau: 10 def solve(): i = cols[i] = -1 while True: # Thu gia tri tiep theo j = ++cols[i] if j < N: # Neu o (i, j) chua bi chiem if a[j] && b[i+j] && c[i-j]: a[j] = b[i+j] = c[i-j] = False 11 12 13 14 15 16 if i < N-1: # Bat dau tim cols[i+1] cols[++i] = -1 else: PrintResult() 17 13 # Tra lai trang thai cu a[j] = b[i+j] = c[i-j] = True 18 19 elif i > 0: # Tro lai thu gia tri tiep theo cua cols[i-1] j = cols[ i] 20 21 22 23 # Tra lai trang thai cu a[j] = b[i+j] = c[i-j] = True else: break 24 25 26 27 1.2.2 Listing chương trình Listing chương trình chứa file queens_nonrecursive.c, chương trình viết ngôn ngữ C theo chuẩn C99 nên số trình biên dịch cũ khơng biên dịch 10 11 12 13 14 /* * ============================================================== * * Filename: queens_nonrecursive.c * * Description: Bài toán xếp hậu, cách giải không đệ quy * * Version: 1.0 * Created: 03/12/2012 07:12:42 PM * Revision: none * Compiler: gcc * * ============================================================== */ 15 16 17 18 19 #include #include #include #include 20 21 void solve(int); 22 23 24 25 void print_result(int const*, int); void print_result_board(int const*, int); void (*print_func)(int const*, int) = print_result; 26 27 28 int main(int argc, char *argv[]) { int n = 8; 29 30 31 // Xử lý tham số dòng lệnh if(argc >= 2) { 14 n = atoi(argv[1]); if(n 3) { fprintf(stderr, "Usage: %s [NSIZE] [-b | board] \n " , argv[0]); return EXIT_FAILURE; } 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 // Thực solve(n); 53 54 55 return EXIT_SUCCESS; 56 57 } 58 59 60 61 62 63 64 65 /* * === FUNCTION ============================================== * Name: print_result * Description: In kết hình * ============================================================= */ 66 67 68 void print_result(int const *cols, int n) { static int count = 0; 69 printf( "Kết thứ %d: \n " , ++count); 70 71 /* giá trị thứ i kết số cột * quân hậu có số hàng i */ 72 73 74 for(int i = 0; i < n; ++i) { printf( "%2d " , cols[i]); } printf( " \n\n " ); 75 76 77 78 79 } 15 80 /* - end of function print_result - */ 81 82 83 84 85 86 87 88 89 90 91 /* * === FUNCTION ============================================== * Name: print_result_board * Description: In bàn cờ dạng ASCII biểu diễn kết thay * in số vị trí quân Hậu * ============================================================= */ void print_result_board(int const *cols, int n) { static int count = 0; 92 93 printf( "Kết thứ %d: \n " , ++count); 94 95 96 97 for(int i = 0; i < n; ++i) printf( " _" ); putchar( ’\n’ ); 98 99 100 101 102 103 104 for(int i = n-1; i >= 0; i) { for(int j = 0; j < cols[i]; ++j) printf( "|_" ); printf( "|*" ); for(int j = cols[i]+1; j < n; ++j) printf( "|_" ); 105 printf( "| \n " ); 106 107 } 108 109 110 111 printf( " \n\n " ); } /* - end of function print_result_board - */ 112 113 114 115 116 117 118 119 120 121 122 123 124 /* * === FUNCTION ============================================== * Name: solve * Description: Tìm tất cách xếp quân hậu thoả mãn * yêu cầu * ============================================================= */ void solve(int n) { bool a[n]; // Cột thứ i bool b[2*n]; // Đường chéo 1, i + j = const bool raw_c[2*n], *c= raw_c + n; // Đường chéo 2, i - j = const int cols[n]; 125 126 127 // Init for(int i = 0; i < n; ++i) 16 a[i] = b[i] = raw_c[i] = true; for(int i = n; i < 2*n; ++i) b[i] = raw_c[i] = true; 128 129 130 131 cols[0] = -1; int curr_row = 0, curr_col = 0; 132 133 134 while(true) { if((curr_col = ++cols[curr_row]) < n) { if(a[curr_col] /* cột curr_col chưa bị chiếm */ /* đường chéo loại I curr_row + curr_col chưa bị chiếm */ && b[curr_row + curr_col] /* đường chéo loại II curr_row - curr_col chưa bị chiếm */ && c[curr_row - curr_col]) { // Cập nhật trạng thái a[curr_col] = b[curr_row + curr_col] = c[curr_row - curr_col] = false; 135 136 137 138 139 140 141 142 143 144 145 146 if(curr_row < n - 1) { cols[++curr_row] = -1; } else { print_func(cols, n); 147 148 149 150 151 // trả lại trạng thái cũ a[curr_col] = b[curr_row + curr_col] = c[curr_row - curr_col] = true; 152 153 154 } } } else if(curr_row > 0) { curr_col = cols[ curr_row]; 155 156 157 158 159 // trả lại trạng thái cũ a[curr_col] = b[curr_row + curr_col] = c[curr_row - curr_col] = true; } else break; 160 161 162 163 164 165 166 167 } } /* - 1.2.3 end of function solve - */ Kết Kết giống với kết cách giải không đệ quy Ví dụ, với N = 4: 17 18 Bài tốn tháp Hà Nội Đề bài: Có cọc a, b, c Trên cọc a có chồng gồm n đĩa đường kính giảm dần từ lên Cần phải chuyển chồng đĩa từ cọc a dang cọc c tuân thủ qui tắc: lần chuyển đĩa xếp đĩa có đường kính nhỏ lên đĩa có đường kính lớn Trong trình chuyển phép dùng cọc b làm cọc trung gian 2.1 2.1.1 Cách giải đệ quy Phân tích Với n = 1, ta việc chuyển đĩa từ cọc a sang cọc c Với n = 2, ta di chuyển đĩa (cái nhỏ hơn) từ cọc a sang cọc b, chuyển đĩa lại cọc a sang cọc c cuối chuyển đĩa cọc b sang cọc c Cả ba bước di chuyển đề hợp lệ Lập luận tương tự trên, ta di chuyển n đĩa (n > 1) sau: Chuyển n − đĩa từ cọc a sang cọc trung gian b, Chuyển đĩa lớn từ cọc a sang cọc đích c, Chuyển n − đĩa từ cọc b sang cọc c Có thể chứng minh việc di chuyển 2n − bước 2.1.2 Thuật tốn Cách làm đưa tồn di chuyển n đĩa toán di chuyển n − đĩa, n − đĩa, Rất thích hợp cho thuật tốn đệ quy: # Ham thuc hien viec di chuyen def move(n, source, dest, mid): if n > 1: move(n-1, source, mid, dest) move( 1, source, dest, mid) move(n-1, mid, dest, source) else: print "Move disk from " + source + " to " + dest move(n, a, c, b) 10 2.1.3 Listing chương trình /* * ============================================================= * * Filename: towerofhanoi.c * * Description: 19 10 11 12 13 14 * * Version: 1.0 * Created: 03/17/2012 02:24:36 PM * Revision: none * Compiler: gcc * * ============================================================= */ 15 16 17 18 #include #include #include 19 20 #define MAX_DISK 12 21 22 void move(size_t, char, char, char); 23 24 25 26 27 28 29 30 int main(int argc, char *argv[]) { /* Xử lý tham số dòng lệnh */ size_t ndisk; if(argc != 2) { fprintf(stderr, "Usage: %s NDISKS \n " , argv[0]); return EXIT_FAILURE; } 31 ndisk = atoi(argv[1]); 32 33 if(ndisk > MAX_DISK || ndisk == 0) { fprintf(stderr, "Số đĩa không hợp lệ \n " ); return EXIT_FAILURE; } 34 35 36 37 38 /* Thực việc di chuyển */ move(ndisk, ’a’ , ’c’ , ’b’ ); 39 40 41 return EXIT_SUCCESS; 42 43 } 44 45 46 47 48 49 50 51 52 53 54 /* * === FUNCTION ============================================== * Name: move * Description: Di chuyển ndisk đĩa từ cột src đến cột dst, * sử dụng cột trung gian mid * ============================================================= */ void move (size_t ndisk, char src, char dst, char mid) { static size_t count = 0; 20 55 if(ndisk) { move(ndisk - 1, src, mid, dst); printf( "Bước %2u: chuyển đĩa từ cột %c -> %c \n " , ++count, src, dst); move(ndisk - 1, mid, dst, src); } 56 57 58 59 60 61 62 63 } /* - 2.1.4 end of function move - */ Kết Với n = 3, cần tất bước di chuyển; n = 4, cần 15 bước: 21 2.2 2.2.1 Cách giải không đệ quy Phân tích thuật tốn Với tốn tháp Hà Nội có nhiều phương án để thực việc phá bỏ đệ quy sử dụng vòng lặp, sử dụng stack số sử dụng nhi phân(binary tree) Như mã hóa tốn thơng qua biểu diễn nhị phân số thứ tự di chuyển, dãy dãy tượng trưng cho dãy đĩa liền kề cọc, chữ số thay đổi đĩa dời sang trái hay phải cọc(hay chuyển sang cọc đối diện).Quy tắc: Dãy bit đọc từ trái qua phải, bit sử dụng để xác định vị trí tương ứng Bit có giá trị nghĩa đĩa ương ứng xếp chồng lên đỉnh đĩa trước cung cọc Bit có giá trị khác trước nghĩa đĩa tương ứng có vị trí bên trái phải.Xác định bên trái hay phải theo quy tắc sau: ❼ Giả thiết cọc đích nằm bên trái cọc nguồn nằm bên phải ❼ Cũng giả thiết:cọc phải tính cọc trái cọc trái ngược lại ❼ Giả sử n số đĩa lớn cọc.Nếu n chẵn , đĩa đặt bên trái, n lẻ đĩa đặt cọc phải Để dễ hiểu ta xét ví dụ tốn tháp Hà Nội với ndisk = (3 cọc a, b, c) Với ndisk = 3: Ba đĩa cọc A kí hiệu (000)2 Chuyển đĩa từ a sang c, kí hiệu (001)2 = 110 Chữ số biểu thị đĩa chuyển Chuyển đĩa từ a sang b, kí hiệu (010)2 Chữ số biểu thị đĩa thứ chuyển, ba chữ số 0, 1, biểu thị ba đĩa ba cọc khác Chuyển đĩa từ c sang b, kí hiệu (011)2 = (3)10 Chữ số vị trí thứ biểu thị đĩa số chuyển Ba chữ số 0, 1, biểu thị hai đĩa nhỏ cọc c (đĩa thứ hai chồng lên đĩa thứ nhất), đĩa lớn cọc khác (cọc a) Cọc c trống Chuyển đĩa số từ cọc a sang cọc c, kí hiệu (100)2 Chữ số thứ ba biểu thị đĩa số chuyển Ba chữ số 1, 0, biểu thị hai đĩa nhỏ cọc b (đĩa thứ hai chồng lên đĩa thứ nhất) không chuyển, đĩa lớn cọc khác (cọc c) Cọc a trống 22 Chuyển đĩa số từ cọc b sang cọc a, kí hiệu (101)2 Chữ số thứ ba (khác số bước 5) biểu thị đĩa số chuyển.Ba chữ số 1, 0, khác biểu thị ba đĩa ba cọc khác Chuyển đĩa số từ cọc b sang cọc c, kí hiệu (110)2 Chữ số thứ n (khác số bước 5) biểu thị đĩa số chuyển.Ba chữ số 1, 0, biểu thị ba đĩa ba cột khác Chuyển đĩa từ cột b sang c Chuyển đĩa từ b sang c Với cách thực nguồn cọc đích cho lần di chuyển thứ k tìm thấy từ biểu diễn nhị phân k sử dụng toán tử phân theo bit (bitwise operations) Để sử dụng cú pháp ngơn ngữ lập trình C, di chuyển k từ cọc (k& k-1)% tới cọc ((k|k-1)+1)% 3,nơi mà đĩa bắt đầu rừ cọc kết thúc cọc tùy theo số đĩa chãn hay lẻ Thêm phát biểu khác từ (k-(k-& k )% 3) tới cọc (k+(k-& k )% 3) 2.2.2 Thuật tốn Ta có mã giả: # Ham thuc hien viec di chuyen def move(size_t ndisk, int source, int dest, int mid): size_t x; for (x=1; x < (1 MAX_DISK || ndisk == 0) { fprintf(stderr, "Số đĩa không hợp lệ \n " ); return EXIT_FAILURE; } 33 34 35 36 37 move(ndisk, 1, 3, 2); 38 39 return EXIT_SUCCESS; 40 41 } 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 /* * === FUNCTION ========================================================= * Name: move * Description: Di chuyển ndisk đĩa từ cột src đến cột dst, * sử dụng cột trung gian mid * ======================================================================== */ void move (size_t ndisk, int src, int dst, int mid) { size_t x; for (x=1; x < (1