1. Trang chủ
  2. » Tất cả

Bài giảng Thiết kế và đánh giá thuật toán: Phần 2 - ĐH Sư Phạm Kỹ Thuật Nam Định

110 5 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 110
Dung lượng 868,16 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

- Mảng a để lưu trữ số có 6 chữ số khác nhau tìm được ở đây ta chấp nhận cả số mà chữ số đầu tiên bằng 0, khi hiển thị ta không quan tâm tới những số này - Mảng b mỗi phần tử dùng để ghi

Trang 1

Chương 4

KỸ THUẬT QUAY LUI

4.1 Nội dung kỹ thuật

Nột đặc trưng của kỹ thuật quay lui là cỏc bước hướng tới lời giải hoàn toàn được làm thử Tại mỗi bước, nếu cú một lựa chọn được chấp nhận thỡ ghi nhận lựa chọn này và tiến hành cỏc bước thử tiếp theo Ngược lại nếu khụng cú lựa chọn nào thớch hợp thỡ làm lại bước trước, xoỏ bỏ sự ghi nhận và quay về chu trỡnh thử cỏc lựa chọn cũn lại

Hành động này đ-ợc gọi là quay lui, thuật toỏn sử dụng kỹ thuật này là thuật toỏn quay lui

Lời giải của bài toỏn thường được biểu diễn bằng một bộ gồm n thành phần x

= (x1, , xn ) phải thoả món cỏc điều kiện nào đú Để chỉ ra lời giải x, ta phải xõy dựng dần cỏc thành phần xi

Nhu vậy nội dung chớnh của kỹ thuật này là việc xõy dựng dần cỏc thành phần xi bằng cỏch thử tất cỏc khả năng Giả sử đó xỏc định được i -1 thành phần x1,

x2,…, xi-1 (mà ta sẽ gọi là lời giải bộ phận cấp i- 1), bõy giờ ta xỏc định thành phần

xi bằng cỏch duyệt tất cả cỏc khả năng cú thể đề cử cho nú (đỏnh số cỏc khả năng từ

1 đến ni ) Với mỗi khả năng j, kiểm tra xem j cú chấp nhận được khụng Xảy ra hai trường hợp:

- Nếu chấp nhận j thỡ xỏc định xi theo j Sau đú nếu i = n thỡ ta được một cấu hỡnh, cũn trỏi lại ta tiến hành xỏc định xi+1

- Nếu thử tất cả cỏc khả năng mà khụng cú khả năng nào chấp nhận được thỡ quay lại bước trước để xỏc định lại xi-1

Điểm quan trọng của thuật toỏn là phải ghi nhớ tại mỗi bước đó đi qua những khả năng nào đó thử để trỏnh trựng lặp Rừ ràng những thụng tin này cần được lưu

trữ theo cơ cấu ngăn xếp (Stack- Vào sau ra trước) Vỡ thế kỹ thuật này rất phự hợp

với việc lập trỡnh trờn một ngụn ngữ cho phộp gọi đệ qui Bước xỏc định xi cú thể diễn tả qua hàm được tổ chức đệ qui dưới đõy:

Trang 2

<Xác định xi theo j>

if (i == n) <ghi nhận một cấu hình>;

else try(i+1);

} }

Phần quan trọng nhất trong hàm trên là việc đưa ra một danh sách các khả

năng đề cử và việc xác định giá trị biểu thức <Chấp nhận j> thông thường giá trị

này, ngoài việc phụ thuộc j, còn phụ thuộc vào việc đã chọn các khả năng tại i -1

bước trước Trong những trường hợp như vậy, cần ghi nhớ trạng thái mới của quá

trình tìm kiếm sau khi <xác định xi theo j> và trả lại trạng thái cũ sau lời gọi

Try(i+1) Các trạng thái này được ghi nhận nhờ một số biến tổng thể, gọi là biến trạng thái

Sau khi xây dựng thủ tục đệ qui Try, đoạn chương trình chính giải bài toán liệt kê có dạng:

Kỹ thuật quay lui thường được áp dụng cho các bài toán liệt kê tổ hợp

4.2 Các ví dụ áp dụng

4.2.1 Đƣa ra các dãy nhị phân độ dài n

1) Bài toán

Cho một số nguyên dương n Hãy đưa ra các dãy nhị phân độ dài n

2) Thiết kế thuật toán

Biểu diễn dãy nhị phân dưới dạng (b1, b2,…, bn), trong đó bi{0,1}

Trang 3

Mỗi thành phần bi có các giá trị đề cử là 0 và 1 Các giá trị này mặc nhiên được chấp nhận mà không phải thoả mãn điều kiện gì

Mảng b lưu trữ dãy nhị phân xây dựng được

Hàm Try(i) xác định thành phần thứ i và hàm Result() đưa ra dãy tìm được

Trang 4

Ta nhận thấy rằng mỗi một hoán vị của tập X tương đương với một hoán vị của tập chỉ số {1, 2, , n}

Biểu diễn hoán vị tập chỉ số dưới dạng a1, a2, …, an trong đó ai nhận giá trị từ

1 đến n và ai aj với i  j Các giá trị từ 1 đến n được lần lượt đề cử cho ai, trong đó giá trị j được chấp nhận nếu nó chưa được dùng Vì thế cần phải ghi nhớ đối với mỗi giá trị j xem nó đã được dùng hay chưa Điều này được thực hiện nhờ một dãy biến bj, trong đó bj bằng 1 nếu j chưa được dùng Sau khi xác định ai theo j cần gán giá trị 0 cho bj Khi thực hiện xong Result hay Try(i+1) cần phải gán lại giá trị 1 cho bj

Tổ chức dữ liệu:

- Mảng x: dãy số nguyên đã cho

- Mảng a: lưu trữ một hoán vị của tập chỉ số {1, 2, , n}

- Mảng b: lưu trữ trạng thái các phần tử của x với b[j]=1 thì x[j] đã được dùng, b[j]=0 thì x[j] chưa được dùng

Các hàm

- Hàm init(): nhập n và khởi tạo

- Hàm result(): đưa ra hoán vị vừa tìm được

- Hàm Try(i): xác định thành phần thứ i của hoán vị

Trang 5

2) Thiết kế thuật toán

Mỗi tập con của X gồm m phần tử tương đương với một tập con gồm m phần

tử của tập chỉ số {1, 2, , n}

Mỗi tập con gồm m phần tử của tập chỉ số {1, 2, , n} có thể biểu diễn bởi

bộ có thứ tự gồm m thành phần a = (a1, a2, , am) thỏa mãn

1 a1 < a2 < <am  n Trên tập các tập con gồm m phần tử của tập chỉ số {1, 2, , n} ta xác định một thứ tự như sau:

Ta nói tập con a = (a1, a2, , am) đi trước tập con a’ = (a1’, a2’, , am’) nếu tìm được chỉ số k (1 k  m) sao cho:

a1 = a’1

Trang 6

a2= a’2

ak-1 = a’k-1

ak < a’k

Chẳng hạn X = {1, 2, 3, 4, 5} thì các tập con gồm 3 phần tử của X được liệt

kê theo thứ tự từ điển như sau:

Tổ chức dữ liệu:

- Mảng x: dãy số nguyên đã cho

- Mảng a: lưu trữ một tập con gồm m phần tử của tập chỉ số {1, 2, , n}

Các hàm

- Hàm init(): nhập n, m và khởi tạo

- Hàm result(): đưa ra tập con gồm m phần tử của x vừa tìm được

- Hàm Try(i): xác định thành phần thứ i của tập con

init()

{

scanf(n,m)

Trang 7

2) Thiết kế thuật toán

Đánh số cột và số dòng của bàn cờ từ 1 đến 8 Mỗi dòng được xếp đúng một quân hậu Vấn đề là xem mỗi quân hậu trên mỗi hàng được xếp vào cột nào Từ đó,

ta có thể biểu diễn một cách xếp bằng một bộ 8 thành phần (x1, x2, , x8) trong đó

Trang 8

xi = j nghĩa là quân hậu dòng i được xếp vào cột j Các giá trị đề cử cho xi là từ 1 đến 8 Giá trị j là được chấp nhận nếu ô (i, j) chưa bị quân hậu nào chiếu đến (quân hậu có thể ăn ngang, dọc và hai đường chéo) Để kiểm soát được điều này, ta cần phải ghi nhận trạng thái của bàn cờ trước cũng như sau khi xếp được một quân hậu

Việc kiểm soát theo chiều ngang là không cần thiết vì mỗi dòng được xếp đúng một quân hậu Việc kiểm soát chiều dọc được ghi nhận nhờ dãy biến aj với qui ước rằng aj bằng 1 nếu cột j còn trống Hai đường chéo đi qua ô (i, j) thì một đường chéo gồm những ô có i + j không đổi, còn một đường chéo gồm những ô có i - j không đổi (2 i+j 16, -7 i-j 7)

Từ đó đường chéo thứ nhất được ghi nhận nhờ dãy biến bj (2 j 16) và đường chéo thứ hai nhờ nhờ dãy biến cj (-7 j  7) với qui ước các đường này còn trống nếu biến tương ứng có giá trị 1

Các biến trạng thái aj, bj, cj được khởi gán giá trị 1 trong hàm Init() Như vậy giá trị j được chấp nhận khi và chỉ khi cả 3 biến ai, bi+j, ci-j cùng có giá trị 1 Các biến này phải được gán lại giá trị 0 khi xếp xong quân hậu thứ i và được trả lại giá trị 1 sau khi gọi Result() hay Try(i+1)

Tổ chức dữ liệu:

- Mảng x để lưu trữ cách xếp 8 quân hậu tìm được

- Mảng c với các phần tử c[j] để ghi nhận trạng thái cột j (c[j]=1 thì cột j chưa có quân hậu nào xếp, c[j]=0 thì cột j đã có quân hậu xếp)

- Mảng ct để ghi nhận trạng thái đường chéo tổng i+j:

+ ct[i+j]=1 thì đường chéo i+j chưa có quân hậu nào chiếu đến

+ ct[i+j]=0 thì đường chéo i+j đã có quân hậu chiếu đến

- Mảng ch để ghi nhận trạng thái đường chéo hiệu i-j: Để chỉ số của mảng chạy từ 1 nên với i-j chạy từ -7 đến 7 ta đặt tương ứng với i-j+8 chạy từ 1 đến 15 + ct[i-j+8]=1thì đường chéo i-j chưa có quân hậu nào chiếu đến

+ ct[i-j+8]=0 thì đường chéo i-j đã có quân hậu chiếu đến

Trang 10

G = (V, E) là đơn đồ thị (có hướng hoặc vô hướng) V = {1, ., n} là tập các đỉnh,

E là tập cạnh (cung) Với s, t  V, tìm tất cả các đường đi từ s đến t

Các thuật toán tìm kiếm cơ bản :

Thuật toán DFS : Tìm kiếm theo chiều sâu

Thuật toán BFS : Tìm kiếm theo chiều rộng

2) ThiÕt kÕ thuËt to¸n

* Thuật toán DFS tiến hành tìm kiếm trong đồ thị theo chiều sâu Thuật toán thực

hiện việc thăm tất cả các đỉnh có thể đạt được cho tới đỉnh t từ đỉnh s cho trước Đỉnh được thăm càng muộn sẽ càng sớm được duyệt xong (cơ chế LIFO –Vào sau

ra trước) Nên thuật toán có thể tổ chức bằng một thủ tục đệ quy quay lui

Ta cần mô tả dữ liệu đồ thị và các mệnh đề được phát biểu trong mô hình

Ðồ thị sẽ được biểu diễn bằng ma trận kề : a =(aij) 1<=i, j<=n và:

aij = 1 nếu (i, j )  E

aij = 0 nếu (i, j )  E

Ghi nhận đỉnh được thăm để tránh trùng lặp khi quay lui bằng cách đánh dấu

Ta sữ dụng một mảng một chiều Daxet[] với qui ước :

Trang 11

Daxet[u] = 1 , u đã được thăm

Daxet[u] = 0 , u chưa được thăm

Mảng Daxet[] lúc đầu khởi t?o bằng 0 tất cả

Điều kiện chấp nhận được cho đỉnh u chính là u kề với v (avu = 1) và u chưa được thăm ( Daxet[u] = 0)

Để ghi nhận các đỉnh trong đường đi, ta dùng một mảng một chiều Truoc[ ], với qui ước :

Truoc[u] = v với v là đỉnh đứng trước đỉnh u, và u kề với v

Ta khởi tạo mảng Truoc[ ] bằng 0 tất cả

Thuật toán đượcc làm mịn hơn :

Trang 12

: yAi-1, (y,x)E} …

Thuật toán có không quá n bước lặp, một trong hai trường hợp sau xảy ra :

- Nếu với mọi i, t  Ai : không có đường đi từ s đến t;

- Ngược lại, tAm với m nào đó Khi đó tồn tại đường đi từ s tới t, và đó là một đường đi ngắn nhất từ s đến t

Trong trường hợp này, ta xác định được các đỉnh trên đường đi bằng cách quay

Trang 13

ngược lại từ t đến các đỉnh trước t trong từng các tập trước cho đến khi gặp s

Trong thuật toán BFS, đỉnh được thăm càng sớm sẽ càng sớm trở thành duyệt xong, nên các đỉnh được thăm sẽ được lưu tr? trong hàng đợi queue Một đỉnh sẽ trở thành duyệt xong ngay sau khi ta xét xong tất cả các đỉnh kề của nó

Ta dùng một mảng Daxet[] để đánh dấu các đỉnh được thăm, Daxet[i]=0 là đỉnh i chưa được thăm, Daxet[i]= là đỉnh i đã được thăm Mảng này được khởi động bằng 0 tất cả để chỉ rằng lúc đầu chưa đỉnh nào được thăm

Một mảng truoc[ ] để lưu trữ các đỉnh nằm trên đường đi ngắn nhất cần tìm (nếu có), với ý nghĩa Truoc[i] là đỉnh đứng trước đỉnh i trong đường đi Mảng Truoc[ ] được khởi tạo bằng 0 tất cả để chỉ rằng lúc đầu chưa có đỉnh nào

Đồ thị sẽ được biểu diễn bằng ma trận kề : a =(aij) 1<=i, j<=n và:

Trang 14

Ta có thể thấy mỗi lần gọi DFS(s), BFS(s) thì mọi đỉnh thuộc cùng một thành phần liên thông với s sẽ được thăm, nên sau khi thực hiện hàm trên thì :

• Truoc[t] = 0 : không tồn tại đường đi từ s đến t,

• Ngược lại, có đường đi từ s đến t Khi đó lời giải được cho bởi :

2) ThiÕt kÕ thuËt to¸n

Cách giải quyết rõ ràng là xét xem có thể thực hiện một nước đi kế nữa hay không Sơ đồ đầu tiên có thể phát thảo như sau :

Trang 15

- Các khả năng chọn lựa cho xi

h[x][y] = 0: Ô <x,y> ngựa chưa đi qua;

h[x][y] = i : Ô <x,y> ngựa đã đi qua ở bước thứ i (1  i  n2 )

* Các khả năng lựa chọn cho xi? Đó chính là các nước đi của ngựa mà xi có thể chấp nhận được

Với ô bắt đầu <x,y> như trong hình vẽ, có tất cả 8 ô <u,v> mà con ngựa có thể đi đến Giả sử chúng được đánh số từ 0 đến 7 như hình sau :

( ô có dấu * là ô ở hàng x cột y nơi ngựa đang đứng)

Trang 16

Ta dùng mảng a lưu trữ các sai khác về chỉ số hàng của các ơ trên so với x và dùng mảng b lưu trữ các sai khác về chỉ số cột của các ơ trên so với y thì mảng a

và mảng b sẽ được khởi tạo như sau:

Trang 17

thái); và để hủy một nước đi thì ta gán h[u][v] = 0 (hoàn trả trạng thái)

* Ma trận h ghi nhận kết quả nghiệm Nếu có <x,y> sao cho h<x,y> = 0 thì không có đường đi , còn ngược là h chứa đường đi của ngựa

Thuật toán có thể mô tả như sau :

Trang 18

init();

h[x0 ][y0 ] = 1;

try(2,x0 , y0);

} Với n=5 và x0=1, y0=1 thì thuật toán cho một trong các lời giải là:

Trang 19

BÀI TẬP CHƯƠNG 4

1.Cho một dãy số nguyên a1, a2, , an và một số nguyên S Liệt kê tất cả các cách phân tích S = ai1 + ai2 + + aik (ai1, ai2, , aik là các số của dãy đã cho)

Hướng dẫn:

Với mỗi giá trị k từ 1 đến n ta xây dựng các tập con gồm k phần tử của dãy

số nguyên a1, a2, ., an Với mỗi tập con này ta tính tổng các phần tử của nó, nếu tổng bằng S thì hiển thị tổng đó ra

Ta áp dụng thuật toán quay lui để liệt kê các cách phân tích S = ai1 + ai2 + + aik

Sử dụng :

- Mảng a để lưu trữ n số nguyên a1, a2, , an

- Mảng b với các phần tử b[i] dùng để ghi nhận chỉ số của phần tử của mảng

a Phần tử có chỉ số này của mảng a được gán cho c[i]

- Mảng c dùng để lưu trữ tập con gồm k phần tử xây dựng được

- Hàm khoitao để nhập n, nhập S, nhập n số nguyên a1, a2, , an

- Hàm Try để xây dựng các tập con gồm k phần tử

- Hàm ht để hiển thị ra tổng thoả mãn điều kiện Với mỗi tập con gồm k phần

tử lấy từ n số nguyên a1, a2, , an, hàm ht sẽ kiểm tra xem tổng của k phần tử này

có bằng S không? Nếu bằng thì hiển thị tổng đó ra

Trang 20

Nếu i=n ta được một tổng đại số, khi đó kiểm tra xem tổng đại số đó có kết quả bằng 0 hay không Nếu bằng 0 thì đưa ra tổng đó, ngược lại thì không đưa ra

Ta dùng hàm in() để thực hiện điều này

Dùng mảng b mà phần tử bi lưu trữ dấu của số nguyên ai Qui ước chẳng hạn

0 ứng với dấu + và 1 ứng với dấu - tức là:

Nếu bi =0 thì dấu của ai là +

Nếu bi =1 thì dấu của ai là -

Try(i)

{

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

Trang 21

- Mảng a để lưu trữ số có 6 chữ số khác nhau tìm được (ở đây ta chấp nhận

cả số mà chữ số đầu tiên bằng 0, khi hiển thị ta không quan tâm tới những số này)

- Mảng b mỗi phần tử dùng để ghi nhận một chữ số đã được dùng hay chưa Nếu b[j]=0 thì chữ số j chưa được dùng, nếu b[j]=1 thì chữ số j đã được dùng

- Hàm khoitao: khởi tạo giá trị các phần tử mảng b và biến d (biến d dùng để dếm các số thoả mãn điều kiện)

Trang 22

- Hàm ht kiểm tra số vừa tìm được, nếu thoả mãn điều kiện thì hiển thị số đó

Trang 23

- Mảng c dùng để lưu trữ chỉnh hợp không lặp chập k của tập n số nguyên a1,

- Hàm ht để hiển thị ra chỉnh hợp không lặp chập k của tập n số nguyên a1,

a2, , an vừa xây dựng được

Trang 24

a11 được lưu trữ trong x[1]

a12 được lưu trữ trong x[2]

Trang 25

a13 được lưu trữ trong x[3]

a21 được lưu trữ trong x[4]

a22 được lưu trữ trong x[5]

a23 được lưu trữ trong x[6]

a31 được lưu trữ trong x[7]

a32 được lưu trữ trong x[8]

a33 được lưu trữ trong x[9]

- Dùng thuật toán quay lui xây dựng tất cả các hoán vị của 9 phần tử 1, 2, 3, , 9 Mỗi hoán vị sẽ được lưu trữ trong các phần tử của mảng x

- Mỗi khi được một hoán vị thì kiểm tra điều kiện, tức là kiểm tra các tổng: x[1] + x[2] + x[3]; x[4] + x[5] + x[6]; x[7] + x[8] + x[9] (tổng các hàng) x[1] + x[4] + x[7]; x[2] + x[5] + x[8]; x[3] + x[6] + x[9] (tổng các cột)

x[1] + x[5] + x[9]; x[3] + x[5] + x[7] (tổng các đường chéo) nếu các tổng đó bằng nhau thì hiển thị các phần tử của mảng x ra màn hình dưới dạng ma trận (Nếu i mod 3 = 0 thì xuống hàng)

Trang 26

Chương 5

KỸ THUẬT NHÁNH VÀ CẬN

5.1 Nội dung kỹ thuật

Kỹ thuật quay lui, vét cạn có thể được sử dụng để giải các bài toán tối ưu tổ hợp bằng cách tiến hành duyệt các phương án của bài toán, đối với mỗi phương án

ta đều tính giá trị hàm mục tiêu tại nó, sau đó so sánh giá trị hàm mục tiêu tại tất cả các phương án để tìm ra phương án tối ưu Tuy nhiên trong thực tế với nhiều bài toán phương pháp áp dụng kỹ thuật này rất khó có thể chấp nhận được về thời gian

Vì vậy cần phải có những biện pháp nhằm hạn chế việc tìm kiếm thì mới có hy vọng giải được các bài toán tối ưu tổ hợp thực tế Tất nhiên để có thể đề ra những biện pháp như vậy cần phải nghiên cứu kỹ tính chất của bài toán tối ưu tổ hợp cụ thể Nhờ những nghiên cứu như vậy, trong một số trường hợp cụ thể ta có thể xây dựng những thuật toán hiệu quả để giải bài toán đặt ra Khi đó, một vấn đề đặt ra là trong quá trình liệt kê lời giải ta cần tận dụng các thông tin đã tìm được để loại bỏ những phương án chắc chắn không phải là tối ưu Kỹ thuật nhánh và cận (gọi tắt là nhánh cận) là một cải tiến của kỹ thuật quay lui theo hướng này

Ta sẽ mô tả nội dung của kỹ thuật nhánh cận trên mô hình bài toán tối ưu tổ hợp tổng quát sau:

min { f(x): xD }

trong đó D là tập hữu hạn các phần tử

Giả thiết D được mô tả như sau:

D = { x = (x1, x2, , xn)  A1 A2 An: x thỏa mãn tính chất P}, với

A1, A2, , An là các tập hữu hạn, còn P là tính chất cho trên tích Đềcac

A1 A2 An

Với giả thiết về tập D nêu trên, chúng ta có thể sử dụng thuật toán quay lui

để liệt kê các phương án của bài toán Trong quá trình liệt kê theo thuật toán quay

lui, ta sẽ xây dựng dần các thành phần của phương án Một bộ gồm k thành phần

(a1, a2, , ak) xuất hiện trong quá trình thực hiện thuật toán sẽ gọi là phương án bộ

phận cấp k

Kỹ thuật nhánh cận có thể áp dụng để giải bài toán đặt ra nếu như có thể tìm

được một hàm g xác định trên tập tất cả các phương án bộ phận của bài toán thỏa

mãn bất đẳng thức sau:

g(a1, a2, , ak)  min{f(x): xD, xi =ai, i= 1,2, , k } (*)

Trang 27

với mọi lời giải bộ phận ( a1,a2, , ak ), và với mọi k= 1,2,

Bất đẳng thức (*) có nghĩa là giá trị của hàm g tại phuơng án bộ phận (a1, a2, , ak ) là không vượt quá giá trị nhỏ nhất của hàm mục tiêu của bài toán trên tập con

các phương án

D(a1, a2, , ak ) = { x D :xi= ai , i = 1, 2, , k}

hay nói một cách khác, g(a1, a2, , ak ) là cận dưới của giá trị hàm mục tiêu trên tập D(a1, a2, , ak) Vì lẽ đó, hàm g được gọi là hàm cận dưới , và giá trị g(a1, a2, , ak) được gọi là cận dưới của tập D(a1, a2, , ak) Do có thể đồng nhất tập D(a1, a2, ,ak)

với phương án bộ phận (a1, a2, , ak), nên ta cũng gọi giá trị g(a1, a2, , ak) là cận dưới của phương án bộ phận (a1, a2, , ak)

Giả sử đã có hàm g Ta xét cách sử dụng hàm này để giảm bớt khối lượng duyệt trong quá trình duyệt tất cả các phương án theo thuật toán quay lui Trong quá trình liệt kê các phương án có thể đã thu được một số phương án của bài toán Gọi

x* là phương án với giá trị hàm mục tiêu nhỏ nhất trong số các phương đã tìm được,

kí hiệu f*= f(x*) Ta sẽ gọi x* là phương án tốt nhất hiện có, còn f là kỷ lục Giả sử

ta đã có f* Khi đó nếu

g(a1, a2, , ak) > f* thì từ bất đẳng thức (*) suy ra

f* < g(a1, a2, , ak)  min {f(x): xD, xi =ai, i= 1,2, , k },

vì thế tập con các phương án của bài toán D(a1, a2, ,ak) chắc chắn không chứa

phương án tối ưu Trong trường hợp này ta không cần tiếp tục phát triển phương án

bộ phận (a1, a2, ,ak), nói cách khác là ta có thể loại bỏ các phương án trong tập

D(a1, a2, ,ak) khỏi quá trình tìm kiếm

Thuật toán quay lui liệt kê phương án cần sửa đổi lại như sau

Trang 28

else

Try(k+1)

Trang 29

thì hàm Try sẽ liệt kê toàn bộ các phương án của bài toán, và ta thu đựơc thuật toán duyệt toàn bộ.Việc xây dựng hàm g phụ thuộc vào từng bài toán tối ưu tổ hợp cụ thể Thông thường ta cố gắng xây dựng nó sao cho:

 Việc tính giá trị của g phải đơn giản hơn việc giải bài toán tối ưu tổ hợp ở vế phải cuả (*)

Giá trị của g( a1, a2, , ak) phải sát với giá trị của vế phải của (*)

Tuy nhiên hai yêu cầu này trong thực tế thường đối lập nhau

5.2 Các ví dụ áp dụng

5.2.1 Bài toán người du lịch

1) Bài toán

Một người du lịch muốn đi tham quan n thành phố được đánh số là 1, 2, ,

n Xuất phát từ một thành phố nào đó người du lịch muốn đi qua tất cả các thành phố còn lại, mỗi thành phố đúng một lần, rồi quay trở lại thành phố xuất phát Biết

cij là chi phí đi từ thành phố i đến thành phố j ( i, j = 1, 2,…, n), hãy tìm hành trình

(một cách đi thoả mãn điều kiện đặt ra) với tổng chi phí là nhỏ nhất

Phân tích bài toán:

Với nội dung như vậy của bài toán rất khó hình dung mô hình của nó, ta sẽ phân tích và đưa về dạng tổng quát Có hai yếu tố cần phải xác định và chỉ ra: Tập

và ký hiệu D là tập tất cả các hoán vị  = ((1), (2),…, (n)) của n số tự nhiên 1, 2,

…, n Khi đó bài toán được đưa về dạng tổng quát như sau:

min { F() :  D }

2) Thiết kế thuật toán

Bài toán người du lịch dẫn về bài toán:

Tìm cực tiểu của hàm:

f(x1, x2, x3, ,xn) = c[x1, x2] + c[ x2, x3] + + c[ xn-1 + xn] + c[ xn, x1]

Với điều kiện (x1, x2, x3, ,xn) là hoán vị của các số 1, 2, 3, , n

Trang 30

input: ma trận chi phí C = (cij)

output: phương án tối ưu x* và f*=f(x*)

Hàm try() có thể phác thảo như sau:

Vì vậy, chi phí phải trả theo hành trình bộ phận này sẽ là

 = c[u1,u2] + c[u2,u3] + + c[ui-1,ui]

Trang 31

Để phát triển hành trình bộ phận này thành hành trình đầy đủ, ta còn phải đi qua n-k thành phố còn lại rồi quay trở về thành phố u1, tức là còn phải đi qua n-i+1 đoạn đường nữa

Do chi phí phải trả cho việc đi qua mỗi một trong số n-i+1 đoạn đường còn lại đều không ít hơn cmin nên cận dưới cho phương án bộ phận (u1,u2, ,ui) có thể tính theo công thức

g(u1, u2, , ui) = + (n-i+1).cmin

- Điều kiện chấp nhận được của j là thành phố j chưa đi qua Ta dùng một mảng Daxet[] để biểu diễn trang thái này với qui ước:

+ Daxet[j] = 0 : thành phố j chưa được đi qua

+ Daxet[j] = 1: thành phố j đã được đi qua

Mảng Daxet[] được khởi tạo bằng 0 (mọi phần tử bằng 0)

- Xác định xi theo j bằng câu lệnh : xi = j

- Ghi nhận trạng thái : Daxet[j] = 1

- Cập nhật lại chi phí khi xác định được xi : S = S+ Cxi −1,xi

- Cập nhật lời giải tối ưu :

Tính chi phí hành trình vừa tìm được :

Tong = S + Cxn, 1 ; Nếu (Tong < f*) thì

Trang 34

Thông tin về một phương án bộ phận trên cây được ghi trong các ô ở trên hình

vẽ tuơng ứng theo thứ tự sau: Đầu tiên là các thành phần của phương án, tiếp đến 

là chi phí theo hành trình bộ phận và g – cận duới

Kết thúc thuật toán , ta thu đựơc phương án tối ưu (1, 2, 3, 5, 4) tương ứng với hành trình

1 -> 2 -> 3 -> 4 ->5->1 với chỉ nhỏ nhất là 22

Một cải tiến của kỹ thuật nhánh cận giải bài toán người du lịch

Ở trên chúng ta đã xem xét một cách xây dựng cận dưới khá đơn giản cho bài toán người du lịch Chương trình được cài đặt theo các thuật toán đó, tuy rằng làm việc tốt hơn nhiều so với duyệt toàn bộ, nhưng cũng chỉ có thể áp dụng để giải các bài toán với kích thước nhỏ Muốn giải được bài toán người du lịch với kích thước lớn cần có cách đánh giá cận tốt hơn Dưới đây ta sẽ xem xét một trong những phương pháp xây dựng thuật toán theo kỹ thuật nhánh cho phép giải bài toán người

du lịch với kích thước lớn hơn

Mỗi hành trình của người du lịch (1 , 2, , n) có thể là viết lại dưới dạng (1, 2), (2, (3),… (n-1, n), (n, 1)

Trong đó mỗi thành phần (j-1, j) sẽ được gọi là một cạnh của hành trình

Trong bài toán người du lịch khi tiến hành tìm kiếm lời giải chúng ta sẽ phân tập các hành trình ra thành hai tập con: một tập gồm những hành trình chứa một cạnh(i,j) nào đó còn tập kia gồm những hành trình không chứa cạnh này Ta gọi việc làm đó là phân nhánh và mỗi tập con nói trên sẽ được gọi là nhánh hay một nút tìm kiếm Việc phân nhánh được minh hoạ bởi cây tìm kiếm:

Hình 5.2 Phân nhánh bởi cạnh (i,j)

Việc phân nhánh sẽ được thực hiện trên một quy tắc Ơristic nào đó cho phép

ta rút ngắn quá trình tìm kiếm phương án tối ưu Sau khi phân nhánh chúng ta sẽ tính cận dưới của giá trị hàm mục tiêu trên mỗi một trong hai tập con nói trên Việc

hành trình chứa (i,j)

Tập tất cả các hành trình

HT không chứa ((i,j)

Trang 35

tìm kiếm sẽ được tiếp tục trên tập con có giá trị cận dưới nhỏ hơn Thủ tục này sẽ được tiếp tục cho đến khi thu đươc một hành trình đầy đủ, tức là một phương án của bài toán người du lịch Khi đó ta chỉ cần xét những tập con các phương án nào

có cận dưới nhỏ hơn giá trị mục tiêu tại phương án đã tìm được Quá trình phân nhánh và tính cận trên tập các phương án của bài toán thông thường cho phép rút ngắn một cách đáng kể quá trình tìm kiếm do ta loại được khá nhiều tập con chắc chắn không chứa phương án tối ưu

Rút gọn ma trận (tính chi phí cận dưới)

Rõ ràng tổng chi phí của một hành trình của người du lịch sẽ chứa đúng một phần tử của mỗi dòng và một phần tử của mỗi cột trong ma trận chi phí C Do đó, nếu ta trừ bớt mỗi phần tử của một dòng (hay một cột) của ma trận C đi cùng một

số  thì độ dài của tất cả các hành trình sẽ cùng giảm đi  Vì thế hành trình tối ưu cũng sẽ không thay đổi Vì vậy nếu ta tiến hành trừ bớt các phần tử của mỗi dòng

và mỗi cột đi một hằng số sao cho thu được ma trận gồm các phần tử không âm mà trong mỗi dòng và mỗi cột của nó đều có ít nhất một số không thì tổng các hằng số trừ đó sẽ cho ta cận dưới của mọi hành trình Thủ tục trừ bớt này sẽ được gọi là thủ tục rút gọn Các hằng số trừ ở mỗi dòng (cột) sẽ được gọi là hằng số rút gọn theo mỗi dòng (cột) còn ma trận thu được sẽ gọi là ma trận rút gọn

<bớt mỗi phần tử của dòng i đi r[i] >;

sum := sum +r[i];

}

}

for(j=1;j<=k;j++)

Trang 36

Để chọn cạnh (i, j) trong ma trận rút gọn ta tìm số 0 nào mà khi ta thay nó bằng  thì sẽ được tổng các hằng số rút gọn là lớn nhất khi đó cạnh (i, j) cần chọn

Minr = < phần tử nhỏ nhất trên dòng i khác với a[i;j] >;

Minc =< phần tử nhỏ nhất trên cột j khác với a[i;j] >;

Total = minr + minc;

Trang 37

If total > beta then

- Với nhánh gồm những hành trình không chứa cạnh (i, j) ta xoá khỏi ma trận hàng i cột j, ngăn cấm việc tạo thành hành trình con và rút gọn ma trận (nếu được)

Ngăn cấm tạo thành hành trình con

Trong quá trình phân nhánh (theo nhánh có cận dưới nhỏ hơn) luôn phải chú

ý ngăn cấm việc tạo thanh hành trình con Đó là hành trình chưa đi qua tất cả các thành phố Khi phân nhánh dựa vào cạnh (i, j) ta phải thêm cạnh này vào hành trình ở mỗi bước khi lựa chọn cạnh để kết nạp ta cần phải xét xem nó có tạo với các cạnh đã có một hành trình con không, nếu có cần phải loại bỏ cạnh này và chọn cạnh có độ ưu tiên tiếp theo

Việc ngăn cấm tạo thành hành trình con có thể thực hiện như sau:

Input S danh sách các cạnh đã chọn, (i, j) cạnh mới

Nếu N(S) < n thì:

- Sắp xếp lại các cạnh trong tập S’ = S  (i, j) theo dạng danh sách

- Nếu đỉnh đầu trùng với đỉnh cuối trong S’ thì kt=1, ngược lại kt=0 và

S = S’

Output kt, S

Trang 38

Tiếp tục việc chọn cạnh phân nhánh (và ngăn cấm việc tạo thành hành trình con) thì kích thước của ma trận chi phí C sẽ giảm dần theo một nhánh (nhánh trái) Cuối cùng ma trận sẽ còn kích thước 2 x 2 thì chấm dứt việc phân nhánh và kết nạp hai cạnh còn lại để thu được hành trình của người du lịch Ma trận rút gọn cuối cùng này chỉ có thể có một trong hai dạng sau:

w x w x

u 0  u  0

v  0 v 0 

trong đó u,v,w, x có thể là bốn đỉnh khác nhau hoặc chỉ có 3 đỉnh khác nhau Khi đó

ta sẽ chọn đưa vào hành trình các cạnh tương ứng với các phần tử 0 và ta được một hành trình T với chi phí thực tế là G Nếu giá trị G nhỏ hơn tất cả các cận dưới của các nhánh đã bỏ qua thì hành trình nhận được là hành trình tối ưu, nếu không thì G lớn hơn giá trị cận dưới của một nhánh nào đó và khi đó thì hành trình nhận được chưa phải là hành trình tối ưu, ta tiếp tục áp dụng thuật toán nhánh cận cho nhánh này để tìm hành trình mới

Các bước chính của thuật toán nhánh cận giải bài toán người du lịch được mô

tả trong hàm đệ qui TSP sau đây Hàm TSP xét hành trình bộ phận với Edges cạnh

đã được chọn và tiến hành tìm kiếm tiếp theo Ta sử dụng các biến:

Edges - số cạnh trong hành trình bộ phận ;

A - ma trận chi phí tương ứng với kích thước (n-edges)(n-edges);

cost -chi phí của hành trình bộ phận

Mincost - chi phí của hành trình tốt nhất đã tìm được

Trong hàm sử dụng hàm Reduce(A, K) và hàm BestEdge(A, k, r, c, beta)

Trang 39

else

{

BestEdge(A,n-edges,g,c,beta);

lowerBound= cost + beta;

<Ngăn cấm tạo thành hành trình con>;

newA=<A loại bỏ dòng r cột c>

TSP(edges+1, cost, newA); /* đi theo nhánh trái */

<Khôi phục A bằng cánh bổ sung lại dòng r cột k>;

Dùng thuật toán nhánh cận (bằng cách chọn cạnh phân nhánh) tìm hành trình tối

ưu cho bài toán người du lịch với ma trận chi phí như sau:

Trang 40

- Nhánh gồm các hành trình có chứa cạnh (5, 4)

Với nhánh này ta xoá khỏi ma trận rút gọn hàng 5 cột 4, thay giá trị ở hàng 4 cột 5 bằng  (ngăn cấm việc tạo thành hành trình con) và rút gọn ma trận, cụ thể bớt mỗi phần tử ở hàng 3 một lượng là 2 Ta được ma trận rút gọn của nhánh này và cận dưới của nhánh là 34

C =

Ngày đăng: 06/05/2021, 18:31

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

🧩 Sản phẩm bạn có thể quan tâm