BÀI GIẢNG GIẢI THUẬT VÀ LẬP TRÌNH - QUY HOẠCH ĐỘNG - LÊ MINH HOÀNG - 4 ppt

36 645 4
BÀI GIẢNG GIẢI THUẬT VÀ LẬP TRÌNH - QUY HOẠCH ĐỘNG - LÊ MINH HOÀNG - 4 ppt

Đ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

Cấu trúc liệu Giải thuật 95 thời gian thực trung bình phức tạp hơn, ta ghi nhận kết chứng minh độ phức tạp trung bình HeapSort O(nlog2n) Có thể nhận xét thêm QuickSort đệ quy cần thêm khơng gian nhớ cho Stack, cịn HeapSort ngồi nút nhớ phụ để thực việc đổi chỗ, khơng cần dùng thêm khác HeapSort tốt QuickSort phương diện lý thuyết khơng có trường hợp tồi tệ HeapSort mắc phải Cũng nhờ có HeapSort mà giải tốn có chứa mơ-đun xếp, ta nói độ phức tạp thủ tục xếp khơng q O(nlog2n) 8.8 SẮP XẾP BẰNG PHÉP ĐẾM PHÂN PHỐI (DISTRIBUTION COUNTING) Có thuật tốn xếp đơn giản cho trường hợp đặc biệt: Dãy khoá k1, k2, …, kn số nguyên nằm khoảng từ tới M (TKey = M) Ta dựng dãy c0, c1, …, cM biến đếm, cV số lần xuất giá trị V dãy khoá: for V := to M cV := 0; {Khởi tạo dãy biến đếm} for i := to n cki := cki + 1; Ví dụ với dãy khoá: 1, 2, 2, 3, 0, 0, 1, 1, 3, (n = 10, M = 3), sau bước đếm ta có: c0 = 2; c1 = 3; c2 = 2; c3 = Dựa vào dãy biến đếm, ta hồn tồn biết được: sau xếp giá trị V phải nằm từ vị trí tới vị trí Như ví dụ giá trị phải nằm từ vị trí tới vị trí 2; giá trị phải đứng liên tiếp từ vị trí tới vị trí 5; giá trị đứng vị trí cịn giá trị nằm ba vị trí cuối 8, 9, 10: 0 1 2 3 Tức sau xếp: Giá trị đứng đoạn từ vị trí tới vị trí c0 Giá trị đứng đoạn từ vị trí c0 + tới vị trí c0 + c1 Giá trị đứng đoạn từ vị trí c0 + c1 + tới vị trí c0 + c1 + c2 … Giá trị v đoạn đứng từ vị trí c0 + c1 + … + cv-1 + tới vị trí c0 + c1 + c2 + … + cv … Để ý vị trí cuối đoạn, ta tính lại dãy c sau: for V := to M cV := cV-1 + cV Thì cV vị trí cuối đoạn chứa giá trị V dãy khoá xếp Muốn dựng lại dãy khoá xếp, ta thêm dãy khoá phụ x1, x2, …, xn Sau duyệt lại dãy khoá k, gặp khoá mang giá trị V ta đưa giá trị vào khố xcv giảm cv for i := n downto begin V := ki; XcV := ki; cV := cV - 1; end; Lê Minh Hoàng 96 Chuyên đề Khi dãy khố x dãy khố xếp, công việc cuối gán giá trị dãy khoá x cho dãy khoá k procedure DistributionCounting; {TKey = M} var c: array[0 M] of Integer; {Dãy biến đếm số lần xuất giá trị} x: TArray; {Dãy khoá phụ} i: Integer; V: TKey; begin for V := to M cV := 0; {Khởi tạo dãy biến đếm} for i := to n cki := cki + 1; {Đếm số lần xuất giá trị} for V := to M cV := cV-1 + cV; {Tính vị trí cuối đoạn} for i := n downto begin V := ki; xcV := ki; cV := cV - 1; end; k := x; {Sao chép giá trị từ dãy khoá x sang dãy khoá k} end; Rõ ràng độ phức tạp phép đếm phân phối O(max(M, n)) Nhược điểm phép đếm phân phối M q lớn cho dù n nhỏ khơng thể làm Có thể có thắc mắc thao tác dựng dãy khoá x, phép duyệt dãy khố k theo thứ tự kết xếp vậy, ta lại chọn phép duyệt ngược từ lên? Để trả lời câu hỏi này, ta phải phân tích thêm đặc trưng thuật tốn xếp: 8.9 TÍNH ỔN ĐỊNH CỦA THUẬT TOÁN SẮP XẾP (STABILITY) Một phương pháp xếp gọi ổn định bảo tồn thứ tự ban đầu ghi mang khoá danh sách Ví dụ ban đầu danh sách sinh viên xếp theo thứ tự tên alphabet, xếp danh sách sinh viên theo thứ tự giảm dần điểm thi, sinh viên điểm dồn đoạn danh sách giữ nguyên thứ tự tên alphabet Hãy xem lại thuật toán xếp trước, thuật tốn đó, thuật tốn xếp bọt, thuật toán xếp chèn phép đếm phân phối thuật toán xếp ổn định, cịn thuật tốn xếp khác (và nói chung thuật tốn xếp địi hỏi phải đảo giá trị ghi vị trí bất kỳ) không ổn định Với phép đếm phân phối mục trước, ta nhận xét hai ghi có khố xếp đưa giá trị vào dãy ghi phụ, ghi vào trước nằm phía sau Vậy nên ta đẩy giá trị ghi vào dãy phụ theo thứ tự ngược để giữ thứ tự tương đối ban đầu Nói chung, phương pháp xếp tổng quát cho dù khơng ổn định biến đổi để trở thành ổn định, phương pháp chung thể qua ví dụ sau: Giả sử ta cần xếp sinh viên danh sách theo thứ tự giảm dần điểm thuật toán xếp ổn định Ta thêm cho sinh viên khoá Index thứ tự ban đầu Đại học Sư phạm Hà Nội, 1999-2002 Cấu trúc liệu Giải thuật 97 danh sách Trong thuật toán xếp áp dụng, chỗ cần so sánh hai sinh viên A B xem anh phải đứng trước, trước hết ta quan tâm tới điểm số: Nếu điểm A khác điểm B anh điểm cao đứng trước, điểm số anh có Index nhỏ đứng trước Trong số toán, tính ổn định thuật tốn xếp định tới tính đắn tồn thuật tốn lớn Chính tính "nhanh" QuickSort tính ổn định phép đếm phân phối sở tảng cho hai thuật toán xếp cực nhanh dãy khố số mà ta trình bày 8.10 THUẬT TỐN SẮP XẾP BẰNG CƠ SỐ (RADIXSORT) Bài tốn đặt là: Cho dãy khoá số tự nhiên k1, k2, …, kn xếp chúng theo thứ tự không giảm (Trong trường hợp ta xét, TKey kiểu số tự nhiên) 8.10.1 Sắp xếp số theo kiểu hoán vị khoá (Exchange RadixSort) Hãy xem lại thuật tốn QuickSort, bước phân đoạn phân đoạn xét thành hai đoạn thoả mãn khoá đoạn đầu ≤ khoá đoạn sau thực tương tự hai đoạn tạo ra, việc phân đoạn tiến hành với so sánh khoá với giá trị khoá chốt Đối với số ngun ta coi số nguyên dãy z bit đánh số từ bit (bit hàng đơn vị) tới bit z - (bit cao nhất) Ví dụ: bit 11 = 1 (z = 4) Hình 34: Đánh số bit Vậy bước phân đoạn dãy khoá từ k1 tới kn, ta đưa khố có bit cao đầu dãy, khố có bit cao cuối dãy Dễ thấy khoá bắt đầu bit phải nhỏ khố bắt đầu bit Tiếp tục q trình phân đoạn với hai đoạn dãy khoá: Đoạn gồm khố có bit cao đoạn gồm khố có bit cao Với khố thuộc đoạn có bit cao giống nhau, nên ta áp dụng q trình phân đoạn tương tự theo bit thứ z - tiếp tục … Quá trình phân đoạn kết thúc đoạn xét rỗng hay ta tiến hành phân đoạn đến tận bit đơn vị, tức tất khoá thuộc hai đoạn tạo có bit đơn vị (điều đồng nghĩa với tất bit khác, tức giá trị khố) Ví dụ: Lê Minh Hồng 98 Chun đề Xét dãy khố: 1, 3, 7, 6, 5, 2, 3, 4, 4, 5, 6, Tương ứng với dãy bit: 001 011 111 110 101 010 011 100 100 101 110 111 111 100 100 101 110 111 100 100 111 110 110 111 101 110 110 111 111 Trước hết ta chia đoạn dựa vào bit (bit cao nhất): 001 011 011 010 101 110 Sau chia tiếp hai đoạn tạo dựa vào bit 1: 001 011 011 010 101 101 Cuối cùng, chia tiếp đoạn tạo dựa vào bit 0: 001 010 011 011 100 100 101 Ta dãy khoá tương ứng: 1, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, dãy khố xếp Q trình chia đoạn dựa vào bit b chia thành đoạn rỗng đoạn gồm tồn phần tử cịn lại, việc chia đoạn không bị rơi vào trình đệ quy vơ hạn lần đệ quy phân đoạn dựa vào bit b - 1, b - …và xét đến bit phải dừng lại Cơng việc cịn lại cố gắng hiểu đoạn chương trình sau phân tích xem hoạt động đúng: Đại học Sư phạm Hà Nội, 1999-2002 Cấu trúc liệu Giải thuật 99 procedure ExchangeRadixSort; var z: Integer; {Độ dài dãy bit biểu diễn khoá} procedure Partition(L, H, b: Integer); {Phân đoạn [L, H] dựa vào bit b} var i, j: Integer; begin if L ≥ H then Exit; i := L; j := H; repeat {Hai vòng lặp cầm canh i < j} while (i < j) and (Bit b ki = 0) i := i + 1; {Tìm khố có bit b = từ đầu đoạn} while (i < j) and (Bit b kj = 1) j := j - 1; {Tìm khố có bit b = từ cuối đoạn} ; until i = j; if then j := j + 1; {j điểm bắt đầu đoạn có bit b 1} if b > then {Chưa xét tới bit đơn vị} begin Partition(L, j - 1, b - 1); Partition(j, R, b - 1); end; end; begin Partition(1, n, z - 1); end; Với RadixSort, ta hồn tồn làm hệ số R khác không thiết phải làm hệ nhị phân (ý tưởng tương tự trên), nhiên q trình phân đoạn khơng phải chia làm mà chia thành R đoạn Về độ phức tạp thuật toán, ta thấy để phân đoạn bit thời gian C.n để chia tất đoạn cần chia bit (C số) Vậy tổng thời gian phân đoạn z bit C.n.z Trong trường hợp xấu nhất, độ phức tạp RadixSort O(n.z) Và độ phức tạp trung bình RadixSort O(n.min(z, log2n)) Nói chung, RadixSort cài đặt thể tốc độ tối đa hệ thống cho phép xử lý trực tiếp bit: Hệ thống phải cho phép lấy bit dễ dàng thao tác với thời gian nhanh hẳn so với thao tác Byte Word Khi RadixSort tốt nhiều QuickSort (Ta thử lập trình xếp dãy nhị phân độ dài z theo thứ tự từ điển để khảo sát) Trên máy tính cho phép xử lý trực tiếp Byte (hay Word, DWord v.v…), việc tách bit khỏi Byte để xử lý lại chậm làm ảnh hưởng khơng nhỏ tới tốc độ RadixSort Chính vậy, phương pháp hay, cài đặt cụ thể tốc độ ngang ngửa qua mặt QuickSort 8.10.2 Sắp xếp số trực tiếp (Straight RadixSort) Ta trình bày phương pháp xếp số trực tiếp ví dụ: Sắp xếp dãy khố: 925 Lê Minh Hồng 817 821 638 639 744 742 563 570 166 100 Chuyên đề Trước hết, ta xếp dãy khoá theo thứ tự tăng dần chữ số hàng đơn vị thuật toán xếp khác, dãy khoá: 570 821 742 563 744 925 166 817 638 639 Sau đó, ta xếp dãy khố tạo thành theo thứ tự tăng dần chữ số hàng chục thuật toán xếp ổn định, dãy khoá: 817 821 925 638 639 742 744 563 166 570 Vì thuật tốn xếp ta sử dụng ổn định, nên hai khố có chữ số hàng chục giống khố có chữ số hàng đơn vị nhỏ đứng trước Nói có nghĩa dãy khố thu có thứ tự tăng dần giá trị tạo thành từ hai chữ số cuối Cuối cùng, ta xếp lại dãy khoá theo thứ tự tăng dần chữ số hàng trăm thuật toán xếp ổn định, thu dãy khoá: 166 563 570 638 639 742 744 817 821 925 Lập luận tương tự dựa vào tính ổn định phép xếp, dãy khố thu có thứ tự tăng dần giá trị tạo thành ba chữ số, dãy khố Nhận xét: Ta hồn tồn coi số chữ số khố nhau, ví dụ có số 15 dãy khố ta coi 015 Cũng từ ví dụ, ta thấy số lượt thao tác xếp phải áp dụng số chữ số tạo thành khoá Với hệ số lớn, biểu diễn giá trị khố phải dùng chữ số Ví dụ số 12345 hệ thập phân phải dùng tới chữ số, hệ số 1000 cần dùng chữ số AB mà thôi, A chữ số mang giá trị 12 B chữ số mang giá trị 345 Tốc độ xếp số trực tiếp phụ thuộc nhiều vào thuật tốn xếp ổn định bước Khơng có lựa chọn khác tốt phép đếm phân phối Tuy nhiên, phép đếm phân phối không cài đặt hiệu tập giá trị khố q rộng, khơng cho phép dựng dãy biến đếm phải sử dụng dãy biến đếm dài (Điều xảy chọn hệ số lớn) Một lựa chọn khôn ngoan nên chọn hệ số thích hợp cho trường hợp cụ thể để dung hoà tới mức tối ưu ba mục tiêu: Việc lấy chữ số số thực dễ dàng Sử dụng lần gọi phép đếm phân phối Phép đếm phân phối thực nhanh Đại học Sư phạm Hà Nội, 1999-2002 Cấu trúc liệu Giải thuật 101 procedure StraightRadixSort; const radix = …; {Tuỳ chọn hệ số radix cho hợp lý} var t: TArray; {Dãy khoá phụ} p: Integer; nDigit: Integer; {Số chữ số cho khoá, đánh số từ chữ số thứ hàng đơn vị đến chữ số thứ nDigit - 1} Flag: Boolean; {Flag = True dãy k, ghi kết vào dãy t; Flag = False dãy t, ghi kq vào k} function GetDigit(Num: TKey; p: Integer): Integer; {Lấy chữ số thứ p số Num (0≤p X, ta tiến hành tìm kiếm tiếp với đoạn từ kinf tới kmedian - Nếu kmedian = X việc tìm kiếm thành cơng (kết thúc q trình tìm kiếm) Quá trình tìm kiếm thất bại đến bước đó, đoạn tìm kiếm rỗng (inf > sup) {Tìm kiếm nhị phân dãy khố k1 ≤ k2 ≤ … ≤ kn; hàm thử tìm xem dãy có khố = X khơng, thấy trả số khố ấy, khơng thấy trả 0} function BinarySearch(X: TKey): Integer; var inf, sup, median: Integer; begin inf := 1; sup := n; while inf ≤ sup begin median := (inf + sup) div 2; if kmedian = X then begin BinarySearch := median; Exit; end; if kmedian < X then inf := median + else sup := median - 1; end; BinarySearch := 0; end; Người ta chứng minh độ phức tạp tính tốn thuật tốn tìm kiếm nhị phân trường hợp tốt O(1), trường hợp xấu O(log2n) trường hợp trung bình O(log2n) Tuy nhiên, ta không nên quên trước sử dụng tìm kiếm nhị phân, dãy khố phải xếp rồi, tức thời gian chi phí cho việc xếp phải tính đến Nếu dãy khố ln ln biến động phép bổ sung hay loại bớt lúc chi phí cho xếp lại lên rõ làm bộc lộ nhược điểm phương pháp 9.4 CÂY NHỊ PHÂN TÌM KIẾM (BINARY SEARCH TREE - BST) Cho n khoá k1, k2, …, kn, khố có quan hệ thứ tự tồn phần Cây nhị phân tìm kiếm ứng với dãy khố nhị phân mà nút chứa giá trị khoá n khoá cho, hai giá trị chứa hai nút khác Đối với nút cây, tính chất sau ln thoả mãn: • Mọi khố nằm trái nút nhỏ khố ứng với nút • Mọi khố nằm phải nút lớn khố ứng với nút Lê Minh Hồng 118 Chun đề Hình 37: Cây nhị phân tìm kiếm Thuật tốn tìm kiếm mơ tả chung sau: Trước hết, khố tìm kiếm X so sánh với khoá gốc cây, tình xảy ra: Khơng có gốc (cây rỗng): X khơng có cây, phép tìm kiếm thất bại X trùng với khố gốc: Phép tìm kiếm thành cơng X nhỏ khố gốc, phép tìm kiếm tiếp tục trái gốc với cách làm tương tự X lớn khoá gốc, phép tìm kiếm tiếp tục phải gốc với cách làm tương tự Giả sử cấu trúc nút mô tả sau: type PNode = ^TNode; {Con trỏ chứa liên kết tới nút} TNode = record {Cấu trúc nút} Info: TKey; {Trường chứa khoá} Left, Right: PNode; {con trỏ tới nút trái phải, trỏ tới nil khơng có nút trái (phải)} end; Gốc lưu trỏ Root Cây rỗng Root = nil Thuật tốn tìm kiếm nhị phân tìm kiếm viết sau: {Hàm tìm kiếm BST, trả nút chứa khố tìm kiếm X tìm thấy, trả nil khơng tìm thấy} function BSTSearch(X: TKey): PNode; var p: PNode; begin p := Root; {Bắt đầu với nút gốc} while p ≠ nil if X = p^.Info then Break; else if X < p^.Info then p := p^.Left else p := p^.Right; BSTSearch := p; end; Thuật toán dựng nhị phân tìm kiếm từ dãy khố k1, k2, …, kn làm gần giống trình tìm kiếm Ta chèn khoá vào cây, trước chèn, ta tìm xem khố có hay chưa, có bỏ qua, chưa có ta thêm nút chứa khố cần chèn nối nút vào nhị phân tìm kiếm Đại học Sư phạm Hà Nội, 1999-2002 Cấu trúc liệu Giải thuật 119 {Thủ tục chèn khoá X vào BST} procedure BSTInsert(X); var p, q: PNode; begin q := nil; p := Root; {Bắt đầu với p = nút gốc; q trỏ chạy đuổi theo sau} while p ≠ nil begin q := p; if X = p^.Info then Break; else {X ≠ p^.Info cho p chạy sang nút con, q^ ln giữ vai trò cha p^} if X < p^.Info then p := p^.Left else p := p^.Right; end; if p = nil then {Khố X chưa có BST} begin New(p); {Tạo nút mới} p^.Info := X; {Đưa giá trị X vào nút tạo ra} p^.Left := nil; p^.Right := nil; {Nút chèn vào BST trở thành nút lá} if Root = nil then Root := NewNode {BST rỗng, đặt Root nút tạo} else {Móc NewNode^ vào nút cha q^} if X < q^.Info then q^.Left := NewNode else q^.Right := NewNode; end; end; Phép loại bỏ nhị phân tìm kiếm không đơn giản phép bổ sung hay phép tìm kiếm Muốn xố giá trị nhị phân tìm kiếm (Tức dựng lại chứa tất giá trị lại), trước hết ta tìm xem giá trị cần xố nằm nút D nào, có ba khả xảy ra: • Nút D nút lá, trường hợp ta việc đem mối nối cũ trỏ tới nút D (từ nút cha D) thay nil, giải phóng nhớ cấp cho nút D (Hình 38) 4 9 Hình 38: Xóa nút BST • Nút D có nhánh con, ta đem nút gốc nhánh vào chỗ nút D, tức chỉnh lại mối nối: Từ nút cha nút D không nối tới nút D mà nối tới nhánh nút D Cuối cùng, ta giải phóng nhớ cấp cho nút D (Hình 39) Lê Minh Hồng 120 Chun đề 4 7 6 9 Hình 39 Xóa nút có nhánh BST • Nút D có hai nhánh trái phải, có hai cách làm hợp lý cả: o Hoặc tìm nút chứa khố lớn trái, đưa giá trị chứa sang nút D, xố nút Do tính chất BST, nút chứa khố lớn trái nút cực phải trái nên khơng thể có hai được, việc xố đưa hai trường hợp (Hình 40) 7 9 Hình 40: Xóa nút có hai nhánh BST thay nút cực phải trái o Hoặc tìm nút chứa khố nhỏ phải, đưa giá trị chứa sang nút D, xố nút Do tính chất BST, nút chứa khoá nhỏ phải nút cực trái phải nên khơng thể có hai được, việc xố đưa hai trường hợp 5 7 6 9 Hình 41: Xóa nút có hai nhánh BST thay nút cực trái phải Đại học Sư phạm Hà Nội, 1999-2002 Cấu trúc liệu Giải thuật 121 {Thủ tục xoá khoá X khỏi BST} procedure BSTDelete(X: TKey); var p, q, Node, Child: PNode; begin p := Root; q := nil; {Về sau, p trỏ sang nút khác, ta giữ cho q^ cha p^} while p ≠ nil {Tìm xem có khố X khơng?} begin if p^.Info = X then Break; {Tìm thấy} q := p; if X < p^.Info then p := p^.Left else p := p^.Right; end; if p = nil then Exit; {X không tồn BST nên không xoá được} if (p^.Left ≠ nil) and (p^.Right ≠ nil) then {p^ có trái phải} begin Node := p; {Giữ lại nút chứa khoá X} q := p; p := p^.Left; {Chuyển sang nhánh trái để tìm nút cực phải} while p^.Right ≠ nil begin q := p; p := p^.Right; end; Node^.Info := p^.Info; {Chuyển giá trị từ nút cực phải nhánh trái lên Node^} end; {Nút bị xoá nút p^, có nhiều con} {Nếu p^ có nút đem Child trỏ tới nút đó, khơng có Child = nil } if p^.Left ≠ nil then Child := p^.Left else Child := p^.Right; if p = Root then Root := Child; {Nút p^ bị xoá gốc cây} else {Nút bị xố p^ khơng phải gốc lấy mối nối từ cha q^ nối thẳng tới Child} if q^.Left = p then q^.Left := Child else q^.Right := Child; Dispose(p); end; Trường hợp trung bình, thao tác tìm kiếm, chèn, xố BST có độ phức tạp O(log2n) Cịn trường hợp xấu nhất, nhị phân tìm kiếm bị suy biến thao tác có độ phức tạp O(n), với n số nút BST Nếu ta mở rộng khái niệm nhị phân tìm kiếm sau: Giá trị lưu nút lớn giá trị lưu trái nhỏ giá trị lưu phải Thì cần sửa đổi thủ tục BSTInsert chút, chèn vào n giá trị, BST có n nút (có thể có hai nút chứa giá trị) Khi ta duyệt nút theo kiểu trung thứ tự (inorder traversal), ta liệt kê giá trị lưu theo thứ tự tăng dần Phương pháp xếp người ta gọi Tree Sort Độ phức tạp tính tốn trung bình Tree Sort O(nlog2n) Phép tìm kiếm BST hiệu bị suy biến, người ta có nhiều cách xoay xở để tránh trường hợp Đó phép quay để dựng nhị phân cân đối AVL, hay kỹ thuật dựng nhị phân tìm kiếm tối ưu Những kỹ thuật ta tham khảo tài liệu khác cấu trúc liệu giải thuật Lê Minh Hoàng 122 Chuyên đề 9.5 PHÉP BĂM (HASH) Tư tưởng phép băm dựa vào giá trị khoá k1, k2, …, kn, chia khố thành nhóm Những khố thuộc nhóm có đặc điểm chung đặc điểm khơng có nhóm khác Khi có khố tìm kiếm X, trước hết ta xác định xem X thuộc vào dãy khố cho phải thuộc nhóm tiến hành tìm kiếm nhóm Một ví dụ từ điển, bạn sinh viên thường dán vào 26 mảnh giấy nhỏ vào trang để đánh dấu trang trang khởi đầu đoạn chứa từ có chữ đầu Để tra từ cần tìm trang chứa từ có chữ đầu với từ cần tìm A B Z Một ví dụ khác dãy khố số tự nhiên, ta chia làm m nhóm, nhóm gồm khố đồng dư theo mơ-đun m Có nhiều cách cài đặt phép băm: Cách thứ chia dãy khoá làm đoạn, đoạn chứa khoá thuộc nhóm ghi nhận lại vị trí đoạn Để có khố tìm kiếm, xác định cần phải tìm khố đoạn Cách thứ hai chia dãy khố làm m nhóm, Mỗi nhóm danh sách nối đơn chứa giá trị khoá ghi nhận lại chốt danh sách nối đơn Với khố tìm kiếm, ta xác định phải tìm khố danh sách nối đơn tiến hành tìm kiếm danh sách nối đơn Với cách lưu trữ này, việc bổ sung loại bỏ giá trị khỏi tập hợp khoá dễ dàng nhiều phương pháp Cách thứ ba chia dãy khố làm m nhóm, nhóm lưu trữ dạng nhị phân tìm kiếm ghi nhận lại gốc nhị phân tìm kiếm đó, phương pháp nói tốt hai phương pháp trên, nhiên dãy khố phải có quan hệ thứ tự tồn phần làm 9.6 KHỐ SỐ VỚI BÀI TỐN TÌM KIẾM Mọi liệu lưu trữ máy tính số hố, tức lưu trữ đơn vị Bit, Byte, Word v.v… Điều có nghĩa giá trị khố bất kỳ, ta hồn tồn biết mã hố số Và điều chắn hai khoá khác lưu trữ hai số khác Đại học Sư phạm Hà Nội, 1999-2002 Cấu trúc liệu Giải thuật 123 Đối với toán xếp, ta khơng thể đưa việc xếp dãy khố việc xếp dãy khoá số mã khoá Bởi quan hệ thứ tự số khác với thứ tự cần khoá Nhưng tốn tìm kiếm khác, với khố tìm kiếm, Câu trả lời "Khơng tìm thấy" "Có tìm thấy chỗ …" nên ta hồn tồn thay khố mã số mà khơng bị sai lầm, lưu ý điều là: hai khoá khác phải mã hố thành hai số khác mà thơi Nói có nghĩa việc nghiên cứu thuật tốn tìm kiếm dãy khố số quan trọng, ta trình bày số phương pháp 9.7 CÂY TÌM KIẾM SỐ HỌC (DIGITAL SEARCH TREE - DST) Xét dãy khoá k1, k2, …, kn số tự nhiên, giá trị khoá đổi hệ nhị phân có z chữ số nhị phân (bit), bit đánh số từ (là hàng đơn vị) tới z - từ phải sang trái Ví dụ: bit 11 = 1 (z = 4) Hình 42: Đánh số bit Cây tìm kiếm số học chứa giá trị khố mơ tả sau: Trước hết, nhị phân mà nút chứa giá trị khố Nút gốc có tối đa hai con, ngồi giá trị khố chứa nút gốc, tất giá trị khố có bit cao nằm trái, cịn tất giá trị khố có bit cao nằm phải Đối với hai nút nút gốc, vấn đề tương tự bit z - (bit đứng thứ nhì từ trái sang) So sánh tìm kiếm số học với nhị phân tìm kiếm, chúng khác cách chia hai trái/phải Đối với nhị phân tìm kiếm, việc chia thực cách so sánh với khoá nằm nút gốc, cịn tìm kiếm số học, nút gốc có mức d việc chia thực theo bit thứ d tính từ trái sang (bit z - d) khoá Ta nhận thấy khoá bắt đầu bit chắn nhỏ khoá bắt đầu bit 1, điểm tương đồng nhị phân tìm kiếm tìm kiếm số học: Với nút nhánh: Mọi giá trị chứa trái nhỏ giá trị chứa phải (Hình 43) Lê Minh Hoàng 124 Chuyên đề 10 12 10 12 11 = = = = = = = = = 0110 0101 0010 0111 1000 1010 1100 1011 0100 11 Hình 43: Cây tìm kiếm số học Giả sử cấu trúc nút mô tả sau: type PNode = ^TNode; {Con trỏ chứa liên kết tới nút} TNode = record {Cấu trúc nút} Info: TKey; {Trường chứa khoá} Left, Right: PNode; {con trỏ tới nút trái phải, trỏ tới nil nút trái (phải)} end; Gốc lưu trỏ Root Ban đầu nút Root = nil (cây rỗng) Với khố tìm kiếm X, việc tìm kiếm tìm kiếm số học mơ tả sau: Ban đầu đứng nút gốc, xét bit X từ trái sang phải (từ bit z - tới bit 0), gặp bit rẽ sang nút trái, gặp bit rẽ sang nút phải Quá trình tiếp tục gặp hai tình sau: • Đi tới nút rỗng (do rẽ theo liên kết nil), q trình tìm kiếm thất bại khố X khơng có • Đi tới nút mang giá trị X, q trình tìm kiếm thành cơng {Hàm tìm kiếm tìm kiếm số học, trả nút chứa khố tìm kiếm X tìm thấy, trả nil khơng tìm thấy z độ dài dãy bit biểu diễn khoá} function DSTSearch(X: TKey): PNode; var b: Integer; p: PNode; begin b := z; p := Root; {Bắt đầu với nút gốc} while (p ≠ nil) and (p^.Info ≠ X) {Chưa gặp phải tình trên} begin b := b - 1; {Xét bit b X} if then p := p^.Left {Gặp rẽ trái} else p := p^.Right; {Gặp rẽ phải} end; DSTSearch := p; end; Thuật toán dựng tìm kiếm số học từ dãy khố k1, k2, …, kn làm gần giống trình tìm kiếm Ta chèn khoá vào cây, trước chèn, ta tìm xem khố có hay chưa, có bỏ qua, chưa có ta thêm nút chứa khố cần chèn nối nút vào tìm kiếm số học mối nối rỗng vừa rẽ sang khiến trình tìm kiếm thất bại Đại học Sư phạm Hà Nội, 1999-2002 Cấu trúc liệu Giải thuật 125 {Thủ tục chèn khố X vào tìm kiếm số học} procedure DSTInsert(X: TKey); var b: Integer; p, q: PNode; begin b := z; p := Root; while (p ≠ nil) and (p^.Info ≠ X) begin b := b - 1; {Xét bit b X} q := p; {Khi p chạy xuống nút q^ ln giữ vai trò nút cha p^} if then p := p^.Left {Gặp rẽ trái} else p := p^.Right; {Gặp rẽ phải} end; if p = nil then {Giá trị X chưa có cây} begin New(p); {Tạo nút p^} p^.Info := X; {Nút tạo chứa khoá X} p^.Left := nil; p^.Right := nil; {Nút trở thành cây} if Root = nil then Root := p {Cây rỗng nút thêm trở thành gốc} else {Khơng móc p^ vào mối nối vừa rẽ sang từ q^} if then q^.Left := p else q^.Right := p; end; end; Muốn xoá bỏ giá trị khỏi tìm kiếm số học, trước hết ta xác định nút chứa giá trị cần xố nút D nào, sau tìm nhánh gốc D nút bất kỳ, chuyển giá trị chứa nút sang nút D xoá nút {Thủ tục xoá khoá X khỏi tìm kiếm số học} procedure DSTDelete(X: TKey); var b: Integer; p, q, Node: PNode; begin {Trước hết, tìm kiếm giá trị X xem nằm nút nào} b := z; p := Root; while (p ≠ nil) and (p^.Info ≠ X) begin b := b - 1; q := p; {Mỗi lần p chuyển sang nút con, ta đảm bảo cho q^ nút cha p^} if then p := p^.Left else p := p^.Right; end; if p = nil then Exit; {X khơng tồn khơng xố được} Node := p; {Giữ lại nút chứa khoá cần xoá} while (p^.Left ≠ nil) or (p^.Right ≠ nil) {chừng p^ chưa phải lá} begin q := p; {q chạy đuổi theo p, p chuyển xuống nhánh con} if p^.Left ≠ nil then p := p^.Left else p := p^.Right; end; Node^.Info := p^.Info; {Chuyển giá trị từ nút p^ sang nút Node^} if Root = p then Root := nil {Cây gồm nút gốc xoá gốc} else {Cắt mối nối từ q^ tới p^} if q^.Left = p then q^.Left := nil else q^.Right := nil; Dispose(p); end; Lê Minh Hoàng 126 Chuyên đề Về mặt trung bình, thao tác tìm kiếm, chèn, xố tìm kiếm số học có độ phức tạp O(log2n) trường hợp xấu nhất, độ phức tạp thao tác O(z), tìm kiếm số học có chiều cao khơng q z + 9.8 CÂY TÌM KIẾM CƠ SỐ (RADIX SEARCH TREE - RST) Trong tìm kiếm số học, nhị phân tìm kiếm, phép tìm kiếm bước phải so sánh giá trị khoá X với giá trị lưu nút Trong trường hợp khố có cấu trúc lớn, việc so sánh nhiều thời gian Cây tìm kiếm số phương pháp khắc phục nhược điểm đó, nội dung tóm tắt sau: Trong tìm kiếm số nhị phân, có nút chứa giá trị khố, cịn giá trị chứa nút nhánh vơ nghĩa Các nút tìm kiếm số nằm mức z + Đối với nút gốc tìm kiếm số, có tối đa hai nhánh con, khố chứa nút nhánh trái có bit cao 0, khoá chứa nút nhánh phải có bit cao Đối với hai nhánh nút gốc, vấn đề tương tự với bit thứ z - 2, ví dụ với nhánh trái nút gốc, lại có tối đa hai nhánh con, khố chứa nút nhánh trái có bit thứ z - (chúng bắt đầu hai bit 00), khoá chứa nút nhánh phải có bit thứ z - (chúng bắt đầu hai bit 01)… Tổng quát với nút mức d, có tối đa hai nhánh con, nút nhánh trái chứa khố có bit z - d 0, nút nhánh phải chứa khố có bit thứ z - d (Hình 44) 0 0 0010 1 1 1 0 0 10 11 12 0100 0101 0111 1000 1010 1011 1100 Hình 44: Cây tìm kiếm số Khác với nhị phân tìm kiếm hay tìm kiếm số học Cây tìm kiếm số khởi tạo gồm có nút gốc, nút gốc tồn suốt q trình sử dụng: khơng bị xoá Đại học Sư phạm Hà Nội, 1999-2002 Cấu trúc liệu Giải thuật 127 Để tìm kiếm giá trị X tìm kiếm số, ban đầu ta đứng nút gốc duyệt dãy bit X từ trái qua phải (từ bit z - đến bit 0), gặp bit rẽ sang nút trái cịn gặp bit rẽ sang nút phải, tiếp tục hai tình sau xảy ra: Hoặc tới nút rỗng (do rẽ theo liên kết nil) trình tìm kiếm thất bại X khơng có RST Hoặc duyệt hết dãy bit X đứng nút lá, q trình tìm kiếm thành cơng chắn nút chứa giá trị X {Hàm tìm kiếm tìm kiếm số, trả nút chứa khố tìm kiếm X tìm thấy, trả nil khơng tìm thấy z độ dài dãy bit biểu diễn khoá} function RSTSearch(X: TKey): PNode; var b: Integer; p: PNode; begin b := z; p := Root; {Bắt đầu với nút gốc, RST gốc ln có sẵn} repeat b := b - 1; {Xét bit b X} if then p := p^.Left {Gặp rẽ trái} else p := p^.Right; {Gặp rẽ phải} until (p = nil) or (b = 0); RSTSearch := p; end; Thao tác chèn giá trị X vào RST thực sau: Đầu tiên, ta đứng gốc duyệt dãy bit X từ trái qua phải (từ bit z - bit 0), gặp rẽ trái, gặp rẽ phải Nếu trình rẽ theo liên kết nil (đi tới nút rỗng) tạo nút mới, nối vào theo liên kết để có đường tiếp Sau duyệt hết dãy bit X, ta dừng lại nút RST, công việc cuối đặt giá trị X vào nút Ví dụ: 0 1 0 1 0 1 5 010 101 101 010 101 101 111 Hình 45: Với độ dài dãy bit z = 3, tìm kiếm số gồm khố 2, 4, sau thêm giá trị Lê Minh Hồng 128 Chun đề {Thủ tục chèn khố X vào tìm kiếm số} procedure RSTInsert(X: TKey); var b: Integer; p, q: PNode; begin b := z; p := Root; {Bắt đầu từ nút gốc, RST gốc ln ≠ nil} repeat b := b - 1; {Xét bit b X} q := p; {Khi p chạy xuống nút q^ ln giữ vai trò nút cha p^} if then p := p^.Left {Gặp rẽ trái} else p := p^.Right; {Gặp rẽ phải} if p = nil then {Khơng đặt thêm nút để tiếp} begin New(p); {Tạo nút đem p trỏ tới nút đó} p^.Left := nil; p^.Right := nil; if then q^.Left := p {Nối p^ vào bên trái q^} else q^.Right := p; {Nối p^ vào bên phải q^} end; until b = 0; p^.Info := X; {p^ nút để đặt X vào} end; Với tìm kiếm số, việc xố giá trị khố khơng phải xố riêng nút mà cịn phải xố tồn nhánh độc đạo tới nút để tránh lãng phí nhớ (Hình 46) 0 1 0 1 1 0 010 101 101 111 010 101 101 Hình 46: RST chứa khố 2, 4, 5, RST sau loại bỏ giá trị Ta lặp lại q trình tìm kiếm giá trị khố X, trình từ gốc xuống lá, bước đi, gặp nút ngã ba (nút có trái phải - nút cấp hai), ta ghi nhận lại ngã ba hướng rẽ Kết thúc trình tìm kiếm ta giữ lại ngã ba qua cuối cùng, từ nút tới nút chứa X đường độc đạo (khơng có chỗ rẽ), ta tiến hành dỡ bỏ tất nút đoạn đường độc đạo khỏi tìm kiếm số Để khơng bị gặp lỗi suy biến (khơng có nút cấp 2) ta coi gốc nút ngã ba Đại học Sư phạm Hà Nội, 1999-2002 Cấu trúc liệu Giải thuật 129 {Thủ tục xoá khoá X khỏi tìm kiếm số} procedure RSTDelete(X: TKey); var b: Integer; p, q, TurnNode, Child: PNode; begin {Trước hết, tìm kiếm giá trị X xem nằm nút nào} b := z; p := Root; repeat b := b - 1; q := p; {Mỗi lần p chuyển sang nút con, ta đảm bảo cho q^ nút cha p^} if then p := p^.Left else p := p^.Right; if (b = z - 1) or (q^.Left ≠ nil) and (q^.Right ≠ nil) then {q^ nút ngã ba} begin TurnNode := q; Child := p; {Ghi nhận lại q^ hướng rẽ} end; until (p = nil) or (b = 0); if p = nil then Exit; {X không tồn khơng xố được} {Trước hết, cắt nhánh độc đạo khỏi cây} if TurnNode^.Left = Child then TurnNode^.Left := nil else TurnNode^.Right := nil p := Child; {Chuyển sang đoạn đường độc đạo, bắt đầu xoá} repeat q := p; {Lưu ý p^ có tối đa nhánh mà thôi, cho p trỏ sang nhánh có} if p^.Left ≠ nil then p := p^.Left else p := p^.Right; Dispose(q); {Giải phóng nhớ cho nút q^} until p = nil; end; Ta có nhận xét là: Hình dáng tìm kiếm số khơng phụ thuộc vào thứ tự chèn khoá vào mà phụ thuộc vào giá trị khoá chứa Đối với tìm kiếm số, độ phức tạp tính tốn cho thao tác tìm kiếm, chèn, xố trường hợp xấu trung bình O(z) Do khơng phải so sánh giá trị khố dọc đường đi, nhanh tìm kiếm số học gặp khoá cấu trúc lớn Tốc độ nói tốt, vấn đề nhớ khiến ta phải xem xét: Giá trị chứa nút nhánh tìm kiếm số vơ nghĩa dẫn tới lãng phí nhớ Một giải pháp cho vấn đề là: Duy trì hai dạng nút tìm kiếm số: Dạng nút nhánh chứa liên kết trái, phải dạng nút chứa giá trị khoá Cài đặt số ngôn ngữ định kiểu mạnh đơi khó Giải pháp thứ hai đặc tả tương tự RST, sửa đổi chút: có nút chứa giá trị X nối với nhánh độc đạo cắt bỏ nhánh độc đạo đó, thay vào chỗ nhánh nút chứa giá trị X Như giá trị khoá chứa nút nút không nằm mức z + mà nằm mức khác Phương pháp tiết kiệm nhớ mà cịn làm cho q trình tìm kiếm nhanh Giá phải trả cho phương pháp thao tác chèn, xoá phức tạp Tên cấu trúc liệu Trie (Trie khơng phải Tree) tìm kiếm số Lê Minh Hồng 130 Chuyên đề 0 1 0 1 1 0 10 11 12 a) 0 1 12 10 11 b) Hình 47: Cây tìm kiếm số a) Trie tìm kiếm số b) Tương tự phương pháp xếp số, phép tìm kiếm số khơng thiết phải chọn hệ số Ta chọn hệ số lớn để có tốc độ nhanh (kèm theo tốn nhớ), lưu ý tìm kiếm số học tìm kiếm số trường hợp khơng cịn nhị phân mà R_phân với R hệ số chọn Trong phương pháp tìm kiếm số, thực phương pháp tinh t thơng minh nhất, có cấu trúc gần giống khơng có nút dư thừa, q trình duyệt bit khố tìm kiếm khơng phải từ trái qua phải mà theo thứ tự bit kiểm soát lưu nút qua Phương pháp có tên gọi Practical Algorithm To Retrieve Information Coded In Alphanumeric (PATRICIA) Morrison đề xuất Tuy nhiên, việc cài đặt phương pháp phức tạp (đặc biệt thao tác xoá giá trị khoá), ta tham khảo nội dung tài liệu khác Đại học Sư phạm Hà Nội, 1999-2002 ... dựa vào bit b - 1, b - ? ?và xét đến bit phải dừng lại Công việc cịn lại cố gắng hiểu đoạn chương trình sau phân tích xem hoạt động đúng: Đại học Sư phạm Hà Nội, 199 9-2 002 Cấu trúc liệu Giải thuật. .. dung thuật tốn đó, mà cách thuộc tốt cài đặt chúng vài lần với ràng buộc liệu khác (nếu thử hai ngơn ngữ lập trình tốt) đừng quên kỹ thuật xếp số Bài tập Bài Viết thuật tốn QuickSort khơng đệ quy. .. alphabet Hãy xem lại thuật tốn xếp trước, thuật tốn đó, thuật toán xếp bọt, thuật toán xếp chèn phép đếm phân phối thuật toán xếp ổn định, cịn thuật tốn xếp khác (và nói chung thuật tốn xếp địi

Ngày đăng: 13/08/2014, 20:22

Từ khóa liên quan

Tài liệu cùng người dùng

Tài liệu liên quan