Giáo trình Cấu trúc dữ liệu và giải thuật: Phần 2 cung cấp cho sinh viên những kiến thức về đồ thị, tập hợp và bảng tìm kiếm, sắp xếp và tìm kiếm, một số phương pháp thiết kế thuật giải. Thông qua giáo trình này, có thể rèn luyện cho sinh viên cách áp dụng các cấu trúc dữ liệu đã học và tư duy thuật toán dễ có thể thiết kế và cài đặt một số chương trình bằng một ngôn ngữ bậc cao. Mời các bạn cùng tham khảo.
Trang 1Chuong 5
ĐỒ THỊ
Trong chương này ta sẽ trình bày khái niệm đồ thị Đó là khái niệm
tổng quát nhất trong những cấu trúc dữ liệu mà ta mô tả trong giáo trình này Các cấu trúc tuyến tính, cấu trúc phân cấp và cả tập hợp mà ta đã học trong những chương trước, đều có thể xem như những trường hợp riêng của
đồ thị Cấu trúc đề thị không chỉ được nghiên cứu và ứng dụng trong Toán
học, Tin học mà còn trong nhiều lĩnh vực khoa học công nghệ khác Một đồ
thị có thể xem là một cấu trúc dữ liệu mà mỗi thành phần dữ liệu của nó có
thể có quan hệ với một số tuỳ ý các thành phần đữ liệu trong cấu trúc dó
Tức là, mỗi thành phần đữ liệu trong cấu trúc đồ thị có thể có nhiều phần
tử “đứng trước” nó, và có nhiều phần tử “đứng sau” nó
5.1 Đồ thị và một số khái niệm cơ bản
5.1.1 Khái niệm đồ thị
Có hai loại dé thi, dé thị có hướng và đồ thị vô hướng Đồ thị có hướng
(directed graph hay digraph) G 1A mét cap (V E), trong dé V 1A mét tap hop hữu hạn các phần tử, mỗi phần tử của nó dược gọi là một đỉnh (vertex), còn E là tập tập các cung (arc) có hướng, mỗi cung nối hai đỉnh của đổ thí một cách có thứ tự, tức là có phân biệt đỉnh đầu và dỉnh cuối Đỉnh cũng còn được gọi là nút (node), cung có hướng cũng còn được gọi là cạnh có hướng (directed edge) Tap Ð các cung có thể đặt tương ứng với một tập con của
tích Đề-các VxV, mỗi phần tử của nó là một cặp đỉnh có thứ tự (v, w) và được biểu thị hình họe bằng một mũi tên có đầu ở w và đuôi ở v Người ta hay gọi cung nối hai đỉnh v và w là cung (v, w) Đặc biệU, v và w có thể
trùng nhau, khi đó ta có cung (v, v) Hình 5.1.a thể hiện một đổ thị có hướng với bốn đỉnh và sáu cung
Đồ thị uô hướng là đồ thị mà trong đó mỗi cạnh (edge) JA mét cung néi hai đỉnh không kể đến thứ tự của hai đỉnh đó Nói cách khác, nếu (v w) là
cạnh của một đồ thị vô hướng thì (w, v) = (v, w) Hình 6.1.b thể hiện một đồ
Trang 2@—92
oy @——@
a) b)
Hình 5.1 Đồ thị có hướng và đồ thị vô hướng
5.1.2 Một số khái niệm cơ bản khác
Đồ thị con của dé thi G = (V, E) là G'= (V, E’), trong d6 Vcr V, Pc E và G' làm thành một đồ thị (tức là E V'xV9,
Đường đi (path) từ đỉnh v đến dỉnh w trong dé thi 1A day đỉnh vụ,
v„, , v„ sao cho đỉnh đầu tiên của dãy vị chính là v, đỉnh cuối cùng của dãy
chính là w, và giữa mỗi cặp đỉnh liên tiếp, theo đúng thứ tự của dãy đều có
một cung (cạnh), nghĩa là (vị, v;), (v;, vạ), , (V„ ¡, vạ) là các cung Đường đi này được gợi là đi qua các đỉnh vị, vạ, , vụ Độ dài của đường đi được tính bằng số cung trên đường đi, trong trường hợp này là n~-1 Một đỉnh v được xem là một đường đi đặc biệt có độ dài bằng 0 từ v đến v
Đường đi đơn là đường đi không tự cắt Nói cách khác, đường đi đơn là đường di không chứa cặp đỉnh nào trùng nhau ngoại trừ hai đỉnh đầu và cuối của nó Chu frinh là đường đi có đúng một cặp đỉnh trùng nhau là đỉnh đầu và đỉnh cuối của đường đi đó, Một đề thị dược gọi là liên thông
(connected) néu véi hai dinh bat kì của nó luôn có một đường đi từ đỉnh này
tới đỉnh kia Mỗi dé thị con liên thông tối đại của một đồ thị được gợi là một thành phần liên thông của đỗ thị đó
Hình 5.2 Đồ thị vô hướng với hai thành phần liên thông
Trang 3Một đồ thị vô hướng liên thông, không chứa chu trình sẽ được gọi là
một cây tự do (free tree) Hình 5.2 chỉ ra ví dụ về một đồ thị vô hướng gồm hai thành phần liên thông mà mỗi thành phần liên thông là một cây tự do
Thuật ngữ cây tự do hàm ý rằng nếu ta xác định một đỉnh bất kì của nó làm gốc thì ta sẽ có một cây thông thường như đã trình bày ở chương trước 5.1.3 Đồ thị có trọng số
Trong nhiều trường hợp, người ta gán cho mỗi cạnh của đồ thị một, gia tri ma ta goi 1A chi phi (cost) hay giá của cạnh Khái niệm này xuất hiện khi nghiên cứu các bài toán thực tế Chang han, bài toán tim đường đi ngắn nhất giữa mỗi cặp thành phố trong một vùng, hay bài toán phải xây dựng mạng lưới giao thông giữa các thành phố trong một quốc gia sao cho tổng chỉ phí là nhỏ nhất Khi đó độ dài của một đường đi không được tính bằng số cạnh trên đường đi đó mà được tính bằng tổng chi phí của các cạnh trên đường đi Chú ý rằng ta nói về “độ dài” của đường đi ngay cả khi chi phí là một đại lượng khác, như thời gian chẳng hạn
5.2 Kiểu dữ liệu trừu tượng đồ thị
Ta sẽ trình bày một số thao tác cơ bản trên đề thị Những thao tác cơ
bản đó có thể cài đặt dưới dạng hàm hay thủ tục như trong bảng sau Trong những tình huống cụ thể, chẳng hạn với đồ thị vô hướng hay với đồ thị không có trọng số, cần có những thay đổi phù hợp
Thủ tục CREATE(G) Tạo một đồ thị rỗng G
Thủ tục INSERTNODE(G, v) Bổ sung một đỉnh mới v, V = VLÿ {v}
Thủ tục INSERTEDGE(G, u, v, w) Bổ sung thêm một cung mới (u, v) có trọng số
w vào đổ thị G E = E U tu, v)}
Thi tuc DELETENODE(G, v) Xoá đỉnh v và các cạnh liên quan đến nó ra khỏi đồ thị
Thủ tục DELETEEDGE(G, u, v) Xoá cạnh (u, v) khỏi đồ thị
Hàm FIRST (v) Trả ra chỉ số của đỉnh kể đầu tiên của v, Hàm NEXT (y, i} Trả ra chỉ số của đỉnh kể (sau đỉnh có chỉ số i)
của v
Trang 4lu — Ví dụ: Để có đỗ thị như trong hình sau, ta sẽ thực hiện các thao tác tương ứng: CREATE(G); INSERTNODEG, 1); INSERTNODE(, 2); INSERTEDGEG, 1, 2); INSERTNODE(GG, 3); INSERTEDGEG, 1, 3); INSERTEDGE(G, 38, 2); INSERTNODE(G, 4);
INSERTEDGE(G, 2, 4); INSERTEDGE(, 4, 2); INSERTEDGE(G, 3, 4);
Ví dụ: Cần duyệt (đi qua) toàn bộ các đỉnh kể với đỉnh i, tai mỗi đỉnh đó làm một số thao tác: 1 ;= FIRST(v); while i<> NullVertex đo beginw:= VERTEX(v,i); (* Một số thao tác trên w *) i:= NEXT(v,i) end; Hinh 5.3 Lap trén caéc dinh ké vdiv 5.3 Biểu diễn đồ thị
- Có nhiều cách để biểu diễn đổ thị trong máy tính, chẳng hạn biểu
diễn mối quan hệ đỉnh-canh hay biểu diễn mối quan hệ đỉnh-dỉnh Trong việc cài đặt một đồ thị, người ta thường lựa chọn mối quan hệ dỉnh-dỉnh
mà cụ thể là quan hệ kể cận hay láng giềng giữa các đỉnh Sau dây, ta sẽ trình bày hai cấu trúc dữ liệu biểu điễn mối quan hệ này Đó là ma trận các đỉnh kể và danh sách các đỉnh kể,
Trang 55.3.1 Ma trận các đỉnh kề
Một trong các phương pháp thông dụng biểu diễn đề thị là sử dụng
ma tran ké (adjacency matrix) Gia st (V, E) 1A mét dé thi V = (1, 2, 3, , n} Ma trận kể biểu diễn G là ma trận vuông A cỡ n x n gồm các giá trị hai trạng
thai (0-1 hay true-false) được xác định như sau:
lnếu(v,w)eE néu(vw)eE nấu G là đồ thị không có trọng số „ Aly, wis! 0 néu(v,w)¢E Đối với đồ thị G có trọng số ta đặt r nếur là giá của cung (V, w Atssd| g g (v, w) œ_ nếu không tổn tại cung (v, w) Ví dụ: Bốn đồ thị sau 2) @}——@) lần lượt tương ứng với ma trận: 0110 02 2ø 0 110 0220 0001 œ 0 œ 3 1011 20 4 3 0101 œ4 03 11-01 240 3 0000 œ oo 0 0110 0330 (a) (b) () (d) Để thị có hướng Đổ thị có hướng, có Dé thị vô hướng Đồ thị vô hướng, có trọng số trọng số Hình 5.4 Ma trận kể
Ta có thể cài đặt một số hàm như sau:
Type AdjType = arrayv[1 n,1 n] of boolean;
Vertex = integer;
var a: AdjType;
function FIRST(v: vertex): vertex ;
Trang 6var u: vertex; begin u:= 1; while (u<=n) and not Af v,u] do u:= utl; FIRST := u mod (n + 1) end; function NEXT (v,i: vertex): vertex; var u: vertex; begin u:= i + 1 ; while (u<=n) and not A[v,u]} do u:= utl; NEXT := u mod (n + 1) end;
Hinh 5.5 Cài đặt một số hàm trên đồ thị vô hướng bằng ma trận kế
5.3.2 Biểu diễn đồ thị bằng danh sách các đỉnh kể
Phương pháp thứ hai biểu diễn đồ thị là sử dụng danh sách các đỉnh kề ) (2) Header Bon = Hình 5.6 Danh sách liên kết các đỉnh kề `
Ta xem mỗi đồ thị như một mảng các dang sách Danh sách thứ ¡ bao gém
các nút kể với nút ¡, với ¡ = 1, 2, , n Ta có thể cài đặt một số hàm như sau:
var Header = arraVy[1 n] o£ LIST; function FIRST (v: vertex): vertex ; begin FIRST:= RETRIEVE (FIRST (Header{ v] ) ) end; function NEXT(v, i: vertex): vertex; var p: POSITION;
begin p:= LOCATE(i, Header{ v]) ;
NEXT:= RETRIEVE (FIRST (Header v] )) end;
Hình 5.7, Cai đặt một số hàm trên đồ thị bằng danh sách liên kết các dinh ké
Trang 75.4 Duyệt đồ thị
Cũng như đã thực hiện đối với cây, trong mục này chúng ta xét bài toán đi qua các đỉnh của một đồ thị (còn gọi là duyệt hay tìm kiếm) Ta sẽ trình bày trong mục này hai thuật toán duyệt đồ thị: ưu tiên độ sâu
(depth — first) vA uu tién bé réng (breath —first)
5.4.1 Tìm kiêm ưu tiên độ sâu (Depth First Searching)
Duyệt theo chiều sâu là đi qua tất cả các đỉnh trên một đường đi nào đó của đồ thị cho đến khi không thể đi tiếp được nữa hoặc đường đi tạo
thành một chu trình (có đỉnh bị duyệt lại) Việc duyệt được thực hiện tiếp theo bằng cách quay lại đỉnh ngay phía trước và đi theo một đường đi khác
nếu thấy xuất hiện Cứ như vậy, cho đến khi các đỉnh của dé thị được đi qua hết
Về cấu trúc đữ liệu, để tiện cho việc trình bày, trong các thuật toán duyệt đổ thị dưới đây, ta sử dụng mảng order để ghi thứ tự các đỉnh đã được duyệt, với những đỉnh ¡ chưa được duyệt, ta quy ước order[1] = 0
Ngoài ra, ta cần có thủ tục visit thực hiện chức năng đi qua một đỉnh v, Thủ tục này sẽ tăng biến đếm count và gán cho order[v] giá trị của
count Điều đó hàm ý rằng count là thứ tự của đỉnh v trong phép duyệt
#1? OO)
Hình 5.8 Đồ thị cô hướng
Ví dụ: Duyệt đổ thị có hướng được cho ở hình 5.8 sẽ cho một thứ tự tuyến tính giữa các đỉnh: ø b c deƒg
Việc duyệt đổ thị được mô tả như sau:
Tvpe Vertex = integer;
var count: integer;
i: Vertex;
order: array{1 n) of integer;
Trang 8begin count:=counttl; order[ v] :=count end; (a) Khai bao procedure DFS(v: Vertex); var u: Vertex; begin visit(v);? u:= FIRST(v); while u<>0 do begin if order{ u] =0 then DFS(u); u:= NEXT(v,u) end end; (b) Thủ tục đệ quy BEGIN count:=0;
for i:=1 to n do order i} :=0;
for i:=1 ton do
if order[ i} =0 then DFS (i)
END
(c) Lời gọi thủ tục đệ quy từ chương trình chính
Hình 5.9 Lược đồ đệ quy tìm kiếm ưu tiên độ sâu
Để có một thủ tục duyệt dé thị ưu tiên độ sâu không đệ quy, hãy chú ý rằng ta đã có một thủ tục như vậy khi duyệt cây theo thứ tự trước với quy tắc DEPTH-FIRST-LEFT-MOST Khi áp dụng thuật toán đã xét vào đồ ' thi, cần chú ý rằng một đỉnh chỉ được duyệt khi nó chưa được duyệt một
Trang 9MAKENULL (S) ; repeat af (u<>0) and (orderf uj =0) then begin visit(u); PUSH (u, 8); u:= FIRST (u) end else if not EMPTY (s) then begin m:= NEXT(TOP(S)); POP(s) end until EMPTY (s) end; BEGIN count :=0; for i:=1 to n do order{ i] =0; for i:=1 to n do if order{ i] =0 then N_DFS (i) END;
Hình 5.10 Lược đồ không đệ quy tìm kiếm ưu tiên độ sâu
Tuy nhiên, việc duyệt một đồ thị trả lại thông tin nhiều hơn một thứ tự tuyến tính giữa các đỉnh Đó là một rừng với các cây được sắp thứ tự
Mỗi thành phần liên thông cho một cây của rừng Các cây này nếu được duyệt theo thứ tự trước sẽ cho lại các đỉnh theo thứ tự mà lần đầu tiên
chúng được gặp trong quá trình tìm kiếm Còn nếu được duyệt theo thứ tự sau, các cây này sẽ cho lại các đỉnh theo thứ tự mà quá trình duyệt đỉnh đó
hoàn tất
Hình 5.11 Rừng khung ưu tiên độ sâu của đồ thị được cho trong hình 5.8
Trang 10Giả sử đồ thị G có ø đỉnh và m cạnh Khi đó thời gian tìm kiếm ưu tiên độ sâu trên một đổ thị biểu điễn theo danh sách kể cố O(m+n) còn thời gian tìm kiếm ưu tiên độ sâu trên một đổ thị biểu diễn theo ma trận các
đỉnh ké 14 cd O(n’)
5.4.2 Tim kiếm ưu tiên bề rộng (Breath First Searching)
Duyệt theo chiều rộng có nghĩa là sau khi thăm đỉnh N, thì sẽ thăm các đỉnh kể với N Có thể hình dung một cách trực quan như sau Giả sử người ta đặt đổ thị vào một dãy những vòng tròn đồng tâm theo cách sau:
Đỉnh xuất phát của quá trình duyệt được đặt ở hình tròn trong cùng Các đỉnh kể với đỉnh xuất phát được đặt trong hình tròn thứ hai (kể từ trong) Các đỉnh kể với các đỉnh này (nếu chưa được đặt) sẽ được đặt trong
hình tròn tiếp theo, và cứ như thế cho đến hết các đỉnh Khi đó các đỉnh sẽ được duyệt theo thứ tự từ trong ra ngoài của các hình tròn đồng tâm Dĩ
Trang 11while y<>0 do begin if order[ y] =0 then begin Visit(y); ENQUEUE (y, 0) ; TNSERT((x,y),T) end; V:= NEXT((%,y),T) end end end; Hình 5.13 Thủ tục duyệt đồ thị trụ tiên bề rộng 5.4.3 Nhận xét
Trong cả hai thuật toán đã nêu, ở mỗi bước duyệt, các đỉnh được chia
thành ba lớp: các đỉnh đã được duyệt cùng với cạnh làm cho nó được duyệt làm nên cấu trúc cây, dược gọi là các đỉnh cây (tree vertice), các đỉnh nằm trong một danh sách chờ được duyệt được gọi là các đỉnh ven (fringe vertice) và các đỉnh chưa gặp lần nào trong quá trình duyệt được gọi là các đỉnh không nhìn thấy (unseen vertice)
Sự giống nhau của hai thuật toán là ở quy tắc:
s Chuyển một đỉnh (gọi nó là v) từ tập đỉnh ven vào tập đỉnh cây, ® Đặt tất cả những dỉnh không nhìn thấy, kể với v vào tập đình ven
Sự khác nhau của hai phương pháp duyệt là ở chỗ chúng quyết định đỉnh
nào được chuyển từ tập đỉnh ven vào tập đỉnh cây
Trong phép duyệt ưu tiên độ sâu, người ta chọn đỉnh cuối cùng được nạp vào danh sách đỉnh ven Điều này ứng với việc danh sách các đỉnh ven được lưu trữ bởi một ngăn xếp Trong phép duyệt ưu tiên bể rộng người ta
chọn đỉnh được nạp đầu tiên trong các đỉnh thuộc danh sách đỉnh ven, Điều
này ứng với việc danh sách các đỉnh ven được lưu trữ bởi một hàng đợi
Điểm khác nhau cơ bẵn nói trên dẫn đến sự khác nhau về trong việc cài đặt thuật toán Phép duyệt ưu tiên bể rộng có thể cài đặt đơn giản bằng
cách sử dụng một hàng đợi Trong khi đó, phép duyệt ưu tiên chiều sâu
Trang 125.5 Bài toán tìm đường đi ngắn nhất 8.5.1 Đường đi ngắn nhất từ một đỉnh
Trong mục này, ta xét bài toán tìm đường đi trên một đô thị có hướng Cho để thị có hướng G = (V, E), trong đó, mỗi cung được gắn với một giá trị mà ta gọi là chi phí (cost) của cùng Giả sử có một đỉnh đã cho được gọi là đỉnh nguồn Vấn đề đặt ra là tìm độ dài đường di ngắn nhất từ đỉnh
nguồn đến mỗi đỉnh còn lại trong V, trong đó độ dài của một đường đi chính là tổng chỉ phí của các cung trên đường đi đó Chú ý rằng ta nói về
“độ dài” của đường đi ngay cả khi chỉ phí là một đại lượng khác, như thời gian chẳng hạn,
Bài toán được giải quyết bằng kĩ thuật “tham lam” với một giải thuật được E.W Dijkstra phát triển vào khoảng 1959 và thường được gọi là giải thuật Dijkstra Giả sử đồ thị đã cho G = (V, E) có V = (1, 2, , n} và 1 là đỉnh nguồn Chi phi cha các cung đồ thị được cho bởi mảng hai chiều c,
trong đó c1, J] là chi phí gắn với cung (, j) Trong trường hợp không có cung từ ¡ tời j thì ta đặt c[1, j] là ( mà thực chất là một giá trị lớn hơn các giá trị có trong thực tế Thuật tốn Dijkstra được mơ tả trong hình sau Procedure Dijkstra; (* Tim dé dai dudéng đi ngắn nhất từ đỉnh 1 tới mọi đỉnh của Digraph *) begin S:= (1)? for i:=2 ton đo c[ J] :=c[ 1, j]; for ¡:=1 to n-1 do begin Chọn ueV\S sao cho Du] là nhỏ nhất; Bổ sung u vao S; for v €V\S đo dị v] :=min(d[ v], d[ u] tc[ u,V] ) end; end;
Hình 5.14 Thuật toán Dijkstra
Ví dụ: Cho đỗ thị có hướng như trong hình 1 Khi đó, ma trận chỉ phí sẽ được mô tả trong hình 2 và quá trình thực hiện thuật toán Dijkstra sẽ
được thể hiện bằng bằng trong hình ä
Trang 13c 1 2 3 4 5 1 — 10 œ 30 | 100 2 œ - 50 Ca œ 3 œ 00 - œ 10 4 œ 20 = 60 5 œ œ œ ~- Hinh 5.16 Ma tran chi phi Bước lặp s w D[2] D[3] DỊ4] D5] Initial {1} - 10 œ 30 100 1 {1,2} 2 - 60 30 100 2 {1,2,4} 4 ~ 50 - 90 3 {!,2,4,3} 3 - - - 60 4 {1,2,4,3,5} 5 10 50 30 60
Hình 5.17 Tính toán theo thuật toan Dijkstra với đồ thị trong hình 5.15
Để xác định được đường đi với độ dài ngắn nhất tìm được trong thuật
toán trên, người ta sử dụng một mảng P lưu trữ các đỉnh trung gian khi di từ một đỉnh tới một đỉnh khác Ban đầu P[v] được gán bằng 1 (dỉnh xuất phát) với mọi v z 1 Cứ mỗi khi điều kiện d[u]+e{u,v] < dịv] được thoả mãn,
ngoài việc gán lại giá trị cho d[v] người ta lại gần P[v] bởi u Khi thuật toán
kết thúc, đường đi tới mỗi đỉnh được khôi phục bằng cách lần ngược từ dích nhờ vào mảng P
Trang 145.5.2 Đường đi ngắn nhất giữa mọi cặp đỉnh
Khi cần tìm dường di ngắn nhất giữa mọi cặp đỉnh của một đồ thị có
hướng, có trọng số, người ta thường dùng giải thuật Floyd Giải thuật này được mô tả như sau:
Procedure Floyd (var A: arrav[l n,1 n] o£ real; C: array[l n,1 n) of real); var i,j,ka: integer; begin for i:=1 to n do for j3:=1 to n do A[ 1,3] :=Œ 1,3]; for i:=1 to n do A[ i,i]} =0; for k:=1 ton do for i:=1 ton do for j:=1 to n do
if Al i,k] +A[ k,3] <Al i, 3]
Trang 15Vi du: Trong vi du nay, ta xét bài toán tìm tâm đồ thị Cho một đỗ thị có hướng có trọng số G = (V, E) Ta kí hiệu độ đài đường đi ngắn nhất từ đỉnh w đến đỉnh v là d(w,v) Độ lệch tâm của đỉnh v được định nghĩa là
max d(w,v) Tâm của để thị là đỉnh có độ lệch tâm nhỏ nhất ° By sẾ 3n non (a) Đồ thị có hướng có trọng số Đỉnh a b c d -e Độ lệch tâm ” 6 8 15 7
(b) Bang gia trị độ lệch tâm
Hình 5.21 Độ lệch tâm của các đỉnh trong đổ thị có hướng cô trọng số
Hình 5.21.) liệt kê độ lệch tâm của các đỉnh thuộc đổ thị được cho ở
Hình 5.21.a Từ bằng giá trị này, thấy ngay rằng đỉnh d có độ lệch Lâm nhỏ nhất nên là tâm của để thị
Tìm tâm đổ thị là một ứng dụng của thuật toán Floyd Giả sử G là ma
trận chi phí của G Khi đó, thuật toán tìm tâm gồm ba bước sau:
1 Ap dung thủ tục Floyd được cho ở Hình 5.18 để tính ma trận A lưu
độ dài đường đi ngắn nhất giữa mọi cặp đỉnh của G
2 Tìm độ lệch tâm của mỗi đỉnh bằng cách tính giá trị lớn nhất của
từng cột
3 Tim đỉnh có độ lệch tâm nhỏ nhất Đó chính là tâm của G
Trang 16
max ¡ œ 6 8 5 7
Hình 5.22 Ma trận chỉ phí đường đi ngắn nhất giữa mọi cặp đỉnh
5.6 Cây khung với giá tối thiểu
Giả sử G = (V, E) là một đổ thị vô hướng liên thông, trong đó mỗi cạnh được gắn với một giá trị được gọi là chí phí hay giá của cạnh đó Cây khung của G là cây tự do nối tất cả các đỉnh trong V Giá của cây khung là
tổng giá của các cạnh trong cây Trong mục này ta sẽ bài toán tìm cây
khung với giá tối thiểu của G
Ví dụ: Hình 5.23 thể hiện một đổ thị vô hướng có trọng số và cây khung với giá tối thiểu của nó
Hình 5.23 Đồ thị vô hướng cô trọng số và cây khung có chỉ phí tối thiểu
Có hai cách thường được sử dụng để tìm cây khung với giá tối thiểu
Một trong hai cách đó là thuật toán Prim Giả sử V = {1, 2, n} Thuật toán Prim được bắt đầu với tập Ù = {1} và cây T rỗng Cây khung T sẽ lớn
dần lên theo cách mỗi lần thêm một cạnh Tại mỗi bước của thuật toán, sau khi tìm được tìm cạnh có giá nhỏ nhất trong các cạnh (u, v) với ueU va
veV \ U, người ta bổ sung đỉnh v vào U và bổ sung cạnh (u, v) vào T
Thuật toán Prim được phác thảo như sau:
210
Trang 17Procedure Prim(G:GRAPH; var T:Tap các cạnh); Var U:Tap các đỉnh; u,v: Dinh; begin T:= Ø; U:= (1); while U <> V do begin Goi (u,v) là cạnh có giá nhỏ nhất với ueU, veV\U; T:=T U { (u,v) }; U:= UU (v} end End;
Hình 5.24 Các cạnh được bổ sung theo thuật toán Prim
Có thể thấy ngay rằng nếu G có n đỉnh thì thuật toán Prim thực hiện
n—1 phép lặp có độ phức tạp cỡ O(n) Vì vậy độ phức tạp của giải thuật
Trang 18Một cách khác để tìm cây khung với giá tối thiểu của G là sử dụng
thuật toán Kruskal Theo đó, cây T được khởi đầu bằng đô thị (V, Ø), tức là đồ thị gồm tất cả các đỉnh của G nhưng không có một cạnh nào Khi đó, để thị gồm nhiều thành phần liên thông mà mỗi thành phần liên thông gồm
đúng một đỉnh Quá trình thức hiện thuật toán sẽ được thực hiện với từng
cạnh của đổ thị G đã cho theo thứ tự tăng dần của giá Nếu cạnh được xét
nối hai đỉnh của hai thành phần liên thông khác nhau của T thì ta bổ sung
nó vào T, còn nếu nó nối hai đỉnh của cùng một thành phần liên thông thì
bỏ qua (vì nếu bổ sung vào T, nó sẽ tạo thành chu trình) ;
Như một bài tập, bạn đọc hãy chỉ ra rằng độ phức tạp tính theo thời
gian của giải thuật K’ruskal 1A O(m.logm) véi m là số cạnh của đồ thị đã cho Như vậy, nếu m lớn hơn hay xấp xỉ n° thì thuật toán Prim là tốt bơn
Trang 19Câu hỏi và bài tập
1 Hãy biểu diễn đổ thị có hướng trong hình 5.27, a Bằng ma trận kể chỉ phí đã cho trên cung
b Bằng danh sách móc nối các đỉnh kể với chỉ phí đã cho trên cung
Hình 5.27 Đồ thị có hướng cô trọng số
9 Thực hiện phép duyệt đề thị được cho ở hình 5.27 theo phương pháp: a Ưu tiên theo chiều sâu
b Ưu tiên theo bề rộng
8 Mô tả mơ hình tốn học cho bài toán xếp lịch sau Cho các nhiệm
vụ Tụ, T;, ,T„ tương ứng với thời gian cần thiết để hoàn thành mọi nhiệm vụ
4 Cài đặt các thao tác FIRST, NEXT và VERTEX cho đề thị có hướng được biểu diễn bởi:
a Ma trận kể
b Danh sách (liên kết) các đỉnh kể
5, Viết chương trình tìm đường đi đài nhất trong một đồ thị có hướng,
có trọng số, không chứa chu trình
6 Xét đồ thị có hướng được cho ở hình 5.27
a Hãy sử dụng giải thuật Dijkstra để tìm đường đi ngắn nhất từ a
đến các đỉnh khác
b Hãy sử dụng giải thuật Floyd để tìm khoảng cách ngắn nhất giữa mỗi cập đỉnh Đồng thời xây dựng ma trận P cho ta cái phủ của các đường đi ngắn nhất
Trang 207 Tim tâm của đồ thị được cho ở hình ð.27
8 (*) Chứng minh rằng chương trình Dijkstra hoạt động không dúng nếu các giá trên cung là âm
9 (*) Chứng minh rằng chương trình Floyd vẫn hoạt động đúng nếu
một số cung có giá âm nhưng không chu trình nào có giá âm cả
10 Xét đồ thị vô hướng nền của đồ thị cho trong hình 5.27 Giả sử giá
các cung của đồ thị có hướng được gán cho các cạnh của dé thi vô
hướng Tìm cây khung với giá tối thiểu của để thị này
Trang 21Chuong 6
TAP HOP VA BANG TIM KIEM
Chúng ta vừa nghiên cứu một số cấu trúc đữ liệu tuyến tính và một
số cấu trúc dữ liệu phân cấp Trong chương này và chương tiếp theo ta sẽ nghiên cứu một số cấu trúc đữ liệu khác, đó là tập hợp (set) và bảng tìm kiếm (search table)
6.1 Tập hợp
6.1.1 Định nghĩa và các thao tác
Ta đã biết trong toán học về khái niệm tập hợp Tập hợp được hiểu là một họ các phần tử có những tính chất chung nào đó Ta đã biết các tập hợp quen thuộc như tập hợp số tự nhiên N, tập hợp số nguyên Z, tập hợp số
thực R Một cách tổng quát, ta không có quy ước gì ràng buộc về các phần
tử của một tập hợp Chẳng hạn, ta có thể có tập hợp như sau:
-S = {1, (2, 3}, ‘a}
và một tập hợp cũng có thể gồm vô hạn phần tử
Tuy nhiên, trong máy tính, nếu ta phải cài đặt những tập hợp theo nghĩa tổng quát như thế thì công việc sẽ rất khó khăn Chính vì vậy, trong
các ngôn ngữ lập trình, người ta thường có những quy ước nhất định về các phần tử của một tập hợp để thuận tiện cho việc cài đặt tập hợp Những quy
ước đó như sau:
1, Các phần tử của một tập hợp phải thuộc cùng một kiểu và được gọi là kiểu cơ sở
2 Kiểu cơ sở phải là kiểu có thứ tự Kiểu cơ sở thường gặp nhất là kiểu số nguyên, kiểu kí tự, hay một kiểu có thứ tự do người sử dụng định nghĩa Không có khái niệm tập hợp những mảng hay tập hợp bản ghi
3 Số các phần tử của một tập hợp bị giới hạn
Như vậy, khái niệm tập hợp trong phạm vi của chúng ta tương đối
Trang 22các phần tử của nó không thuộc cùng một kiểu Tập hợp U = {1, 3, 7, 9} là
hoàn toàn thỏa mãn các quy ước trên, Thứ tự của các phần tử trong một tập hợp là không quan trọng Tập hợp U có thể viết là U = {3,-7, 1, 9} Tính không có thứ tự của các phần tử trong một tập hợp là tính chất đặc trưng
làm cho nó khác hẳn với các cấu trúc dữ liệu tuyến tính và phân cấp Với
cấu trúc đữ liệu tuyến tính chúng ta thường có khái niệm phần tử đầu tiên, phần tử cuối cùng, phần tử liển trước, phần tử liền sau Những khái niệm
ấy hồn tồn khơng có trong tập hợp Trong cấu trúc đữ liệu phân cấp, ta cũng có khái niệm cha, con, gốc, lá Những khái niệm này cũng không hé có trong tập hợp Các phần tử trong một tập hợp không có quan hệ gì với nhau trừ việc chúng đều là thành viên của tập hợp đó Những thao tác trên tập hợp là những thao tác quen thuộc và chúng ta cũng không khó khăn lắm để đặc tả môđun ngoài cho kiểu dữ liệu trừu tượng tập bợp
Thao tác tạo tập hợp được gọi là CreateSets: CreateSetQ : Set
nó tạo ra một tập hợp không chứa phần tử nào và được gọi là tập hợp rỗng
Trong lớp thao tác có tính chất biến đối gồm có năm thao tác chính
Thao tác thứ nhất là thao tác chèn một phần tử E vào tập hợp S để có một tập hợp mới Nếu phần tử E đã thuộc 8 thì S không có gì thay đổi sau thao
tác chèn vì trong một tập hợp không cho phép có hai phần tử trùng nhau
Thao tác thứ hai là thao tác xóa một phần tử E khỏi tập hợp 8 Nếu E không phải là một phần tử của 8 thì tap S sẽ không thay đổi Thao tác này cũng được xem như là một lỗi về cú pháp
Insert(Set, Element) : Set (* thêm một phần tử vào một tập hợp*)
Delete(Set, Element) : Set # Xoá một phần tử khối một tập hgp *)
Ba thao tác biến đổi còn lại liên quan đến việc tạo ra tập hợp mới từ hai tập hợp ban đầu Thao tác thứ nhất là phép lấy hợp (Union) của hai tập hợp S1 và 82 để có tập hợp 83 = S1 L¿ S2 mà các phần tử của nó bao hàm cả
những phần tử của Š1 và của 82 (đương nhiên là không có phần tử trùng
nhau) Thao tac lay giao (Intersection) cha hai tap hdp S1 va S2 dé có tập
hợp 83 = S1 ¬ 52 gồm những phần tử thuộc cả hai tập hợp S1 và §2 Cuối
Trang 23cùng là thao tác lấy hiệu (Differenee) của hai tập hợp S1 và S9 để có tập
hợp 53 = §1 — 82 gồm những phần tử thuộc 81 nhưng không thuộc S9 Ta
sẽ minh họa những thao tác này bằng phép sử dụng biểu đồ Venn dưới đây
Phần tô đậm của các hình tròn biểu diễn các phần tử được chứa trong tập
hợp mới S3 Cú pháp của các thao tác này là: Union(Set, Set) : Set
Intersection(Set,Set) : Set Difference(Set,Set) : Set
{A
Hinh 6.1 Biéu dé Venn vé ba thao tac co ban trên tập hợp
Có ba thao tác thuộc lớp thao tác mang tính chất quan sát, đó là thao tác Empty kiểm tra xem một tập hợp có phải là rỗng không, thao tác Member kiểm tra xem một phần tử có thuộc một tập hợp không và thao tác Subset kiểm tra xem một tập hợp có phải là tập hợp con của một tập hợp
khác không Cú pháp của chúng lần lượt như sau:
Empty (Set) ': Boolean Member(Set, Element) : Boolean Subset(Set, Set) : Boolean
Thao tac Empty cho két qua 1a true néu tập hợp không chứa phần tử
nào, cho giá trị là False trong trường hợp ngược lại Thao tác Member cho
giá trị là True nếu phần tử đó thuộc tập hợp và False trong trường hợp ngược lại Thao tác Subset sẽ cho kết quả là True nếu mợi phan tử của tập hợp thứ nhất đều là phần tử của tập hợp thứ hai, ngược lại nó cho kết quả
là False Ví dụ:
Trang 24Insert (S, 4) = {1, 3, 5, 7, 4} Insert (S, 3) = {1, 3, 5, 7} Delete (T, 4) = {3, 5} Delete (T, 6) = {3, 4, 5} Union (S,T) = {1, 3, 4, 5, 7} Intersection (8, T) = {3, 5} Difference (S, U) = {1, 5, 7} Difference (U, T) = @ Empty (V) = True Member (S, 2) = False Member (T, 3) = True Subset (U,T) = True Subset (T, S) = False
Đặc tả hoàn chỉnh cú pháp và ngữ nghĩa của các thao tác trên tập
hợp được cho trong hình sau: Cài đặt
Có rất nhiều cách cài đặt tập hợp trong các ngôn ngữ lập trình bậc
Trang 25Cú phúp:
Define Set[Element] a CreateSet() : Set
b Insert(Set, Element) : Set c Delete(Set, Element) : Set d Union(Set, Set) : Set e Intersection(Set, Set) : Set f Difference(Set, Set) : Set g Empty(Set) : Boolean
h Member(Set, Element) : Boolean j Subset(Set, Set): Boolean
Ngữ nghĩa:
a Empty (CtreateQ) = True b Member (Insert(S,E), E) = True c Empty (nsert (S,E)) = False d Member (Delete(S,E), E) = False
e Empty (Delete(Insert (Createset(), E), E) = True f If Member (81, E) or Member (82, E) Then
Member (Union(S1, 82), E) = True Else
Member (Union(S1, 82), E) = False
g If Member (S1, E) And (Member (82, E)) Then Member (Intersection(S1, $2), E) = True
Else
Member (Intersection(S1, $2), E) = False
h If Member ($1, E) And (not(Member (52, E)) Then Member (Difference(S1,82), E) = True
Else
Trang 26Else
Subset (S1, $2) = False
Hình 6.2 Cú pháp và ngữ nghĩa hình thức của kiểu tập hop
Trong đó Max được giả định là số phần tử tối đa cho phép trong một
tập hợp còn kiểu cơ sở (BaseType) chính là kiểu của các phần tử trong tập
hợp Với những khai báo như thế thì tập hợp S = { 25, 13, 10, —4, 22} có thể được lưu trữ như sau: S^.Data[1] S^.Data[2] S^.Data[3] S^.Data[4] S^.Data[5] S^.Size = 5
Ta để ý rằng thứ tự được lưu trữ trong máy không nhất thiết trùng
với thứ tự ta liệt kê các phần tử trong một tập hợp
Sau đây ta khai báo mơđun ngồi và môđun trong của kiểu dữ liệu trừu tượng tập hợp
(* Môdun sau đây chứa đặc tả bên ngoài của kiểu đữ liệu trừu tượng tập hợp như ta đã mô tả trong hình 5.1 Cú pháp và ngữ nghĩa của kiểu này đã dược mô tả trong hình ð.2*),
Trang 27Postcondition: Sau khi gọi thủ tục Insert, chắc chốn tập hợp sẽ chúa
phan tu vita thém
Member(Insert(S, E),E) = True
Empty(Insert(S, E), E) = False _*#)
Procedure Insert (Var Set : SefType; Element : DataBlementType j; (* PrecondiHon: Không
Postcondition: Sau khi gọi thủ tục Delete, chắc chắn tập hợp không
còn chứa phần tử đó nữa
Member(Delete(S, E),E) = False
Empty(Delete(Insert(Create(), E), E)) = True*)
Procedure Delete (Var Set : SetType; Element : DataElementType ); (* Precondition: Không
Postcondition: Ham hay thu tuc Union tra ra mét idp hop gém nhiing phân tử hoặc thuộc S1 hoặc thuộc S2 hoặc thuộc cả hai tap hop
If Member(S1, E) Or Member(S2, E) Then Member(Union(S1, $2), E) = True Else Member(Union(S1, S2), E) = False *) Procedure Union (set1: SetT ype; set2: SetType }: SetT ype; (* Precondition: Khéng
Postcondition: Ham hay thủ tuc Intersection tra ra mét tap hop gém những phần tử thuộc có hai tập hợp S1 uò S9
If Member(S1, E) And Member(S2, E) Then Member(Intersection(S1, $2), E) = True Else Member(Intersection(S1, S2), E) = False *) Procedure Intersection (set1: SetType; set2: SetType ): SetType; (* Precondition: Khéng
Postcondition: Ham hay thi: tuc Difference trả ra một tập hợp gồm
Trang 28If Member(S1, E) And (not(Member(S2, E)) Then Member(Difference(S1, $2), E) = True Else Member(Difference(S1, S2), E) = False *) Procedure Difference( Set! : SetType; Set2: setT ype): SetType; (* Precondition: Khéng
PostcondiHon: Thủ tục Empty trẻ ra giá trị True nếu tập hợp không chứa phần tử nào, trả ra giá trị False nếu ngược lại *)
Procedure Empty( Set: SetType ): Boolean; (* Precondition: Khéng
Postcondition: Thit tue Member trd ra giá trị True nếu phần tử đó
thuộc tập hợp, trả ra giá trị False nếu ngược lại *)
Procedure Member( Set: SetType; Element: DataElementType ): - Boolean;
(* Precondition: Khéng
Postcondition: Thủ tục Subset trả ra giá tri True néu.moi phan ti cia tập hợp S1 đêu là phần tử của tập hợp S2, trả ra giá trị False nếu ngược lại *) Procedure Subset( Set1l: SetType; Set2: SetType ): Boolean; End SetPackage Hình 6.3 Định nghĩa môđun ngoài của kiểu dữ liệu trừu tượng tập hợp
Khi sử dụng những khai báo ở trên, những thao tác mà chúng ta giới thiệu trong mục trước sẽ chuyển thành những thao tác trên mảng Chẳng
hạn, ta có thể tìm kiếm tuần tự trên mảng xem một phần tử đã cho có
thuộc tập hợp hay không Hình 5.4 cho ta thấy khi dùng mảng cài đặt tập hợp, thao tác lấy giao S3 của hai tập hợp S1 và S2 được thực hiện như thế
nào Với mỗi phần tử của tập hợp S1 ta lần lượt duyệt S2 xem nó có thuộc
Trang 29không thuộc 52 thì ta bỏ qua nó Nếu hai mang S1 và 82 chưa được sắp xếp
và mỗi mảng đều có n phần tử thì độ phức tạp về thời gian của thao tác này
sẽ là O(n”) Độ phức tạp về thời gian sẽ tốt hơn nếu S1 và S2 da được sắp xếp Sau đây là thao tác lấy giao của hai tập hợp nếu ta dùng mảng để cài đặt tập hợp
(* Precondition: Khéng
Postcondition: Intersection tra ra tép hop gém những phần tử thuộc cả hai tập hợp Set1 va Set2*)
Procedure Intersection( Set1 : SetType; Set2: setType): SetType; Var L : Integer; NewSet : SetT ype; Begin NewSet := CreateSet(); For i := 1 To Set1*.Size Do If (Member(Set2, Set1*.Datafi]) Then NeuSet^.Size := NeuSet^.Size + 1; NeuSet^.Data[NeuSet^.Size] := Set1^.Data[i]; Endif; Return NewSet; End Intersection;
Hinh 6.4, Dung mang cài đặt thao tác giao của hai tập hợp mảng
Nếu chúng ta dùng danh sách móc nối để cài đặt tập hợp ta cũng có kết quả tương tự như dùng mảng
Trong mục trước, chúng ta đã quy ước 3 điểu về kiểu cơ sở của tập hợp Cụ thể là ta quy ước tất cả các phần tử của tập hợp phải thuộc cùng
' một kiểu, và đó phải là kiểu đơn chứ không phải kiểi phức hợp Hơn thế
nữa, số phần tử của tập hợp không phải là tùy ý mà phải tương đối nhỏ Nếu chúng ta có thêm một quy ước nữa cho kiểu cơ sở ta sẽ có một cách cài đặt tập hợp rất hiệu quả và được gọi là biểu diễn bằng vectd bit
Trang 30đến N—1 mà ta kí hiệu là [0 N-1], trong đó, N là số các phần tử của kiểu cơ sở của tập hợp fE>I Trong đó, E là kiểu cổ sở của tập hợp còn I là tập những số nguyên từ 0 đến N~1 Vì f là ánh xạ 1—1 nên f(e1) # f(e2) khi e1 # e2, với mọi phần tử el và e2 thuộc E :
Tất cả các kiểu có thứ tự của Pascal như kiểu Integer, kiéu Chareter,
Boolean hay kiểu liệt kê đều có tính chất trên Trong những trường hợp này, hàm f mà ta nói đến ở trên có thể chọn chính là hàm chuẩn Ord(x),
Trong trường bợp hàm f như trên tốn tại thì để biểu diễn một tập hợp Š có không quá N phần tử ta chỉ đơn giản tạo một mảng với N phần tử,
mỗi phần tử của mắng nhận một trong hai giá trị là Có và Không hay Yes và No Yes tương ứng với việc phần tử có xuất hiện trong tập hợp 8, No sẽ tương ứng với việc phần tử đó không có trong tập hợp 8 Nói cụ thể hơn,
mỗi tập hợp 8 bây giờ tương ứng với một mảng N phần tử, mỗi phần tử có
một trong hai trạng thái là Yes hoặc No Việc khai báo thiết lập vectơ bịt đơn giản chỉ là: Type SetType = Array [0 N-1] of (Yes, No); Var S: SetTvpe:
Dùng cách biểu diễn này thì việc cài đặt các thao tác trên tập hợp rất đơn giản Ví dụ, để thêm một phần tử e nào đó vào một tập hợp 8, ta chỉ việc gần giá trị Yes cho S[f(@)], tức là làm cho trạng thái của phần tử đó trỏ
thành Yes Để xoá phần tử e khỏi tập hợp 8 ta chỉ cần gán giá trị No cho
S[f(e)], tức là làm cho trạng thái của phần tử đó trở thành No Để kiểm tra
xem e có phải là phần tử của S hay không chỉ đơn giản là kiểm tra vị trí
S[f(@)] xem giá trị tại đó là Yes hay No Ta thấy ngay với cách biểu diễn
như vậy, nhiều thao tác có độ phức tạp là O(1)
Để làm ví du, gia sử bây giờ chúng ta phải tạo ra một tập hợp biểu diễn các ngày trong một tuần: Thứ hai (Monday), Thứ ba (Tuesday), Thứ tư
Trang 31(Wednesday), Tha nam (Thursday), Tha sau (Friday), Thi bay (Saturday) và Chủ nhật (Sunday) Hàm thứ tự Ord sẽ ánh xạ như sau: Ord(Monday) = 0 Ord(Tuesday) = 1 Ord(Sunday) = 6 và biểu diễn vectơ bịt của tập hợp S = (Monday, Wednesday, Friday} như sau: Yes No Yes No ˆ [ves : | No | No | 810] Sa S22] S3] S4] ` SIBI S16)
Monday Tuesday Wednesday Thursday Friday Saturday Sunday
Để xác định xem phần tử Tuesday có thuộc tập hợp không ta chỉ việc kiém tra gia tri cha S[Ord(Tuesday)} = S[1] Ta thấy nó có giá trị là No Điều đó chứng tỏ rằng Tuesday không thuộc tập S
Để xóa phần tử Friday khdi tap hợp ta chỉ việc gán giá trị của S{Ord(Friday)] = S[4] bang No
Ta xét ví dụ thứ hai Giá sử tập hợp cơ sở là tập các chữ cái thường
trong bảng chữ cái ['a' Z], nó có thứ tự lần lượt từ 97 tới 122 Hàm f mà ta chọn bây giờ là: f(e) = Ord(e) ~ 97 Để xem chữ '€© có phải là một phân tử thuộc tập hợp § khơng, trước hết ta tác động hàm f vào phần tử “e và ta có: fC’) = Ord(‘c’) — 97 =99-97 =2
Nhu vay, dé xem ‘ec’ cé phai lA phan tt’ cha S khéng ta chi cdn kiém -tra S[2] xem nó mang giá tri Yes hay No
Trang 32For i:= 0 To N-1 Do If (S1[i] = Yes) And (S2[i]) = Yes) Then S3[i} := Yes Else S3[i] := No
Cách cài đặt tập hợp này giúp cho phép lấy giao hai tập hợp hiệu quả
hơn nhiều so với việc ta dùng mang hay danh sách móc nối để cài đặt Vì
nếu dùng máng hay danh sách móc nối, ta sẽ cần tới O(N?) phép so sánh Cũng tương tự như vậy cho phép lấy hợp hay hiệu của hai tập hợp
Sau đây ta trình bày môđun cài đặt (Internal Module) kiểu đữ liệu trừu tượng tập hợp khi dùng vectd bịt
(* Môđun sau đây là phần chỉ tiết bên trong của hiểu dữ liệu trừu
tượng Tập hop Set sw dung vecto bit Médun ngoài ta đã uiết ở hình 6.3%)
Internal Module SetPackage; From UserModule Import
(* biểu dữ liệu của các phần tử của tập hợp *) DataElementType, (+ Số phần tử của tập hợp DataElementType *) MaxElement, (* Ham tit DataElementType — [0 MaxElement — 1] *) Ord; Type
(* Kiểu dữ liệu trừu tượng *)
Trang 33NewSet : SetType; i : Integer; Begin
New(NewSet);
For i:= 0 to (MaxElement ~ 1) Do
NewSet^[il := No; ®% Chưa có phần tử nào thuộc tập hợp”) Return NewSet; End CreateSet; (Œ Precondition: Khéng Postcondition: Sau khi goi Insert, Tap hop chắc chắn sẽ chứa phần tử E Member(Insert(S,E), E) = True Empty(msert(S,E)) = False *) Procedure Insert( Var Set : SetType: B: DataElemenfType); Begin Set*[Ord(E)] := Yes; End Insert; (* Precondition: Khéng
Postcondition: Sau khi goi Delete, tap hop không còn chúa E nữa”) Procedure Delete( Var Set : SetType; E: DatalilementType);
Begin /
8et^[Ord(f)] := No; End Delete;
(* Precondition: Không
Postcondition: Union tra ra tap hợp gồm những phần tử hoặc thuộc Set1 hoặc thuộc Sei2 hoặc thuộc ca ha)
Procedure Ủnion( Set1 : SeVType: SeU2: setType): SeUType: Var
Trang 34NewSet : SetType; Begin New(NewSet); For i := 0 To (Maxsize — 1) Do If (Set1[i] = Yes) Or (Set2“[i] = Yes)) Then NewSet^lI] := Yes; Return NewSet; End Union; (* Precondition: Khéng
Postcondition: Intersection trả ra tập hợp gồm những phần tử thuộc cả hai tập hợp Set1 và Set2*)
Procedure Intersection( Set1 : SetType; Set2: setType): SetType; Var i : Integer; NewSet : SetType; Begin New(NewSet); For i := 0 To (Maxsize — 1) Do
If (Set1*{i] = Yes) And (Set2“[i] = Yes)) Then NewSet* [i] := Yes;
Return NewSet; End Intersection;
(* Precondition: Khéng
Postcondition: Difference tra ra lập hợp gồm những phần từ thuộc
tập Set1 nhưng không thuộc thập Set2 *)
Trang 35For i := 0 To (Maxsize — 1) Do If (Set1“[i] = Yes) And (Set2*[i] = No)) Then NewSet^li] := Yes; Return NewSet; End Difference; Œ Precondition: Khéng
Postcondition: Empty tra ra gid tri True néu Set khéng chita phén
tử nào, ngược lại, nó trẻ ra giá trị False*) Procedure Empty( Set : SetType): Boolean; Var
1 : Integer; Begin
1= 0; x 2
While (i < MaxElement) Do (*duyét méi phần tử thuộc kiểu cơ sở*)
If (Set“[i] = Yes) Then (*dén khi tim duge mét phần tử thuộc Set *)
Return False; (* tap hdp Set sẽ khác rỗng *) 1:=it),; Endwhile; Return True; End Empty; Œ® Precondition: Không
Postcondition: Member tra ra gid tri True néu Element là một phần tử thuộc Set, ngược lại, nó trả ra giá trị False*)
Trang 36Postcondition: Subset tra ra giá trị True nếu tập hợp thử nhấi SeL1
la tập hợp con của tập hop thit hai Set2, nguec lai n6 tra ra gid tri ‘alse *) Procedure Subset( Set1 : SetType; Set2: setType): Boolean; Begin Return Empty(Difference(Set1l, Set2)); End Subset; End SetPackage
Hình 6.5 Dung vecto bit cai dat kiểu dữ liệu trừu tượng tập hợp
Ở mức trừu tượng cao nhất, ta được phép xem tập hợp là một kiểu diz liệu trừu tượng mà không cần phải để ý đến việc kiểu đữ liệu trừu tượng được cài đặt thế nào trong ngôn ngữ lập trình Ta có thể xem nó như một phần của ngôn ngữ lập trình, chẳng hạn ta có thể viết:
Var
S : Set;(* Kiểu đữ liệu Set và Element được nhập từ môdun *) E: Element; (* ngoài của chương trình *)
Chúng ta sử dụng những đối tượng của kiểu dữ liệu trừu tượng tập hợp này qua các thao tác được xây dựng trong mơdun ngồi (hình 6.3) mà
Trang 37Theo quan điểm của người sử dụng, họ chỉ cần biết là họ có thể dùng được những gì, dùng chúng ra sao (cú pháp) và hiệu ứng (ngữ nghĩa) của chúng như thế nào
Ở mức tiếp theo, sử dụng các cấu trúc dữ liệu cho bởi một ngôn ngữ lập trình, chúng ta cần phải quan tâm đến kiểu đữ liệu trừu tượng được cài đặt ra sao Chẳng hạn như việc cài đặt tập hợp, ta có thể dùng mắng, danh sách móc nối hay vectơ bit Do khuôn khổ của giáo trình ta chỉ thực
hiện chỉ tiết việc cài đặt bằng vectơ bịt Bây giờ giả sử trong một chương trình, bạn cài đặt tập hợp như là mảng Ñ phần tử và sử dụng nó ngay
trong chương trình đó Chắc chắn các công việc đơn giản như kiểm tra xem một phần tử có thuộc một tập hợp hay không cũng có nhiều việc hơn rất nhiều so với việc bạn sử dụng kiểu dữ liệu trừu tượng tập hợp đã xây
dựng ở mức trên
Tóm lại, việc bạn quan niệm tập hợp như trong toán học hay là một mảng, một danh sách, một vectơ bịt là tùy theo nhu cầu của bạn và tùy theo bạn là một người nghiên cứu về tập hợp, người sử dụng, người lập trình hay người viết chương trình địch Tuy nhiên, nếu bạn là người sử
dụng hay người lập trình thì không nên bận tâm nhiều đến những chỉ tiết ở mức dưới hoặc để chúng làm cần trở đến công việc của bạn
6.2 Bảng tìm kiếm
6.2.1 Định nghĩa và các thao tác
Tập hợp là một khái niệm quan trọng trong toán học và nó được
nghiên cứu rất nhiều trong một chuyên ngành hẹp gọi là lí thuyết tập hợp
Tuy nhiên, cấu trúc tập hợp mà ta vừa nghiên cứu ở mục trước không được đánh giá quan trọng lắm và cũng không được dùng nhiều trong khoa học
máy tính Những hạn chế đó có thể là do ta đã giới hạn kiểu cơ sở của tập hợp là những kiểu có thứ tự và giới hạn về số phần tử của tập hợp
Tuy nhiên, điểu đó không có nghĩa là tập hợp không đóng vai trò
quan trọng trong việc xây dựng phần mềm Với một số thay đổi trong định nghĩa tập hợp ở mục 6.1 ta có thể xây dựng được một khái niệm hết sức
quan trọng và có nhiều ứng dụng trong khoa học máy tính, đó là khái niệm
Trang 381 Thay vì giới hạn các phần tử của tập hợp là kiểu có thứ tự, ta gid sử rằng chúng bao gồm những bộ hai phần tử dạng (ki, vi) trong đó, thành phần thứ nhất được gọi là trường khóa còn thành phần thứ hai được gọi là trường giá trị Chúng ta không có hạn chế gì nữa trên số lượng các bộ trên nhưng đương nhiên là chúng phải hữu hạn
S = (Œ&0,v0), (k1,v1), kn,vn)}
các phần tử ki thuộc kiểu đữ liệu mà ta gọi là KeyType còn những phần tử vi thuộc kiểu đữ liệu mà ta gọi là ValueType Những kiểu này không nhất thiết phải là những kiểu đơn Phần tử ki là duy nhất xác dinh trong
tập hợp với một tập 5 nhưng phần tử vi thì có thể không cần thiết phải
duy nhất
2 Chúng ta cũng không quan tâm đến các thao tác lấy hợp Union giao Intersection hay hiệu Difference của hai tập hợp nữa Bây giờ ta giới hạn là chỉ quan tâm đến các thao tác tạo lập (Create), chèn (InserÐ), xoá (Delete) và xem một phần tử nào dó có thuộc tập hợp đã cho không (Member)
8 Cú pháp của các thao tác trên một bang tìm kiếm sẽ bị thay đổi chút ít phần ánh sự khác nhau về cấu trúc của các phần tử của bảng Cú
pháp này được cho ở hình sau đây
Cú pháp
Define SearchTable[Key, Value] 1 CreateQ : T
Thao tác này tạo ra một bảng tìm kiếm TT, còn rỗng, T' = ‡‡, tức là T không chứa một bộ phần tử nào
2 Insert (T1, K,V):T2
Thao tac nay tạo ra một bảng tìm kiếm mới T9 chứa tất cả các phần tử
của T1 và còn chứa cả bộ (K,V) nếu như (K,V) không thuộc T1 Nếu như TI chứa một bộ phần tử dạng (K,X), trong đó X là giá trị bất kì thì thay vì thêm
giá trị (K,V) vào T2, ta thay giá trị X trong bộ đã tồn tại đó bằng giá trị V
3 Delete(T1,K) : T2
Trang 39T2 Nếu không tìm thấy bộ phần tử như thế thì nó trả ra bảng cũ mà không hề thay đổi gì
4, Member(T,K) : V
Thao tác này tìm trong T bộ phần tt dang (K.V), va néu tim được thì
trả ra giá trị V liên kết với khóa K Nếu không tìm thấy K, nó trả ra một
gia tri dac biét trong V, goi 1A Null Ngữ nghĩa Member(CreateQ, K) = Null Member(Delete(T,K),K) = Null Member(Insert(T,K,V), K) = V Delete(Insert(T,K,V),K) = T Member(Insert(Insert(T,K,V1), K, V2), K) = V2 6 Delete(Create(), K) = Create() 7 Delete(nsert(CreateQ, K1,V), K2) = Insert(CreateQ, K1,V) Rw nN mỉ an
Hình 6.6 Cú pháp và ngữ nghĩa của cấu trúc bảng tìm kiếm
Nội dung mà chúng ta vừa mô tả trong hình 6.6 là một ví dụ về một
cấu trúc đữ liệu truy cập trực tiếp Trong cấu trúc này, tất cả mọi giá trị dữ liệu (data value) V trong cấu trúc tại thời điểm chèn đều liên kết với một
giá trị duy nhất được gọi là khóa K và được lưu trữ dưới dạng bộ (Ñ, V) Mọi sự truy cập sau đó tới giá trị đữ liệu này đều thông qua khóa của nó, chứ không phải bởi vị trí của nó trong bằng như chúng ta đã biết trong mảng Do giá trị khóa được lưu trữ trực tiếp trong bộ nên thứ tự của các bộ trong bang là không quan trọng theo nghĩa xác định thuộc tính thành viên
(membership) Hơn thế nữa, do tính duy nhất của giá trị khóa, nên mỗi bộ
cũng được xác định duy nhất trong bảng, tức là không có hai bộ trùng nhau
trong một bảng Hai đặc tính này cùng làm cho cấu trúc bảng tìm kiếm mà ta vừa mô tả thỏa mãn định nghĩa của tập hợp
Trang 40
Trường khóa Trường giá trị
Số thẻ sinh viên (Tên, Chuyên ngành học, Năm nhập học)
Số thẻ bảo hiểm (Tên, Địa chỉ, Loại bảo hiểm) Số biển kiểm soát (Loại xe, Nơi đăng kí, Màu)
Trong tất cả các ví dụ trên, ta thường muốn biết giá trị dữ liệu khi
biết giá trị khóa (hay ta cần biết không tổn tại bộ có khóa đó) Thao tác này
chính là thao tác xác định thành viên Member trong hình 6.6
Médun ngoài của kiểu đữ liệu bằng tìm kiếm được mô tả trong hình 6.7 sau đây Thao tác xác định thành viên Member được đối tên thành thao
tác lấy thông tin Retrieve cho rõ nghĩa hơn Trong mục sau ta sẽ nghiên cứu xem làm thế nào để cài đặt cấu trúc này cho hiệu quả và chúng ta sẽ
đưa ra một số ví dụ về sử dụng chúng 6.2.2 Dùng mảng để cài đặt bảng tìm kiếm
Cũng như cấu trúc tập hợp trong phần 5.1, có nhiều cách đơn giản để cài đặt bảng tìm kiếm Tuy nhiên, ta sẽ thấy nói chung chúng không hiệu
quả và nhiều khi khó có thể chấp nhận được
(* Môdun sau đây chứa đặc tả ngoài cho hiểu dữ liệu trừu tượng bảng tìm hiếm như đã mô tả trong hình ð.6 *)
External Module SearchTablePackage; From UserModule Import KeyType, ValueType; Type SearchTableType; (* Kiểu đữ liệu trừu tượng *) (* Precondition: Khéng Postcondition: Create tra ra mét bang tim kiém mdi, chia c6 phần tử nào *) Procedure Create() : SearchTableType; (*Precondition: Khéng
Postcondition: Insert thém b6 (K, Value) vao bang Table, sao cho bat kì bộ nào đã có trong Table dạng (K, Val) đều được thay thế *)