Thuật toán Kuhn-Munkes Nội dung chủ yếu của phương pháp là xuất phát từ một tập cặp ghép nào đó chưa đâỳ đủ co thể lập hợp rỗng , ta tăng dần số cặp ghép sao cho khi trở thành đầy đủ , [r]
(1)Sơ lược các chủ đề Sau đây là sơ lược các chủ đề đề cập phần này chương trình: + Phần sở: là các công cụ và phương pháp dùng xuyên suốt cho tất các chương sau phần này Nó gồm phần bàn luận ngắn Pascal, theo sau là giới thiệu các cấu trúc liệu gồm mảng, xâu liên kết, ngăn xếp, hàng đợi và cây Chúng ta bàn luận công dụng thực tiễn đệ quy và bắt đầu hướng tới việc phân tích và tiếp cận thực toán + Sắp xếp: Các phương pháp xếp phát triển, mô tả, so sánh với Các thuật toán cho nhiều vấn đề có liên quan xem xét gồm có hàng đợi ưu tiên, phép chọn và phép trộn Một vài tảng số này dùng là tảng cho các thuật toán khác tiếp sau phần này + Xử lý chuỗi: gồm loạt các phương pháp để phân tích câu Các ky thuật nén tập và mật mã khảo sát Cũng vậy, giới thiệu các chủ đề nâng cao cùng cung cấp qua việc xem xét vài bài toán quan trọng phạm vi chúng + Hình học: là tập hợp có chọn lọc các phương pháp để giải các bài toán liên quan đến điểm và đường ( và các đối tượng hình học đơn giản khác ) Chúng ta xem xét các thuật toán để tìm bao lồi tập điểm, phần giao các đối tượng, để giải bài toán điểm gần nhất, tìm kiếm nhiều chiều + Đồ thị: Một chiến lược tổng quát để tìm kiếm trên các đồ thị phát triển và áp dụng cho các bài toán liên thông bản, gồm có đường ngắn nhất, cây liên thông tối thiểu, mạng và so khớp Một xem xét thống các thuật toán này chứng tỏ tất dựa vào thủ tục và thủ tục này phụ thuộc vào cấu trúc liệu đã phát triển trước đó + Các thuật toán toán học: gồm các phương pháp từ số học và các số nguyên, đa thức, và ma trận các thuật toán để giải cac vấn đề toán học mà nó phát sinh nhiều ngữ cảnh : việc tạo số ngẫu nhiên, lỡi giải các chương trình đồng thời, xấp xỉ liệu, và lấy tích phân Sự nhấn mạnh thiên các khía cạnh thuật toán phương pháp, không phải trên tảng toán học + Các chủ đề cao cấp: thảo luận nhằm mục đích liên hệ các chủ đề tập sách này với nhiều lĩnh vực nghiên cứu cao cấp khác Phần cứng chuyên dụng, quy hoạch động, quy hoạch tuyến tính, tìm kiếm- vét cạn I Sắp xếp: Khái niệm: a) Sắp xếp là quá trình tổ chức lại dãy các liệu theo trật tự định b) Mục đích việc xếp là nhằm giúp cho việc tìm kiếm liệu cách dễ dàng và nhanh chóng Sắp xếp là việc làm và dùng rộng rãi các kĩ thuật lập trình nhằm sử lý liệu c) Các giải thuật xếp phân chia thành hai nhóm chính là: - Sắp xếp (hay xếp mảng) Toàn sở liệu cần xếp phải đưa vào nhớ chính máy tính Do đó nó thường sử dụng khối lượng liệu không vượt quá dung lượng nhớ chính Nhóm xếp bao gồm các phương pháp : * Phương pháp đếm * Phương pháp chèn * Phương pháp chọn * Phương pháp đổi chổ * Phương pháp trộn - Sắp xếp ngoài (hay xếp tập tin) Vận dụng trường hợp ta phải xếp các tập tin chứa nhiều mẫu tin và mẫu tin có chiều dài tương đối lớn đó ta không thể nạp toàn vào nhớ chính để xếp thứ tự Vì ta phải có phương pháp thích hợp cho việc xếp tập tin Sắp xếp trong: a) Khái niệm: Cấu trúc liệu thích hợp cho các phần tử cần xếp thứ tự là Record Mỗi phần tử có hai vùng đặc trương là: Vùng Key để chứa khoá phần tử và sử dụng các giải thuật tìm kiếm, vùng info dùng để chứa đữ liệu các phần tử Ta khai báo : Type Item = Record (2) key : Integer; Info : Integer; End; Var A : Array[1 n] of Item; Khoá phần tử có thể là chữ số Yêu cầu giải thích là dùng ít vùng nhớ và thời gian thực nhanh b) Phương pháp đếm (Counting sort) * Giải thích: Nội dung phương pháp này là đếm các phần tử có khoá nhỏ hay khoá các phần tử A[i] Nếu có j phần tử có khoá nhỏ khoá phần tử A[i] thì phần tử A[i] có vị trí theo thứ tự (j+1) dãy đã có thứ tự Trong giải thuật, ta dùng mảng Count[i] ( i = 1, 2, n ) với Count[i] cho biết số phần tử có khoá nhỏ khoá phần tử A[i] Như Count[i+1] là vị trí phần tử A[i] dãy đã có thứ tự * Ví dụ: Sắp xếp dãy i: Count: 20416573 Như phần tử có khoá vị trí vì Count[9]=7 * Thể Pascal: Procedure Count_Sort; Var Count : Array[1 n] of Integer; A : Array[1 n] of Item; i,j : Integer; Begin For i := to n Count[i] := 0; For i := n downto For j := i-1 downto If A[i].Key < A[j].Key Then Count[j] := Count[j] + Else Count[i] := Count[i] + 1; For i := to n S[Count[i] + 1] := A[i]; For i := to n A[i] := S[i]; End; c) Phương pháp chèn (Insertion Sort) * Giải thích: Nội dung phương pháp này là giả sử ta có dãy A[1] A[i-1] đã có thứ tự, có phải xác định vị trí thích hợp phần tử A[i] dãy A[1] A[i - 1] phương pháp tìm kiếm từ A[i - 1] trở A[1] để tìm vị trí thích hợp A[i] Ta chèn A[i] vào vị trí này và kết là đãy A[1] A[i] có thứ tự Ta áp dụng cách làm này với i = 2, 3, , n * Ví dụ: Ta phải xếp dãy số: 39 50 37 89 13 62 i=2 39 50 37 89 13 62 i=3 39 50 37 89 13 62 i=4 39 50 37 89 13 62 i=5 37 39 50 89 13 62 i=6 37 39 50 89 13 62 i=7 13 37 39 50 89 62 i=8 13 37 39 50 89 62 13 37 39 50 89 62 * Thể Pascal: Procedure Insertion_Sort; Var x : Item; i,j : Integer; (3) Begin Begin For i := to n A[j+1] := A[j]; Begin j := j - 1; x := A[i]; End; A[0] := x; A[j+1] := x; j := j - 1; End; While x.Key < A[j].Key End; d) Phương pháp chọn (Selection Sort) * Giải thích:Nội dung phương pháp này là bước thứ i (i = 1, 2, 3, ,n-1 ) ta lựa chọn phần tử nhỏ dãy A[i] A[n] đổi chổ phần tử này với phần tử A[i] Cuối cùng ta có dãy A[1] A[n] có thứ tự * Ví dụ: Ta phải xếp dãy số : 39 50 37 89 13 62 i=1 39 50 37 89 13 62 i=2 50 37 89 13 39 62 i=3 50 37 89 13 39 62 i=4 13 37 89 50 39 62 i=5 13 37 89 50 39 62 i=6 13 37 50 50 39 62 i=7 13 37 39 50 89 62 13 37 39 50 89 62 * Thể Pascal: Procedure Selection_Sort; For j := i+1 to n Var If A[j].Key < Then x : Item; Begin i,j ,k : Integer; := A[j].Key; : Integer; k := j; Begin End; For i := to n-1 x := A[k]; Begin A[k] := A[i]; := A[i].Key; A[i] := x; k := i; End; For i := to n-1 End; e) Phương pháp đổi chỗ: Có nhiều phương pháp xếp dựa trên việc đổi chỗ phần tử dãy Sau đây chúng ta xét các phương pháp: - Bubble Sort - Shake Sort - Sell Sort - Quick Sort * Bubble Sort: * Giải thích: Nội dung phương pháp này là duyệt các dãy A[1], , A[n] Nếu A[i].Key > A[i+1].Key (i = 1, 2, 3, , n-1)#0 thì ta đổi chỗ A[i].Key với phần tử A[i+1].Key Lập lại quá trình duyệt dãy này không còn việc đổi chổ hai phần tử Chú ý lúc nào phần tử nhỏ gặp trước tiên Nó bột khí nhẹ lên trên đun nước Sau đó thứ hai phần tử nhỏ thứ đặ vào đúng vị trí Vì xếp bột thao tác kiểu xếp chọn, mặc dù nó không làm nhiều việc để đưa phần tử vào đúng vị trí * Ví dụ: Ta phải xếp dãy số: 39 50 37 89 13 62 Bước 50 1 1 13 62 39 39 7 7 7 (4) 50 39 13 13 13 13 13 37 50 39 37 37 37 37 89 37 13 50 39 39 39 39 13 89 37 37 50 50 50 50 13 89 62 62 62 62 62 62 62 62 89 89 89 89 89 * Thể Pascal: Procedure Bubble_Sort; If A[j-1].Key > A[j].Key Then Var Begin x : Item; x := A[j-1]; i,j : Integer; A[j-1] := A[j]; Begin A[j-1] := x; For i := to n End; For j := n downto i End; * Cải tiến: Ta nhận thấy lần duyệt dãy nào đó mà không co s xẩy đổi chổ hai phần tử thì dãy dang đã có thứ tự và giải thuật kết thúc Ta có thể cài đặt cờ để ghi nhận điều này và có chương trình sau: Procedure Bubble_Sort2; For i := to n-1 Var If A[i].Key > A[i+1].Key Then x : Item; Begin i : Integer; x := A[i]; flag : Boolean; A[i] := A[i+1]; Begin A[i+1] := x; flag := true; End; While flag End; Begin End; flag := False; f* Shake Sort: * Giải thích: Phương pháp này là cải tiến phương pháp Bubble Sort theo hướng "Không phần tử nhẹ lên trên mà phần tử nặng xuống dưới" giống ta rung"rung" cái nồi và thuật toán xếp phải điều khiển hai quá trình "nổi lên" và "chìm xuống" này cách tự giác Muốn ta phải ghi nhớ lần đổi chổ cuối cùng duyệt dãy từ trên lên và duyệt từ trên xuoóng để định chu trình duyệt từ đâu đến đâu * Ví dụ: Sắp xếp dãy số: 39 50 37 89 13 62 d= 3 4 c= 8 7 39 1 1 50 39 39 7 50 39 13 37 37 13 37 89 37 50 37 39 13 89 13 50 50 13 62 62 62 62 62 89 89 89 * Thể Pascal: Procedure Shake_Sort; Var x : Item; i,k,d,c : Integer; Begin d := 2; c := n; k := n; (5) Repeat If A[i-1].Key > A[i].Key Then For i := c downto d Begin If A[i-1].Key > A[i].Key Then x := A[i-1]; Begin A[i-1] := A[i]; x := A[i-1]; A[i-1] := x; A[i-1] := A[i]; k := i; A[i-1] := x; End; k := i; c := k-1; End; Until d>c; d := d + 1; End; For i := d to c g * Shell Sort: * Giải thích: Các phương pháp xếp dã trình bày trên nói chung di chuyển phần tử vị trí bước Phương pháp Shell Sort dựa trên ý tưởng chính là hoán các phần tử xa Để làm việc đó ta cần phải các tập tin để nó có tính chất là việc lấy phần tử thứ h (bắt đầu từ vị trí bất kì nào) cho tập tin đã Một tập tin gọi là theo độ dài bước h Một cách nói khác, tập tin dược theo độ dài bước h là tập tin độc lập với nhau, đan xen vào Bằng cách xếp theo độ dài bước h ứng với vài giá trị h khá lớn, chúng ta có thể di chuyển các phần tử khoảng cách xa mảng và vì dễ dàng để xếp độ dài bước h các giá tri nhỏ Dùng thủ tục cho bất kì dãy các giá trị h tận cùng là cho tập tin đã xong: Dó chính là Shell Sort * Ví dụ: Ta phải xếp dãy số: 39 50 39 89 13 62 Bước 1: 4-Sort 39 50 39 89 13 62 39 13 37 89 50 62 Bước 2: 2-Sort 39 13 37 89 50 62 13 37 39 50 89 62 Bước 3: 1-Sort 13 37 39 50 89 62 13 37 39 50 89 62 * Thể Pascal: Chú ý: - Ta dùng dãy phụ chứa dộ tăng h[i], , h[t] để điều khiển quá trình xếp thứ tự với h[t]=1 Việc chộn các độ tăng thích hợp làm giảm thời gian thứ tự - Dặt h1 = h[1] ta phải khai báo dãy a sau: A : Array[1 n] of Item; các phần tử A[i] (i<=0) là các lính canh Sau đây ta chọn: h[1] = 9, h[2] = 5, h[3] = 3, h[4] = Procedure Shell_Sort; Const t = 4; Var x : Item; i,j,k,s,m: Integer; h : Array[1 t] of Integer; Begin h[1] = 9; h[2] = 5; h[3] = 3; h[4] = 1; For m := to t (6) Begin k := h[m]; s := -k; For i := k+1 to n Begin x := A[i]; j := i - k; If s = Then s:=-k; s := s + 1; A[s] := x; While x.Key<A[j].Key Begin A[j+k] :=A[j]; j := j - k; End; A[j+k] := x; End; End; End; h Quick Sort: * Giải thích: Nội dung phương pháp này là chọn phần tử x dãy làm chuẩn để so sánh Ta phân hoạch dãy này thành dãy liên tiếp nhau: - Dãy thứ gồm phần tử có khoá nhỏ x.key - Dãy thứ hai gồm các phần tử có khoá x.key - Dãy thứ ba gồm các phần tử có khoá lớn x.key Sau đó áp dụng giải thuật phân hoạch này cho dãy thứ nhất và dãy thứ ba, các dãy có nhiều phần tử (Đệ qui) Cụ thể là xét doạn dãy từ thành phần L đến thành phần thứ R - Lấy giá trị thành phần thứ (L+R) Div gán vào biến X - Cho i ban đầu là L - Cho j ban đầu là R - Lập lại * Chừng nào còn A[i] < X thì tăng i * Chừng nào còn A[j] > X thì giảm j * i<=j thì + Hoán vị A[i] và A[j] + Tăng i + Giảm j Cho đến i>j + Sắp xếp đoạn từ A[L] đến A[j] + Sắp xếp đoạn từ A[i] đến A[R] * Ví dụ: Sắp xếp dãy số: 39 50 37 89 13 62 X = 37 Sau lần đổi chổ ta dãy: 13 37 89 50 39 62 Xử lý dãy con: 13 Ta được: 13 Sử lý dãy con: 89 50 39 62 (7) Ta được: 39 50 89 62 39 50 62 89 Vậy dãy đã xếp là: 13 39 50 62 89 * Thể Pascal: Để đơn giản ta viết thủ tục mảng số nguyên truyền tham biến Procedure Quick_Sort(Var A : Array[1 n] of Integer); Procedure Sort(L,R : Integer); A[i] := A[j]; Var A[j] := Tg; i,j,Tg,X : Integer; Inc(i); Begin Dec(j); X := A[(L+R) Div 2]; End; i := L; Until i>j; j := R; If L < j Then Sort(L,j); Repeat If i < R Then Sort(i,R); While (A[i] < X) Inc(i); End; While (A[j] > X) Dec(j); Begin If i <= j Then Sort(1,n); Begin End; Tg := A[i]; i Phương pháp trọn (Merging Sort) * Giải thích: Nội dung phương pháp này là chia dãy số cần thành các dãy đã có thứ tự(goi là các Run) và có số phần tử là luỹ thừa sau đó tìm cách trộn dần chúng với thành các Run có thứ tự chiều dài tăng dần còn Run thì quá trình xếp kết thúc Ta có giải thuật sau đây để trộn Run x và y cùng thứ tự có chiều dài là m và n thành run z có chiều dài là m + n Procedure Merge; Var i,j,k : Integer; Begin i := 1; j := 1; k := 1; While (i <= m) and (j <= n) Begin If X[i] < Y[j] Then Begin Z[k] := X[j]; i := i + 1; End Else Begin Z[k] := Y[j] j := j + 1; End; k := k + 1; End; While i<=m Begin Z[k] := X[j]; k := k + 1; i := i + 1; End; While j<=n Begin Z[k] := X[j]; k := k + 1; j := j + 1; End; End; Cụ thể là ta phải xếp dãy: A[1], A[2], ,A[n] Ta phải sử dụng 2*n phần tử chia thành vùng Vùng gồm các phần tử A[1] A[n], vùng gồm các phần tử A[n+1] A[2*n] Ta trộn các Run từ vùng này và phân phối vào vùng Khi trộn và phân phối, ta trộn các Run ngược chiều vùng trộn và phân phối luân phiên vào đầu vùng phân phối bước dễ trộn Quá trình xếp kết thúc vùng phân phối còn Run Khi kết thúc, vùng phân phối gồm các phần tử A[n+1] A[2*n] thì ta chép dãy A[n+1] A[2*n] vào dãy A[1] A[n] Thể Pascal (8) Procedure mergesort ( l , r : integer ); Var i , j , k , m: integer; Begin If r-l>0 then Begin m := (r+l) div 2; mergesort (l,m); mergesort (m+1,r); for i:=m downto l b [i] := a [i]; for j:=m+1 to r b [r+m+1-j] := a [j]; for k := l to r if b[i] < b [j] then begin a [k] := b[i]; i:=i+1; end else begin a [k] := b[j]; j:=j-1; end; end; End; End; II Hình học: - Điểm là đối tượng sở hình học Mỗi điểm mà chúng ta xét sau đây biểu diễn cặp số nguyên- toạ độ điểm đó hệ trục Descart thường dùng - Một đoạn thẳng là cặp điểm nối với phần đường thẳng - Một đa giác là danh sách các điểm, với hai điểm cạnh nối đường thẳng và điểm đầu nối với điểm cuối tạo thành hình đóng Thông thường chúng ta dùng mảng để biểu diễn đa giác, dù số trường hợp ta có thể dùng danh sách liên kết hay các kiểu khác Hầu hết các chương trình chúng ta dùng kiểu sau đây: Type point = record x , y : integer; end; line = record p1 , p2 : pointer; end; Var polygon : array [0 nmax] of point; Chú ý các điểm biểu diễn trên toạ độ nguyên, có thể dùng số thực dùng tọa độ nguyên thì thuật toán đơn giản nhiều và phép tính thực nhanh đáng kể Nhiều đối tượng hình học phức tạp biểu diễn dựa trên các phần tử sở này 1/Giao các đoạn thẳng: Trong bài học sơ cấp đầu tiên, chúng ta xét xem đoạn thẳng có giao hay không Một phương pháp dễ hiểu để giải bài toán này là tìm giao điểm các đường thẳng xác định các đoạn thẳng đó kiểm tra xem nó có nằm hai điểm đầu hai đoạn thẳng đó hay không Một cách dễ dàng khác là xem thử xem đường từ điểm thứ sang điểm thứ sang điểm thứ theo chiều kim đồng hồ hay ngược chiều kim đồng hồ: Function ccw ( p0 , p1 , p2 : pointer ) : integer; Var dx1 , dx2 , dy1 , dy2 : integer; Begin dx1:=p1.x; dy1:=p1.y-p0.y; dx2:=p2.x; dy2:=p2.y-p0.y; If dx1*dy2>dy1*dx2 then ccw:=1; If dx1*dy2<dy1*dx2 then ccw:=1; If dx1*dy2=dy1*dx2 then Begin If (dx1*dx2<0) or (dy1*dy2<0) then ccw:=-1 else If (dx1*dx1+dy1*dy1) >= (dx2*dx2+dy2*dy2) then ccw := else ccw := 1; End; End; Để hiểu chương trình hoạt động nào, đầu tiên ta giả sử tất các giá trị dx1 , dx2 , dy1 , dy2 dương Sau đó nhận xét độ dốc đường nối p0 với p1 là dy1 / dx1, đường nối p0 với p2 là dy2 / dx2 Do đó, độ dốc đường thứ hai lớn đường thứ thì đường từ p0 sang p1 , p2 là ngược chiều kim đồng hồ và ngược lại So sánh độ dốc bất tiện vì đường có thể theo phương thẳng đứng ( dx1 hay dx2 = ), chúng ta tính tích dx1 * dx2 để tránh trường hợp này Do đó độ dốc không cần phải dương đúng Hàm ccw trả lại giá trị cho trường hợp p2 p0 và p1, -1 p0 p2 và p1 và p1 p0 và p2 thì chúng ta gán ccw = (9) Chúng ta có thể dùng trực tiếp ccw để cài đặt hàm intersect ( xét giao ) Nếu hai đầu đoạn thẳng hai bên đoạn kia, nghĩa là có giá trị ccw khác thì chúng giao nhau: Function intersect ( l1 , l2 : line ) : boolean; Begin intersect:=(( ccw(l1.p1,l1.p2,l2.p1) * ccw(l1.p1,l1.p2,l2 p2)) <= 0) and (( ccw(l1.p1,l1.p2,l2 p1) * ccw(l1.p1,l1.p2,l2 p2)) <= 0); End; Giải pháp này có vẽ đã dùng số lượng lớn tính toán để giải bài toán đơn giản Người đọc hãy mạnh dạn thử tìm phương pháp đơn giảm chú ý phải đầy đủ các trường hợp 2/ Đường khép kín đơn: Để thấy đặc điểm riêng bài toán ứng với tập hợp các điểm, chúng ta xét bài toán tìm đường đi, qua tập hợp n điểm xác định, qua tất các điểm, không giao và cuối cùng trở điểm bắt đầu Đường trên gọi là đường khép kín đơn Để giái bài toán này ta phải chọn điểm làm "điểm gốc" Sau đó tính góc tạo cách vẽ đờng từ điểm tập hợp đến gốc vẽ theo phương ngang Sau đó, thứ tự các điểm theo thứ tự tăng dần góc tương ứng, cuối cùng nối các điểm cạnh lại Gọi dx , dy là khoảng cách từ điểm gốc đến điểm khác theo trục hoành và tung thì góc cần tính giải thuật này là cotan dy / dx Nhưng hàm này có vẽ chậm, vì ta có thể thay hàm khác tương tự dễ tính hơn, đó là dy / ( dy + dx ) Chương trình sau trả lại giá trị từ đến 360, không phải là góc tạo p1 và p2 so với phương ngang có cùng thuộc tính góc đó Function theta ( p1 , p2 : pointer ) : real; Var dx , dy , ax , ay : integer; Begin dx:=p2.x - p1.x; ax:= abs(dx); dy:=p2.y - p1.y; ay:= abs(dy); If (dx=0) and (dy=0) then t:=0 else t:=dy/(ax+ay); If dx < then t:=2-t else If dy < then t:=4+t; theta := t*90; End; 3/ Điểm nằm đa giác: Tiếp theo, chúng ta xét bài toán tự nhiên: cho điểm và đa giác biểu diễn mảng các điểm, xác định xem điểm này nằm hay ngoài đa giác Một giải pháp dễ hiểu để giải bài toán này là vẽ đưọan thẳng dài điểm đó theo hướng và đếm số lượng đoạn thẳng tạo nó cắt qua đa giác Nếu là số lẽ, điểm đó nằm đa giác và ngược lại điều này dễ thấy chúng ta theo dõi gì xãy từ điểm nằm ngoài đa giác Tuy nhiên, không phải thế, vì số giao điểm có thể trùng với đa giác, đoạn thẳng dùng để kiểm tra trùng với cạnh đa giác Nhu cầu xử lý các tình các đỉnh cảu đa giác rơi trên đoạn thẳng kiểm tra buộc chúng ta phải làm nhiều là đếm số giao điểm các cạnh đa giác với đoạn thẳng kiểm tra Thực chất, chúng ta muốn vòng quanh đa giác, tăng biến đếm nào ta di từ bên đoạn thẳng kiểm tra sang bên khác Một cách để thực là đơn giản bỏ qua các điểm rơi trên đoạn thẳng kiểm tra, chương trình sau đây: Function inside (t:point):boolean; Var count,i,j:integer; lt,lp:line; Begin count:=0; j:=0; p[0]:=p[N]; p[N+1]:=p[1]; lt.p1:=t; lt.p2:=t; lt.p2.x:=maxint; For i:=1 to N Begin lp.p1 := p [i]; lp.p2 := p [i]; If not intersect (lp,lt) then Begin lp p2 := p [j]; j := i; If intersect (lp,lt) then count := count + 1; End; (10) End; inside := ((count mod 2) = 1); End; Chương trình này dùng đường thẳng kiểm tra theo phương ngang để dễ tính toán Biến j dùng để lưu trữ số điểm cuối cùng đa giác mà không nằm trên đoạn kiển tra Chương trình giả sử p [ ] là điểm có tọa độ x nhỏ số tất các điểm có tọa độ y nhỏ nhất, vì p [ ] nằm trên đoạn kiểm tra thì p [ ] không thể Cùng đa giác có thể biểu diễn N mảng khác nhau, điều này cho thấy áp dụng quy luật chuẩn cho p [ ] đôi lại tiện lợi Nếu điểm kết tiếp trên đa giác mà không nằm trên đoạn kiểm tra, cùng phía điểm thứ j đoạn kiểm tra thì chúng ta không cần phải tăng biến đếm giao điểm ( count ) Ngược lại, chúng ta có giao điểm Nếu đa giác có hay cạnh, thường gặp nhiều ứng dụng thì chương trình phức tạp không sử dụng, thủ tục đơn giản đặt sở trên việc gọi ccw thích hợp Một trường hợp đặc biệt quan trọng khác lá đa giác lồi, xét chương kế, nó có đặc điểm là không có đoạn kiểm tra nào có giao điểm với đa giác Trong trường hợp này, thủ tục tìm nhị phân có thể dùng để xác định O ( log N ) bước để biết điểm có bên hay không III Cặp ghép: Giới thiệu chung: Xét hai tạp hữu hạn X, Y gồm n phần tử: X= { x1, x2, xn } Y= { y1, y2, yn } Cặp phần tử (x, y) với x thuộc X, y thuộc Y gọi là cặp ghép Hai cặp ghép (x , y) và (x1, y1) gọi là rời x # x1 và y # y1 Tập M gồm các cặp ghép rời gọi là tập cặp ghép Thông thường bài toán xây dựng các cặp ghép tiếp cận theo hướng: Hoặc thoả mãn số điều kiện ghép cặp nào đấy, đó người ta quan tâm đến khả ghép cặp tối đa, lượng hoá việc ghép cặp, đó người ta quan tâm đến việc tối ưu hoá theo các giá trị đã lượng hoá Vì số tập cặp ghép là hữu hạn, nên có phương pháp xây dựng tầm cỡ là thử tất các khả Tuy nhiên, số khả lớn (cỡ n!) Vì thế, người ta quan tâm đến việc tìm kiếm thuật giải hữu hiệu, dễ cài đặt chương trình và có tính khả thi cao Bài toán này nhằm giới số mô hình ghép cặp Cặp ghép đầy đủ tối ưu a Giới thiệu bài toán Một cặp ghép, cho tất các phần tử X và Y ghép cặp (nghĩa là có đủ n cặp với n là số phần tử X và Y), gọi là ghép cặp đầy đủ Rõ ràng song ánh p: X -> Y xác định tập ghép cặp đầy đủ, đó cặp ghép viết dạng (x , p(x)), x thuộc X Từ đó suy có tất n! cách xây dựng tập cặp ghép đầy đủ khác Với các tập cặp ghép đầy đủ, cách tự nhiên, người ta quan tâm đến tập cặp ghép "tốt nhất" theo nghĩa nào đó đã lượng hoá Tập cặp ghép này gọi là "Tập cặp ghép đầy đủ và tối ưu", Bài toán tìm cặp ghép đầy đủ tối ưu có nhiều mô hình ứng dụng thực tế Một mô hình này người ta quan tâm dến việc ghép cặp cho có hiệu qủa Để lượng hoá việc ghép cặp phần tử x thuộc X với phần tử y thuộc Y, người ta đưa vào ma trận trọng số Cij (i,j = 1, 2, , n) với ý nghĩa Cij mô tả hiệu việc ghép xi với ỵ Bài toán đặt là: Xây dựng tập cập ghép đầy dủ có tổng hiệu lớn Bài toán vừa nêu thường phát biểu dạng mô hình thực tế là bài toán phân công đây: Bài toán phân công: Có n người và n công việc Biết Cij là số tiền làm giao công việc j cho người thứ i thực Hãy tìm cách phân công người việc để tổng số tiền làm là lớn b Phương pháp tham lam Đây là phương pháp gần đúng, xuất phát từ việc chọn tối ưu bước vì nó không đảm bảo tính tối ưu toàn cục Tuy nhiên, nó cho phương án, gần đúng với phương án tối ưu: Xuất phát từ Cij đóng vai trò bảng hành Tập cặp ghép khởi gán rỗng Tìm dòng i cột j khỏi bảng hành và lặp lại bước thứ bảng rỗng Xoá dòng i, cột j khỏi bảng hành và lặp lại bước bảng rỗng Thí dụ, xét bảng trọng số với n = 4: (11) 2516 8764 6935 5127 các cặp ghép chọn là (1 7) (x3, y2), (x2, y1), (x4, y4), (x1, y3) Với phương án trên ta có tổng trọng số là 25 Trường hợp này các cách ghép tìm chưa phải là cặp ghép đầy đủ và tối ưu( xem lại ví dụ này dưới) c Định lý sở Việc xây dựng tập cặp ghép đầy đủ tối ưu dựa vào dấu hiệu nhận biết tập ghép đầy đủ nào là tối ưu Dĩ nhiên việc thử dấu hiệu này không phải là việc so sánh với tất các cặp ghép, mà phải mang tính khả thi Để lam điều này người ta xây dựng hàm số F, xác định trên tập các phần tử Xi thuộc X , Yj thuộc Y , mà ta gọi là nhãn các phần tử Nhãn F gọi là nhãn chấp nhận thõa mãn bất đẳng thức F(Xi)+F(Yj)>=Cij với Xi thuộc X , Yj thuộc Y Tập cặp ghép M và nhãn F gọi là tương thích với thoã mãn đẳng thức F(Xi)+F(Yj)=Ci với (Xi,Yj)thuộc M , Noi riêng , tập cặp ghép rỗng xem tương thích với nhãn Định lý: Tập cặp ghép đầy đủ M* là tối ưu tồn nhãn F chấp nhận là tương thích với nó Dựa vào định lý vừa chứng minh , người ta có hướng tiếp cận cặp ghép đầy đủ tối ưu : * Một là , xuất phát từ cặp ghép đầy đủ M nào đó , người ta xây dựng nhãn F tương thích với M Nếu F chấp nhận , thì M tối ưu Trái lại , người ta điều chỉnh M F tương thích là chấp nhận đó M là tội ưu * Hai là , xuất phát từ nhãn F chấp nhận và cặp ghép M tương ứng với F ( có thể rỗng ) , người ta tăng dần số cặp ghép M cho đảm bảo tim nhãn F tương thích với M chấp nhận Quá trình tăng kết thúc M đầy đủ và đó M là tối ưu Dưới đây trình bầy thuật toán tim cặp ghép đầy đủ tội ưu theo hướng thứ hai d Thuật toán Kuhn-Munkes Nội dung chủ yếu phương pháp là xuất phát từ tập cặp ghép nào đó chưa đâỳ đủ (co thể lập hợp rỗng ) , ta tăng dần số cặp ghép cho trở thành đầy đủ , các cặp ghép thu đồng thời thoã mãn tính tối ưu Có nhiều hình thức trình bày phương pháp này Dưới đây là cách trình bầy trên ngôn ngữ đồ thị với các thao tác tìm kiếm Cách này có nhiều ưu điểm : trực giác , dễ pháp biểu , dể chứng minh và đặc biệt , dể cài đặt chương trình vì việc tìm dường trên đồ thị là thao tác và quen thuộc Giả sử F là nhãn chấp nhận và M tập cặp ghép tương thích với F Xem các phần tử X và Y đỉnh đồ thị có hai hướng (một phía X phía Y ) Các cạnh đồ thị này xác định tuỳ thuộc nội dung nhãn F và tập cặp ghép M sau : - Mỗi cặp phần tử Xi thuộc X , Yj thuộc Y thoã mãn đẳng thức F(Xi)+F(Yj)=Cij xác định cạnh đồ thị , - Cạnh này có hướng từ X sang Y cặp (Xi ,Yj) không thuộc M gọi (là cạnh thuận) và ngược lại , có xu hướng từ Y sang X cặp (Xi ,Yj) thuộc M (gọi là cạnh nghịch) Đồ thị xây dựng theo quy tắc vừa nêu gọi là đồ thị cân tương ứng với F , M và ký hiệu là G(F,M) Bước 1: Khởi tạo : Xây dựng nhãn F chấp nhận sau : F( xi ) := Max {C[ i , j ] , yj thuộc Y } F( yj ) := yj thuộc Y M là tập cặp ghép rỗng Chú ý , có thể xuất pháp từ nhãn F nào chấp nhận và bất ký tập cặp ghép M nào tương ứng với F Bước 2: Tìm đỉnh tự thuộc X: Tìm đỉnh u thuộc X chưa ghép cặp Nếu không còn đỉnh nào X chưa ghép cặp thì kết thúc, tập cặp ghép M hành là tập cập ghép tối ưu Trái lại sang bước Bước 3: Xuất phát từ u, thực việc tìm kiếm trên đồ thị G(F, M) Kết tìm có hai trường hợp sau: - Nếu đến đỉnh z thuộc Y chưa ghép cặp thì ghi nhận đường từ u -> z (gọi là đường tăng cặp ghép) và chuyển sang bước tăng cặp ghép trên đường này - Nếu không tồn đường đị thì chuyển sang bước sửa nhãn F Bước 4: Tăng cặp ghép: Điều chỉnh M sau: (12) - Giữ nguyên cặp ghép M nằm ngoài đường tăng cặp ghép - Trên đường tăng cặp ghép, thay đổi cặp ghép M (cạnh ngược) băng cặp ghép cạnh thuật (về mặt đồ thị nghĩa là đổi chiều các cạnh trên đường tăng cặp ghép) Sau bước này, số cặp ghép thuộc M tăng them và đỉnh u trở thành đã ghép cặp, ngoài ra, tính tương thích F và M bảo toàn Sau đó quay lại bước để thực việc sửa nhãn tự khác Bước 5: Sửa nhãn: Gọi S là tập các đỉnh thuộc X và T là tập cặp ghép thuộc Y đã quá trình tìm đường bước Việc sửa nhãn tiến hành sau: - Tìm lượng sửa nhãn: d := Min { F(xi) + F(yj) - C[i,j] , yj thuộc T} - Gán lại nhãn: F(xi) := F(xi) - d với xi thuộc S F(yj) := F(yj) + d với yj thuộc T Sau đó, quay trở lại bước để lặp lại tìm đường tăng cặp ghép (với đỉnh xuất phát u cũ và nhãn F mới) Chú ý rằng, sau thay đổi, nhãn F giữ nguyên tính chấp nhận và tính tương thích M Ngoài có thêm ít cặp (xi, yj) thoả mãn F(xi) + F(yj) = C[i,j], vì thế, sau số lần đổi sữa nhãn, chắn tăng cặp ghép Dưới đây là sơ đồ minh hoạ các bước đã nêu: (Sơ đồ) Nhận xét rằng, tăng cặp ghép, các đỉnh đã tiến ghép cặp không trở thành tự do, vì việc tìm đỉnh tự có thể tiến hành Quá trình tìm tập cặp ghép đầy đủ tối ưu có thể mô qua doạn chương trinh sau: <Khởi tạo nhãn và tập cặp ghép ban đầu> FOR <U thuộc X> IF <u còn tự do> THEN BEGIN WHILE NOT <Tìm thấy đường tăng cặp ghép từ u> DO <sửa nhãn> END; e Ví dụ Quay trỏ lại thí dụ trước Nhãn F chấp nhận ban đầu là: F 0000 2516 8764 6935 5127 Xuất phát từ cặp ghép M rỗng, tăng cặp ghép cho các đỉnh tự x1, x2, x3, ta nhận M={ (x1, y4), (x2, y1), (x3, y2) } và đồ thị G(F,M) tương ứng: Việc tìm đường tăng cặp ghép tương đỉnh tự x4 không tìm thấy và cho ta các tập S = {x1, x4}, T ={y4) Tiến hành sữa nhãn trên các tập cặp ghép này, ta d=1 và nhãn F là F 0001 2516 8764 6935 5127 (13) nhãn F này thêm cẵp1, y2 thoả mãm F(x1) + F(x2) = C[1,2] và ta đồ thị G(F,M): Đường tăng cặp ghép x4 không tồm Tuy nhiên S và T đã mở rộng thêm: S ={x1, x3, x4} và T = {y2, y4} và lượng nhãn sủa trên các tập này là d = 1: F 0102 8 2516 8764 6935 5127 Lần này F thêm cặp x4, y1 thoả mãm F(x4) + F(y1) = C[4,1] và đồ thị G(F,M): Việc tìm kiếm chưa kết thúc và cho S = {x1, x2, x3, x4}, T = {y1, y2, y4} Lượng nhãn sửa lần này là 2, và ta nhận được: F 2304 6 2516 8764 6935 5127 cùng với đồ thị G(F,M) (thêm cạnh x2, y3): Việc tìm kiếm trên đồ thị này kết thúc y1 cho ta đường tăng cặp ghép: x4 -> y1 -> x2 -> y3 và trên đường này, cặp ghép cũ (x2, y1) thay cặp ghép (x2, y3) và (x4, y1) Kết cuối cùng cho tập cặp ghép đầy đủ tối ưu M = {(x1, y4), (x2, y1), (x3, y2), (x4, y1)} với tổng trọng số là + + + = 26 (14)