1. Trang chủ
  2. » Công Nghệ Thông Tin

Giáo trình Cấu trúc dữ liệu và giải thuật: Phần 2

173 5 0
Tài liệu được quét OCR, nội dung có thể không chính xác

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

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

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 173
Dung lượng 20,56 MB

Nội dung

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 1

Chuong 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 3

Mộ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 4

lu — 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 5

5.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 6

var 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 7

5.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 8

begin 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 9

MAKENULL (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 10

Giả 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 11

while 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 12

5.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 13

c 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 14

5.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 15

Vi 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 17

Procedure 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 18

Mộ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 19

Câ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 20

7 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 21

Chuong 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 22

cá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 23

cù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 24

Insert (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 25

Cú 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 26

Else

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 27

Postcondition: 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 28

If 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 29

khô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 32

For 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 33

NewSet : 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 34

NewSet : 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 35

For 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 36

Postcondition: 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 37

Theo 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 38

1 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 39

T2 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ế *)

Ngày đăng: 06/07/2022, 21:21

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

TÀI LIỆU LIÊN QUAN