Tải bản đầy đủ (.docx) (13 trang)

Cac thuat toan HSG Pacl ve mang

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (149.22 KB, 13 trang )

<span class='text_page_counter'>(1)</span>Sơ lược về các chủ đề Sau đây là sơ lược về các chủ đề sẽ được đề cập trong phần này của chương trình: + Phần cơ sở: là các công cụ và phương pháp được dùng xuyên suốt cho tất cả các chương sau của phần này. Nó gồm một phần bàn luận ngắn về Pascal, theo sau là giới thiệu về các cấu trúc dữ liệu cơ bản gồm mảng, xâu liên kết, ngăn xếp, hàng đợi và cây. Chúng ta sẽ bàn luận về 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 sắp xếp sẽ được phát triển, được mô tả, được so sánh với nhau. Các thuật toán cho nhiều vấn đề có liên quan sẽ được 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 nền tảng trong số này được dùng như là nền tảng cho các thuật toán khác tiếp sau trong phần này. + Xử lý chuỗi: gồm một 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ã cũng sẽ được khảo sát. Cũng vậy, một giới thiệu về các chủ đề nâng cao cùng được cung cấp qua việc xem xét một vài bài toán cơ bản quan trọng trong phạm vi của chúng. + Hình học: là một sự tập hợp có chọn lọc các phương pháp để giải quyết 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 sẽ xem xét các thuật toán để tìm bao lồi của một tập điểm, phần giao của 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ị sẽ được phát triển và được áp dụng cho các bài toán liên thông cơ bản, gồm có đường đi ngắn nhất, cây liên thông tối thiểu, mạng và so khớp. Một sự xem xét thống nhất đối với các thuật toán này chứng tỏ rằng tất cả đều dựa vào một thủ tục và thủ tục này phụ thuộc vào một cấu trúc dữ liệu cơ bản đã phát triển trước đó. + Các thuật toán toán học: gồm các phương pháp cơ bản từ số học và các số nguyên, đa thức, và ma trận cũng như các thuật toán để giải quyết cac vấn đề toán học mà nó phát sinh trong nhiều ngữ cảnh : việc tạo số ngẫu nhiên, lỡi giải của các chương trình đồng thời, xấp xỉ dữ liệu, và lấy tích phân. Sự nhấn mạnh thiên về các khía cạnh thuật toán của phương pháp, chứ không phải trên nền tảng của toán học + Các chủ đề cao cấp: được thảo luận nhằm mục đích liên hệ các chủ đề trong 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: 1. Khái niệm: a) Sắp xếp là một quá trình tổ chức lại một dãy các dữ liệu theo một trật tự nhất định. b) Mục đích của việc sắp xếp là nhằm giúp cho việc tìm kiếm dữ liệu một cách dễ dàng và nhanh chóng. Sắp xếp là một việc làm hết sức cơ bản và được dùng rộng rãi trong các kĩ thuật lập trình nhằm sử lý dữ liệu. c) Các giải thuật sắp xếp được phân chia thành hai nhóm chính là: - Sắp xếp trong (hay sắp xếp mảng). Toàn bộ cơ sở dữ liệu cần sắp xếp phải được đưa vào bộ nhớ chính của máy tính. Do đó nó thường được sử dụng khi khối lượng dữ liệu không vượt quá dung lượng bộ nhớ chính. Nhóm sắp xếp trong 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 sắp xếp tập tin). Vận dụng trong trường hợp ta phải sắp xếp các tập tin chứa nhiều mẫu tin và mỗi mẫu tin có chiều dài tương đối lớn do đó ta không thể nạp toàn bộ vào bộ nhớ chính để sắp xếp thứ tự. Vì vậy ta phải có những phương pháp thích hợp cho việc sắp xếp tập tin. 2. Sắp xếp trong: a) Khái niệm: Cấu trúc dữ liệu thích hợp cho các phần tử cần sắp 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á của phần tử và được sử dụng trong 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.

<span class='text_page_counter'>(2)</span> key : Integer; Info : Integer; End; Var A : Array[1..n] of Item; Khoá của phần tử có thể là chữ hoặc số. Yêu cầu giải thích là dùng ít vùng nhớ và thời gian thực hiện nhanh. b) Phương pháp đếm (Counting sort) * Giải thích: Nội dung của phương pháp này là đếm các phần tử có khoá nhỏ hơn hay bằng khoá của các phần tử A[i]. Nếu có j phần tử có khoá nhỏ hơn khoá của phần tử A[i] thì phần tử A[i] sẽ có vị trí theo thứ tự (j+1) trong 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ỏ hơn khoá của phần tử A[i]. Như vậy Count[i+1] là vị trí của phần tử A[i] trong dãy đã có thứ tự. * Ví dụ: Sắp xếp dãy 2 3 1 5 2 7 6 9 4 i: 1 2 3 4 5 6 7 8 Count: 20416573 Như vậy phần tử có khoá 9 ở vị trí 8 vì Count[9]=7. * Thể hiện bằng Pascal: Procedure Count_Sort; Var Count : Array[1..n] of Integer; A : Array[1..n] of Item; i,j : Integer; Begin For i := 1 to n do Count[i] := 0; For i := n downto 2 do For j := i-1 downto 1 do If A[i].Key < A[j].Key Then Count[j] := Count[j] + 1 Else Count[i] := Count[i] + 1; For i := 1 to n do S[Count[i] + 1] := A[i]; For i := 1 to n do A[i] := S[i]; End; c) Phương pháp chèn (Insertion Sort) * Giải thích: Nội dung của 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 của phần tử A[i] trong dãy A[1]..A[i - 1] bằng phương pháp tìm kiếm tuần tự từ A[i - 1] trở về A[1] để tìm ra vị trí thích hợp của A[i]. Ta chèn A[i] vào vị trí này và kết quả 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 sắp xếp dãy số: 39 50 7 37 89 13 1 62 i=2 39 50 7 37 89 13 1 62 i=3 39 50 7 37 89 13 1 62 i=4 7 39 50 37 89 13 1 62 i=5 7 37 39 50 89 13 1 62 i=6 7 37 39 50 89 13 1 62 i=7 7 13 37 39 50 89 1 62 i=8 1 7 13 37 39 50 89 62 1 7 13 37 39 50 89 62 * Thể hiện bằng Pascal: Procedure Insertion_Sort; Var. x : Item; i,j : Integer;.

<span class='text_page_counter'>(3)</span> Begin Begin For i := 2 to n do 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 do End; d) Phương pháp chọn (Selection Sort) * Giải thích:Nội dung của 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ỏ nhất trong dãy A[i]..A[n] rồi đổi chổ phần tử này với phần tử A[i]. Cuối cùng ta sẽ có dãy A[1]..A[n] có thứ tự. * Ví dụ: Ta phải sắp xếp dãy số : 39 50 7 37 89 13 1 62 i=1 39 50 7 37 89 13 1 62 i=2 1 50 7 37 89 13 39 62 i=3 1 7 50 37 89 13 39 62 i=4 1 7 13 37 89 50 39 62 i=5 1 7 13 37 89 50 39 62 i=6 1 7 13 37 50 50 39 62 i=7 1 7 13 37 39 50 89 62 1 7 13 37 39 50 89 62 * Thể hiện bằng Pascal: Procedure Selection_Sort; For j := i+1 to n do Var If A[j].Key < min Then x : Item; Begin i,j ,k : Integer; min := A[j].Key; min : Integer; k := j; Begin End; For i := 2 to n-1 do x := A[k]; Begin A[k] := A[i]; min := A[i].Key; A[i] := x; k := i; End; For i := 1 to n-1 do End; e) Phương pháp đổi chỗ: Có rất nhiều phương pháp sắp xếp dựa trên việc đổi chỗ giữa 2 phần tử của 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 của 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 cho đến khi không còn việc đổi chổ của hai phần tử. Chú ý rằng bất cứ lúc nào phần tử nhỏ nhất cũng gặp trước tiên. Nó như những bột khí nhẹ sẽ nổi lên trên khi đun nước. Sau đó ở thứ hai phần tử nhỏ thứ 2 sẽ được đặ vào đúng một vị trí. Vì vậy sắp xếp nổi bột thao tác như một kiểu sắp xếp chọn, mặc dù nó không làm nhiều việc hơn để đưa từng phần tử vào đúng vị trí. * Ví dụ: Ta phải sắp xếp dãy số: 39 50 7 37 89 13 1 62 Bước 0 1 2 3 4 5 6 7 50 1 1 1 1 13 1 62 39 39 7 7 7 7 7 7.

<span class='text_page_counter'>(4)</span> 7 50 39 13 13 13 13 13 37 7 50 39 37 37 37 37 89 37 13 50 39 39 39 39 13 89 37 37 50 50 50 50 1 13 89 62 62 62 62 62 62 62 62 89 89 89 89 89 * Thể hiện bằng 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 := 2 to n do End; For j := n downto i do End; * Cải tiến: Ta nhận thấy rằng nếu ở một lần duyệt dãy nào đó mà không co s xẩy ra sự đổi chổ giữa hai phần tử thì dãy dang sắp đã 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 := 1 to n-1 do 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 do End; Begin End; flag := False; f* Shake Sort: * Giải thích: Phương pháp này là một cải tiến của phương pháp Bubble Sort theo hướng "Không những phần tử nhẹ nổi lên trên mà cả phần tử nặng cũng xuống dưới" giống như khi ta rung"rung" một cái nồi và thuật toán sắp xếp phải được điều khiển cả hai quá trình "nổi lên" và "chìm xuống" này một cách tự giác. Muốn vậy ta phải ghi nhớ lần đổi chổ cuối cùng khi duyệt dãy từ trên lên và khi duyệt từ trên xuoóng để quyết định chu trình kế tiếp sẽ duyệt từ đâu đến đâu. * Ví dụ: Sắp xếp dãy số: 39 50 7 37 89 13 1 62 d= 2 3 3 4 4 c= 8 8 7 7 4 39 1 1 1 1 50 39 39 7 7 7 50 7 39 13 37 7 37 13 37 89 37 50 37 39 13 89 13 50 50 1 13 62 62 62 62 62 89 89 89 * Thể hiện bằng Pascal: Procedure Shake_Sort; Var x : Item; i,k,d,c : Integer;. Begin d := 2; c := n; k := n;.

<span class='text_page_counter'>(5)</span> Repeat If A[i-1].Key > A[i].Key Then For i := c downto d do 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 do g. * Shell Sort: * Giải thích: Các phương pháp sắp xếp dã trình bày ở trên nói chung đều di chuyển mỗi phần tử đi một vị trí trong mỗi 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 nhau. Để làm được việc đó ta cần phải sắp các tập tin để nó có tính chất là việc lấy mọi phần tử thứ h (bắt đầu từ vị trí bất kì nào) cũng đều cho ra tập tin đã sắp. Một tập tin như vậy được gọi là sắp theo độ dài bước h. Một cách nói khác, một tập tin dược sắp theo độ dài bước h là tập tin được sắp độc lập với nhau, đan xen vào nhau. Bằng cách sắp 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ử ở những khoảng cách xa nhau trong mảng và vì vậy dễ dàng hơn để sắp xếp độ dài bước h các giá tri nhỏ hơn. Dùng thủ tục cho bất kì một dãy các giá trị của h tận cùng là 1 sẽ cho ra một tập tin đã sắp xong: Dó chính là Shell Sort. * Ví dụ: Ta phải sắp xếp dãy số: 39 50 7 39 89 13 1 62 Bước 1: 4-Sort 39 50 7 39 89 13 1 62 39 13 1 37 89 50 7 62 Bước 2: 2-Sort 39 13 1 37 89 50 7 62 1 13 7 37 39 50 89 62 Bước 3: 1-Sort 1 13 7 37 39 50 89 62 1 7 13 37 39 50 89 62 * Thể hiện bằng Pascal: Chú ý: - Ta dùng dãy phụ chứa dộ tăng h[i], ..., h[t] để điều khiển quá trình sắp xếp thứ tự với h[t]=1. Việc chộn các độ tăng thích hợp sẽ làm giảm thời gian sắp thứ tự. - Dặt h1 = h[1] ta phải khai báo dãy a như 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] = 1 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 := 1 to t do.

<span class='text_page_counter'>(6)</span> Begin k := h[m]; s := -k; For i := k+1 to n do Begin x := A[i]; j := i - k; If s = 0 Then s:=-k; s := s + 1; A[s] := x; While x.Key<A[j].Key do 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 của phương pháp này là chọn phần tử x ở giữa của dãy làm chuẩn để so sánh. Ta phân hoạch dãy này thành 3 dãy con liên tiếp nhau: - Dãy con thứ nhất gồm phần tử có khoá nhỏ hơn x.key. - Dãy con thứ hai gồm các phần tử có khoá bằng x.key. - Dãy con thứ ba gồm các phần tử có khoá lớn hơn x.key. Sau đó áp dụng giải thuật phân hoạch này cho dãy con thứ nhất nhất và dãy con thứ ba, nếu các dãy con có nhiều hơn một phần tử (Đệ qui). Cụ thể là xét một doạn của dãy từ thành phần L đến thành phần thứ R. - Lấy giá trị của thành phần thứ (L+R) Div 2 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 khi 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 7 37 89 13 1 62 X = 37 Sau 2 lần đổi chổ ta được dãy: 1 13 7 37 89 50 39 62 Xử lý dãy con: 1 13 7 Ta được: 1 7 13 Sử lý dãy con: 89 50 39 62.

<span class='text_page_counter'>(7)</span> Ta được: 39 50 89 62 39 50 62 89 Vậy dãy đã sắp xếp là: 1 7 13 39 50 62 89 * Thể hiện bằng Pascal: Để đơn giản ta viết thủ tục sắp một mảng số nguyên được 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) do Inc(i); End; While (A[j] > X) do 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 của phương pháp này là chia dãy số cần sắp thành các dãy con đã có thứ tự(goi là các Run) và có số phần tử là luỹ thừa 2 sau đó tìm cách trộn dần chúng với nhau thành các Run có thứ tự chiều dài tăng dần cho đến khi chỉ còn một Run thì quá trình sắp xếp kết thúc. Ta có giải thuật sau đây để trộn 2 Run x và y cùng thứ tự có chiều dài lần lượt là m và n thành một 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) do 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 do Begin Z[k] := X[j]; k := k + 1; i := i + 1; End; While j<=n do Begin Z[k] := X[j]; k := k + 1; j := j + 1; End; End;. Cụ thể là nếu ta phải sắp xếp dãy: A[1], A[2], ...,A[n]. Ta phải sử dụng 2*n phần tử được chia thành 2 vùng. Vùng 1 gồm các phần tử A[1] .. A[n], vùng 2 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 kia. Khi trộn và phân phối, ta trộn các Run ngược chiều nhau của vùng trộn và phân phối luân phiên vào 2 đầu của vùng phân phối bước kế tiếp dễ trộn hơn. Quá trình sắp xếp sẽ kết thúc nếu vùng phân phối chỉ còn một Run. Khi kết thúc, nếu 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ể hiện bằng Pascal.

<span class='text_page_counter'>(8)</span> 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 do b [i] := a [i]; for j:=m+1 to r do b [r+m+1-j] := a [j];. for k := l to r do 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 cơ sở trong hình học. Mỗi điểm mà chúng ta xét sau đây được biểu diễn bằng một cặp số nguyên- toạ độ điểm đó trong hệ trục Descart thường dùng. - Một đoạn thẳng là một cặp điểm được nối với nhau bởi 1 phần của đường thẳng. - Một đa giác là một danh sách các điểm, với hai điểm cạnh nhau được nối bởi một đường thẳng và điểm đầu nối với điểm cuối tạo thành một hình đóng. Thông thường chúng ta dùng một mảng để biểu diễn một đa giác, dù rằng trong một 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 trong 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ú ý rằng các điểm được biểu diễn trên toạ độ nguyên, cũng có thể dùng số thực nhưng dùng tọa độ nguyên thì thuật toán sẽ đơn giản hơn nhiều và phép tính thực hiện nhanh hơn đáng kể. Nhiều đối tượng hình học phức tạp sẽ được biểu diễn dựa trên các phần tử cơ 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 sẽ xét xem 2 đoạn thẳng có giao nhau hay không. Một phương pháp dễ hiểu để giải quyết bài toán này là tìm giao điểm của các đường thẳng xác định bởi các đoạn thẳng đó rồi kiểm tra xem nó có nằm giữa hai điểm đầu của hai đoạn thẳng đó hay không. Một cách dễ dàng khác là xem thử xem đường đi từ điểm thứ nhất sang điểm thứ 2 rồi sang điểm thứ 3 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 := 0 else ccw := 1; End; End; Để hiểu được chương trình hoạt động như thế nào, đầu tiên ta giả sử tất cả các giá trị dx1 , dx2 , dy1 , dy2 đều dương. Sau đó nhận xét rằng độ dốc của đường nối p0 với p1 là dy1 / dx1, của đường nối p0 với p2 là dy2 / dx2. Do đó, nếu độ dốc của đường thứ hai lớn hơn đường thứ nhất thì đường đi từ p0 sang p1 , p2 là ngược chiều kim đồng hồ và ngược lại. So sánh độ dốc hơi bất tiện vì đường có thể theo phương thẳng đứng ( dx1 hay dx2 = 0 ), 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 mới đúng. Hàm ccw trả lại giá trị 0 cho trường hợp p2 ở giữa p0 và p1, bằng -1 nếu p0 ở giữa p2 và p1 và nếu p1 ở giữa p0 và p2 thì chúng ta gán ccw = 1..

<span class='text_page_counter'>(9)</span> Chúng ta có thể dùng trực tiếp ccw để cài đặt hàm intersect ( xét giao nhau ). Nếu cả hai đầu của một đoạn thẳng ở hai bên đoạn kia, nghĩa là có giá trị ccw khác nhau 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 một số lượng lớn tính toán để giải quyết một bài toán đơn giản. Người đọc hãy mạnh dạn thử tìm một phương pháp đơn giảm hơn nhưng chú ý phải đầy đủ các trường hợp. 2/ Đường khép kín đơn: Để thấy được đặc điểm riêng của 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 một đường đi, qua một tập hợp n điểm xác định, qua tất cả các điểm, không giao nhau và cuối cùng trở về điểm bắt đầu. Đường đi như trên gọi là đường khép kín đơn. Để giái quyết bài toán này ta phải chọn một điểm làm "điểm gốc". Sau đó tính góc tạo bằng cách vẽ một đờng từ mỗi điểm trong tập hợp đến gốc vẽ ra theo phương ngang. Sau đó, sắp thứ tự các điểm theo thứ tự tăng dần của góc tương ứng, cuối cùng nối các điểm cạnh nhau lại. Gọi dx , dy là khoảng cách từ điểm gốc đến một điểm khác theo trục hoành và tung thì góc cần tính trong giải thuật này là cotan dy / dx. Nhưng hàm này có vẽ chậm, vì vậy ta có thể thay bằng một hàm khác tương tự nhưng dễ tính hơn, đó là dy / ( dy + dx ). Chương trình sau trả lại giá trị từ 0 đến 360, không phải là góc tạo bởi p1 và p2 so với phương ngang nhưng có cùng thuộc tính 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 < 0 then t:=2-t else If dy < 0 then t:=4+t; theta := t*90; End; 3/ Điểm nằm trong đa giác: Tiếp theo, chúng ta sẽ xét một bài toán rất tự nhiên: cho một điểm và một đa giác biểu diễn bằng một mảng các điểm, xác định xem điểm này nằm trong 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ẽ một đưọan thẳng dài bắt đầu từ điểm đó theo một hướng bất kỳ và đếm số lượng đoạn thẳng tạo được do nó cắt qua đa giác. Nếu là số lẽ, điểm đó nằm trong đa giác và ngược lại. điều này dễ thấy nếu chúng ta theo dõi những gì xãy ra khi đi từ một điểm nằm ngoài đa giác. Tuy nhiên, không phải chỉ như thế, bởi vì một số giao điểm có thể trùng với đa giác, hoặc đoạn thẳng dùng để kiểm tra trùng với một cạnh của đa giác. Nhu cầu xử lý các tình huống khi 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 hơn là chỉ đếm số giao điểm của 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 đi vòng quanh đa giác, tăng một biến đếm khi nào ta di từ một bên của đoạn thẳng kiểm tra sang một bên khác. Một cách để thực hiện là đơn giản bỏ qua các điểm rơi trên đoạn thẳng kiểm tra, như trong 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 do. 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;.

<span class='text_page_counter'>(10)</span> 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 được dùng để lưu trữ chỉ số của điểm cuối cùng của đa giác mà không nằm trên đoạn kiển tra. Chương trình giả sử rằng p [ 1 ] là điểm có tọa độ x nhỏ nhất trong số tất cả các điểm có tọa độ y nhỏ nhất, vì vậy nếu p [ 1 ] nằm trên đoạn kiểm tra thì p [ 0 ] không thể. Cùng một đa giác có thể biểu diễn bằng N mảng khác nhau, nhưng điều này cho thấy áp dụng một quy luật chuẩn cho p [ 1 ] đôi khi 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 một phía như điểm thứ j đối với đ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ó được một giao điểm. Nếu đa giác chỉ có 3 hay 4 cạnh, thường gặp trong nhiều ứng dụng thì một chương trình phức tạp như vậy sẽ không được sử dụng, một thủ tục đơn giản đặt cơ sở trên việc gọi ccw sẽ thích hợp hơn. Một trường hợp đặc biệt quan trọng khác lá đối với đa giác lồi, được xét trong chương kế, nó có đặc điểm là không có đoạn kiểm tra nào có hơn 2 giao điểm với đa giác. Trong trường hợp này, một thủ tục như tìm nhị phân có thể dùng để xác định trong O ( log N ) bước để biết được điểm có ở bên trong hay không. III. Cặp ghép: 1. 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 được gọi là một cặp ghép. Hai cặp ghép (x , y) và (x1, y1) được gọi là rời nhau nếu x # x1 và y # y1. Tập M gồm các cặp ghép rời nhau được gọi là một tập cặp ghép. Thông thường bài toán xây dựng các cặp ghép được tiếp cận theo 2 hướng: Hoặc thoả mãn một số điều kiện ghép cặp nào đấy, khi đó người ta quan tâm đến khả năng ghép cặp tối đa, hoặc lượng hoá việc ghép cặp, khi đó 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ó một phương pháp xây dựng tầm cỡ là thử tất cả các khả năng. Tuy nhiên, số khả năng như vậy rất lớn (cỡ n!). Vì thế, người ta quan tâm đến việc tìm kiếm những 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 một số mô hình ghép cặp như vậy. 2. Cặp ghép đầy đủ tối ưu a. Giới thiệu bài toán Một cặp ghép, sao cho tất cả các phần tử của X và Y đều được ghép cặp (nghĩa là có đủ n cặp với n là số phần tử của X và Y), được gọi là ghép cặp đầy đủ. Rõ ràng mỗi song ánh p: X -> Y xác định một tập ghép cặp đầy đủ, trong đó mỗi cặp ghép được viết dưới dạng (x , p(x)), x thuộc X. Từ đó suy ra có tất cả n! cách xây dựng tập cặp ghép đầy đủ khác nhau. Với các tập cặp ghép đầy đủ, một cách tự nhiên, người ta quan tâm đến tập cặp ghép "tốt nhất" theo một nghĩa nào đó đã được lượng hoá. Tập cặp ghép này được 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 trong những mô hình này người ta quan tâm dến việc ghép cặp sao cho có hiệu qủa nhất. Để lượng hoá việc ghép cặp mỗi phần tử x thuộc X với một 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 quả của việc ghép xi với ỵ. Bài toán được đặt ra là: Xây dựng một tập cập ghép đầy dủ có tổng hiệu quả lớn nhất. Bài toán vừa nêu thường được phát biểu dưới dạng một mô hình thực tế là bài toán phân công dưới đâ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 ra nếu giao công việc j cho người thứ i thực hiện. Hãy tìm cách phân công mỗi người mỗi việc để tổng số tiền làm ra là lớn nhất. b. Phương pháp tham lam Đây là một phương pháp gần đúng, xuất phát từ việc chọn tối ưu trong từng bước vì thế nó không đảm bảo được tính tối ưu toàn cục. Tuy nhiên, nó cho ngay một phương án, gần đúng với phương án tối ưu: 1. Xuất phát từ bản Cij đóng vai trò bảng hiện hành. Tập cặp ghép được khởi gán bằng rỗng. 2. Tìm dòng i cột j ra khỏi bảng hiện hành và lặp lại bước thứ 2 cho đến khi bảng rỗng. 3. Xoá dòng i, cột j ra khỏi bảng hiện hành và lặp lại bước 2 cho đến khi bảng rỗng. Thí dụ, xét bảng trọng số với n = 4:.

<span class='text_page_counter'>(11)</span> 2516 8764 6935 5127 các cặp ghép được chọn lần lượt là (1 8 9 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 được 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ý cơ 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 một tập ghép đầy đủ khi 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 cặp ghép, mà phải được 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ủa các phần tử Xi thuộc X , Yj thuộc Y , mà ta sẽ gọi là nhãn của các phần tử . Nhãn F được gọi là nhãn chấp nhận được nếu thõa mãn bất đẳng thức F(Xi)+F(Yj)>=Cij với mọi Xi thuộc X , Yj thuộc Y . Tập cặp ghép M và nhãn F được gọi là tương thích với nhau nếu thoã mãn đẳng thức F(Xi)+F(Yj)=Ci với mọi (Xi,Yj)thuộc M , Noi riêng , tập cặp ghép rỗng được xem như tương thích với mọi nhãn . Định lý: Tập cặp ghép đầy đủ M* là tối ưu khi tồn tại nhãn F chấp nhận được là tương thích với nó . Dựa vào định lý vừa chứng minh , người ta có 2 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 một nhãn F tương thích với M . Nếu F chấp nhận được , thì M tối ưu . Trái lại , người ta điều chỉnh M cho đến khi F tương thích là chấp nhận được khi đó M là tội ưu . * Hai là , xuất phát từ một nhãn F chấp nhận được và một cặp ghép M bất kỳ tương ứng với F ( có thể rỗng ) , người ta tăng dần số cặp ghép M sao cho vẫn đảm bảo tim được nhãn F tương thích với M chấp nhận được . Quá trình tăng sẽ kết thúc khi M đầy đủ và khi đó M là tối ưu . Dưới đây trình bầy một 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 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 đủ , các cặp ghép thu được cũng đồ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à một thao tác cơ bản và quen thuộc . Giả sử F là một nhãn chấp nhận được và M một tập cặp ghép tương thích với F . Xem các phần tử của X và Y như những đỉnh của đồ thị có hai hướng (một phía X một phía Y ). Các cạnh của đồ thị này được xác định tuỳ thuộc nội dung của nhãn F và tập cặp ghép M như 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 sẽ xác định một cạnh của đồ thị , - Cạnh này có hướng từ X sang Y nếu 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 nếu 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 được gọi là đồ thị cân bằng tương ứng với F , M và được ký hiệu là G(F,M). Bước 1: Khởi tạo : Xây dựng được nhãn F chấp nhận được như sau : F( xi ) := Max {C[ i , j ] , yj thuộc Y } F( yj ) := 0 yj thuộc Y M là tập cặp ghép rỗng . Chú ý rằng , có thể xuất pháp từ bất kỳ nhãn F nào chấp nhận được và bất ký một tập cặp ghép M nào tương ứng với F. Bước 2: Tìm đỉnh tự do thuộc X: Tìm đỉnh u thuộc X chưa được ghép cặp. Nếu không còn đỉnh nào của X chưa ghép cặp thì kết thúc, tập cặp ghép M hiện hành là tập cập ghép tối ưu. Trái lại sang bước tiếp theo. Bước 3: Xuất phát từ u, thực hiện việc tìm kiếm trên đồ thị G(F, M). Kết quả tìm được có hai trường hợp sau: - Nếu đến được một đỉnh z thuộc Y chưa ghép cặp thì ghi nhận đường đi 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 đi này. - Nếu không tồn tại một đường đị như vậy 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 như sau:.

<span class='text_page_counter'>(12)</span> - Giữ nguyên những cặp ghép của M nằm ngoài đường tăng cặp ghép - Trên đường tăng cặp ghép, thay đổi những cặp ghép của M (cạnh ngược) băng nhữ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 được tăng them 1 và đỉnh u trở thành đã ghép cặp, ngoài ra, tính tương thích giữa F và M vẫn được bảo toàn. Sau đó quay lại bước 2 để thực hiện việc sửa nhãn tự do 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 đã được đi trong quá trình tìm đường đi ở bước 3. Việc sửa nhãn được tiến hành 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 3 để 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 khi thay đổi, nhãn F vẫn giữ nguyên tính chấp nhận được và tính tương thích M. Ngoài ra có thêm ít nhất một cặp (xi, yj) thoả mãn F(xi) + F(yj) = C[i,j], vì thế, sau một số lần đổi sữa nhãn, chắc chắn sẽ tăng được cặp ghép. Dưới đây là sơ đồ minh hoạ các bước đã nêu: (Sơ đồ) Nhận xét rằng, khi tăng cặp ghép, các đỉnh đã được tiến ghép cặp rồi không bao giờ trở thành tự do, vì thế việc tìm đỉnh tự do có thể được tiến hành tuần tự. Quá trình tìm tập cặp ghép đầy đủ tối ưu có thể được mô phỏng 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 6 8 9 7. 2516 8764 6935 5127. Xuất phát từ cặp ghép M rỗng, lần lượt tăng cặp ghép cho các đỉnh tự do x1, x2, x3, ta nhận được 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 đối với đỉnh tự do 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 được d=1 và nhãn F mới là F 0001 5 8 9 6. 2516 8764 6935 5127.

<span class='text_page_counter'>(13)</span> nhãn F này thêm được cẵp1, y2 thoả mãm F(x1) + F(x2) = C[1,2] và ta được đồ thị G(F,M): Đường tăng cặp ghép đối với x4 vẫn không tồm tại. Tuy nhiên S và T đã được mở rộng thêm: S ={x1, x3, x4} và T = {y2, y4} và lượng nhãn được sủa trên các tập này là d = 1: F 0102 4 8 8 5. 2516 8764 6935 5127. Lần này F thêm được cặp x4, y1 thoả mãm F(x4) + F(y1) = C[4,1] và được đồ thị G(F,M): Việc tìm kiếm vẫn chưa kết thúc và cho S = {x1, x2, x3, x4}, T = {y1, y2, y4}. Lượng nhãn được sửa lần này là 2, và ta nhận được: F 2304 2 6 6 3. 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 tại 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) được thay bằng 2 cặp ghép mới (x2, y3) và (x4, y1) Kết quả 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à 6 + 6 + 9 + 5 = 26..

<span class='text_page_counter'>(14)</span>

Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×