TRƯỜNG ĐẠI HỌC BÁCH KHOA HÀ NỘI VIỆN CÔNG NGHỆ THÔNG TIN VÀ TRUYỀN THÔNG

Cấu trúc dữ liệu và thuật toán

Nguyễn Khánh Phương

cuu duong than cong . co m

Computer Science department School of Information and Communication technology E-mail: phuongnk@soict.hust.edu.vn

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Nội dung khóa học

cuu duong than cong . co m

Chương 1. Các khái niệm cơ bản Chương 2. Các sơ đồ thuật toán Chương 3. Các cấu trúc dữ liệu cơ bản Chương 4. Cây Chương 5. Sắp xếp Chương 6. Tìm kiếm Chương 7. Đồ thị

2

CuuDuongThanCong.com https://fb.com/tailieudientucntt

TRƯỜNG ĐẠI HỌC BÁCH KHOA HÀ NỘI VIỆN CÔNG NGHỆ THÔNG TIN VÀ TRUYỀN THÔNG

Chương 2. Các sơ đồ thuật toán

Nguyễn Khánh Phương

cuu duong than cong . co m

Computer Science department School of Information and Communication technology E-mail: phuongnk@soict.hust.edu.vn

CuuDuongThanCong.com https://fb.com/tailieudientucntt

N i dung 1. Khái niệm đệ qui

2. Thuật toán đệ qui

3. Một số ví dụ minh hoạ

4. Phân tích thuật toán đệ qui

5. Đệ qui có nhớ

6. Thuật toán quay lui

cuu duong than cong . co m

4

CuuDuongThanCong.com https://fb.com/tailieudientucntt

N i dung 1. Khái niệm đệ qui

2. Thuật toán đệ qui

3. Một số ví dụ minh hoạ

4. Phân tích thuật toán đệ qui

5. Đệ qui có nhớ

6. Thuật toán quay lui

cuu duong than cong . co m

5

CuuDuongThanCong.com https://fb.com/tailieudientucntt

1. Khái niệm đệ qui

• Trong thực tế ta thường gặp những đối tượng bao gồm chính nó hoặc được định nghĩa dưới dạng của chính nó. Ta nói các đối tượng đó được xác định một cách đệ qui.

• Ví dụ:

– Điểm quân số

– Fractal

– Các hàm được định nghĩa đệ qui

– Tập hợp được định nghĩa đệ qui

– Định nghĩa đệ qui của cây

cuu duong than cong . co m

– ...

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ Đệ qui: Điểm quân

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ Đệ qui: Điểm quân

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ Đệ qui: Điểm quân

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ Đệ qui: Điểm quân

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ Đệ qui: Điểm quân

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ Đệ qui: Điểm quân

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ Đệ qui: Điểm quân

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ Đệ qui: Điểm quân

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ Đệ qui: Điểm quân

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ Đệ qui: Điểm quân

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ Đệ qui: Điểm quân

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ Đệ qui: Fractals

fractals là ví dụ về hình ảnh được xây dựng một cách đệ qui (đối tượng lặp lại một cách đệ qui).

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Hàm đệ qui (Recursive Functions)

Các hàm đệ qui được xác định phụ thuộc vào biến nguyên không âm n theo sơ đồ sau:

Bước cơ sở (Basic Step): Xác định giá trị của hàm tại n=0: f(0).

Bước đệ qui (Recursive Step): Cho giá trị của f(k), k ≤ n, đưa ra qui tắc tính giá trị của f(n+1).

Ví dụ 1:

n = 0

f(0) = 3,

n > 0

f(n+1) = 2f(n) + 3,

f(2) = 2 × 9 + 3 = 21, ...

Khi đó ta có: f(1) = 2 × 3 + 3 = 9,

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Hàm đệ qui (Recursive Functions)

Ví dụ 2: Định nghĩa đệ qui của n!

f(0) = 1 f(n) = n * f(n-1)

Để tính giá trị của hàm đệ qui ta thay thế dần theo định nghĩa đệ qui để thu được biểu thức với đối số càng ngày càng nhỏ cho đến tận điều kiện đầu. Chẳng hạn:

đệ qui

5! = 5 · 4! = 5 · 4 · 3! = 5 · 4 · 3 · 2! = 5 · 4 · 3 · 2 · 1! = 5 · 4 · 3 · 2 · 1 · 0! = 5 · 4 · 3 · 2 · 1 · 1 = 120

int factorial(int n){

cuu duong than cong . co m

if (n==0)

điều kiện đầu

return 1;

else

return n*factorial(n-1);

}

CuuDuongThanCong.com https://fb.com/tailieudientucntt

factorial(4);

int factorial(int n){

if (n==0)

return 1;

factorial(4)

else

return n*factorial(n-1);

}

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

factorial(4);

int factorial(int n){

if (n==0)

return 1;

factorial(4)

else

return n*factorial(n-1);

}

n=4 Returns 4*factorial(3)

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

factorial(4);

int factorial(int n){

if (n==0)

return 1;

factorial(4)

else

return n*factorial(n-1);

}

n=4 Returns 4*factorial(3)

n=3 Returns 3*factorial(2)

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

factorial(4);

int factorial(int n){

if (n==0)

return 1;

factorial(4)

else

return n*factorial(n-1);

}

n=4 Returns 4*factorial(3)

n=3 Returns 3*factorial(2)

n=2 Returns 2*factorial(1)

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

factorial(4);

int factorial(int n){

if (n==0)

return 1;

factorial(4)

else

return n*factorial(n-1);

}

n=4 Returns 4*factorial(3)

n=3 Returns 3*factorial(2)

n=2 Returns 2*factorial(1)

n=1 Returns 1*factorial(0)

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

factorial(4);

int factorial(int n){

if (n==0)

return 1;

factorial(4)

else

return n*factorial(n-1);

}

n=4 Returns 4*factorial(3)

n=3 Returns 3*factorial(2)

n=2 Returns 2*factorial(1)

n=1 Returns 1*factorial(0)

n=0 Returns 1

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

factorial(4);

int factorial(int n){

if (n==0)

return 1;

factorial(4)

else

return n*factorial(n-1);

}

n=4 Returns 4*factorial(3)

n=3 Returns 3*factorial(2)

n=2 Returns 2*factorial(1)

n=1 Returns 1*factorial(0)

cuu duong than cong . co m

1

CuuDuongThanCong.com https://fb.com/tailieudientucntt

factorial(4);

int factorial(int n){

if (n==0)

return 1;

factorial(4)

else

return n*factorial(n-1);

}

n=4 Returns 4*factorial(3)

n=3 Returns 3*factorial(2)

n=2 Returns 2*factorial(1)

1

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

factorial(4);

int factorial(int n){

if (n==0)

return 1;

factorial(4)

else

return n*factorial(n-1);

}

n=4 Returns 4*factorial(3)

n=3 Returns 3*factorial(2)

2

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

factorial(4);

int factorial(int n){

if (n==0)

return 1;

factorial(4)

else

return n*factorial(n-1);

}

n=4 Returns 4*factorial(3)

6

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

factorial(4);

int factorial(int n){

if (n==0)

return 1;

factorial(4)

else

return n*factorial(n-1);

}

24

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Chương trình chạy theo thứ tự thế nào?

n=4 Returns 4*factorial(3)

Thực thi hàm factorial(4) sẽ dừng cho đến khi hàm factorial(3) trả về kết quả

cuu duong than cong . co m

n=3 Returns 3*factorial(2)

Khi hàm factorial(3) trả về kết quả, hàm factorial(4) tiếp tục được thực hiện

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Các hàm đệ quy đều có thể cài đặt lại bằng cách sử dụng vòng lặp while/for

• Chúng ta cũng có thể cài đặt tính n! bằng cách sử dụng vòng lặp while

int factorial (int n) {

int factorial(int n){

if (n==0)

return 1;

i=n; fact = 1; while(i >= 1) {

else

return n*factorial(n-1);

}

fact=fact*i; i--;

} return fact;

cuu duong than cong . co m

} Chú ý: Việc thay thế hàm đệ qui bởi hàm không đệ qui thường được gọi là việc khử đệ qui. Khử đệ qui không phải bao giờ cũng là dễ thực hiện như trong tình huống bài toán tính giai thừa.

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ 3

Viết hàm prod(list) trả về tích các số có trong danh sách list Ví dụ: prod({1,3,3,4}) = 36

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ 3: Cài đặt hàm prod()

float prod(float[] list){

return helperProd(list, 0);

}

float helperProd(float[] list, int k){

if (k==list.size) return 1;

else

return list[k]*helperProd(list,k+1);

}

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ 4. Fibonacci - Sự phát triển của bày thỏ Người nông dân nuôi một cặp thỏ mới sinh. Khi được 2 tháng tuổi, cứ mỗi cặp thỏ lại sinh ra một cặp thỏ khác sau mỗi tháng. Hỏi người nông dân đó có bao nhiêu con thỏ sau n tháng ? 

n = 1: f1 = 1

Dãy Fibonacci: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,...

n = 2: f2 = 1

n > 2: fn = fn-1 + fn-2

Công thức đúng cho n>2 vì mỗi cặp thỏ mới được sinh ra từ một cặp thỏ ít nhất 2 tháng tuổi

Month

>=2-month rabbits

1-month rabbits

Tổng fn

New-born rabbits

1

0

1

0

1

2

1

1

3

1

2

4

1 1 2 3

cuu duong than cong . co m

2

3

5

5

3

5

6

8

5

8

7

13

8

13

8

21

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ 4. Fibonacci

Người nông dân nuôi một cặp thỏ mới sinh. Khi được 2 tháng tuổi, cứ mỗi cặp thỏ lại sinh ra một cặp thỏ khác sau mỗi tháng. Hỏi người nông dân đó có bao nhiêu con thỏ sau n tháng ?

cuu duong than cong . co m

 n = 1: f1 = 1  n = 2: f2 = 1  n > 2: fn = fn-1 + fn-2

Công thức đúng cho n>2 vì mỗi cặp thỏ mới được sinh ra từ một cặp thỏ ít nhất 2 tháng tuổi

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ 4. Fibonacci

Hàm đệ quy F(n) : • Giá trị đầu : F(n=0) =0; F(n=1) = 1 • Công thức đệ quy: F(n) = F(n-1) + F(n-2)

Cài đặt dùng vòng lặp

Cài đặt đệ quy int F(int n)

int F(int n)

if n < 2 then

return n;

if n < 2 then return n; else {

else

F(n-2)

F(n-1)

return F(n-1) + F(n-2);

F(n)

x= 0; y= 1; for k = 1 to n-1 { z = x+y;

cuu duong than cong . co m

x = y; y = z;

}

NGUYỄN KHÁNH PHƯƠNG Bộ môn KHMT – ĐHBK HN

} return y; //y is F(n)

38

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Hàm đệ qui (Recursive Functions)

n

Ví dụ 5: Định nghĩa đệ qui của tổng

s

a k

n

 

k

1 

s1 = a1

sn = sn-1 + an

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ 6. Tập hợp được xác định đệ qui (Recursively defined sets)

Tập hợp có thể xác định một cách đệ qui theo sơ đồ tương tự như hàm đệ qui:

Basis Step: định nghĩa tập cơ sở (chẳng hạn tập rỗng).

Recursive Step : Xác định qui tắc để sản sinh tập mới từ các tập đã có.

Ví dụ: Basis Step: 3 là phần tử của S. Recursive Step: nếu x thuộc S và y thuộc S thì x+y thuộc S. 3  S= {3} 3+3=6  S = {3, 6} 3+6 = 9 & 6+6=12  S = {3, 6, 9, 12} ...

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ 7. Độ dài của xâu

• Định nghĩa đệ qui độ dài length(w) của xâu w  *

– Basic step: length(l) = 0

– Recursive step: Nếu w* và x thì length(wx)= length(w)+1.

• Định nghĩa đệ qui của tập các xâu nhị phân độ dài chẵn.

– Basic step: lS (l là xâu rỗng)

– Recursive step: Nếu b S thì 0b0, 0b1, 1b0, 1b1 S .

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ 8. Cây có gốc

Cây là cấu trúc dữ liệu quan trọng thường được dùng để tổ chức tìm kiếm và sắp xếp dữ liệu Cây có gốc (Rooted Trees): Cây có gốc bao gồm các nút, có một nút đặc biệt được gọi là gốc (root) và các cạnh nối các nút.

• Basic Step: Một nút là cây có gốc.

• Recursive Step: Giả sử T1,...,Tn,... là các cây với gốc là r1,....rn,...

Nếu ta tạo một gốc mới r và nối gốc này với mỗi một trong số các gốc r1,...rn,...

bởi một cạnh tương ứng, ta thu được một cây có gốc mới.

Step 2:

Basis step:

cuu duong than cong . co m

Step 1:

...

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Định nghĩa đệ qui và Qui nạp

Định nghĩa đệ qui và Qui nạp toán học có những nét tương đồng và là bổ sung cho nhau:

Định nghĩa đệ qui thường giúp cho chứng minh bằng qui nạp các tính chất của các đối tượng được định nghĩa đệ qui.

Ngược lại, các chứng minh bằng qui nạp toán học thường là cơ sở để xây dựng các thuật toán đệ qui để giải quyết nhiều bài toán.

Chứng minh bằng qui nạp toán học thường bao gồm hai phần: 1. 2.

Bước cơ sở qui nạp – giống như bước cơ sở trong định nghĩa đệ qui Bước chuyển qui nạp – giống như bước đệ qui

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

N i dung 1. Khái niệm đệ qui

2. Thuật toán đệ qui

3. Một số ví dụ minh hoạ

4. Phân tích thuật toán đệ qui

5. Đệ qui có nhớ

6. Thuật toán quay lui

cuu duong than cong . co m

44

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Định nghĩa

• Thuật toán đệ qui là thuật toán tự gọi đến chính mình với đầu vào kích

thước nhỏ hơn.

• Việc phát triển thuật toán đệ qui là thuận tiện khi cần xử lý với các đối tượng được định nghĩa đệ qui (chẳng hạn: tập hợp, hàm, cây, ...trong các ví dụ nêu trong mục trước).

• Các thuật toán được phát triển dựa trên phương pháp chia để trị thông

thường được mô tả dưới dạng các thuật toán đệ qui (xem ví dụ mở đầu)

• Các ngôn ngữ lập trình cấp cao thường cho phép xây dựng các hàm (thủ tục) đệ qui, nghĩa là trong thân của hàm (thủ tục) có chứa những lệnh gọi đến chính nó. Vì thế, khi cài đặt các thuật toán đệ qui người ta thường xây dựng các hàm (thủ tục) đệ qui.

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Cấu trúc của thuật toán đệ qui

Thuật toán RecAlg(input) {

if (kích thước của input là nhỏ nhất) then

Thực hiện Bước cơ sở; //giải bài toán kích thước đầu vào nhỏ nhất

else {

RecAlg(input với kích thước nhỏ hơn); // bước đệ qui // có thể có thêm những lệnh gọi đệ qui Tổng hợp lời giải của các bài toán con để thu được lời_giải; return (lời_giải) ;

}

} Thời gian tính:

T(n) = if (base case) then chi phí hằng số

else ( thời gian giải các bài toán con +

thời gian tổng hợp lời giải)

cuu duong than cong . co m

Kết quả thời gian tính phụ thuộc vào: – Số lượng bài toán con – Kích thước bài toán con – Thời gian tổng hợp lời giải các bài toán con

CuuDuongThanCong.com https://fb.com/tailieudientucntt

N i dung 1. Khái niệm đệ qui

2. Thuật toán đệ qui

3. Một số ví dụ minh hoạ

4. Phân tích thuật toán đệ qui

5. Đệ qui có nhớ

6. Thuật toán quay lui

cuu duong than cong . co m

47

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Cài đặt các thuật toán đệ qui

• Để cài đặt các thuật toán đệ qui trên các ngôn ngữ lập trình,

ta thường xây dựng các hàm (thủ tục) đệ qui.

• Trong mục này ta xét một số ví dụ minh hoạ cài đặt thuật

toán đệ qui:

– Hàm đệ qui tính n!

– Hàm đệ qui tính số Fibonacci

– Hàm đệ qui tính hệ số nhị thức

– Tìm kiếm nhị phân

– Bài toán tháp Hà nội

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ 1: Tính hệ số nhị thức

• Hệ số nhị thức C(n,k) được định nghĩa đệ qui như sau:

C(n,0) = 1, C(n,n) =1; với mọi n >=0,

C(n,k) = C(n-1,k-1)+C(n-1,k), 0 < k < n

• Cài đặt hàm đệ qui trên C: int C(int n, int k){

if ((k==0)||(k==n)) return 1; else return C(n-1,k-1)+C(n-1,k);

cuu duong than cong . co m

}

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ 2: Tìm kiếm nhị phân

Bài toán: Cho mảng số A[1..n] được sắp xếp theo thứ tự không giảm và số K. Cần tìm chỉ số i (1  i  n) sao cho A[i] = K.

• Để đơn giản ta giả thiết rằng chỉ số như vậy là tồn tại. Thuật toán để giải bài toán được xây dựng dựa trên lập luận sau: Số K cho trước

– hoặc là bằng phần tử nằm ở vị trí ở giữa mảng A

– hoặc là nằm ở nửa bên trái (L) của mảng A

– hoặc là nằm ở nửa bên phải (R) của mảng A.

(Tình huống L (R) xảy ra chỉ khi K nhỏ hơn (lớn hơn) phần tử ở giữa của mảng A)

cuu duong than cong . co m

• Phân tích trên dẫn đến thuật toán sau đây:

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ 2: Tìm kiếm nhị phân

int Bsearch(int low, int hight, int A[], int K) {

middle = (low + high)/2; if (A[middle] == K) return middle; else {

if (A[middle] > K)

return Bsearch(low, middle-1, A, K);

else

// A[middle] < K:

return Bsearch(middle+1, high, A, K)

}

}

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Tìm kiếm nhị phân (xét cả trường hợp không tìm thấy)

int Bsearch(int low, int hight, int A[], int K) {

middle = (low + high)/2; if (low == high) { // Base step if (A[middle]== K) return middle; else return -1; //notFound

} if (A[middle] ==K) return middle; else if (A[middle] > K)

return Bsearch(low, middle-1, A, K);

else // A[middle] < K:

return Bsearch(middle+1, high, A, K);

}

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ 3. Bài toán tháp Hà nội

Trò chơi Tháp Hà nội được trình bày như sau: “Có 3 cọc a, b, c. Trên cọc a có một chồng gồm n cái đĩa, đường kính giảm dần từ dưới lên trên. Cần phải chuyển chồng đĩa từ cọc a sang cọc c tuân thủ quy tắc:

1. Mỗi lần chỉ chuyển 1 đĩa

2. Chỉ được xếp đĩa có đường kính nhỏ hơn lên trên đĩa có đường kính lớn hơn.

Trong qu¸ trình chuyÓn ®ưîc phÐp dïng cäc b lµm cäc trung gian.

Bài toán đặt ra là: Tìm số lần di chuyển đĩa ít nhất cần thực hiện để hoàn thành nhiệm vụ đặt ra trong trò chơi Tháp Hà nội.

cuu duong than cong . co m

53

Cọc a

Cọc c

Cọc b

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Tower of Hanoi: n=5

1. Mỗi lần chỉ chuyển 1 đĩa

2. Chỉ được xếp đĩa có đường kính nhỏ hơn lên trên đĩa có đường kính lớn hơn

cuu duong than cong . co m

Cọc a

Cọc c

Cọc b

54

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Bài toán: chuyển n đĩa từ cọc a sang cọc c sử dụng cọc b làm trung gian

Cần tìm hn là sè lÇn di chuyÓn ®Üa Ýt nhÊt cÇn thùc hiÖn

hn = 2hn-1 + 1, n ≥ 2.

Việc di chuyển đĩa gồm 3 giai đoạn:

(1) ChuyÓn n-1 ®Üa tõ cäc a ®Õn cäc b sö dông cäc c lµm trung gian.

Bài toán kích thước n-1  Số lần di chuyển = hn-1

(2) ChuyÓn 1 ®Üa (®Üa víi ®ưêng kÝnh lín nhÊt) tõ cäc a ®Õn cäc c.

 Số lần di chuyển = 1 (3) ChuyÓn n-1 ®Üa tõ cäc b ®Õn cäc c (sö dông cäc a lµm trung gian).

Bài toán kích thước n-1  Số lần di chuyển = hn-1

cuu duong than cong . co m

Cọc a

Cọc c

Cọc b

55

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Tower of Hanoi: n=5

(1) ChuyÓn n-1 ®Üa tõ cäc a ®Õn cäc b sö dông cäc c lµm trung gian.

Bài toán kích thước n-1  Số lần di chuyển = hn-1

(2) ChuyÓn 1 ®Üa (®Üa víi ®ưêng kÝnh lín nhÊt) tõ cäc a ®Õn cäc c.

 Số lần di chuyển = 1 (3) ChuyÓn n-1 ®Üa tõ cäc b ®Õn cäc c (sö dông cäc a lµm trung gian).

Bài toán kích thước n-1  Số lần di chuyển = hn-1

cuu duong than cong . co m

Cọc a

Cọc c

Cọc b

56

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Thuật toán tháp Hà nội

Thuật toán có thể mô tả trong thủ tục đệ qui sau đây: //chuyển n đĩa từ cọc a sang cọc c sử dụng cọc b làm trung gian: HanoiTower(n, a, b, c); {

if (n==1) then else {

HanoiTower(n-1,a,c,b); HanoiTower(1,a,b,c); HanoiTower(n-1,b,a,c);

}

cuu duong than cong . co m

}

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Cài đặt

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ 4: Palindrome

• Định nghĩa. Ta gọi palindrome là xâu mà đọc nó từ trái qua phải cũng

giống như đọc nó từ phải qua trái.

Ví dụ: NOON, DEED, RADAR, MADAM

Able was I ere I saw Elba

• Để nhận biết một xâu cho trước có phải là palindrome hay không: ta tiến

hành – So sánh kí tự đầu và kí tự cuối của xâu.

• Nếu bằng nhau: xâu mới = xâu cũ bỏ đi kí tự đầu và kí tự cuối. Lặp

lại bước so sánh.

• Nếu khác nhau: xâu đã cho không phải là palindrome

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ 4: Palindrome: xau[start….end]

• Bước cơ sở (Base case) : xâu chỉ có 1 kí tự (start == end) return true if (xau[start] == xau[end])

• Bước đệ quy:

return true if (xau[start]==xau[end] &&

palindrome(xau, start+1, end-1))

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ 5: Đường đi trên lưới ô

• Cho lưới ô kích thước D*C. • Bạn chỉ được phép đi từ nút này sang nút kia trên lưới theo hướng hoặc

xuống dưới hoặc sang phải.

• Hỏi có bao nhiêu đường đi từ nút (i, j) đến nút (D, C).

– Hàm: int CountPaths(int i, int j, int D, int C)

(0,C)

(0,0)

(i, j)

cuu duong than cong . co m

(D,C)

(D,0)

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ 5: Đường đi trên lưới ô

(0,C)

(0,0)

(i, j)

(D,C)

(D,0) • Để giải bài toán CountPaths(i,j, D, C)cần giải những bài toán con

cùng dạng nào ? – Từ ô (i, j) chỉ có 2 cách đi:

CountPaths(i+1,j, D, C)

cuu duong than cong . co m

CountPaths(i,j+1, D, C)

• Đi xuống dưới: đến ô (i+1, j) • Đi sang phải: đến ô (i, j+1)

CountPaths(i,j, D, C) = CountPaths(i+1,j, D, C)

+ CountPaths(i,j+1, D, C)

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Bước cơ sở

• Nếu bạn bước quá cạnh của lưới (không còn thuộc lưới), sẽ không tồn tại đường đi đến ô

(D, C): – if (i > D) OR (j > C): CountPaths(i,j, D, C)trả về giá trị 0

DONE ?

NO: vì khi đó kết quả của mọi bài toán con đều =0

cuu duong than cong . co m

Cần xét thêm 1 trường hợp đặc biệt nữa: đó là khi bạn đang ở ô (D,C)  có 1 đường đi (đường đi này không cần bước bước nào)

63

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ 5: Đường đi trên lưới ô

• Cho lưới ô kích thước D*C. • Bạn chỉ được phép đi từ nút này sang nút kia trên lưới theo hướng hoặc

xuống dưới hoặc sang phải.

• Hỏi có bao nhiêu đường đi từ nút (i, j) đến nút (D, C).

– Hàm: int CountPaths(int i, int j, int D, int C)

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

N i dung 1. Khái niệm đệ qui

2. Thuật toán đệ qui

3. Một số ví dụ minh hoạ

4. Phân tích thuật toán đệ qui

5. Đệ qui có nhớ

6. Thuật toán quay lui

cuu duong than cong . co m

65

CuuDuongThanCong.com https://fb.com/tailieudientucntt

4. Phân tích thuật toán đệ qui

• Để phân tích thuật toán đệ qui ta thường tiến hành như sau:

– Gọi T(n) là thời gian tính của thuật toán

– Xây dựng công thức đệ qui cho T(n).

– Giải công thức đệ qui thu được để đưa ra đánh giá cho T(n)

• Nói chung ta chỉ cần một đánh giá sát cho tốc độ tăng của T(n) nên việc giải

công thức đệ qui đối với T(n) là đưa ra đánh giá tốc độ tăng của T(n) trong

ký hiệu tiệm cận

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ: Tìm kiếm nhị phân

int Bsearch(int low, int high, int A[],int K) {

mid = (low+high)/2; if (A[m] == K) return mid; else if (A[mid] > K)

return Bsearch(low,mid-1, A, K);

else

//A[mid] < K

return Bsearch(mid+1,high, A, K);

}

• Gọi T(n) là thời gian tính của việc thực hiện Bsearch(1, n, A, K), ta có

cuu duong than cong . co m

T(1) = c T(n) = T(n/2) + d trong đó c và d là các hằng số.

Giải công thức đệ quy trên, ta có T(n) = (log n).

CuuDuongThanCong.com https://fb.com/tailieudientucntt

N i dung 1. Khái niệm đệ qui

2. Thuật toán đệ qui

3. Một số ví dụ minh hoạ

4. Phân tích thuật toán đệ qui

5. Đệ qui có nhớ

6. Thuật toán quay lui

cuu duong than cong . co m

68

CuuDuongThanCong.com https://fb.com/tailieudientucntt

5. Đệ qui có nhớ

• Trong phần trước ta đã thấy các thuật toán đệ qui để tính số

Fibonacci và tính hệ số nhị thức là kém hiệu quả.

• Để tăng hiệu quả của các thuật toán đệ qui mà không cần tiến hành xây dựng các thủ tục lặp hay khử đệ qui, ta có thể sử dụng kỹ thuật đệ qui có nhớ.

• Sử dụng kỹ thuật này,

trong nhiều trường hợp,

ta giữ nguyên được cấu trúc đệ qui của thuật toán và đồng thời lại đảm bảo được hiệu quả của nó. Nhược điểm lớn nhất của cách làm này là đòi hỏi về bộ nhớ.

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Bài toán con trùng lặp

• Nhận thấy là trong các thuật toán đệ qui là mỗi khi cần đến lời giải của một bài toán con ta lại phải trị nó một cách đệ qui. Do đó, có những bài toán con bị giải đi giải lại nhiều lần. Điều đó dẫn đến tính kém hiệu quả của thuật toán. Hiện tượng này gọi là hiện tượng bài toán con trùng lặp.

Ví dụ: Thuật toán tính hệ số nhị thức C(5,3). Cây đệ qui thực hiện lệnh gọi hàm C(5,3) được chỉ ra trong hình sau đây

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ: Bài toán con trùng lặp trong việc tính C(5,3)

C(5,3)

C(4,2)

C(4,3)

C(3,2)

C(3,1)

C(3,2)

C(3,3)

C(2,1)

C(2,2)

C(2,1)

C(2,0)

C(2,1)

C(2,2)

cuu duong than cong . co m

C(1,0)

C(1,1)

C(1,0)

C(1,1)

C(1,0)

C(1,1)

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ: Bài toán con trùng lặp khi tính Fibonaci F(4)

int F (int n) {

if n <2 then return n; else return F (n-1)+F(n-2);

}

F (4)

F (2)

F (3)

F(0)

F (1)

F (2)

F (1)

cuu duong than cong . co m

F (0)

F (1)

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Đệ qui có nhớ

• Để khắc phục hiện tượng này, ý tưởng của đệ qui có nhớ là: Ta sẽ dùng biến ghi nhớ lại thông tin về lời giải của các bài toán con ngay sau lần đầu tiên nó được giải. Điều đó cho phép rút ngắn thời gian tính của thuật toán, bởi vì, mỗi khi cần đến có thể tra cứu mà không phải giải lại những bài toán con đã được giải trước đó.

Ví dụ: Thuật toán đệ qui tính hệ số nhị thức, ta đưa vào biến • D[n][k] để ghi nhận những giá trị đã tính. • Đầu tiên D[n][k]=0, mỗi khi tính được C(n, k) giá trị này sẽ được ghi nhận vào D[n][k]. Như vậy, nếu D[n][k]>0 thì điều đó có nghĩa là không cần gọi đệ qui hàm C(n, k)

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ: Hàm tính C(n,k) có nhớ

int C(int n,int k){

if (D[n][k]>0) return D[n][k]; else{

D[n][k] = C(n-1,k-1)+C(n-1,k); return D[n][k];

}

}

Trước khi gọi hàm C(n, k) cần khởi tạo mảng D[ ][ ] như sau: • •

D[i][0] = 1, D[i][i]=1, với i = 0,1,..., n; D[i][j] = 0, với những i, j còn lại.

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

N i dung 1. Khái niệm đệ qui

2. Thuật toán đệ qui

3. Một số ví dụ minh hoạ

4. Phân tích thuật toán đệ qui

5. Đệ qui có nhớ

6. Thuật toán quay lui

cuu duong than cong . co m

75

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Sơ đồ thuật toán quay lui

• Bài toán liệt kê (Q): Cho A1, A2,..., An là các tập hữu hạn. Ký hiệu A = A1 A2  ... An = { (a1, a2, ..., an): ai  Ai , i=1, 2, ..., n}. Giả sử P là tính chất cho trên A. Vấn đề đặt ra là liệt kê tất cả các phần tử của A thoả mãn tính chất P:

D = { a = (a1, a2, ..., an)  A: a thoả mãn tính chất P }. • Các phần tử của tập D được gọi là các lời giải chấp nhận được.

cuu duong than cong . co m

NGUYỄN KHÁNH PHƯƠNG Bộ môn KHMT – ĐHBK HN

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Sơ đồ thuật toán quay lui

Tất cả các bài toán liệt kê tổ hợp cơ bản đều có thể phát biểu dưới dạng bài toán liệt kê (Q).

Ví dụ:

• Bài toán liệt kê xâu nhị phân độ dài n dẫn về việc liệt kê các phần tử

của tập

Bn = {(a1, ..., an): ai  {0, 1}, i=1, 2, ..., n}.

• Bài toán liệt kê các tập con m phần tử của tập N = {1, 2, ..., n} đòi hỏi

liệt kê các phần tử của tập:

S(m,n) = {(a1,..., am)Nm: 1 ≤ a1 < ... < am ≤ n }.

cuu duong than cong . co m

• TËp c¸c ho¸n vÞ cña c¸c sè tù nhiªn 1, 2, ..., n lµ tËp

n = {(a1,..., an)  Nn: ai ≠ aj ; i ≠ j }.

NGUYỄN KHÁNH PHƯƠNG Bộ môn KHMT – ĐHBK HN

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Lời giải bộ phận

Lời giải của bài toán là bộ có thứ tự gồm n thành phần (a1, a2, ..., an), trong đó ai  Ai , i = 1, 2, ..., n. Định nghĩa. Ta gọi lời giải bộ phận cấp k (0≤k≤ n) là bộ có thứ tự gồm k thành phần

(a1, a2, ..., ak), trong đó ai  Ai , i = 1, 2, ..., k. • Khi k = 0, lời giải bộ phận cấp 0 được ký hiệu là ( ) và còn

được gọi là lời giải rỗng.

cuu duong than cong . co m

• Nếu k = n, ta có lời giải đầy đủ hay đơn giản là một lời giải

của bài toán.

NGUYỄN KHÁNH PHƯƠNG Bộ môn KHMT – ĐHBK HN

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Sơ đồ thuật toán quay lui

Thuật toán quay lui được xây dựng dựa trên việc xây dựng dần từng thành phần của lời giải. • Thuật toán bắt đầu từ lời giải rỗng ( ). • Trên cơ sở tính chất P ta xác định được những phần tử nào của tập A1 có thể chọn vào vị trí thứ nhất của lời giải. Những phần tử như vậy ta sẽ gọi là những ứng cử viên (viết tắt là UCV) vào vị trí thứ nhất của lời giải. Ký hiệu tập các UCV vào vị trí thứ nhất của lời giải là S1. Lấy a1  S1, bổ sung nó vào lời giải rỗng đang có ta thu được lời giải bộ phận cấp 1: (a1).

cuu duong than cong . co m

NGUYỄN KHÁNH PHƯƠNG Bộ môn KHMT – ĐHBK HN

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Sơ đồ thuật toán quay lui

• Bước tổng quát: Giả sử ta đang có lời giải bộ phận cấp k-1:

(a1, a2, ..., ak-1),

cần xây dựng lời giải bộ phận cấp k:

(a1, a2, ..., ak-1, ak)

– Trên cơ sở tính chất P, ta xác định được những phần tử nào của tập Ak có

thể chọn vào vị trí thứ k của lời giải.

– Những phần tử như vậy ta sẽ gọi là những ứng cử viên (viết tắt là UCV) vào vị trí thứ k của lời giải khi k-1 thành phần đầu của nó đã được chọn là (a1, a2, ..., ak-1). Ký hiệu tập các ứng cử viên này là Sk.

cuu duong than cong . co m

– Xét 2 tình huống:

• Sk ≠ 

NGUYỄN KHÁNH PHƯƠNG Bộ môn KHMT – ĐHBK HN

• Sk = 

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Sơ đồ thuật toán quay lui

• Sk ≠ : Lấy ak  Sk bổ sung nó vào lời giải bộ phận cấp k-1 đang có (a1, a2, ...,

ak-1) ta thu được lời giải bộ phận cấp k (a1, a2, ..., ak-1, ak). Khi đó – Nếu k = n thì ta thu được một lời giải, – Nếu k < n, ta tiếp tục đi xây dựng thành phần thứ k+1 của lời giải.

• Sk=: Điều đó có nghĩa là lời giải bộ phận (a1, a2, ..., ak-1) không thể tiếp tục phát triển thành lời giải đầy đủ. Trong tình huống này ta quay trở lại tìm ứng cử viên mới vào vị trí thứ k-1 của lời giải (chú ý: ứng cử viên mới này nằm trong tập Sk-1) – Nếu tìm thấy UCV như vậy, thì bổ sung nó vào vị trí thứ k-1 rồi lại tiếp tục

đi xây dựng thành phần thứ k.

cuu duong than cong . co m

– Nếu không tìm được thì ta lại quay trở lại thêm một bước nữa tìm UCV mới vào vị trí thứ k-2, ... Nếu quay lại tận lời giải rỗng mà vẫn không tìm được UCV mới vào vị trí thứ 1, thì thuật toán kết thúc.

NGUYỄN KHÁNH PHƯƠNG Bộ môn KHMT – ĐHBK HN

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Cây liệt kê lời giải theo thuật toán quay lui

Gốc (lời giải rỗng)

Tập UCV S1

a1

(a1)

Tập UCV S2 khi đã có (a1)

a2

(a1, a2)

Tập UCV S3 khi đã có (a1, a2)

(a1, a2, a3)

a3

cuu duong than cong . co m

Tập UCV S4 khi đã có (a1, a2 , a3)

a4

a’4

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Thuật toán quay lui (đệ qui)

Thuật toán quay lui (không đệ qui)

void Bactrack(int k) {

void Bactraking ( ) {

trí thứ k của lời giải>;

k=1; ; while (k > 0) {

for y  Sk //Với mỗi UCV y từ Sk {

while (Sk   ) {

ak = y; if (k == n) ; else Backtrack(k+1);

ak  Sk; // Lấy ak từ Sk if <(k == n) > then ; else {

}

}

k = k+1; ;

}

Lệnh gọi để thực hiện thuật toán quay lui là: Bactrack(1);

} k = k - 1; // Quay lui

}

}

cuu duong than cong . co m

• Nếu chỉ cần tìm một lời giải thì cần tìm cách chấm dứt các thủ tục gọi đệ qui lồng nhau sinh bởi lệnh gọi Backtrack(1) sau khi ghi nhận được lời giải đầu tiên.

Lệnh gọi để thực hiện thuật toán quay lui là: Bactraking ( );

NGUYỄN KHÁNH PHƯƠNG Bộ môn KHMT – ĐHBK HN

• Nếu kết thúc thuật toán mà ta không thu được một lời giải nào thì điều đó có nghĩa là bài toán không có lời giải.

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Hai vấn đề mấu chốt

• Để cài đặt thuật toán quay lui giải các bài toán tổ hợp cụ thể

ta cần giải quyết hai vấn đề cơ bản sau:

– Tìm thuật toán xây dựng các tập UCV Sk – Tìm cách mô tả các tập này để có thể cài đặt thao tác liệt kê các phần tử của chúng (cài đặt vòng lặp qui ước for y  Sk ).

• Hiệu quả của thuật toán liệt kê phụ thuộc vào việc ta có xác

định được chính xác các tập UCV này hay không.

cuu duong than cong . co m

NGUYỄN KHÁNH PHƯƠNG Bộ môn KHMT – ĐHBK HN

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Chú ý

• Thuật toán dễ dàng mở rộng cho bài toán liệt kê trong đó lời giải có thể mô tả như là bộ (a1, a2, ..., an,...) độ dài hữu hạn, tuy nhiên giá trị của độ dài là không biết trước và các lời giải cũng không nhất thiết phải có cùng độ dài. Khi đó chỉ cần sửa lại câu lệnh if (k == n) ; else Backtrack(k+1);

thành

if <(a1, a2, ..., ak) là lời giải> ; else Backtrack(k+1);

 Cần xây dựng hàm nhận biết (a1, a2, ..., ak) đã là lời giải hay chưa.

cuu duong than cong . co m

NGUYỄN KHÁNH PHƯƠNG Bộ môn KHMT – ĐHBK HN

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ 1: LiÖt kª x©u nhÞ ph©n ®é dµi n

• Bài toán liệt kê xâu nhị phân độ dài n dẫn về việc liệt kê các phần tử

của tập

Bn = {(b1, ..., bn): bi  {0, 1}, i=1, 2, ..., n}.

• Ta xét cách giải quyết hai vấn đề cơ bản để cài đặt thuật toán quay lui: – Xây dựng tập ứng cử viên Sk: Rõ ràng ta có S1 = {0, 1}. Giả sử đã có xâu nhị phân cấp k-1 (b1, ..., bk-1), khi đó rõ ràng Sk = {0,1}. Như vậy, tập các UCV vào các vị trí của lời giải đã được xác định. – Cài đặt vòng lặp liệt kê các phần tử của Sk: ta có thể sử dụng vòng

lặp for

for (y=0;y<=1;y++)

cuu duong than cong . co m

NGUYỄN KHÁNH PHƯƠNG Bộ môn KHMT – ĐHBK HN

CuuDuongThanCong.com https://fb.com/tailieudientucntt

C©y liÖt kª d·y nhÞ ph©n ®é dµi 3

Sk = {0,1}

( )

0

1

(0)

(1) 1

0

0

(00)

(10)

(11) 1

1

0

0

1 (01) 0

1

0

1

cuu duong than cong . co m

(000)

(001) (010)

(011) (100)

(111)

(101) (110)

NGUYỄN KHÁNH PHƯƠNG Bộ môn KHMT – ĐHBK HN

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Chương trình trên C++ (Đệ qui)

void Xau(int i){

int j; for (j = 0; j<=1; j++) {

#include using namespace std; int n, count; int b[100];

void Ghinhan() {

b[i] = j; if (i==n) Ghinhan(); else Xau(i+1);

}

}

int main() {

int i, j; count++; cout<<"Xau thu " << count<<": "; for (i=1 ; i<= n ;i++) {

j=b[i]; cout<

cout<<"Nhap n = ";cin>>n; count = 0; Xau(1); cout<<"So luong xau = "<

}

} cout<

cuu duong than cong . co m

}

NGUYỄN KHÁNH PHƯƠNG Bộ môn KHMT – ĐHBK HN

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Chương trình trên C++ (không đệ qui)

void Xau( ) {

k=1; s[k]=0; while (k > 0) {

#include using namespace std; int n, count,k; int b[100], s[100];

void Ghinhan() {

while (s[k] <= 1) { b[k]=s[k]; s[k]=s[k]+1; if (k==n) Ghinhan(); else {

k++; s[k]=0;

int i, j; count++; cout<<"Xau thu " << count<<": "; for (i=1 ; i<= n ;i++) {

}

j=b[i]; cout<

} k--;

// Quay lui

}

cuu duong than cong . co m

} cout<

}

}

NGUYỄN KHÁNH PHƯƠNG Bộ môn KHMT – ĐHBK HN

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Chương trình trên C++ (không đệ qui)

int main() {

cout<<"Nhap n = ";cin>>n; count = 0; Xau(); cout<<"So luong xau = "<

}

cuu duong than cong . co m

NGUYỄN KHÁNH PHƯƠNG Bộ môn KHMT – ĐHBK HN

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ 2. Liệt kê các m-tập con của n-tập

Bài toán: Liệt kê các tập con m phần tử của tập N = {1, 2, ..., n}.

Ví dụ: Liệt kê các tập con 3 phần tử của tập N = {1, 2, 3, 4, 5} Giải: (1, 2, 3), (1, 2, 4), (1, 2, 5), (1, 3, 4), (1, 3, 5), (1, 4, 5), (2, 3, 4), (2, 3, 5), (2, 4, 5), (3, 4, 5)

 Bài toán dẫn về: Liệt kê các phần tử của tập: S(m,n)={(a1,..., am)Nm: 1 ≤ a1<...

cuu duong than cong . co m

NGUYỄN KHÁNH PHƯƠNG Bộ môn KHMT – ĐHBK HN

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ 2. Liệt kê các m-tập con của n-tập

Ta xét cách giải quyết 2 vấn đề cơ bản để cài đặt thuật toán quay lui: • Xây dựng tập ứng cử viên Sk:

S1 = {1, 2, ..., n-(m-1) }.

– Từ điều kiện: 1  a1 < a2 < ... < am  n suy ra – Giả sử có tập con (a1, ..., ak-1). Từ điều kiện ak-1 < ak < . . . < am

≤ n, ta suy ra: Sk = {ak-1+1, ak-1+2, ..., n-(m-k)}. • Cài đặt vòng lặp liệt kê các phần tử của Sk: for (y=a[k-1]+1;y<=n-m+k;y++) ...

cuu duong than cong . co m

NGUYỄN KHÁNH PHƯƠNG Bộ môn KHMT – ĐHBK HN

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Chương trình trên C++ (Đệ qui)

void MSet(int i){

#include using namespace std;

int j; for (j = a[i-1] +1; j<= n-m+i; j++) {

a[i] = j; if (i==m) Ghinhan(); else MSet(i+1);

int n, m, count; int a[100]; void Ghinhan() {

}

} int main() {

int i; count++; cout<<"Tap con thu " <

cout<<"Nhap n, m = "; cin>>n; cin>>m; a[0]=0; count = 0; MSet(1); cout<<"So tap con "<

cout<

cuu duong than cong . co m

}

cout<<"Nhap n, m = "; cin>>n; cin>>m; a[0]=0; count = 0; MSet(); cout<<"So tap con "<

cout<

}

NGUYỄN KHÁNH PHƯƠNG Bộ môn KHMT – ĐHBK HN

CuuDuongThanCong.com https://fb.com/tailieudientucntt

int main() {

void Hoanvi(int i) {

int j; for (j = 1; j<=n; j++)

cout<<("Nhap n = "); cin>>n; count = 0; Hoanvi(1); cout<<"So hoan vi = " << count;

}

if (UCV(j,i)) { a[i] = j;

if (i==n) Ghinhan( ); else Hoanvi(i+1);

cuu duong than cong . co m

}

}

NGUYỄN KHÁNH PHƯƠNG Bộ môn KHMT – ĐHBK HN

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Cây liệt kê hoán vị của 1, 2, 3

( )

Hoanvi(1);

S1 = ?

3

1

2

(1)

(3)

(2)

S2 = ?

3

2

1

3

1

2

(2,1)

(3,1)

(1,2)

(2,3)

(3,2)

3

1

2

(1,3) S3 = ? 2

3

1

cuu duong than cong . co m

(1,2,3)

(1,3,2)

(2,1,3)

(2,3,1)

(3,1,2)

(3,2,1)

Sk = N \ { a1, a2, ..., ak-1}

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Ví dụ 4. Bài toán xếp hậu

• Liệt kê tất cả các cách xếp n quân Hậu trên bàn cờ nn sao cho chúng không ăn được lẫn nhau, nghĩa là sao cho không có hai con nào trong số chúng nằm trên cùng một dòng hay một cột hay một đường chéo của bàn cờ.

n cột

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

The n-Queens Problem

Hai con hậu bất kỳ không được xếp trên cùng một dòng ...

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

The n-Queens Problem

Hai con hậu bất kỳ không được xếp trên cùng một cột ...

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

The n-Queens Problem

Hai con hậu bất kỳ không được xếp trên cùng một dòng, một cột hay một đường chéo!

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Biểu diễn lời giải

• Đánh số các cột và dòng của bàn cờ từ 1 đến n. • Một cách xếp hậu có thể biểu diễn bởi bộ có n thành phần (a1, a2 ,..., an), trong đó ai là toạ độ cột của con hậu ở dòng i.

• Các điều kiện đặt ra đối với bộ (a1, a2 ,..., an):

– ai  aj , với mọi i  j (nghĩa là hai con hậu ở hai dòng i và j không

được nằm trên cùng một cột);

– | ai – aj |  | i – j |, với mọi i  j (nghĩa là hai con hậu ở hai ô (i, ai)

và (j, aj) không được nằm trên cùng một đường chéo).

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Phát biểu bài toán

• Như vậy bài toán xếp Hậu dẫn về bài toán liệt kê các phần

tử của tập:

D={(a1, a2, ..., an)Nn: ai ≠ aj và |ai – aj| ≠ |i – j|, i ≠ j }

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Bài toán xếp hậu: Duyệt toàn bộ

• Cần xếp N quân hậu lên bàn cờ có tất cả NxN ô, hỏi có tất cả bao nhiêu cách

xếp ?

– Quân thứ 1 có thể đặt vào 1 trong số N*N ô  có N2 cách

– Sau khi đặt quân thứ 1, tiến hành đặt quân thứ 2: bỏ qua ô đã đặt quân

thứ 1  chỉ còn N2 – 1 ô có thể đặt quân thứ 2 có N2 – 1 cách

– …

==> tổng cộng có tất cả (N2)! cách đặt. Với mỗi cách ta kiểm tra xem có thỏa mãn điều kiện không có quân nào cùng dòng/cột/đường chéo; nếu thỏa mãn thì thu được lời giải tương ứng.

• Có một cách khác: giảm không gian xét duyệt từ (N2)! xuống còn N!

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Bài toán xếp hậu: Duyệt toàn bộ

• Có một cách khác: giảm không gian xét duyệt từ (N2)! xuống còn N!

– Mỗi cách xếp N quân hậu lên bàn cờ sao cho không có quân nào cùng dòng, cùng cột ~

một hoán vị N phần tử

Mảng a gồm 4 phần tử a[1] =3; a[2] = 1; a[2] = 4; a[4] = 2 Dòng 1: xếp quân hậu ở cột 3 Dòng 2: xếp quân hậu ở cột 1 Dòng 3: xếp quân hậu ở cột 4 Dòng 4: xếp quân hậu ở cột 2

Hoán vị: (3, 1, 4, 2)

– Chú ý: không phải hoán vị nào cũng là lời giải của bài toán xếp hậu:

Thuật toán: Liệt kê tất cả N! hoán vị; kiểm tra điều kiện không cùng đường chéo tại mỗi hoán vị. Nếu hoán vị nào thỏa mãn điều kiện  cho ta 1 lời giải

cuu duong than cong . co m

? Làm thế nào để kiểm tra được điều kiện Không cùng đường chéo

Hoán vị: (3, 1, 4, 2) => lời giải

Hoán vị: (3, 2, 1, 4) => Không phải là lời giải

Vì còn phải kiểm tra điều kiện: không có quân hậu nào cùng đường chéo

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Bài toán xếp hậu: Duyệt toàn bộ

• Có một cách khác: giảm không gian xét duyệt từ (N2)! xuống còn N!

– Kiểm tra hai quân hậu đặt ở ô (i1, j1) và ô (i2, j2) không thuộc cùng một đường chéo

8

7

|i1-i2| ≠ |j1-j2|

6

5

4

3

2

cuu duong than cong . co m

1

1

2

3

4

5

6

7

8

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Bài toán xếp hậu: Thuật toán quay lui

Thuật toán: Xếp từng quân hậu lên lần lượt từng dòng của bàn cờ: tại bước lặp thứ k (k=1,..,n): ta cần tìm tọa độ cột ak để đặt quân cờ lên dòng k [tức là đặt lên ô (dòng k, cột ak)]

• Giả sử đã xây dựng được lời giải bộ phận (a1, a2, …, ak-1): tức là đã xếp được

(k-1) quân hậu lần lượt vào các ô (1, a1), (2, a2), …(k-1, ak-1) thỏa mãn điều kiện đề bài.

• Giờ tiếp tục xây dựng thành phần thứ k của lời giải: tức là cần tìm tọa độ cột để

xếp quân hậu lên dòng thứ k của bàn cờ. Ta tiến hành như sau:

– Duyệt lần lượt từng cột j =1,2,..,n:

• Kiểm tra xem có thể xếp quân hậu lên ô (k, j) - dòng k cột j hay không:

bằng cách dùng hàm nhận biết ứng cử viên

cuu duong than cong . co m

UCVh(int j, int k) trả về giá trị 1 nếu xếp được; ngược lại hàm trả về giá trị 0

 UCVh nhận giá trị 1 khi và chỉ khi jSk

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Thuật toán quay lui: Hàm nhận biết ứng cử viên

int UCVh(int j, int k) //check xếp quân hậu vào ô (k, j)

{

// UCVh nhận giá trị 1 khi và chỉ khi j Sk for (i=1; i

if ((j == a[i]) || (fabs(j-a[i])== k-i)) return 0;

return 1;

}

void Hau(int k) //tìm a[k]: cột xếp quân hậu thứ k lên dòng k

{

for (int j=1; j<=n; j++) //duyệt qua lần lượt từ cột 1..n: xem có xếp được lên ô (k, j) không

if (UCVh(j,k)) //nếu xếp được quân hậu lên ô (k, j)

{

cuu duong than cong . co m

a[k]=j;

if (k==n) Ghinhan(); //nếu đã xếp được đủ cả n quân hậu thì in ra lời giải

else Hau(k+1); //nếu không thì tiếp tục tìm vị trí xếp quân hậu thứ k+1 lên dòng thứ k+1

}

CuuDuongThanCong.com https://fb.com/tailieudientucntt

cuu duong than cong . co m

114

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Chú ý

• Rõ ràng là bài toán xếp hậu không phải là luôn có lời giải, chẳng hạn bài toán không có lời giải khi n = 2, 3. Do đó điều này cần được thông báo khi kết thúc thuật toán.

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Thuật toán làm việc như thế nào

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Thuật toán làm việc như thế nào

ROW 1, COL 1

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Thuật toán làm việc như thế nào

Xếp con hậu ở dòng 1 vào vị trí cột 1

ROW 1, COL 1

cuu duong than cong . co m

đã đặt

1

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Thuật toán làm việc như thế nào

Thử xếp con hậu ở dòng 2 vào vị trí cột 1

ROW 2, COL 1

ROW 1, COL 1

cuu duong than cong . co m

đã đặt

1

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Thuật toán làm việc như thế nào

Thử xếp con hậu ở dòng 2 vào vị trí cột 2

ROW 2, COL 2

ROW 1, COL 1

cuu duong than cong . co m

đã đặt

1

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Thuật toán làm việc như thế nào

Thử xếp con hậu ở dòng 2 vào vị trí cột 3

ROW 2, COL 3

ROW 1, COL 1

cuu duong than cong . co m

đã đặt

1

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Thuật toán làm việc như thế nào

Chấp nhận xếp con hậu ở dòng 2 vào vị trí cột 3

ROW 2, COL 3

ROW 1, COL 1

cuu duong than cong . co m

đã đặt

2

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Thuật toán làm việc như thế nào

Thử xếp con hậu ở dòng 3 vào cột đầu tiên

ROW 3, COL 1

ROW 2, COL 3

ROW 1, COL 1

cuu duong than cong . co m

đã đặt

2

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Thuật toán làm việc như thế nào

Thử cột tiếp theo

ROW 3, COL 2

ROW 2, COL 3

ROW 1, COL 1

cuu duong than cong . co m

đã đặt

2

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Thuật toán làm việc như thế nào

Thử cột tiếp theo

ROW 3, COL 3

ROW 2, COL 3

ROW 1, COL 1

cuu duong than cong . co m

đã đặt

2

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Thuật toán làm việc như thế nào

Thử cột tiếp theo

ROW 3, COL 4

ROW 2, COL 3

ROW 1, COL 1

cuu duong than cong . co m

đã đặt

2

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Thuật toán làm việc như thế nào

...không có vị trí đặt con hậu ở dòng 3.

ROW 3, COL 4

ROW 2, COL 3

ROW 1, COL 1

cuu duong than cong . co m

đã đặt

2

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Thuật toán làm việc như thế nào

Quay lại dịch chuyển con hậu ở dòng 2

ROW 2, COL 3

ROW 1, COL 1

cuu duong than cong . co m

đã đặt

1

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Thuật toán làm việc như thế nào

Đẩy con hậu ở dòng 2 sang cột thứ 4.

ROW 2, COL 4

ROW 1, COL 1

cuu duong than cong . co m

đã đặt

1

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Thuật toán làm việc như thế nào

Xếp được con hậu ở dòng 2 ta tiếp tục xếp con hậu ở dòng 3 . . .

ROW 2, COL 4

ROW 1, COL 1

cuu duong than cong . co m

đã đặt

2

CuuDuongThanCong.com https://fb.com/tailieudientucntt

Mét lêi gi¶i cña bµi to¸n xÕp hËu khi n = 8

cuu duong than cong . co m

CuuDuongThanCong.com https://fb.com/tailieudientucntt