11
Cây nhị phân tổng quát là một tập hợp hữu hạn các đỉnh được xác định đệ quy như sau: - Một tập trống là một cây nhị phân. - Giả sử T 1 và T 2 là hai cây nhị phân không cắt nhau và r là một đỉnh mới không thuộc T1, T2. Khi đó ta có thể thành lập một cây nhị phân mới T với gốc r có T1 là cây con bên trái, T2 là cây con bên phải của gốc. Cây nhị phân có thể trống, mỗi đỉnh của nó luôn luôn có hai cây con là cây con bên trái và cây con bên phải. Mỗi đỉnh của cây nhị phân chỉ có nhiều nhất là hai đỉnh con: một đỉnh con bên trái (đó là gốc của cây con trái) và một đỉnh con bên phải (đó là gốc của cây con phải). Ví dụ cây nhị phân: Hnh 1 Cách cài đặt cây nhị phân: type nodePtr= ^node; node= record key: integer; left,right: NodePtr end; var root: nodePtr; Con trỏ root trỏ tới gốc cây. Con trỏ left trỏ tới đỉnh con bên trái (nếu cây con bên trái là rỗng th left = NIL), con trỏ

Cây tìm kiếm nhị phân

  • Upload
    thanh

  • View
    168

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Cây tìm kiếm nhị phân

Cây nhị phân tổng quát là một tập hợp hữu hạn các đỉnh được xác định đệ quy như sau:

- Một tập trống là một cây nhị phân.

- Giả sử T1 và T2 là hai cây nhị phân không cắt nhau và r là một đỉnh mới không thuộc T1, T2. Khi đó ta có thể thành lập một cây nhị phân mới T với gốc r có T1 là cây con bên trái, T2 là cây con bên phải của gốc.

Cây nhị phân có thể trống, mỗi đỉnh của nó luôn luôn có  hai cây con là cây con bên trái và cây con bên phải. Mỗi đỉnh của cây nhị phân chỉ có nhiều nhất là hai đỉnh con: một đỉnh con bên trái (đó là gốc của cây con trái) và một đỉnh con bên phải (đó là gốc của cây con phải). Ví dụ cây nhị phân:

Hinh 1

Cách cài đặt cây nhị phân:

type  nodePtr= ^node;

      node= record

             key: integer;

             left,right: NodePtr

            end;

var root: nodePtr;

Con trỏ root trỏ tới gốc cây. Con trỏ left trỏ tới đỉnh con bên trái (nếu cây con bên trái là rỗng thi left = NIL), con trỏ right trỏ tới đỉnh con bên phải. Thông tin về mỗi đỉnh: giá trị khoá của đỉnh và các thông tin cần thiết khác.

Trong các thuật toán thực hiện bằng máy tính, khi cần tim kiếm nhanh trên một tập các đối tượng nào đó, ta nên nghĩ ngay tới việc xác định cho mỗi đối tượng một khoá và sắp xếp chúng trên một cây tim kiếm nhị phân. Cây tìm kiếm nhị phân là cây nhị phân hoặc trống, hoặc thoả mãn 3 điều kiện sau: khoá của các đỉnh thuộc cây con trái nhỏ hơn khoá của gốc, khoá của gốc nhỏ hơn khoá của các đỉnh thuộc cây con phải của gốc, cây con trái và cây con phải của gốc cũng là cây tim kiếm nhị phân.

Page 2: Cây tìm kiếm nhị phân

Hinh 2 biểu diễn một cây tim kiếm nhị phân, trong đó khoá của đỉnh là một số nguyên (khoá của đỉnh cũng có thể là một ký tự hoặc một xâu ký tự).

Chương trinh CayTknp.c cho cách cài đặt một cây tim kiếm nhị phân và các phép toán cơ bản trên cây. Dòng 2-7: nêu cách cài đặt cây tim kiếm nhị phân. Mỗi một đỉnh của cây gồm có khoá của đỉnh,  con trỏ left trỏ tới đỉnh  con bên trái, con trỏ right trỏ tới đỉnh con bên phải (nếu cây con bên phải là trống thi right=NIL). Chương trinh cho hai cách tạo một cây tim kiếm nhị phân: nhập cây từ bàn phím và nhập cây từ một tệp.

Nhập cây từ bàn phím. Chọn chức năng "1. Chèn" của menu chính, vào giá trị khoá  của đỉnh mới và đưa vào biến x, gọi thủ tục Chen(). Trong thủ tục này ta dùng biến con trỏ p chạy trên các đỉnh của cây bắt đầu từ gốc. Khi đang ở một đỉnh nào đó, nếu x nhỏ hơn khoá của đỉnh thi p chạy xuống đỉnh con trái, nếu x lớn hơn khoá của đỉnh thi  p chạy xuống đỉnh con phải. Nếu tại một đỉnh nào đó, p cần phải chạy xuống đỉnh con trái (phải) nhưng đỉnh đó không có cây con trái (phải) thi ta treo đỉnh mới vào bên trái (phải) đỉnh đó. Điều kiện p=NIL sẽ kết thúc vòng lặp. Chọn nhiều lần chức năng "1. Chèn" ta sẽ nhập được toàn bộ cây từ bàn phím. Bất kỳ lúc nào trong khi nhập nếu ta muốn xem cấu trúc của cây hiện tại chỉ cần chọn chức năng "4. InRong".  

Nhập cây từ tệp. Tạo một tệp văn bản Cay.dat. Dòng đầu chứa số đỉnh của cây, các dòng tiếp theo chứa các giá trị khoá của các đỉnh theo thứ tự cần chèn vào cây. Với cây ở Hinh 2 tệp Cay.dat có dạng sau:

11

10  6  5  8  7  15  12  18  16  19  14

Sau khi nhập cây từ tệp (dùng chức năng 8. NhapTep) ta có thể dùng chức năng "1. Chèn" để chèn thêm các đỉnh mới vào cây với dữ liệu nhập vào từ bàn phím.

Duyệt cây theo chiều rộng. Để dễ dàng vẽ lại cây trên giấy ta dùng cách duyệt cây theo chiều rộng (chọn chức năng "4. InRong" từ menu chính). Các đỉnh của cây in lần lượt từ mức 1 tới mức cuối cùng, trong một mức in từ bên trái sang phải. Đối với mỗi đỉnh in khoá của đỉnh, kèm theo là các khoá của đỉnh con trái và đỉnh con phải của nó (nếu đỉnh con nào không

Page 3: Cây tìm kiếm nhị phân

có thi in NIL). Thứ tự các đỉnh của cây trong Hinh 2 khi duyệt theo chiều rộng: 10, 6, 15, 5, 8, 12, 18, 7, 14, 16, 19.

Thuật toán duyệt cây theo chiều rộng trong thủ tục InRong: dùng một hàng đợi h, lúc đầu h chứa đỉnh gốc. Mỗi lần loại một phần tử ở đầu hàng đợi ta thêm vào cuối hàng đợi tất cả các con của nó, làm như vậy cho đến khi hàng đợi rỗng.

Duyệt cây theo thứ tự Preorder (còn gọi là đi qua cây theo chiều sâu). Danh sách các đỉnh của cây theo thứ tự preorder được xác định như sau:

- Nếu T là một cây gồm 1 đỉnh duy nhất thi danh sách preorder chứa 1 đỉnh duy nhất.

- Nếu T là cây có gốc r và các cây con của gốc là T1, T2 thi danh sách preorder các đỉnh của T bắt đầu là r, theo sau là các đỉnh của T1 theo thứ tự preorder, rồi đến các đỉnh của T2 theo thứ tự preorder.  

Thứ tự các đỉnh của cây trong Hinh 2 khi duyệt theo thứ tự preorder: 10, 6, 5, 8, 7, 15, 12, 14, 18, 16, 19 (chọn chức năng "5. PreOrder" từ menu chính). Thuật toán trong chương trinh là thuật toán đệ quy.

Duyệt cây theo thứ tự Inorder. Danh sách các đỉnh của cây theo thứ tự inorder được xác định như sau:

- Nếu T là một cây gồm 1 đỉnh duy nhất thi danh sách inorder chứa 1 đỉnh duy nhất.

- Nếu T là cây có gốc r và các cây con của gốc là T1, T2 thi danh sách inorder các đỉnh của T bắt đầu là các đỉnh của T1 theo thứ tự inorder, rồi đến gốc r, các đỉnh của T2 theo thứ tự inorder.  

Thứ tự các đỉnh của cây trong Hinh 2 khi duyệt theo thứ tự inorder: 5, 6, 7, 8, 10, 12, 14, 15, 16, 18, 19 (chọn chức năng "6. Inorder" từ menu chính).

Duyệt cây theo thứ tự Postorder. Danh sách các đỉnh của cây theo thứ tự postorder được xác định như sau:

- Nếu T là một cây gồm 1 đỉnh duy nhất thi danh sách postorder chứa 1 đỉnh duy nhất.

- Nếu T là cây có gốc r và các cây con của gốc là T1, T2 thi danh sách postorder các đỉnh của T bắt đầu là các đỉnh của T1 theo thứ tự postorder, các đỉnh của T2 theo thứ tự postorder, sau cùng là gốc r.  

Thứ tự các đỉnh của cây trong Hinh 2 khi duyệt theo thứ tự postorder: 5, 7, 8, 6, 14, 12, 16, 19, 18, 15, 10 (chọn  chức năng "7. PostOrder").

Tìm kiếm. Tim trên cây có chứa đỉnh với khoá x cho trước hay không. Thủ tục TimKiem(x, p) dùng một con trỏ p để chạy trên các đỉnh của cây bắt đầu từ gốc. Khi đang ở một đỉnh nào

Page 4: Cây tìm kiếm nhị phân

đó, nếu x trùng với khoá của đỉnh thi tim kiếm thành công và p là đỉnh cần tim, nếu x nhỏ hơn khoá của đỉnh thi p chạy xuống đỉnh con trái, nếu x lớn hơn khoá của đỉnh thi  p chạy xuống đỉnh con phải. Nếu tim kiếm không thành công thi p = NIL. Nếu tim kiếm thành công thi p chứa địa chỉ của đỉnh có khoá là x.

Loại bỏ một đỉnh khỏi cây. Chức năng "2. Xoá" nhằm loại bỏ khỏi cây một đỉnh  có khoá x cho trước (khoá x nhập từ bàn phím). Trước tiên chương trinh tim kiếm đỉnh có giá trị khoá bằng x. Thủ tục Xoa(p) làm nhiệm vụ loại bỏ khỏi cây một đỉnh do con trỏ p trỏ tới.

Thuật toán loại bỏ một đỉnh. Nếu đỉnh cần loại bỏ là lá thi ta chỉ cần cắt lá đó đi. Nếu đỉnh cần loại bỏ có một trong hai cây con là cây trống, ta chỉ cần treo cây con khác trống vào vị trí của đỉnh bị loại. Nếu cả hai cây con của đỉnh cần loại bỏ đều khác trống thi ta thay khoá của đỉnh cần loại bỏ bởi khoá của đỉnh ngoài cùng bên phải của cây con trái (chính là đỉnh có khoá lớn nhất của cây con trái), rồi loại bỏ đỉnh ngoài cùng bên phải của cây con trái. Sau khi loại bỏ đỉnh theo cách như vậy thi cây vẫn là cây tim kiếm nhị phân.

uses crt;

type  nodePtr= ^node;

      node= record

             key: integer;

             left,right: NodePtr

            end;

var root,tk: nodePtr; x,chon,i,n: integer;

    a: array[1..100] of integer; f: text;

 procedure Chen(x: integer);

var p,q: nodePtr;

begin new(q); q^.key:=x; q^.left:=Nil; q^.right:=nil;

if root=NIL then root:=q else

begin p:=root;

  while p<>NIL do

    begin if x < p^.key then begin if p^.left <> nil then p:=p^.left

                                 else begin p^.left:=q; p:=nil end

Page 5: Cây tìm kiếm nhị phân

                           end

          else begin if x>p^.key then begin

                        if p^.right<>nil then p:=p^.right

                        else begin p^.right:= q; p:=nil end

                                      end

                     else p:=nil;

               end

    end;

end;

end;

 procedure TimKiem(x: integer; var p: nodePtr);

var found: boolean;

begin p:= root; found:= false;

while(p<>nil) and (not found) do

  if p^.key=x then found:= true

  else if x < p^.key  then p:= p^.left else p:= p^.right;

end;

 procedure InRong;

const max =50;

var truoc,cuoi,dem: integer; p,q: nodePtr;

    h: array[1..max] of nodePtr;

begin h[1]:=root; truoc:=1; cuoi:=1; dem:=1;

while (dem>0) do

  begin q:=h[truoc]; write(q^.key,' : '); p:= q^.left;

Page 6: Cây tìm kiếm nhị phân

       if p<>nil then begin write(p^.key,' - '); inc(cuoi);

                            h[cuoi]:=p; inc(dem);

                      end else write('NIL - ');

       p:= q^.right;

       if p<>nil then begin writeln(p^.key);

                            inc(cuoi); h[cuoi]:=P; inc(dem);

                      end else writeln('NIL');

       h[truoc]:=nil; inc(truoc); dec(dem);

  end;

end;

 procedure Xoa(var p: nodePtr);

var q,q1: nodePtr;

begin

if p^.right=NIL then begin q:=p; p:=p^.left end

else if p^.left=NIL then begin q:=p; p:=p^.right end

     else begin q:= p^.left;

              if q^.right=nil then begin p^.key:=q^.key;

                                         p^.left:=q^.left

                                   end

              else begin

                     repeat q1:=q; q:=q^.right until q^.right=nil;

                     p^.key:=q^.key; q1^.right:= q^.left;

                   end

          end;

Page 7: Cây tìm kiếm nhị phân

dispose(q);

end;

 procedure PreOrder(p: nodePtr);

begin if p<> nil then

begin writeln(p^.key); PreOrder(p^.left); PreOrder(p^.right) end;

end;

 procedure InOrder(p: nodePtr);

begin if p<> nil then

begin InOrder(p^.left);writeln(p^.key); InOrder(p^.right) end;

end;

 procedure PostOrder(p: nodePtr);

begin if p<> nil then

begin PostOrder(p^.left); PostOrder(p^.right); writeln(p^.key) end;

end;

begin root:=nil; clrscr;

repeat

writeln('1.Chen    2.Xoa       3.TimKiem  4.InRong  5.PreOder ');

  write('6.InOrder 7.PostOrder 8.NhapTep  9.Exit.   Chon:');

readln(chon);

case chon of

1: begin write('Nhap khoa moi: '); readln(x); Chen(x);

   end;

2: begin write('Nhap khoa can xoa: '); readln(x);

    TimKiem(x,tk);

Page 8: Cây tìm kiếm nhị phân

    if tk<>nil then Xoa(tk)

    else writeln('Khong tim thay gia tri khoa');

   end;

3: begin write('Nhap khoa can tim : '); readln(x);

    TimKiem(x,tk);

    if tk<>NIL then writeln('Da tim thay dinh co khoa : ',tk^.key)

                    else writeln('Khong tim thay dinh co khoa nay');

   end;

4: InRong;

5: begin writeln('Danh sach PreOrder: '); PreOrder(Root)  end;

6: begin writeln('Danh sach InOrder: '); InOrder(Root) end;

7: begin writeln('Danh sach PostOrder: '); PostOrder(Root) end;

8: begin assign(f,'Cay.dat'); reset(f); readln(f,n);

         for i:=1 to n do begin read(f,a[i]); Chen(a[i]) end;

         close(f); write('Da nhap xong cay tu tep...'); readln;

   end;

end

until chon=9;

end.

Ta có nhận xét rằng, thời gian thực hiện phép toán tim kiếm là số phép so sánh giá trị khoá x cho trước với khoá của các đỉnh nằm trên đường đi từ gốc tới một đỉnh cần tim, tức là bằng độ dài đường đi từ gốc tới đỉnh cần tim. Trong trường hợp tốt nhất, mỗi đỉnh của cây đều có hai con trừ mức thấp nhất thi thời gian thực hiện phép toán tim kiếm là O(logn). Trong trường hợp xấu nhất cây tim kiếm nhị phân suy biến thành danh sách liên kết (xảy ra khi tạo cây bằng cách chèn vào cây trống lần lượt các đỉnh với các giá trị khoá đã được sắp xếp theo thứ tự tăng dần) thi thời gian thực hiện phép toán tim kiếm là O(n). Trong trường hợp tổng quát ta xây dựng cây từ cây trống bằng cách chèn vào các đỉnh với các giá trị khoá được sắp xếp một cách

Page 9: Cây tìm kiếm nhị phân

ngẫu nhiên thi có thể chứng minh được rằng thời gian trung binh để thực hiện các phép toán trên cây tim kiếm nhị phân là O(logn).