4. Phương phỏp nghiờn cứu
3.4.2.1. Duyệt theo chiều sõu
- Thủ tục BUILD_SPANNING_TREE_DFS thực hiện việc thăm một đỉnh trờn đồ thị đó cho theo hướng duyệt theo chiều sõu:
Giả mó 3.1. BUILD_SPANNING_TREE_DFS
- Chương trỡnh chớnh thực hiện việc duyệt đồ thị G đó cho để thu được cõy phủ T: root là một đỉnh bất kỡ thuộc V.
Giả mó 3.2. BUILD_SPANNING_TREE_DFS_MAIN_PROGRAM 3.4.2.2. Duyệt theo chiều rộng
- Thủ tục BUILD_SPANNING_TREE_BFS thực hiện việc thăm một đỉnh trờn đồ thị đó cho theo hướng duyệt theo chiều rộng:
BEGIN
for u ∈ V do visited[u] := false;
F := ỉ; BUILD_TREE_DFS(root); END. procedure BUILD_TREE_DFS(v) begin visited[v] := true; for u ∈ neighbour[v] do
if not visited[u] then begin
F := F ∪ (v,u); BUILD_TREE_DFS(u);
end end
Giả mó 3.3. BUILD_SPANNING_TREE_BFS
- Chương trỡnh chớnh thực hiện việc duyệt đồ thị G đó cho để thu được cõy phủ T: root là một đỉnh bất kỡ thuộc V.
Giả mó 3.4. BUILD_SPANNING_TREE_BFS_MAIN_PROGRAM
3.4.3. Nhận xột
- Thuật toỏn BUILD_SPANNING_TREE_DFS và BUILD_SPANNING_TREE_BFS trỡnh bày ở trờn cú độ phức tạp tớnh toỏn O(n+m), với n là số đỉnh và m là số cạnh của đồ thị đó cho.
- Cõy phủ thu được từ thuật toỏn BUILD_SPANNING_TREE_BFS là cõy đường đi ngắn nhất từ đỉnh root đến tất cả cỏc đỉnh cũn lại của đồ thị.
procedure BUILD_TREE_BFS(r) begin QUEUE := ỉ; QUEUE ← r; visited[r] := true; while QUEUE != ỉ do begin v ← QUEUE; for u ∈ neighbour[v] do if not visited[u] then begin QUEUE ← u; visited[u] := true; F := F ∪ (v,u); end end end BEGIN
for u ∈ V do visited[u] := false; F := ỉ;
BUILD_TREE_BFS(root);
3.5. Cõy phủ nhỏ nhất
3.5.1. Phỏt biểu bài toỏn
- Cho đơn đồ thị vụ hướng G = (V, E) với tập đỉnh V = {1, 2, ... , n} và tập cạnh E gồm m cạnh. Mỗi cạnh e của G được gỏn một số thực c(e), được gọi là độ dài của cạnh đú. Giả sử H = (V, T) là cõy phủ của đồ thị G. Ta gọi độ dài c(H) của cõy phủ H là tổng độ dài cỏc cạnh của nú:
c(H) =
Bài toỏn đặt ra là trong tất cả cỏc cõy phủ của đồ thị G cho trước, hóy tỡm cõy phủ với độ dài nhỏ nhất. Cõy phủ như vậy được gọi là cõy phủ nhỏ nhất của đồ thị.
- Nếu chỳng ta sử dụng phương phỏp vột cạn: liệt kờ tất cả cỏc cõy phủ cú thể của đồ thị và tỡm ra cõy phủ nhỏ nhất, chi phớ tỡm kiếm sẽ ở mức khụng chấp nhận được. Đồ thị Kn cú nn-2 cõy phủ, do đú độ phức tạp tớnh toỏn sẽ cỡ O(nn-2), rừ ràng khụng thể thực hiện được với cả những đồ thị chỉ cú vài chục đỉnh.
- Ứng dụng thực tế:
+ Chỳng ta cú một mạng lưới n thành phố. Yờu cầu đặt ra là xõy dựng hệ thống đường bộ nối giữa cỏc thành phố sao cho từ một thành phố bất kỡ cú thể đi tới tất cả cỏc thành phố cũn lại và chi phớ xõy dựng là nhỏ nhất. Chi phớ xõy dựng đường giữa hai thành phố là khỏc nhau; tựy thuộc vào độ dài quóng đường, điều kiện xõy dựng, chi phớ vật liệu nhõn cụng, vv... và được định lượng bằng cỏc số thực c(i,j) với i, j là hai thành phố đầu và cuối đường. Rừ ràng nếu ta biểu diễn cỏc thành phố bằng cỏc đỉnh, cỏc con đường cú thể xõy dựng bằng cỏc cạnh ta sẽ thu được một đồ thị cú trọng số và bài toỏn trở thành tỡm cõy phủ nhỏ nhất trờn đồ thị này.
+ Bài toỏn nối mạng mỏy tớnh: cần nối mạng một hệ thống gồm n mỏy vi tớnh đỏnh số từ 1 → n. Biết chi phớ nối mỏy i với mỏy j là c[i,j] (thụng thường chi phớ này tuyến tớnh với độ dài cỏp nối cần sử dụng). Hóy tỡm cỏch nối mạng sao cho tổng chi phớ nối mạng là nhỏ nhất.
phộp biến đổi trọng số của đồ thị:
+ Bài toỏn cõy phủ lớn nhất: rừ ràng, nếu đảo dấu trọng số cỏc cạnh trờn cõy, ta sẽ chuyển từ bài toỏn cõy phủ lớn nhất thành bài toỏn cõy phủ nhỏ nhất.
+ Bài toỏn độ tin cậy của mạng nối tiếp: trong một mạng điện hay mạng mỏy tớnh nối tiếp, độ tin cậy của một mạch được tớnh bằng tớch độ tin cậy cỏc đoạn mạch thành phần trờn mạng đú:
p = Π pi
Bài toỏn đặt ra là xõy dựng mạng sao cho độ tin cậy là lớn nhất. Do đú, nếu chỳng ta sử dụng cụng thức biến đổi:
- ln(p) = - ln(Πpi) = ∑ (- ln (pi))
thỡ bài toỏn xõy dựng mạng cú độ tỡn cậy lớn nhất sẽ trở thành bài toỏn cõy phủ nhỏ nhất.
3.5.2. Thuật toỏn Kruskal
3.5.2.1. í tưởng thuật toỏn
- Tập cạnh của cõy phủ nhỏ nhất sẽ được xõy dựng lần lượt theo nguyờn tắc sau: cạnh được kết nạp vào cõy phủ nhỏ nhất là cạnh chưa được kết nạp cú trọng số nhỏ nhất và việc kết nạp nú vào tập cạnh T sẽ khụng tạo ra chu trỡnh trờn cõy H. Cụng việc xõy dựng tập cạnh T kết thỳc khi H liờn thụng, tương đương |T| = n-1.
- Ta cú giả mó chương trỡnh
Giả mó 3.5. BUILD_MINIMUM_SPANNING_TREE_KRUSKAL
- Dễ thấy: thuật toỏn duyệt lần lượt cỏc cạnh của đồ thị ban đầu theo thứ tự trọng số tăng dần, do đú trước hết ta sắp xếp cỏc cạnh của đồ thị theo thứ tự trọng số tăng dần. Thao tỏc sắp xếp cú thể thực hiện bằng cỏc thuật toỏn sắp xếp cổ điển: sắp xếp nhanh, sắp xếp vun đống, vv... Chi phớ cho thao tỏc sắp xếp m cạnh theo cỏc thuật toỏn này sẽ là O(m logm).
- Việc lựa chọn một cạnh e trờn đồ thị G để bổ sung vào tập cạnh T đang xõy dựng của cõy H đũi hỏi một thủ tục hiệu quả để kiểm tra xem tập cạnh T U {e} cú tạo ra chu trỡnh hay khụng. Một cỏch cài đặt đơn giản được giới thiệu ở đõy sẽ sử dụng cấu trỳc dữ liệu rừng để đỏnh dấu cỏc cạnh đó được kết nạp vào tập cạnh T của cõy H: trong mỗi bước trung gian, tập đỉnh V và tập cỏc cạnh đó được kết nạp vào tập T sẽ tạo ra một rừng. Một cạnh được kết nạp vào T khi và chỉ khi cú hai đầu mỳt thuộc hai cõy khỏc nhau trờn rừng đú và cạnh này sẽ kết nối hai cõy phõn biệt chứa hai đầu mỳt của cạnh thành một cõy mới. Cài đặt cụ thể cho cấu trỳc rừng này cú thể sử dụng danh sỏch liờn kết hoặc một vector một chiều để đỏnh dấu đỉnh nào thuộc cõy nào.
3.5.2.3. Chứng minh thuật toỏn và đỏnh giỏ độ phức tạp
- Chứng minh tớnh đỳng đắn của thuật toỏn: Nếu đồ thị G liờn thụng, H thu được là đồ thị cú n đỉnh và n-1 cạnh, do đú H là cõy, đồng nghĩa H là cõy
procedure BUILD_MINIMUM_SPANNING_TREE_KRUSKAL begin T := ỉ; while((|T| < n-1)and(E != ỉ)) begin e ← E: c(e) = min;
if(T U {e} khụng chứa chu trỡnh) then T:= T U {e};
end
if (|T| < n-1)
then đồ thị khụng liờn thụng; end
phủ của G. Ta chứng minh H thu được từ thuật toỏn trờn là cõy cú độ dài nhỏ nhất theo tư tưởng phản chứng. Giả sử tồn tại cõy phủ K = (V,S) của đồ thị G cú độ dài nhỏ hơn H: c(K) < c(H). Gọi ek là cạnh đầu tiờn trong dóy cạnh của H khụng thuộc K. Khi đú đồ thị con của G thu được bằng cỏch bổ sung cạnh ek
vào K sẽ chứa duy nhất một chu trỡnh C. Rừ ràng, C sẽ chứa hai cạnh et và ek
với: et ∉ H, et∈K, ek ∉ K và ek ∈ H. Do chu trỡnh C là duy nhất nờn đồ thị K’
thu được từ cõy K bằng cỏch thay cạnh et bởi cạnh ek cũng sẽ là một cõy phủ của đồ thị G. Ngoài ra, theo thuật toỏn Kruskal, c(ek) ≤ c(et) nờn c(K’) ≤ c(K) và số cạnh chung của K’ với H sẽ hơn số cạnh chung của K với H một cạnh. Lặp lại thao tỏc trờn một số bước hữu hạn, chỳng ta sẽ thu được cõy phủ Ki’ ≡ H mà c(Ki’) ≤ c(K), tức là c(H) ≤ c(K). Mõu thuẫn này ngược với giả sử ban đầu cho ta điều phải chứng minh.
- Đỏnh giỏ độ phức tạp: rừ ràng, trong trường hợp xấu nhất chỳng ta cũng chỉ phải xột m cạnh trong quỏ trỡnh kết nạp cạnh cho cõy H. Do đú, chi phớ tớnh toỏn sẽ phụ thuộc vào thao tỏc sắp xếp cỏc cạnh của G theo thứ tự trọng số tăng dần. Nếu sử dụng thuật toỏn sắp xếp vun đống, chi phớ tạo đống ban đầu sẽ là O(m). Mỗi phần tử tiếp theo cú thể được tỡm ra với chi phớ O(logm), do đú nếu cõy T được hoàn thiện sau khi xột tới cạnh thứ p của đồ thị G thỡ chi phớ của thuật toỏn Kruskal sẽ là O(m + p logm).
- Thuật toỏn Kruskal làm việc kộm hiệu quả đối với những đồ thị dày: đồ thị cú số cạnh m ≈ n(n-1)/2.
3.5.3. Thuật toỏn Prim
3.5.3.1. í tưởng thuật toỏn
- Thuật toỏn Prim cũn được gọi là thuật toỏn lõn cận gần nhất. Bắt đầu từ một đỉnh bất kỡ, cõy phủ nhỏ nhất sẽ được xõy dựng lần lượt theo nguyờn tắc sau: cạnh được kết nạp vào cõy phủ nhỏ nhất là cạnh chưa được kết nạp cú trọng số nhỏ nhất và nối một đỉnh thuộc tập đỉnh đó kết nạp vào cõy phủ với một đỉnh thuộc tập đỉnh chưa được kết nạp vào cõy phủ. Sau khi kết nạp cạnh đú, đỉnh thuộc tập đỉnh chưa được kết nạp vào cõy phủ sẽ được kết nạp vào cõy. Quỏ trỡnh tiếp tục cho đến khi thu được cõy gồm n đỉnh và n-1 cạnh sẽ chớnh là cõy phủ nhỏ nhất cần tỡm.
3.5.3.2. Cài đặt thuật toỏn
- G được cho bởi ma trận trọng số C = {c[i,j], i,j = 1, ... ,n}. Trong quỏ trỡnh thực hiện thuật toỏn, cỏc đỉnh của đồ thị sẽ được gỏn hai nhón:
+ d[v] dựng để ghi nhận độ dài của cạnh cú độ dài nhỏ nhất trong số cỏc cạnh nối đỉnh v với cỏc đỉnh đó được kết nạp vào cõy khung.
d[v] := min{c[v,w]: w ∈ VH} (= c[v,z])
+ near[v] dựng để ghi nhận đỉnh đó được kết nạp vào cõy khung gần đỉnh v nhất: near[v] = z Giả mó 3.6. BUILD_MINIMUM_SPANNING_TREE_PRIM procedure BUILD_MINIMUM_SPANNING_TREE_PRIM begin chọn s là một đỉnh bất kỡ; VH := {s}; T := ỉ; d[s] := 0; near[s] := s; for v ∈ V \ VH do begin d[v] := c[s,v]; near[v] := s; end stop := false; while not stop do begin u ← V \ VH: d[u] = min {d[v]: v ∈ V \ VH}; VH := VH U {u}; T := T U {(u, near[u])}; if |VH| = n then begin H = (VH, T) là cõy khung nhỏ nhất; stop := true; end else begin for v ∈ V \ VH do if d[v] > c[u,v] then begin d[v] := c[u,v]; near[v] := u; end end end end
3.6. Cõy nhị phõn tỡm kiếm
Để khắc phục nhược điểm của phương phỏp tỡm kiếm nhị phõn đối với một dóy khúa biến động liờn tục, chỳng ta tỡm hiểu một phương phỏp mới dựa trờn cơ sở dóy khúa được tổ chức dưới dạng cõy nhị phõn. Cõy nhị phõn dựng trong phương phỏp này được gọi là cõy nhị phõn tỡm kiếm, mỗi nỳt của cõy tương ứng với một khúa. Cỏc thao tỏc cập nhật dóy khúa cũng tương ứng với chỉnh sửa trờn cõy nhị phõn tỡm kiếm.
3.6.1. Khỏi niệm
- Cõy nhị phõn tỡm kiếm ứng với n khúa k1, k2, ... , kn là một cõy nhị phõn mà mỗi nỳt của nú đều được gỏn một giỏ trị khúa nào đú trong cỏc giỏ trị khúa đó cho và đối với mọi nỳt trờn cõy tớnh chất sau đõy luụn được thỏa món:
+ Mọi khúa thuộc cõy con trỏi nỳt đú đều nhỏ hơn khúa ứng với nỳt đú. + Mọi khúa thuộc cõy con phải nỳt đú đều lớn hơn khúa ứng với nỳt đú.
3.6.2. Giải thuật tỡm kiếm
- Giải thuật tỡm kiếm trờn cõy nhị phõn tỡm kiếm cú gốc được trỏ bởi T nỳt cú khúa bằng X. Nếu tỡm kiếm được thỏa món thỡ trả lại giỏ trị 1 và đưa ra con trỏ q trỏ tới nỳt đú, nếu khụng trả lại giỏ trị 0.
- Giải thuật được thực hiện bằng cỏch thực hiện lặp lại cụng việc sau cho tới khi cõy được xột là cõy rỗng: thực hiện phộp so sỏnh giỏ trị cần tỡm với khúa ở gốc. Khi đú xảy ra cỏc trường hợp:
+ Khụng cú gốc (cõy rỗng): X khụng cú trờn cõy, phộp tỡm kiếm khụng thỏa món.
+ X trựng với khúa gốc: phộp tỡm kiếm được thỏa món.
+ X nhỏ hơn khúa gốc: tỡm kiếm giỏ trị cần tỡm trờn cõy con bờn trỏi. + X lớn hơn khúa gốc: tỡm kiếm giỏ trị cần tỡm trờn cõy con bờn phải. - Cõy nhị phõn tỡm kiếm được cài đặt sử dụng cấu trỳc dữ liệu danh sỏch liờn kết, cỏc nỳt là bản ghi cú dạng
Giả mó 3.7. BINARY_SEARCH_TREE
3.6.3. Phõn tớch và đỏnh giỏ độ phức tạp
- Rừ ràng hỡnh dỏng cõy nhị phõn tỡm kiếm phụ thuộc vào dóy khúa đưa vào. Trong quỏ trỡnh cập nhật cõy, chỳng ta khụng thể dự đoỏn trước xu thế phỏt triển của cõy.
+ Nếu cõy nhị phõn tỡm kiếm là cõn đối (chiều cao của hai cõy con của mọi nỳt trờn cõy chờnh nhau khụng quỏ 1 đơn vị), chi phớ tỡm kiếm sẽ là
Cmin = [log2n] + 1.
+ Nếu cõy nhị phõn là lệch trỏi hoặc lệch phải (khi dóy khúa đưa vào đó được sắp xếp trước) thỡ thao tỏc tỡm kiếm của chỳng ta trở thành một phộp duyệt tuần tự trờn danh sỏch tuyến tớnh cho trước, điều này khiến chi phi tỡm kiếm là Cmax = n.
+ Người ta đó chứng minh được: chi phớ trung bỡnh của giải thuật tỡm kiếm sử dụng cõy nhị phần là Ctb = 1.386 x log2n. Do đú độ phức tạp của giải thuật vẫn là O(logn).
- Nếu cố gắng thực hiện việc cõn đối lại cõy sau mỗi bước bổ sung khúa vào dóy, chi phớ cõn đối cõy sẽ làm tăng chi phớ của cài đặt giải thuật tỡm kiếm nhị phõn. Như trong hỡnh vẽ minh họa dưới đõy, trong trường hợp cõy nhị phõn tỡm kiếm được cõn đối lại thỡ hầu hết cỏc mối nối đều khụng được giữ nguyờn. Điều này chứng tỏ chi phớ cõn đối cõy nhị phõn là rất lớn.
3.6.4. Thờm nỳt mới
- Thao tỏc bổ sung nỳt mới chỉ thực hiện khi việc tỡm kiếm kết thỳc ở nỳt p của cõy nhị phõn tỡm kiếm T và k[p] ≠ X.
Function BINARY_SEARCH_TREE(T,X,q) q := T while q ≠ null do case X < key(q): q := LPTR(q); X = key(q): return 1; X > key(q): q := RPTR(q); end case; return 0;
- Ta cú giả thuật toỏn cho thao tỏc thờm một nỳt mới trờn cõy nhị phõn tỡm kiếm. Chỳ ý rằng đoạn giả mó tỡm kiếm trờn cõy nhị phõn ban đầu được thờm vào vỡ thao tỏc thờm nỳt chỉ được thực hiện khi khúa X khụng được tỡm thấy trờn cõy.
Giả mó 3.8. INSERT_NODE
- Khi đú, chỳng ta chỉ cần khởi tạo cho nỳt mới q và múc nối nỳt p đến nỳt q. Việc múc nối q là con trỏi hay con phải của p phụ thuộc vào giỏ trị khúa của nỳt p và nỳt q.
- Với thao tỏc thờm nỳt mới vào cõy, suy ra chỳng ta cú thể xõy dựng cõy nhị phõn tỡm kiếm từ một dóy khúa cho trước bằng cỏch khởi tạo gốc cõy tương ứng với khúa đầu tiờn của dóy, sau đú lần lượt thờm cỏc nỳt tương ứng với cỏc khúa tiếp theo của dóy cho đến hết dóy.
- Cõy nhị phõn tỡm kiếm được xõy dựng tương ứng với một dóy khúa bất kỡ, chỳng ta khụng cần thao tỏc sắp xếp trước như đối với giải thuật tỡm kiếm nhị phõn. Do đú, sau khi xõy dựng được cõy nhị phõn tỡm kiếm tương ứng với dóy khúa cho trước, chỳng ta chỉ cần duyệt cõy này theo một luật nhất định sẽ
Function INSERT_NODE(T,X) q := T //Tỡm vị trớ cần bổ sung while q ≠ null do case X < key(q): q := LPTR(q); X < key(q): return 1; X > key(q): q := RPTR(q); end case; //Thờm nỳt call new(p); key(p) := X; LPTR(p) := null; RPTR(p) := null; case T = null: T := p; X < key(p) then p.LPTR := q else RPTR(p) := q; end case; return;
được dóy khúa đó sắp xếp.
3.6.5. Loại bỏ nỳt
- Khi loại bỏ một nỳt ra khỏi cõy, chỳng ta phải chỉnh sửa lại cõy để đảm bảo cõy mới vẫn là cõy nhị phõn tỡm kiếm, nghĩa là tỡm một nỳt thay thế cho nỳt