GiaoAn_CTDL_Kiendt_2

72
Giáo án môn : Cu trúc dliu và gii thut ĐỖ TRUNG KIÊN 1 GIÁO ÁN MÔN : CU TRÚC DLIU VÀ GII THUT Ni Dung : Chương 1 : MĐầu Chương 2 : Thiết kế và phân tích gii thut Chương 3 : Đệ Qui Chương 4 : Cu trúc dliu tuyến tính và cách cài đặt bng mng Chương 5 : Cu trúc dliu tuyến tính và cách cài đặt bng danh sách liên kết Chương 6 : Cây Chương 7 : Sp Xếp Chương 8 : Tìm Kiếm Ni Dung Chi Tiết : Chương 1 : MĐẦU I. Cu trúc dliu và gii thut 1. Khái nim gii thut (thut gii, thut toán) a. Định nghĩa : Là tp hu hn có thtcác bước tác động trên mt đối tượng dliu nào đó để sau mt shu hn ln thc hin scho ta kết quCác đặc trưng ca gii thut - Đầu vào (Input) : Gii thut nhn dliu vào tmt tp nào đó - Đầu ra (Output) : Vi mt tp các dliu đầu vào, gii thut đưa ra các dliu tương ng vi li gii ca bài toán. - Chính xác (Precision) : Các bước ca gii thut được mô tchính xác - Hu hn (Finiteness) : Gii thut phi đưa được đầu ra sau mt shu hn (có thrt ln) bước vi mi đầu vào - Đơn tr(Uniqueness) : Các kết qutrung gian ca tng bước thc hin gii thut được xác định mt cách đơn trvà chphthuc vào đầu vào và các kết quca các bước trước. - Tng quát (Generality) : Gii thut có tháp dng để gii mi bài toán có dng đã cho. b. Ví dminh ha : Bài toán : Cho 3 snguyên a, b, c. Mô tgii thut tìm sln nht trong 3 sđã cho. Gii : Gii thut gm các bước sau :

Transcript of GiaoAn_CTDL_Kiendt_2

Page 1: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

1

GIÁO ÁN MÔN : CẤU TRÚC DỮ LIỆU VÀ GIẢI THUẬT Nội Dung :

Chương 1 : Mở Đầu Chương 2 : Thiết kế và phân tích giải thuật Chương 3 : Đệ Qui Chương 4 : Cấu trúc dữ liệu tuyến tính và cách cài đặt bằng mảng Chương 5 : Cấu trúc dữ liệu tuyến tính và cách cài đặt bằng danh sách liên kết Chương 6 : Cây Chương 7 : Sắp Xếp Chương 8 : Tìm Kiếm

Nội Dung Chi Tiết :

Chương 1 : MỞ ĐẦU I. Cấu trúc dữ liệu và giải thuật 1. Khái niệm giải thuật (thuật giải, thuật toán) a. Định nghĩa : Là tập hữu hạn có thứ tự các bước tác động trên một đối tượng dữ liệu nào đó để sau một số hữu hạn lần thực hiện sẽ cho ta kết quả Các đặc trưng của giải thuật

- Đầu vào (Input) : Giải thuật nhận dữ liệu vào từ một tập nào đó - Đầu ra (Output) : Với một tập các dữ liệu đầu vào, giải thuật đưa ra các dữ

liệu tương ứng với lời giải của bài toán. - Chính xác (Precision) : Các bước của giải thuật được mô tả chính xác - Hữu hạn (Finiteness) : Giải thuật phải đưa được đầu ra sau một số hữu hạn

(có thể rất lớn) bước với mọi đầu vào - Đơn trị (Uniqueness) : Các kết quả trung gian của từng bước thực hiện giải

thuật được xác định một cách đơn trị và chỉ phụ thuộc vào đầu vào và các kết quả của các bước trước.

- Tổng quát (Generality) : Giải thuật có thể áp dụng để giải mọi bài toán có dạng đã cho.

b. Ví dụ minh họa : Bài toán : Cho 3 số nguyên a, b, c. Mô tả giải thuật tìm số lớn nhất trong 3 số đã cho. Giải : Giải thuật gồm các bước sau :

Page 2: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

2

Bước 1 : Đặt x := a; Bước 2 : Nếu b > x thì đặt x := b; Bước 3 : Nếu c > x thì đặt x := c.

Tư tưởng của giải thuật là duyệt lần lượt giá trị của từng số và giữ lại giá trị lớn nhất vào biến x. Kết thúc giải thuật x cho số nguyên lớn nhất trong 3 số đã cho. Bây giờ ta theo dõi quá trình thực hiện của giải thuật với những giá trị cụ thể của a, b, c. Trước hết giả sử

a = 1; b = 5 ; c = 3. Tại bước 1, ta gán giá trị của x là a (1). Tại bước 2, do b > x (5> 1), nên x được gán bằng b (5). Tại bước 3, do điều kiện c > x (3 > 5) không được thực hiện, nên ta không phải làm động tác gán. Kết thúc giải thuật, x có giá trị 5 là giá trị lớn nhất trong 3 số a, b, c. Bây giờ ta sẽ thấy rằng giải thuật vừa mô tả có các tính chất nêu ở trên : Giải thuật nhận đầu vào là 3 số a, b, c và đưa kết quả ở đầu ra là x Các bước của giải thuật được mô tả chính xác đến mức ta có thể viết ngay chương trình theo giải thuật trên ngôn ngữ lập trình và thực hiện trên máy tính Nếu đầu vào là đã xác định, kết quả tại mỗi bước của giải thuật được xác định duy nhất. Chẳng hạn với đầu vào như ví dụ trên, tại bước 2 của giải thuật, x luôn được đặt bằng 5 không phụ thuộc vào việc giải thuật được chạy bằng tay hay bởi máy tính Giải thuật kết thúc sau 3 bước và đưa ra lời giải của bài toán, vì vậy giải thuật là hữu hạn Giải thuật trình bày trong ví dụ luôn đưa ra giá trị của số lớn nhất trong 3 số bất kì, như vậy giải thuật có tính tổng quát.

2. Mối liên hệ giữa cấu trúc dữ liệu và giải thuật - Giải thuật chính là phép xử lý - Đối tượng của giải thuật chính là dữ liệu được tổ chức thành các cấu trúc - CTDL & GT gắn chặt với nhau. Niklaus Wirth (người sáng lập ra ngôn ngữ

Pascal) đã tổng kết : Cấu trúc dữ liệu + Giải thuật = Chương trình

- Nếu ta thay đổi cấu trúc dữ liệu thì giải thuật cũng sẽ thay đổi theo Thí dụ : Danh bạ điện thoại Họ tên Số điện thoại

- Nếu danh bạ không có tổ chức gì cả thì dẫn đến giải thuật là tìm tuần tự từ đầu đến cuối (Cho họ tên và tìm số điện thoại)

- Nếu danh bạ (Họ tên) tổ chức theo thứ tự a,b,c thì ta có thể tìm kiếm theo giải thuật tìm kiếm nhị phân

- Nếu danh bạ vừa xếp thứ tự vừa có một bảng mục lục - A Trang 10

Page 3: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

3

- B Trang 40 - C Trang 100

Thì : Tìm trong bảng mục lục trước Nếu thấy thì chỉ cần tìm trong một vần

II. Cấu trúc dữ liệu và các vấn đề liên quan 1. Khái niệm dữ liệu

Dữ liệu thì gồm các phần tử cơ sở như là : một kí hiệu, một số, … -> gọi là dữ liệu nguyên tử. Các dữ liệu nguyên tử kết hợp với nhau theo các cách khác nhau thì cho ta các cẩu trúc khác nhau Thí dụ : ta có một bảng thông tin như sau

Họ Tên Tuổi SBD Toán Lý Hóa NG V A 18 1A 10 10 10 …………….

- Nếu gộp các dữ liệu trên cùng một cột là thành cùng một cấu trúc thì ta có mảng (Với bảng thông tin trên ta có 7 mảng)

- Nếu gộp các dữ liệu trên cùng một hàng lại thành một cấu trúc ta có cấu trúc bản ghi (Toàn bộ bảng là một mảng các bản ghi)

Việc chọn một cấu trúc dữ liệu thích hợp cho bài toán là một vấn đề hết sức quan trọng Khi ta định ra một cấu trúc thì đồng thời ta cũng xác định ngay các phép toán tác động trên nó

- Tạo ra một cấu trúc - Hủy một cấu trúc - Thêm một phần tử vào cấu trúc - ………… Trừu tượng hóa dữ liệu

2. Định nghĩa kiểu dữ liệu Định nghĩa Kiểu dữ liệu T được xác định bởi một bộ <V,O> , với : V : Tập các giá trị hợp lệ mà một đối tượng kiểu T có thể lưu trữ O : Tập các thao tác xử lý có thể thi hành trên đối tượng kiểu T. Ví du: Giả sử có kiểu dữ liệu mẫu tự = <Vc ,Oc> với

Vc = { a-z,A-Z} Oc = { lấy mã ASCII của ký tự, biến đổi ký tự thường thành ký tự hoa…}

Giả sử có kiểu dữ liệu số nguyên = <Vi ,Oi> với Vi = { -32768..32767}

Page 4: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

4

Oi = { +, -, *, /, %} Như vậy, muốn sử dụng một kiểu dữ liệu cần nắm vững cả nội dung dữ liệu được phép lưu trữ và các xử lý tác động trên đó. Các thuộc tính của 1 KDL bao gồm:

- Tên KDL - Miền giá trị - Kích thước lưu trữ - Tập các toán tử tác động lên KDL

3. Một số kiểu dữ liệu a. Các kiểu dữ liệu cơ bản Là các loại dữ liệu đơn giản không có cấu trúc và thường là các giá trị vô hướng, bao gồm :

- Kiểu có thứ tự rời rạc : Số nguyên, ký tự, logic, liệt kê, miền con ,… - Kiểu không rời rạc : số thực

Các kiểu cơ sở rất đơn giản và không thể hiện rõ sự tổ chức dữ liệu trong một cấu trúc => thường chỉ được sử dụng làm nền để xây dựng các kiểu dữ liệu phức tạp khác.

b. Một số kiểu dữ liệu có cấu trúc cơ bản - Kiểu chuỗi ký tự Khai báo : Tùy từng ngôn ngữ Thao tác

- So sánh hai chuỗi - Sao chép hai chuỗi - Kiểm tra một chuỗi nằm trong chuỗi kia - Cắt một từ ra khỏi một chuỗi - Đổi một số ra chuỗi - Đổi một chuỗi ra số - ……

- Kiểu mảng Khai báo : Mảng một chiều : <Kiểu dữ liệu> <Tên biến> [<Số phần tử>];

Mảng hai hoặc nhiều chiều : <Kiểu dữ liệu> <Tên biến> [<Số phần tử 1>] [<Số phần tử 2>]….;

Các thao tác : Sẽ được xem xét kỹ trong chương - Kiểu mẫu tin (cấu trúc) Khai báo :

Page 5: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

5

Typedef struct <tên kiểu struct> {

<KDL> <tên trường>; <KDL> <tên trường>; …

}[<Name>];

Chương 2 : THIẾT KẾ VÀ PHÂN TÍCH GIẢI THUẬT I. Thiết kế giải thuật

Chia nhỏ bài toán : Nếu gọi bài toán là một modul chính thì ta chia modul chính thành các modul con rồi lại chia các modul con thành các modul con nhỏ hơn cho đến khi ta được các modul đã biết cách giải rồi. -> Chiến thuật chia để trị Ví dụ : Giải hệ phương trình đại số tuyến tính n phương trình n ẩn ………………

Để thực hiện chiến thuật này người ta thường dùng cách thiết kế

- Từ trên xuống (Top-Down Design) (Đi từ tổng quát đến chi tiết) - Tinh chỉnh từng bước

o Biểu diễn ý tưởng bằng ngôn ngữ tự nhiên o Cụ thể từng phần, thay đổi bằng ngôn ngữ chương trình o Cuối cùng ta có chương trình

Thí dụ : Bài toán sắp xếp một dãy số

- Chọn số bé nhất trong n số để vào vị trí thứ 1 - Chọn số bé nhất trong n-1 số còn lại để vào vị trí thứ 2 - ………………… - Chọn số bé nhất trong 2 số còn lại để vào vị trí thứ n-1

=>

For i := 1 to n-1 do Begin

Page 6: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

6

+ Chọn số bé nhất trong các số nii xxx ,.....,, 1+ + Đổi chỗ cho xi End.

=> For i := 1 to n-1 do Begin Bn := x[i]

So sánh Bn với các số từ xi+1 -> xn . Nếu x[i] > các số đó thì lại lấy số đó làm số Bn

End. =>

For i := 1 to n-1 do For j := i+1 to n do If (x[i] > x[j]) then Begin Tg := x[i] X[i] := x[i+1] X[i+1] := tg End;

II. Phân tích giải thuật 1. Phân tích tính đúng đắn của giải thuật Ta phải chứng minh giải thuật là đúng Người ta thường làm : Cho chương trình chạy thử với một bộ dữ liệu đã biết kết quả Nếu kết quả chương trình khác với kết quả đã biết thì chắc chắn chương trình sai; nếu bằng thì cũng chưa có kết luận, do đó đây chỉ là phương pháp tìm ra cái sai mà chưa chứng minh cái đúng - Cho chạy chương trình rồi xem kết quả có phù hợp với thực tế không, nếu phù hợp thì hi vọng đúng, còn không thì chắc chắn sai - Để chứng minh tính chính xác thì phải dùng toán học – hay dùng qui nạp (rất khó)

2. Phân tích tính đơn giản Tùy thuộc bài toán được dùng thường xuyên hay không, nếu chương trình chỉ dùng một ít lần rồi bỏ đi thì ta ưu tiên tính đơn giản Bài toán được sử dụng thường xuyên thì ta ưu tiên tính hiệu quả của thuật toán Tính hiệu quả thể hiện về hai mặt

- Không gian (Chương trình bé chiếm ít bộ nhớ) - Thời gian (Chương trình chạy nhanh)

Page 7: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

7

III. Đánh giá thời gian thực hiện của giải thuật 1. Thời gian thực hiện của giải thuật Thời gian thực hiện của giải thuật phụ thuộc vào nhiều yếu tố

- Trước hết phụ thuộc vào độ lớn của dữ liệu đầu vào o Thời gian là một hàm của n (kích thước dữ liệu đầu vào) T = T(n) o Ví dụ : Tìm kiếm tuyến tính trong một mảng n phần tử

- Ngoài ra T còn phụ thuộc vào o Máy o Ngôn ngữ o ….

Tuy nhiên các yếu tố này là không đồng đều do vậy không thể dựa vào chúng khi xác lập T(n) Vậy đơn vị của T(n) tính bằng gì ? Không thể tính bằng đơn vị cụ thể mà tính bằng : Số lần thực hiện các lệnh của giải thuật Cách tính này có thể hiện được tính nhanh hay chậm của giải thuật hay không ? Thí dụ : GT1 có thời gian thực hiện là tỉ lệ với n GT2 có thời gian thực hiện là tỉ lệ với n2 Với n khá lớn thì giải thuật 1 nhanh hơn giải thuật 2 T mà ta đánh giá như trên gọi là độ phức tạp tính toán của giải thuật Ở giải thuật 1 ta nói GT có độ phức tạp tính toán cấp n và kí hiệu T(n) = O(n) Ở giải thuật 2 thì T = O(n2) Định nghĩa : Giả thuật T được gọi là có độ phức tạp tính toán cấp g(n) nếu như tồn tại hằng số dương N0 và số nguyên dương C > 0 sao cho 0Nn ≥∀ ta luôn có T(n) ≤ C. g(n) Và ta kí hiệu là T(n) = O(g(n)) Hàm g(n) thường chọn là

nn nnnnnnnn ;!;2;;;log;;log 32 Các giải thuật có độ phức tạp tính toán là cấp các hàm đa thức thì chấp nhận được. Ví dụ : T(n) = 60n2 + 9n + 9 = O(?) Một số ví dụ đánh giá độ phức tạp tính toán của GT Ví dụ 1 : Tính TBC của một dãy số đọc vào từ bàn phím 1. Nhập vào số n 2. Cho T = 0; d = 1 3. Lặp While d <= n do

Begin 4. - Đọc vào số n 5. - Cộng thêm vào T 6. - Tăng đếm lên 1

End; 7. Tính TBC = T/n; 8. Đưa ra TBC

Hàm đa thức Hàm mũ

Page 8: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

8

Đánh giá GT

Các lệnh Số lần thực hiện 1 1 2 1 3 n 4 n 5 n 6 n 7 1 8 1

T(n) = 4n + 4 T(n) = 4n + 4 ≤ 5n ∀n ≥ 4

T(n) = O(n)

2. Một số qui tắc cơ bản a. Qui tắc loại bỏ hằng số Nếu ta có T(n) = O(C1.g(n)) thì ta cũng có T(n) = O(g(n)) Chứng minh : T(n) ≤ C0.C1g(n) ≤ Cg(n) với C = C0C1

T(n) = O(g(n)) Ví dụ ……………

b. Qui tắc lấy max Nếu ta có T(n) = O(f(n) + g(n)) thì ta cũng có T(n) = O(max(f(n),g(n))) Chứng minh : T(n) ≤ C1.f(n) + C2.g(n) ≤ 2C. Max(f(n),g(n))

T(n) = O(max(f(n),g(n))) Ví dụ ……

c. Qui tắc cộng T1(n) và T2(n) là thời gian thực hiện của 2 đoạn chương trình P1 và P2 và T1(n) =

O(g(n)); T2(n)= O(g(n)) thì thời gian thực hiện 2 đoạn chương trình nối tiếp nhau là T(n) = T1(n) + T2(n)

Hay T(n) = O(max(f(n),g(n)))

d. Qui tắc nhân Thời gian thực hiện hai đoạn chương trình lồng nhau là

T(n) = O(f(n).g(n)) Từ các qui tắc trên => việc đánh giá độ phức tạp thuật toán ta chỉ cần chú ý đến các lệnh tích cực còn các lệnh khác có thể bỏ qua.

Page 9: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

9

Lệnh tích cực là lệnh có số lần thực hiện ≥ số lần thực hiện các lệnh khác Ví dụ : Bài toán tình giá trị gần đúng của ex

Chương 3 : ĐỆ QUI (RECURSSIVE ALGORITHM) I. Khái niệm đệ qui Một đối tượng gọi là đệ qui nếu như một bộ phận của nó là chính nó (hay nói cách khác nó được định nghĩa qua chính nó). Thí dụ : Trong toán hoc ta hay gặp các định nghĩa đệ qui 1. Số tự nhiên

- 1 là số tự nhiên - x là số tự nhiên nếu x-1 là số tự nhiên

2. hàm tính giai thừa

⎩⎨⎧

>−=

=0)!1.(01

!nnnn

n

3. Dãy Fibonaci 1, 1, 2, 3, 5, 8,………..

⎩⎨⎧

>−+−≤

=2)2()1(21

)(nnFibnFibn

nFib

II. Giải thuật đệ qui và thủ tục đệ qui Lời giải T của bài toán được biểu diễn qua T’ giống T thì lời giải T được gọi là lời giải đệ qui. Giải thuật cho lời giải đệ qui gọi là giải thuật đệ qui

Ví dụ : Tìm từ trong quyển từ điển - Nếu từ điển bằng một trang thì tìm tuần tự trong đó - Nếu từ điển khác 1 trang thì

o Chia đôi từ điển, xem từ cần tìm thuộc nửa đầu hay nửa cuối o Lập lại giải thuật tìm từ trong trang ( chỉ còn 1/2)

Tính chất : 1) Có một trường hợp đặc biệt để kết thúc đệ qui ( Suy biến, giá trị neo ) - từ điển

còn một trang 2) Lặp đi lặp lại việc tìm từ trong quyển từ điển nhưng mỗi lần lặp quyển từ điển chỉ

còn một nửa và nó dần đến trường hợp suy biến

Thủ tục đệ qui là điều kiện cần và đủ cho giải thuật đệ qui

Page 10: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

10

Có hai loại thủ tục đệ qui : 1) Thủ tục đệ qui trực tiếp : Thủ tục chứa lời gọi đến chính nó 2) Thủ tục đệ qui gián tiếp : Thủ tục chứa lời gọi đến thủ tục khác mà ở thủ tục này

lại chứa lời gọi đến chính nó (Ví dụ hàm A gọi hàm B, hàm B gọi hàm C và hàm C lại gọi đến hàm A – chương trình dịch)

Tính chất của thủ tục đệ qui trực tiếp:

1) Một thủ tục đệ qui nó chứa lời gọi đến chính thủ tục đó trong thân thủ tục 2) Có một trường hợp suy biến để kết thúc lời gọi đệ qui 3) Mỗi lần gọi thì lại dần đến trường hợp suy biến

Ví dụ 1: Viết hàm đệ qui tính n! Cách tính 3! từ định nghĩa

3! = 3 * 2! = 3 * 2 = 6 2! = 2 * 1! = 2 * 1 = 2 1! = 1 * 0! = 1 * 1 = 1 0! = 1 Ta có chương trình

Funtion Giai_Thua ( n : Integer ) : Integer; Begin If ( n = 0) then Giai_Thua := 1 Else Giai_Thua := n* Giai_Thua(n-1) End; BEGIN Clrscr; Writeln(‘3! = ’; Giai_Thua(3)); Readln END.

Thủ tục không đệ qui : Ví dụ 2 : Viết chương trình tính số Fibonaci thứ n Thủ tục không đệ qui : Từ chương trình chính gọi thủ tục đệ qui – Đệ qui mức 1 Từ thủ tục đệ qui gọi nó lần 2 – Đệ qui mức 2

Page 11: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

11

Khi vào mức thứ i thì Nó thực hiện một phần ở mức thứ i rồi kiếm tra điều kiện suy biến.

- Nếu chưa suy biến thì nó gửi các thông tin o Các tham số mà mức i truyền cho mức i + 1 o Các biến cục bộ ở mức i và địa chỉ lối về vào ngăn xếp rồi vào mức i +1

- Nếu đã suy biến thì nó tính nốt phần còn lại của mức thứ i, rồi lấy thông tin ra từ ngăn xếp để quay về mức i –1; ở mức i – 1 lại tính nốt phần còn lại của mức i –1 rồi quay ra mức i – 2.

(hiểu được đoạn trên có hai ý nghĩa – chạy CT đệ qui cho ra kết quả đúng và có những ý tưởng đệ qui để giải quyết bài toán ) Ví dụ 2 :

Procedure aaa(n : interger); Begin If n < 3 then aaa(n+1); Writeln(n) End; BEGIN aaa(1); Readln END.

Kết quả : 3 2 1 Ví dụ 3 :

Procedure aaa(n : interger); Begin n := n +1; Writeln(n); If n < 10 then aaa(n+1); Writeln(n) End; BEGIN aaa(1); Readln END.

Kết quả : 2, 4, 6 , 8, 10, 10, 8, 6, 4, 2

Page 12: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

12

Chương 4 : CẤU TRÚC DỮ LIỆU TUYẾN TÍNH VÀ CÁCH CÀI ĐẶT BẰNG MẢNG

I. Phân loại cấu trúc dữ liệu Ta có thể phân loại cấu trúc dữ liệu dựa trên mối quan hệ giữa các phần tử. Cấu trúc dữ liệu tuyến tính có quan hệ 1:1 giữa các phần tử; tức là, nếu cấu

trúc dữ liệu tuyến tính có chứa phần tử thì nó phải có phần tử đầu tiên và phần tử cuối cùng, mỗi phần tử (trừ phần tử đầu tiên và phần tử cuối cùng) có đúng một phần tử tiền bối (đứng ngay trước) và một phần tử hậu bối (đứng ngay sau).

Cấu trúc dữ liệu phân cấp có quan hệ 1:n giữa các phần tử; tức là, mỗi phần tử trong cấu trúc có nhiều hậu bối, nhưng chỉ có một tiền bối (Hình 4-1b). (Cây)

Cấu trúc đồ thị: Các phần tử có mối quan hệ n:n. Tức là, mỗi phần tử có thể có quan hệ với một hoặc nhiều phần tử khác (Hình 4-1c).

Cấu trúc tập hợp. Trong một tập hợp, các phần tử không có mối quan hệ trực tiếp với nhau, giữa chúng chỉ có một mối quan hệ là thành viên của tập hợp, ta không cần quan tâm tới vị trí chính xác của một phần tử nào đó trong tập hợp (Hình 4-1d).

Phần tử đầu

PhÇn tö

Cã duy nhÊt mét tiÒn bèi (ngay

í )

Cã duy nhÊt mét hËu bèi (ngay

PhÇn tö ë ®Ønh (T )

Duy nhÊt mét tiÒn bèi

Cã thÓ nhiÒu hËu bèi

a. Cấu trúc tuyến tính

b. Cấu trúc phân cấp

Page 13: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

13

II. Mảng 1. Khái niệm Là một tập hợp có thứ tự các phần tử có cùng một kiểu và số phần tử là cố định Trên mảng thường xác định các phép tính

- Tạo lập mảng - Lấy nội dung của một phần tử mảng - Ghi vào một phần tử mảng một nội dung nào đó

Không có phép tính thêm vào một phần tử Không có phép tính bớt một phần tử của mảng

2. Lưu trữ mảng Mảng được lưu trữ trong máy bởi một vùng nhớ liên tục.

a. Mảng một chiều (có một chỉ số) Khai báo

Example : Array [LowerBound..UpperBound] of ElementType Nếu mỗi phần tử trong mảng có kiểu ElementType chiếm s từ máy, thì bộ nhớ dành ra một vùng nhớ là s*(UpperBound-LowerBound+1) từ máy liên tục đủ để chứa mảng. Địa chỉ của phần tử Example[i] sẽ là :

loc(A[i])=loc(A[LowerBound]) +s* (i – LowerBound)

NhiÒu tiÒn bèi

NhiÒu hËu bèi

Kh«ng cã tiÒn bèi

Kh«ng cã hËu bèi

c. Cấu trúc đồ thị

a. Cấu trúc tập hợp

Hình 4-1: Phân loại cấu trúc dữ liệu

Page 14: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

14

b. Mảng hai chiều Khai báo A : Array [1..m, 1..n] of ElementType {m hàng; n cột} A : array [1..3,1..2] of real; {Giành ra 6 x 6 = 36 Byte} Có hai cách lưu trữ

- Lưu theo hàng (lưu hết hàng này đến hàng kia)

A11 A12 A13 A14 A15 A16

Công thức tổng quát xác định địa chỉ của phần tử tại hàng i cột j là Loc(A[i,j]) = Loc(A[1,1]) + [(i-1)*n + (j-1)]*s

- Lưu theo cột (Lưu trữ hết cột nọ tới cột kia)

A11 A21 A31 A12 A22 A32

Công thức tổng quát xác định địa chỉ của phần tử tại hàng i cột j là Loc(A[i,j]) = Loc(A[1,1]) + [(j-1)*m + (i-1)]*s

III. Stack 1. Định nghĩa hình thức

Các thao tác trên ngăn xếp Khởi tạo

Create(S) Tạo ra Stack S mới, rỗng Biến đổi

Push(E, S) Đẩy phần tử có giá trị E vào đỉnh Stack S Pop(E, S) Lưu phần tử ở đỉnh ngăn xếp S vào E rồi xóa khỏi ngăn xếp

Kiểm tra Empty(S) Kiểm tra Stack có rỗng không Full(S) Kiểm tra Stack có tràn không

Hg1 Hg2 Hg3

Cột 1 Cột 2

Stack là cấu trúc dữ liệu vào sau / ra trước tuyến tính ( Last In / First Out - LIFO) mà phép thêm và bớt được thực hiện ở một đầu gọi là đỉnh (Top)

Đáy

Đỉnh

Page 15: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

15

2. Cài đặt Stack bằng mảng (* Module cài đặt kiểu dữ liệu trừu tượng Stack bằng mảng một chiều *) StackElementType = …..; (* Kiểu dữ liệu cảu phần tử trong Stack*) Const MaxStack = 50; Type StackType = Record Element : Array[1..MaxStack] Of StackElementType; Top: 0.. MaxStack; End; Procedure Create(Var Stack: StackType); (* Thủ tục này tạo ra một Stack mới, rỗng *) Var Begin Stack.top := 0; End; Procedure Empty(Stack: StackType): Boolean;

Begin Empty := (Stack.Top = 0); (*Stack rỗng nếu top = 0*) End;

Procedure Full(Stack: StackType): Boolean; Begin Full := (Stack.Top = MaxStack); (*Stack đầy nếu các vị trí trong mảng đã đủ*) End; Procedure Pop(Var Stack: StackType, Var Item : StackElementType); (* Pop() xoá phần tử đầu tiên ra khỏi Stack *) Begin If Empty(Stack) Then half; Else With Stack Do

Begin E := Element[Top]

Top := Top – 1; End; Endif End Pop; Procedure Push(Item:StackElementType, Var Stack: StackType); (* Push() đẩy phần tử vào đỉnh Stack *) Begin If Full(Stack) Then half;

Page 16: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

16

Else With Stack Do Begin

Error := None; Top := Top + 1; Element[Top] := Item; Endif End;

3. Các ứng dụng trên Stack Bài toán : Biến đổi một số từ hệ thập phân sang hệ cơ số hai và biểu diễn lên màn hình.

Write(“Dua vao so nguyen duong de chuyen doi : ”); Readln(Number); Creat(Stack) While Number <> 0 Do Begin Sdu := Number mod 2; Push(Sdu, Stack)’ Number := Number div 2 End; Write (“Bieu dien co so hai : ”) ; While not Empty(Stack) Do Begin Pop(sdu, Stack); Write(sdu : 2) End;

IV. Queue 1. Định nghĩa hình thức

Queue là cấu trúc dữ liệu vào trước ra trước tuyến tính (First In First Out - FIFO) mà thêm ở một đầu còn bớt ở đầu kia

6 5 4 3 2 1 X X X X Các thao tác trên hàng đợi Khởi tạo

Create(Q) Tạo ra hàng đợi mới, rỗng Biến đổi

AddQueue(E, Q) Chèn phần tử có giá trị E vào cuối hàng đợi Q DeQueue(E, Q) Lưu phần tử ở đầu hàng đợi Q vào E rồi xóa khỏi hàng đợi

Kiểm tra

C (Rear) D ( Front)

Page 17: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

17

Empty(Q) Kiểm tra hàng đợi có rỗng không Full(Q) Kiểm tra hàng đợi có đầy không

2. Cài đặt Queue bằng mảng Thêm một phần tử thường thêm vào cuối hàng Queue [Rear] := Item Rear := Rear + 1; Lấy đi một phần tử Item := Queue [front] Front := Front + 1; Xét một ví dụ DeQueue(Q); EnQueue(d,Q); EnQueue(e,Q); EnQueue(f,Q); Giả sử Queue ban đầu có dạng : =>

Nhận xét : Nếu hàng hoạt động một thời gian thì sẽ chạy khắp bộ nhớ, để khắc phục người ta tổ chức hàng vòng tròn Khi tăng : Front := (Front + 1) mod max Rear := (Rear + 1) mod max Nếu hàng rỗng thì Front = Rear Nếu hàng đầy thì : Front = Rear (Trùng với dấu hiệu hàng rỗng) Khắc phục bằng cách qui định hàng còn một chỗ coi như là đầy : (Rear + 1 ) mod Max = Front (* Module này cài đặt kiểu dữ liệu trừu tượng Queue sử dụng mảng một chiều

vòng *) Const

MaxQueue = 50;

a b c

Front Rear b c d e f

Front Rear

Page 18: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

18

Type QueueElementType; QueueType = Record Queue : Array[0..MaxQueue] Of QueueElementType;

Front, Rear : 0 .. MaxQueue; End; Procedure Create(Var Queue : QueueType);

Begin Error := None; Queue.Front:=0; Queue.Rear:=0;

End Create; Procedure Empty(Queue: QueueType): Boolean;

Begin Error := None; Return Queue.Front := Queue.Rear;

End Empty; Procedure Full(Queue: QueueType):Boolean;

Begin Error := None; With Queue do

Full := ((Rear + 1 ) mod MaxQueue = Front) End Full;

Procedure AddQueue(Item: QueueElementType,Var Queue: QueueType);

Begin If Full(Queue) Then

Error:=QueueOverFlow; Else

Error:=None; With Queue do Begin Queue[Rear] := Item; Queue.Rear := (Queue.Rear + 1) mod MaxQueue end

Endif End Enqueue; Procedure Dequeue(Item :QueueElementType;Var Queue: QueueType);

Begin If Empty(Queue) Then

Page 19: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

19

Error := QueueUnderFlow; Else

Error := None; With Queue do

begin Item := Queue[Front] Front := (Front + 1) mod max

end Endif End Dequeue; End Queuepackage.

Một số kiều hàng đợi

- DeQueue : Thêm và bớt có thể được thực hiện ở cả hai đầu - Hàng đợi ưu tiên (Priority Queue) :

o Có một sự ưu tiên được gán cho mỗi mục o Những mục được ưu tiên nhất ở gần đầu hàng đợi o Những mục có cùng mức ưu tiên thì được sắp xếp như hàng đợi

3. Ứng dụng trên Queue Đổi một số lẻ từ hệ 10 sang hệ 2

0,4510 => 0,01110012 Chương trình

BEGIN Clrscr; Creat(q); Write(‘Nhap mot so le : ’); Readln(Sole); d := 1; While ((Sole <>0) and d < 10) do Begin EnQueue(Trunc(Sole*2),Q); Sole := Frac(Sole*2); Inc(d) End; Write(‘Bieu dien co so 2 : ’); While not Empty(Q) do Begin DeQueue(Item,Q); Write(Item); End; Readln END.

Page 20: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

20

Chương 5 : Cấu trúc dữ liệu tuyến tính và cách cài đặt bằng danh sách liên kết

Nội dung : I. Danh sách tuyến tính

1. Định nghĩa danh sách tuyến tính 2. Cách lưu trữ danh sách 3. Các thao tác trên danh sách tuyến tính 4. Cài đặt tuần tự của danh sách trên cơ sở mảng 5. Ứng dụng

II. Danh sách liên kết 1. Giới thiệu về danh sách liên kết 2. Cài đặt danh sách liên kết trên cớ sở mảng 3. Cài đặt danh sách liên kết trên cơ sở con trỏ 4. Ứng dụng

III. Nghiên cứu thêm về danh sách 1. Ngăn xếp và hàng đợi liên kết 2. Danh sách với nút đầu 3. Danh sách liên kết vòng 4. Danh sách liên kết kép 5. Ứng dụng

I. Danh sách tuyến tính 1. Định nghĩa danh sách tuyến tính Nhận xét :

- Stack chỉ bổ sung/ xoá phần tử ở một đầu - Queue bổ sung phần tử vào đuôi và xoá phần tử ở đầu Queue - Deque có thể bổ sung/ xoá ở cả hai đầu nhưng không thể bổ sung/xoá ở giữa

=> Danh sách tuyến tính – Danh sách (cấu trúc dữ liệu tuyến tính) : Danh sách là một tập có thứ tự các phần tử có cùng kiểu và số phần tử là luôn thay đổi Danh sách hoặc là rỗng hoặc có dạng a1, a2, ……, an.

a1 : Phần tử đầu tiên không có phần tử đi trước an : Phần tử cuối không có phần tử đi sau ai (1 < i < n) luôn có một phần tử đi trước là ai-1và một phần tử đi sau ai+1.

Page 21: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

21

2. Cách lưu trữ danh sách Ta có hai cách lưu danh sách trong bộ nhớ

a. Lưu trữ kết tiếp Toàn bộ danh sách chiếm một vùng nhớ liên tục, các phần tử của danh sách nằm

liên tiếp nhau.

Giả sử L là địa chỉ đầu của vùng nhớ đó thì địa chỉ của phần tử thứ i sẽ là : L + (i-1)* độ dài một phần tử. Ưu điểm :

- Đơn giản - Tốc độ truy cập tới phần tử nhanh và đều nhau

Nhược điểm : - Nếu phép tính thêm bớt được thực hiện thường xuyên thì rất mất công

b. Lưu trữ móc nối Một phần tử còn gọi là một nút của danh sách. Các nút khác nhau nằm tại các vị trí

bất kì không theo một qui luật nào cả nên không tính được địa chỉ của từng nút. Do vậy bên cạnh trường chứa thông tin (Info) còn có trường chứa địa chỉ của nút tiếp theo (Link).

Trường Next của nút 1 chứa địa chỉ của nút 2 Trường Next của nút 2 chứa địa chỉ của nút 3 Trường Next của nút n chứa một giá trị đặc biệt gọi là Null(Nil). Địa chỉ của nút thứ nhất chứa trong một con trỏ L và gọi là danh sách L.

3. Các thao tác trên danh sách tuyến tính a. Các thao tác tạo và hủy

L:= Creat() : Trả về một danh sách rỗng. Destroy(L) : Kết thúc sự tồn tại của danh sách L

b. Các thao tác kiểm tra Empty(L) : Boolean; Trả về True nếu danh sách rỗng; và False nếu ngược lại Full(L) : Boolean; Trả về True nếu danh sách đầy; và False nếu ngược lại Size(L) : Trả về số phần tử hiện có trong danh sách. Nếu danh sách rỗng thì hàm trả về giá trị 0

L

Info Next

……………… L Nil

Page 22: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

22

c. Các thao tác bổ sung và loại bỏ Insert(Var List : ListType; Item : ElementType; Pos : Integer);

{Thủ tục chèn Item vào sau phần tử ở vị trí Pos trong danh sách List, Pos = 0 chỉ ra rằng Item sẽ được chèn vào đầu danh sách. }

Delete(Var List : ListType; Pos : Integer); {Thủ tục xóa Item ở vị trí Pos từ danh sách List}

d. Một số thao tác khác First(L) : Di chuyển con trỏ tới phần tử đầu tiên trong danh sách (Head), nếu L rỗng thì trả về Null. Last(L) : Di chuyển con trỏ tới phần tử cuối cùng trong danh sách (Head), nếu L rỗng thì trả về Null.

Next(L) : Di chuyển con trỏ tới phần tử kế tiếp trong danh sách

InsertAfter(I,L) : chèn nút I vào ngay sau nút hiện thời

InsertFirst(I,L) : Chèn nút I và đầu danh sách và gán P trỏ và nút vừa bổ sung InsertAtEnd(L,I) Bổ sung một nút có giá trị I vào cuối danh sách L DeleteFirst(L) Xoá phần tử đầu tiên của danh sách L Concatenate(L1,L2) Ghép hai danh sách (giống như ghép hai String ) Sort(L) Sắp xếp L theo thứ tự Split(L,N,L1,L2) Tách L thành 2 danh sách ở vị trí N. L1 chứa N

phần tử đầu tiên của L và L2 chứa phần còn lại. 4. Cài đặt tuần tự của danh sách trên cơ sở mảng

Const MaxSize = …; {Độ dài cực đại của danh sách}

Type ElementType = …..; {Kiểu của các phần tử trong danh sách} ListArray = array[1..MaxSize] of ElementType; LisType = Record Element : ListArray; Size : 0..MaxSize; Var List : ListType; Creat(Var List : ListType) List.Size := 0 Empty(List : ListType) : Boolean; Empty := (List.Size = 0); Full(List : ListType) : Boolean; Full := (List.Size = MaxSize)

Page 23: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

23

Procedure Insert(Var List : ListType; Item : ElementType; Pos : Integer); {Thủ tục chèn Item vào sau phần tử ở vị trí Pos trong danh sách List, Pos = 0 chỉ ra rằng Item sẽ được chèn vào đầu danh sách. } Var I: Integer; Begin If Full(List) Then Error := ListOverFlow Else With List do Begin {Dịch các phần tử sang phía phải để tạo chỗ cho Item} For i := Size downto Pos + 1 do Element[i+1] := Element[i] {Chèn Item vào vị trí Pos và tăng độ lớn của danh sách} Element[Pos + 1] := Item; Size := Size + 1 end End;

Procedure Delete(Var List : ListType; Pos : Integer); {Thủ tục xóa Item ở vị trí Pos từ danh sách List} var i : Integer; {Chỉ số của mảng} begin If Empty(List) Then Error := ListUnderFlow Else With List do Begin {*Giảm chiều dài danh sách đi 1 và lấp chỗ trống*} Size := Size –1 ; For I := Pos to Size do Element[i] := Element[i+1] End; end;

Page 24: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

24

5. Ứng dụng Bài toán : Cộng hai đa thức Nhận xét : Chèn và xóa đòi hỏi dịch chuyển nhiều phần tử mảng, mất tương đối nhiều thời gian => Dùng danh sách liên kết.

II. Danh sách liên kết 1. Giới thiệu về danh sách liên kết Danh sách => dùng một phương pháp để thể hiện thứ tự Mảng => thứ tự được thể hiện dưới dạng ẩn => khó khăn trong thêm và bớt một phần tử.

Xét một cách cài đặt khác mà thứ tự các phần tử được biểu diễn dưới dạng hiện => danh sách liên kết.

Nó gồm một tập các phần tử gọi là nút (Node). Mỗi nút chứa hai mục tin : (1) phần tử của danh sách và (2) con trỏ chỉ vị trí của nút chứa phần tử tiếp theo trong danh sách. Ví dụ : Các thao tác cơ bản trên danh sách liên kết -Khởi tạo : Create () List = 0, Nill - Kiểm tra danh sách rỗng : Empty (List) Empty := (List = 0) - Thủ tục đi qua một danh sách liên kết Dùng thêm một con trỏ phụ CurrPtr để duyệt danh sách (Đầu tiên được trỏ tới List) Procedure LinkTraverse (List)

{ Đi qua một danh sách liên kết với nút đầu tiên được trỏ bởi List, xử lý mỗi phần tử đúng một lần }

1. Khởi động CurrPtr tại List 2. While CurrPtr <> Nill do

a. Xử lý Data (CurrPtr)

BrownData Next

BrownData Next

BrownData NextList

BrownData Next

BrownData Next

BrownData NextList

CurrPtr

Page 25: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

25

b. Đặt CurrPtr bằng Next(CurrPtr) - Chèn một phần tử vào trong danh sách liên kết Có hai trường hợp

+ Chèn vào đầu danh sách liên kết + Chèn vào sau một phần tử cho trước trong danh sách

Trường hợp 1 : Chèn nút TempPtr vào đầu danh sách (các thao tác phải được thực hiện theo thứ tự sau ) 1. Đặt Next(TempPtr) = List 2. Đặt List := TempPtr

Nếu đặt theo thứ tự ngược lại : Trường hợp 2 : Chèn vào sau một nút được trỏ bởi PrePtr (Phải theo thứ tự) 1. Đặt Next(TempPtr) = Next(PrePtr)

2. Đặt Next(PrePtr) = TempPtr

ListTempPtr

ListTempPtr

ListTempPtr

……

TempPtr

PrePtr

Page 26: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

26

=> Thuật toán chèn một phần tử vào danh sách liên kết Procedure LinkInsert ( Var List ; Item; PrePtr)

{Thủ tục này chèn Item vào một danh sách liên kết với nút đầu tiên được chỉ bởi List. PrePtr chỉ tới nút đứng trước nút cần chèn hay là con trỏ rỗng (có giá trị 0 )nếu nó chèn vào đầu danh sách hoặc danh sách rỗng } 1. GetNode(TempPtr) {Lấy một nút mới} 2. Đặt Data(TempPtr) := Item 3. If PrePtr = Nill làm các bước sau {Chèn Item vào đầu danh sách} a. Đặt Next(TempPtr) bằng List b. Đặt List bằng TempPtr Else Làm các bước sau {có phần tử đứng trước} a. Đặt Next(TempPtr) bằng Next(PrePtr) b. Đặt Next(PrePtr) bằng TempPtr

Kiểm tra xem thủ tục có đúng với trường hợp + Chèn vào một danh sách rỗng hay không + Chèn vào cuối danh sách không rỗng

- Xóa một phần tử : Giả sử Temp chỉ tới nút cần được xóa. Có hai trường hợp xảy ra. + Xóa phần tử đầu tiên trong danh sách + Xóa một phần tử có nút đứng trước nó

Trường hợp 1 : Đặt List bằng Next(TempPtr) Trong trường hợp đặc biệt danh sách chỉ có một nút thì vẫn đúng Trường hợp 2 : Nếu nút PrePtr chỉ tới nút dứng trước nút cần xóa Đặt

TempPtr List

TempPtr

PrePtr

Page 27: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

27

= > thủ tục xóa một phần tử khỏi danh sách

Procedure LinkedDelete( Var List; PrePtr) {Xóa một nút từ danh sách liên kết với nút đầu được chỉ bởi List; PrePtr chỉ vào

nút đứng trước nút cần xóa hay là con trỏ rỗng nếu nút đầu tiên cần được xóa} If (Danh sách rỗng) Then Error := ListUnderFlow Else làm các bước sau 1. If PrePtr = Nill Then {Xóa nút đầu} a. Đặt TempPtr := List b. Đặt List bằng Next(TempPtr) Else làm các bước sau {Xóa nút có phần tử đứng trước} a. Đặt TempPtr := Next(PrePtr) b. Đặt Next(PrePtr) bằng Next(TempPtr) 2. ReleaseNode (TempPtr)

=> Để xóa cần định vị con trỏ PrePtr = > Thủ tục tìm kiếm trong danh sách liên kết Procedure LinkedSearch(List, Item; Var PrePtr; Var Found : boolean);

{Tìm một nút chứa Item trongdanh sách liên kết có nút đầu tiên được chỉ bởi List. Nếu tìm ra , giá trị Found được đặt bằng đúng, CurrPtr chỉ đến nút ấy, và PrePtr chỉ đến nút đứng trước nó hay là nill nếu không có. Found được đặt bằng giá trị False nếu không tìm ra}

1. Khởi động CurrPtr tại List, PrePtr tại Nil, và Found tại False 2. While not Found and CurrPtr <> Nil làm các bước sau : If Data(CurrPtr) = Item Then Found = True Else Làm các bước sau a. Đặt PrePtr := CurrPtr b. Đặt CurrPtr := Next(CurrPtr).

- Thuật toán tìm kiếm trong một danh sách được sắp thứ tự Procedure LinkedOrderSearch (List; Item ; Var PrePtr , Var Found)

Page 28: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

28

{Tìm một nút chứa Item trong một danh sách liên kết được sắp thứ tự có nút đầu được chỉ bởi List. Nếu tìm ra giá trị Found sẽ đặt bằng True, CurrPtr chỉ đến nút ấy hay tìm vị trí chỉ tới vị trí cần chèn , PrePtr chỉ tới nút đứng trước một nút như thế và Nill nếu Item đứng trước tất cả các phần tử trong danh sách. Thuật toán giả sử các phần tủ được sắp theo thứ tự tăng dần} 1. Khởi động CurrPtr tại List, PrePtr tại Nil, Found tại False, DoneSearching tại False 2. While not DoneSearching and CurrPtr <> Nil làm các bước sau : If Data(CurrPtr) >= Item Then a. Đặt DoneSearching bằng giá trị đúng b. Đặt Found bằng giá trị đúng nếu Data(CurrPtr) = Item Else a. Đặt PrePtr bằng CurrPtr b. Đặt CurrPtr bằng Next(CurrPtr)

2. Cài đặt danh sách liên kết trên cớ sở mảng Nhắc lại : Mỗi nút trong một DSLK gồm 2 phần : Phần dữ liệu để lưu trữ một phần

tử của danh sách và phần liên kết chỉ đến một nút chứa phần tử tiếp theo trong danh sách, hoặc là Nill nếu phần tử đang xét là phần tử cuối cùng của danh sách. => Một nút biểu diễn như một bản ghi, và DSLK là một mảng các bản ghi. Mỗi bản ghi gồm 2 trường : trường DL và trường LK. Trường DL lưu một phần tử trong DS còn trường LK lưu chỉ đển phần tử đứng sau nó bằng cách lưu trữ chỉ số của nó trong mảng. Ngoài ra có một biến List lưu trữ chỉ số của phần tủ đầu tiên trong mảng Ví dụ : Khai báo Const MaxSize = ….; {Độ dài lớn nhất cho phép của danh sách} Type ElementType = …; {Kiểu một phần tử trong DS} PointType = 0..MaxSize; {Kiểu con trỏ, 0 là giá trị Nill } NodeType = Record

Page 29: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

29

Data : ElementType; Next : PointType End; ArrayOfNode = array[1..maxSize] of NodeType; Var Node : ArrayOfNode; {mảng toàn cục các nút} Free : PointerType; {Con trỏ toàn cục chỉ tới nút tự do đầu tiên} List : PointerType; {Con trỏ chỉ đến nút đầu tiên trong danh sách} Procedure Create(Var List : PointerType); List := 0; Function Empty(List : PointerType) : Boolean; Empty := (List = 0) * Thủ tục khởi động vùng lưu trữ các nút tự do Procedure IntializeStorage; Var i : i..MaxSize; Begin For i := 1 to MaxSize – 1 do Node[i].Next := i +1; Node[maxSize] := 0; Free := 1; End; * Thủ tục lấy một nút mới Procedure GetNode (Var P : PointerType); {Thủ tục này trả con trỏ P chỉ đến một nút tự do, nếy không còn, P được trả giá trị 0} Begin P := Free; If Free <> 0 Then Free := Node[Free].Next; Else Writeln(** Vung luu tru rong **); End; Procedure LinkedTraverse( List : PointerType); Var CurrPtr : PointerType; Begin

Page 30: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

30

CurrPtr := List; While CurrPtr <> 0 Do Begin Write(Node[CurrPtr].Data) CurrPtr := Node[CurrPtr].Next End End; Procedure LinkedInsert (Var List : PointerType; Item : ElementType; PrePtr :

PointerType); Var TempPtr : PointerType; {Con trỏ chỉ đến nút mới cần được chèn vào} Begin GetNode(TempPtr); Node[TempPtr].Data = Item If PrePtr = 0 Then {Chèn vào đầu danh sách} Begin Node[TempPtr].Next := List; List := TempPtr End Else {Chèn vào sau một nút} Begin Node[TempPtr].Next := Node[PrePtr].Next Node[PrePtr].Next := TempPtr end End; Procedure Remove( P : Pointer); {Giải phóng một nút được chỉ bởi P bằng cách đặt nó trỏ lại vùng lưu trữ} Begin Node[P].Next := Free; Free := P End; Procedure LinkdeDelete( var List : PointerType; PrePtr : PointerType); Var TempPtr : PointerType;

Page 31: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

31

Begin If Empty(List) Then Error := ListUnderFlow Else begin

If PrePtr = 0 Then Begin TempPtr := Node[PrePtr].Next; Node[PrePtr].Next := Node[TempPtr].Next End

Else Begin TempPtr := Node[PrePtr].Next; Node[PrePtr].Next := Node[TempPtr].Next End;

Remove(TempPtr) End End; Procedure LinkOrderSearch(List : PointerType; Item : ElementType; Var PrePtr :

PointerType; Var Found : Boolean) Var CurrPtr : PointerType; DoneSeaching : Boolean; {Báo hiệu kết thúc việc tìm kiếm} Begin CurrPtr := List; PrePtr := 0; Found := False; DoneSeaching := False; While Not DoneSeaching and (CurrPtr <> 0) Do If Node[CurrPtr].Data >= Item Then Begin DoneSeaching := True; Found := (Node[CurrPtr].Data = Item) End Else

Page 32: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

32

Begin PrePtr := CurrPtr; CurrPtr := Node[CurrPtr].Next End End; Ứng dụng : Bảng từ văn bản

3. Cài đặt danh sách liên kết trên cơ sở con trỏ a. Con trỏ và việc cấp phát/thu hồi bộ nhớ động trong Pascal Định nghĩa DS là số phần tủ có thể thay đổi, không bị giứoi hạn Mảng => không trung thực => Cách cài đặt chính xác đòi hỏi khả năng cấp phát/thu hồi các vùng nhớ cho các nút. New : Cấp phát ; Dispose : Giải phóng Khai báo : PointerName : ^định-danh-kiểu; {hoặc ↑định-danh-kiểu} Ví dụ : Var StringPtr : ^String; Begin New(StringPtr) => Sau khi thực hiện nó gán một địa chỉ vùng nhớ vào StringPtr; đó là địa chỉ vùng nhớ có thể lưu trữ một xâu kí tự Con trỏ đặc biệt Nil (Con trỏ rỗng) có thể được gán cho một con trỏ kiểu bất kì Pointer := Nil; Các phép toán với con trỏ : Phép gán và so sánh hai con trỏ cùng kiểu

+ Phép gán : Pointer1 := Pointer2; Cả hai con trỏ chỉ đến cùng một vùng nhớ. + Các phép toán quan hệ = và <> được dùng để so sánh hai con trỏ cùng kiểu có chỉ đến cùng một vùng nhớ hay không hay cả hai đều rỗng.

Page 33: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

33

Truy cập thông tin con trỏ : Pointer↑ Hoặc Pointer^ Ví dụ : StringPtr^ = ‘COMPUTER’; TempPtr^ = StringPtr^ (Không tương đương với TempPtr = StringPtr)

b. Cài đặt DSLK Type ElementType = …..; PointerType = ^NodeType; {Kiểu con trỏ chỉ đến các nút trong DS} NodeType = Record Data : ElementType; Next : PointerType End; Var List : PointerType; {Con trỏ chỉ tới nút đầu tiên trong DSLK} Procedure Create(Var List : PointerType);

Begin List := Nil; End;

Function Empty(List: PointerType) : Boolean; Begin Empty := (List = Nil) End; Procedure LinkTraverse(List : PointerType); Var CurrPtr : PointerType; Begin CurrPtr := List; While CurrPtr <> Nil do Begin Xử lý CurrPtr^.Data; CurrPtr := CurrPtr^.Next end End;

Page 34: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

34

Chỉ là cách viết lại => Tương tự với các thao tác khác

4. Ứng dụng

III. Nghiên cứu thêm về danh sách 1. Ngăn xếp và hàng đợi liên kết a. Ngăn xếp

Type ElementType = ….; PointerType = ^StackNode; StackNode = Record Data : ElementType; Next : PointerType End; StackType = PointerType; Var Stack : StackType;

Procedure Create(Var Stack : StackType); Function Empty(Stack : StackType): Boolean; Procedure Pop(Var Stack : StackType; var Item : ElementType);

{Tương đương với thủ tục xóa một phần tử ở đầu DS} Procedure Push(Var Stack : StackType; Item);

{Tương đương với thủ tục chèn một phần tử vào đầu DS}

b. Hàng đợi Type ElementType = ….; PointerType = ^QueueNode; QueueNode = Record Data : ElementType; Next : PointerType End; QueueType = Record Front, Rear : PointType End; Var Queue : QueueType;

Học sinh tự viết

Page 35: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

35

2. Danh sách với nút đầu Nhận thấy việc chèn và xóa trong một DSLK phải xét hai trường hợp. => Để đơn giản hơn thêm vào một nút đầu, không chứa dữ liệu gì cả Ví dụ : => Các phần tử trong danh sách luôn có nút đứng trước => chỉ cần xét một trường hợp trong thao tác thêm và xóa Khởi tạo (Create)

New(List); List^.Next = Nil

Kiểm tra rỗng : Empty := (List^.Next = Nil) Thêm

1. GetNode(TempPtr) 2. Data(TempPtr) = Item 3. Next(TempPtr) := Next(PrePtr) 4. Next(PrePtr) = TempPtr

Xóa

1. If Empty(List) Then Error := ListUnderFlow 2. Else begin TempPtr := Next(PrePtr) Next(PrePtr) := Next(TempPtr) end

Duyệt

1. Khởi động CurrPtr tại Next(List) 2. While CurrPtr <> Nil

3. Danh sách liên kết vòng Ở danh sách nối đơn để duyệt danh sách ta phải xuất phát từ đầu danh sách và

không thể đi ngược được. Để có thể duyệt từ bất kì nút nào trong danh sách thì ta cho con trỏ Nil ở cuối danh

sách trỏ về đầu danh sách và như vậy ta có một danh sách nối vòng ƯĐ :

Page 36: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

36

+ Ta có thể duyệt từ bất kì nút nào. + Khi thêm một nút thì không cần phân biệt thêm trước nút đầu hay sau nút đầu Nhưng phải chú ý động tác Duyệt : Từ phép duyệt tổng quát ta có thể viết rất nhanh một thủ tục

1. CurrPtr := Clist 2. While CurrPtr <> Clist

a. Xử lý Data(CurrPtr) b. CurrPtr := Next(CurrPtr)

Do đó nếu ta biết chắc DS không rỗng ta dùng Repeat thay cho While

If CurrPtr <> Nil Then Begin CurPtr := Clist Repeat Xử lý Data(CurPtr) CurrPtr := Next(CurrPtr) End

Cách khác : Dùng DSLK với nút đầu. Chèn vào một danh sách liên kết vòng : Không cần xét trường hợp nút không có phần tử đứng trước, nhưng phải xét trường hợp chèn vào một danh sách rỗng Hình vẽ : Procedure CListInsert (Var Clist; Item; PrePtr) {* Chèn Item vào một danh sách liên kết vòng với nút đầu tiên được chỉ bởi CLIST. PrePtr chỉ đến nút đứng trước nút cần chèn (nếu có)*}

1. GetNode(TempPtr) 2. Data(TempPtr) 3. If DS là rỗng làm các bước sau :

a. Next(TempPtr) := TempPtr b. Clist := TempPtr

Else Làm các bước sau : a. Next(TempPtr) := Next(PrePtr) b. Next(PrePtr) := TempPtr

Xóa từ một danh sách liên kết vòng :

Sai, sẽ không lặp lần nào

Page 37: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

37

Procedure CListDelete( Var Clist; PrePtr) {Xóa một nút từ một danh sách liên kết vòng với nút đầu tiên được chỉ bởi Clist; PrePtr chỉ vào nút đứng trước nút cần xóa } If Empty(CList) Then Error := ClistUnderFlow Else Làm các bước sau

1. TempPtr := Next(PrePtr) 2. If TempPtr = PrePtr then (*DS chỉ có một nút*)

Clist := Nil Else Next(PrePtr) := Next(TempPtr)

3. Remove(TempPtr)

4. Danh sách liên kết kép Trong DSLK trước, để tìm một nút đứng trước nó ta phải bắt đầu từ nút đầu tiên

trong danh sách. => tìm hiểu cách thiết kế và xử lý danh sách hai chiều. Mỗi nút gồm các trường :

• Các trường chứa thông tin của nút • Trường chứa địa chỉ của nút trước (BLink – Backward Link) và nút sau

(FLink – Forward Link) của nút đó Hình vẽ DSLK kép đơn giản DSLK kép nối vòng DSLK kép vừa nối vòng vừa có nút đầu Xét các thao tác với DSLK vừa nối vòng vừa có nút đầu Khai báo

Type DataType = ….; Kiểu của các phần tử trong danh sách

Page 38: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

38

PointerType = ^NodeType; NodeType = Record Data : DataType; Blink, Flink : PointerType; End; Var List : PointerType;

Khởi tạo New(List); List^.Blink := List; List^.Flink := List; Kiểm tra rỗng : sử dụng một trong hai biểu thức logic List^.Blink := List hoặc List^.Flink := List Chèn : Không phải xét trường hợp nhưng lưu ý phải thực hiện theo đúng thứ tự Hình vẽ Procedure SymmLinkedInsert(Var List : PointerType; Item : DataType; PrePtr :

PointerType) ; Var TempPtr : PointerType; Begin New(TempPtr); TempPtr^.Data := Item; TempPtr^.Blink := PrePtr; TempPtr^.Flink := PrePtr^.Flink; PrePtr^.Flink := TempPtr; TempPtr^.Flink^.Blink := Temp End; Xóa một nút Hình vẽ Procedure SymmLinkedDelete(Var List : PointerType; CurrPtr : PointerType); Begin CurrPtr^.Blink^.Flink := CurrPtr^.Flink; CurrPtr^.Flink^.Blink := CurrPtr^.Blink; End;

Page 39: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

39

Chương 6 : CÂY (TREE) I. Định nghĩa và khái niệm 1. Định nghĩa cây Cây là một tập hữu hạn các nút sao cho

- Có một nút đặc biệt gọi là gốc - Các nút khác thuộc vào n tập không giao nhau và n tập này cũng là các cây con

Ví dụ : Mục lục của chương 6 này Chương 6 : cây

Định nghĩa và khái niệm Định nghĩa cây Các khái niệm

Cây nhị phân Định nghĩa và tính chất Biểu diễn cây nhị phân Phép duyệt cây nhị phân Cây biểu diễn biểu thức số học Cây nhị phân nối vòng

Cây tổng quát 6.3.1 Biểu diễn cây tổng quát 6.3.2 Phép duyệt cây tổng quát

2. Các khái niệm a. Cấp (degee)

- Cấp của một nút trên cây là số con của nút đó - Cấp của một cây là cấp cao nhất của một nút trên cây - Nút có cấp là 0 được gọi là nút lá (nút tận cùng ) - Nút có cấp khác 0 được gọi là nhánh (cành)

b. Mức của một nút (level) - Gốc cây có mức là 1 - Con của gốc có mức là 2

c. Chiều cao của cây (Chiều sâu) - Là mức cao nhất của các nút trên cây

d. Cây có thứ tự và cây không thứ tự Nếu trong một cây mà ta phân biệt thứ tự của các cây con thì đó là cây có thứ tự (Ordered Tree) ngược lại là cây không có thứ tự (UnOrdered Tree).

Page 40: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

40

e. Rừng Là tập hợp n cây riêng biệt. Thí dụ một cây bỏ gốc thì thành rừng.

II. Cây nhị phân 1. Khái niệm cây nhị phân Cây nhị phân là cây có thứ tự mà mỗi nút có không quá hai con. Ví dụ : Cây hoàn chỉnh : Là cây ở mọi nút trừ nút cuối cùng đều có số nút là tối đa. Riêng ở nút cuối cùng có thiếu thì chỉ thiếu con phải. Cây đầy đủ : Là cây ở mọi nút (kể cả nút cuối cùng) đều có số nút là tối đa. Bổ đề 1 : Số nút ở mức thứ i thì không quá 2i-1 Bổ đề 2 : Số nút của một cây có độ cao là h thì không quá 2h -1

2. Biểu diễn cây nhị phân Ta có hai cách biểu diễn

a. Biểu diễn kế tiếp Với một cây nhị phân ta dễ dàng đánh số các nút từ trên xuống dưới, từ trái qua phải.

A B C D 0 0 0

A

B C

Cây đầy đủ

A

B

C

Cây lệch trái

A

B

CCây zizzac

A

B C

D Cây hoàn chỉnh

A

B C

D

1

32

4

h = 3

Page 41: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

41

Với một cây có độ cao là h thì ta biểu diễn trong một mảng có 2h - 1 phần tử. Mỗi nút thì biểu diễn trong một phần tử. Nút thứ i thì biểu diễn trong phần tử thứ i của mảng. Với cách biểu diễn này thì nếu nút cha ở phần tử thứ i của mảng thì con trái ở vị trí 2i, con phải ở vị trí 2i + 1. Nếu con nằm ở phần tử thứ j thì nút cha nằm ở j div 2.

b. Lưu trữ móc nối Mỗi nút nằm tại các vị trí bất kì, do đó ta phải lưu lại địa chỉ của con trái và con phải của nó. => Mỗi nút bên cạnh trường chứa thông tin của nút đó còn thêm 2 trường chứa địa chỉ của con trái và địa chỉ của con phải. Địa chỉ nút gốc nằm trong một con trỏ T, ta gọi là cây T. Khai báo

Type DataType = …..; Kiểu dữ liệu TreePointer = ^TreeNode TreeNode = Record Data : DataType; Lchild, Rchild : TreePointer; End;

3. Phép duyệt cây Là phép xử lý (thăm - Visit) các nút trên cây theo một thứ tự nào đó sao cho mỗi nút được xử lý đúng một lần. Người ta thường sử dụng 3 phép duyệt sau :

a. Duyệt cây theo thứ tự trước (PreOrder) Nếu cây khác rỗng thì :

- Thăm gốc - Duyệt cây con trái theo thứ tự trước - Duyệt cây con phải theo thứ tự trước

LChild Info RChild

Page 42: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

42

=> Thủ tục Procedure PreOrder ( T : TreePointer ); Begin If ( T <> Nil ) then Begin Writeln(T^.Info) PreOrder(T^.LChild); PreOrder(T^.RChild); End; End;

b. Duyệt cây theo thứ tự giữa (InOrder) Nếu cây khác rỗng thì

- Duyệt cây con trái theo thứ tự giữa - Thăm gốc - Duyệt cây con phải theo thứ tự giữa

= > Thủ tục

c. Duyệt cây theo thứ tự sau (PosOrder) Nếu cây khác rỗng thì

- Duyệt cây con trái theo thứ tự sau - Duyệt cây con phải theo thứ tự sau - Thăm gốc

= > Thủ tục Ví dụ minh họa : Nhận xét : Nếu như ta men theo các nút của cây xuất phát từ bên trái thì mỗi nút ta gặp đúng 3 lần

- Nếu gặp lần 1 mà đưa ra thì ta có duyệt theo thứ tự trước - Nếu gặp lần 2 mà đưa ra thì ta có duyệt theo thứ tự giữa - Nếu gặp lần 3 mà đưa ra thì ta có duyệt theo thứ tự sau

A

B C

D

1

32

4

T

Trước : A B D C

Giữa : D B A C

Sau : D B C A

Page 43: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

43

4. Cây biểu diễn biểu thức số học th1(Toán hạng 1) PT(Phép tính) th2(Toán hạng 2)

5 * x

Phép toán một ngôi Pt th ( -b ) Trong biểu thức phép tính nào thực hiện cuối cùng sẽ là gốc; biểu thức nằm bên trái phép tính đó là cây con trái; phép tính nằm bên phải biểu thức là cây con phải. Sau đó biểu diễn tiếp theo phép tính ở bên trái và bên phải phép tính cho đến khi toàn bộ biểu thức được biểu diễn thành cây. Ví dụ : Nếu duyệt cây biểu thức theo thứ tự trước được biểu thức tiền tố Giữa trung tố

Sau hậu tố Ví dụ : Khử đệ qui trong phép liệt kê Thí dụ thủ tục duyệt theo thứ tự giữa.

Procedure InOrder( T : PointerTree); Begin If T <> Nil Then Begin InOrder ( T^.LChild);

Pt

Th1 Th2Pt

Th

Page 44: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

44

2: Writeln(T^.Data); 3: InOrder(T^.RChild) end End;

Mỗi lần xong thì phải tìm lại : - Địa chỉ nút hiện tại (NodePos) - Lối về (Back) Type PointerTree = ^TreeNode TreeNode = Record Data : DataType; Lchild, Rchild : PointerTree; End; PT = Record NodePos : PointerTree; Back : 2..3; End; {Stack là một mảng các phần tử} Procedure InOrder( T : PointerTree); Label : 1,2,3; Var St : Array[1..100] of pt; D : integer; P : PointerTree; B : 2..3; Begin D := 0; {Khởi tạo Stack} P := T; 1: If P <> Nil Then begin d := d+1; St[d].NodePos := P; St[d].Back := 2; P := P^.Lchild; Goto(1); 2: Writeln(P^.Data); inc(d); St[d].NodePos := P; St[d].Back := 3; P := P^.Rchild; Goto 1; end;

Page 45: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

45

3: If (d <> 0) Then begin P := St[d].NodePos; B := St[d].Back; D := d - 1; Case B Of

2 : Goto 2; 3 : Goto 3; End Case

end End;

Nhận xét : Khi ta lấy ra từ Stack ra cặp (Địa chỉ nút và lối về = 3) thì không có tác dụng => ta bỏ đi, chỉ còn một lối về là lối về 2 cho nên không cần lưu. Do đó ta có => ST : Array[1..100] Of PointerTree; Và 3 : If d <> 0 Then Begin P := ST[d]; Dec(d) End; Viết lại thủ tục : Type PointerTree = ^TreeNode; TreeNode = Record Data : DataType; Lchild, Rchild : PointerTree; End; Procedure InOrder2 (T : PointerTree); Label 1,2; Var St : array[1..100] of PointerTree; D : integer; P : PointerTree; {để duyệt cây} Begin P := T; d := 0; 1 : If P <> Nil Then begin

Page 46: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

46

inc(d); St[d] := P; P := P^.Lchild; Goto 1; 2 : Writeln(P^.Data); P := P^.Rchild; Go to 1 end; If d <> 0 then Begin P := St[d]; Dec[d]; Go to 2 End End; Viết lại thủ tục để có cấu trúc hơn : Procedure InOrder3 ( T : PointerTree); Var St : Array[1..100] of PointerTree; D : Integer; P : PointerTree; Done : Boolean; Begin P := T; d := 0; Done := false; Repeat While (P <> Nil) do Begin Inc(d); St[d] := P; P := P^.Lchild; End; If d <> 0 Then Begin P := St[d]; dec(d); Writeln(P^.Data); P := P^.Rchild; End Else Done := True; Until Done; End; Viết hàm so sánh hai số : Function Compare1(x,y : Integer) : Char; Begin

Page 47: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

47

If x > y then ss := '>' Else if x < y then ss := '<' Else ss := '=' End;

Procedure Compare2(x,y,z : Integer); {Giả thiết x > y và đồng z là đồng thật} Begin If x > z then Writeln(x, ' : nang '); Else Writeln(y,' : nhe'); End;

Var a, b, c, d, e, f, g, h : integer; Begin Write(' Cho trong luong 8 dong xu '); Readln(a, b, c, d, e, f, g, h); Case Compare1(a+b+c,d+e+f) Of '>' : Case Compare1(a+d,b+e) Of '>' : Compare2(a,e,h); '=' : Compare2(c,f,h); '<' : Compare2(b,d,h); '=' : If g > h Then Compare2(g,h,a) Else Compare2(h,g,a) '<' : Case Compare2(a+d,b+e) Of '>' : Compare2(d,b,h); '=' : Compare2(f,c,h); '<' : Compare2(e,a,h) End End;

Tính đạo hàm của một biểu thức Biểu thức cần tính đạo hàm là một cây. Đạo hàm của biểu thức này cũng là một cây. Ví dụ :

Page 48: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

48

Qui tắc tính đạo hàm : D(c) = 0; D(x) = 1; D(lnx) = 1/x; D(u.v) = d(u).v + d(v).u D(u/v) = (vdu - udv)/v2 D(u ± v) = d(u) ± d(v) Giả thiết trong biểu thức không có phép lũy thừa nhưng trong đạo hàm vẫn có phép lũy thừa. Cây biểu thức gồm các nút có các trường sau :

- Lchild : Con trái; - Rchild : Con phải - Op : Chứa toán hạng - Type : Chứa kiểu toán hạng

o Hằng : Type = 1 o Biến : Type = 2 o Dấu + , -, *, /: Type tương ứng = 3 , 4, 5, 6 o Phép trừ một ngôi ! : Type = 7 o ^ : Type = 8 o Ln : Type = 9

Viết hàm Copy một cây Type St = string[5]; PointerTree = ^TreeNode TreeNode = Record Lchild, Rchild : PointerTree; Op : St5; Type : 1..9; End; Function Copy( T : PointerTree) : PointerTree; Var P : PointerTree; Begin If T = Nil Then Copy := Nil Else Begin New(P); P^.Op := T^.Op;

Page 49: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

49

P^.Type := T^.Type; P^.Lchild := Copy(T^.LChild) R^.Rchild := Copy(T^.Rchild); Copy := p; End; End; Function MakeNode ( Oper : St5); {Dùng xâu Ops := '+ - * / ^ ! ln' Digits := '_ . 0 1 2 …..9' là biến tổng thể} Var P : PointerTree; Begin New(P); P^.Op := Oper; P^.Lchild := Nil; P^.Rchild := Nil; If Pos(Oper,Ops) <> 0 Then P^.Type := Pos(Oper, Ops) + 2 Else If Pos(Oper[1],Digits) <> 0 then P^.Type := 1 Else P^.Type := 2 MakeNode := P End; {Chương trình ghép hai con L, R vào thành cây} Function Create2( N1, N2, N3 : PointerTree) : PointerTree; {Nối N2, n3 thành cây con trái, con phải của N và trả về Create2 là N1} Begin N1^.Lchild := N2; N1^.Rchild := N3; Create2 := N1 End; { Nối con phải vào một nút } Function Create1(N1, N2 : PointerTree) : PointerTree; { Nối N2 thành con phải của N1 và trả về giá trị của hàm Create1 = N1} Begin N1^.Rchild := N2; Create1 := N1

Page 50: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

50

End; Function DaoHam(T : PointerTree; x : St5) : PointerTree; {Tính đạo hàm của cây T theo x} Var Ops, Digits : String[20]; Dht, dhp, ts, ms, tg1, tg2, tg3 : PointerTree; Begin Ops := '+-*/^!ln'; Digits := '-.0123456789'; If T = Nil Then DaoHam := Nil Else If T^.Type = 1 then Daoham := MakeNode('0'); Else if T^.Type = 2 then If T^.Op = x then DaoHam := makeNode('1'); Else Daoham := MakeNode('0'); Else

Begin Dht := daoham(T^.Lchild,x); Dhp:= daoham(T^.Rchild,x); Case T^.Type of 3: Daoham := Create2(MakeNode('+'),dht,dhp) 4: Daoham := Create2(MakeNode('-'),dht,dhp) 5: begin tg1 := Create2(MakeNode(*),dht,Copy(T^.Rchild)) tg2 := Create2(MakeNode(*),Copy(T^.Lchild), dhp) DaoHam := Create2(MakeNode('+'),tg1,tg2); End; 6: begin tg3 := Copy(T^.Rchild); ms := Create2(MakeNode('^'),tg3,MakeNode('2')); tg1 := Create2(MakeNode('*'),dht,tg3); tg2:= Create2(MakeNode('*'),dhp,Copy(T^.Lchild)); Ts := Create2(MakeNode('-',tg1,tg2)); Daoham := Create2(makeNode('/',ts,ms)); end; 7: Daoham := Create1(MakeNode(!),dhp); 9: {lnn}

Page 51: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

51

DaoHam:=Create2(MakeNode('/'),dhp,Copy(T^.RChild));

Else Begin Writeln('Loi'); DaoHam := Nil; end End {End Of Case} End {End Of Else}

End; {End Of Function}

III. Cây nhị phân nối vòng Với một cây có n nút thì ta có tới n + 1 mối nối không. Để tận dụng các mối nối không người ta cho nó trỏ đến một nút nào đó trong một thứ tự duyệt nào đó. Chẳng hạn : Ở thứ tự giữa thì người ta cho con trỏ phải trỏ tời nút đứng sau nút hiện tại; con trỏ trái rỏ tới nút đứng trước nó. Dễ nhầm lẫn giữa mối nối đến con trỏ thật và mối nối đến nút đứng trước hay sau trong mối nối vòng. Do vậy mà ta phải thêm 2 trường : + Trường thứ 1 : LVong để đánh dấu trường con trái Nếu Lvòng = True thì đó là mối nối vòng Nếu Lvòng = false thì đó là mối nối đến con trỏ trái của nó + Trường thứ 2 : RVong để đánh dấu con trỏ phải; định nghĩa tương tự Do vậy mỗi nút tối thiểu gồm 5 trường :

- LChild : Trỏ đến con trái của nó - LCircle : Boolean ( = True thì Lchild là nối vòng; False thì Lchild trỏ tới con trái

của nó) - Rchild : Trỏ đến con phải của nó - RCircle : Boolean ( = True thì Rchild là nối vòng; False thì Rchild trỏ tới con phải

của nó) - Info : Chứa thông tin của nút.

Ví dụ :

Page 52: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

52

Trước nút đầu tiên và sau nút cuối cùng chưa trỏ vào đâu vì vậy người ta thêm một nút đầu ( nút không có thông tin)

- Rchild trỏ đến chính nó - Lchild trỏ đến gốc cây thật của nó.

Nếu cây rỗng thì chỉ có một nút đầu Khai báo :

Type DataType = ….; PointerTree = ^TreeNode TreeNode = Record Data : DataType; Lchild, Rchild : PointerTree; Lcircle, Rcircle : Boolean; End;

Giải thuật tìm nút đi sau nút P Tư tưởng :

TempPtr : = P^.RChild; Nếu P^.Rcircle = True thì TempPtr là nút đi sau Nếu P^.Rcircle = false thì TempPtr là con phải thực sự của P. Để tìm nút đi sau P thì đi xuống nút tận cùng bên trái. While TempPtr^.LCircle = false Do TempPtr = TempPtr^.LChild (Kết thúc lặp thì TempPtr là nút đi sau)

Cài đặt : Function Pos( P : PointerTree) : PointerTree; Var TempPtr : PointerTree; Begin TempPtr := P^.Rcircle; If ( Not P^.RCircle) then

Ta tìm đến nút tận cùng trái của TempPtr

Page 53: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

53

While (Not TempPtr^.LCircle) Do TempPtr := TempPtr^.Lchild; Pos := TempPtr; End;

Duyệt theo thứ tự giữa : Procedure InOrder ( T : PointerTree); Var P : PointerTree; Begin If ( Not T^.Lcircle ) then {Cây khác rỗng} Begin P := Pos(T); While ( P <> T) DO Begin Writeln(P^.Data); P := Pos(P) end End End;

Tìm nút đi trước P trong thứ tự giữa TempPtr := P^.Lchild Nếu P^.Lcircle = true thì TempPtr là nút đi trước Nếu P^.Lcircle = False thì P TempPtr là con trái của P, Khi đó ta đi xuống tận cùng bên phải While TempPtr^.Rcircle = False do TempPtr := TempPtr^.Rchild Thêm một nút thành con trái của nút P, nút thêm có dữ liệu = Item 1) Tạo ra một nút chứa Item : New(Q); Q^.Data := Item; 2) Sửa lại các mối nối trong P và Q

Page 54: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

54

+ Copy mối nối trái của P sang Q Q^.Lchild = P^. Lchild; Q^.Lcircle = P^.Lcircle + Nối phải của Q Q^.Rchild := P; Q^.Rcircle = true; + Sửa mối nối trái của P P^.Lchild = Q; P^.Lcircle := False

3) Sửa nút trước Q ( trước kia nút này trước P còn bây giờ nút này trước Q) If (Q^.Lcircle = false) then Begin { Q có con trái} TempPtr := Pre(Q); TempPtr^.Rchild := Q; TempPtr^.Rcircle := True; End;

=> Cài đặt thủ tục

IV. Cây tổng quát Có thể thêm m trường móc nối vào mỗi nút để trỏ đến m con của nó. Nếu một cây có n nút thì nó sẽ có (n.m) mối nối; trong đó n(m-1) + 1 nút mối nối không => lãng phí. Có thể tùy theo số con của mỗi nút mà tổ chức mỗi nút có số mối nối khác nhau => quá phức tạp Vậy có thể biểu diễn cây tổng quát bằng cây nhị phân không ? làm được Với mỗi nút của cây tổng quát nếu có thì

- Có duy nhất một con cực trái - Có một em cận phải

Vậy ta biểu diễn con cực trái là con trái của nút đó Em cực phải là con phải của nút đó. Ví dụ : …………… Phép duyệt cây tổng quát Phải định nghĩa sao cho

- Nếu duyệt cây tổng quát theo phép duyệt tổng quát thì cũng cho kết quả như là duyệt cây nhị phân tương đương theo phép duyệt cây nhị phân

- ………..

Page 55: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

55

Người ta cũng định nghĩa 3 phép duyệt giống nhau như phép duyệt cây nhị phân - Duyệt theo thứ tự trước

o Nếu cây khác rỗng thì Thăm gốc Duyệt theo thứ tự trước cây T1 Duyệt theo thứ tự trước cây T1, T2, …….Tn.

- Duyệt theo thứ tự giữa - Duyệt theo thứ tự sau

NGười ta cũng xuất phát từ vấn đề là nếu ta vòng theo các nút của cây thì mỗi nút gặp đúng 3 lần

- Gặp lần 1 thì đưa ra ta có duyệt theo thứ tự trước - Gặp lần 2 thì đưa ra ta có duyệt theo thứ tự giữa ( => Nó không tương ứng với

cái nào nên bỏ) - Gặp lần 3 thì đưa ra ta có duyệt theo thứ tự sau

Chương 7 : Sắp xếp I. Các phương pháp sắp xếp đơn giản 1. Phương pháp lựa chọn trực tiếp Chọn số bé nhất trong n số để ở vị trí thứ 1 Chọn số bé nhất trong n - 1 số để ở vị trí thứ 2 ……………………………………………… Chọn số bé nhất trong 2 số để ở vị trí thứ n - 1

For i := 1 to n-1 do Begin

1. Chọn số bé nhất trong các số từ xi , ……xn 2. Đổi chỗ cho xi

End; => Cài đặt

Type Mg = Array[1..100] of integer; Procedure SelectionSort( Var x : Mg; n : integer); Var i, j, m : integer; Tg : integer; Begin

Page 56: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

56

For i := 1 to n-1 do Begin m := i; { tìm vị trí của số nhỏ nhất } For j := i+1 to n do If x[m] > x[j] then m := j; {đổi chỗ} if ( i <> m ) then begin tg := x[m]; x[m] := x[i]; x[i] := tg end End End;

2. Phương pháp nổi bọt x1, x2, ………….. xn-1, xn

So sánh hai số liên tiếp - Nếu đúng thứ tự thì để nguyên - Nếu ngược thứ tự thì đổi chỗ

=> Sau vòng 1 thì số bé nhất nối lên ở vị trí 1 Sau vòng 2 thì số bé nhất trong n-1 số còn lại nổi lên ở vị trí thứ 2 ……………………………………………………………………. Sau vòng n-1 thì số bé nhất trong 2 số còn lại nổi lên ở vị trí thứ n-1 => Thủ tục sắp xếp

Procedure BullbleSort( Var x : Mg; n : integer); Var i, j, tg : integer; Begin For i := 1 to n-1 do For j := n downto i+1 do If x[j] < x[j-1] then Begin Tg := x[j-1];

x[j-1] := x[j];

Page 57: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

57

x[j] := tg end End;

Nhận xét : Nếu dãy đã được sắp xếp, ta vẫn phải so sánh đủ số lần lặp -> lặp phần tử. => Khắc phục khuyết điểm trên ta thêm vào một cờ . Nếu mọi cặp đều đúng thứ tự thì cờ := true

3. Phương pháp chèn trực tiếp Tìm kiếm các giá trị đứng gác Tìm y trong dãy số xi Với mỗi xi ta phải làm hai động tác kiểm tra

1. i <= n 2. y = x[i]

Cải tiến đặt số y ở cuối mỗi dãy x, ta bỏ đi kiểm tra điều kiện i <= n chỉ cần While (x[i] <> y) do inc(i) . Phần tử y được gọi là phần tử đứng gác. => Thủ tục

i := 1 x[n+1] := y {phần tử đứng gác} While (x[i] <> y) do inc(i); If (i = n+1) then Không thấy Else thấy

Cho dãy x1, x2, …………..xn-1, xn

Chia dãy thành hai phần - Phần đầu : x1 đã sắp xếp - Phần hai : x2, …………..xn-1, xn chưa sắp xếp

Lấy từng số của phần 2 chèn vào phần 1 theo đúng thứ tự Thí dụ : Cho dãy 10 9 12 80 42 1/ x[0] x[1] x[2] x[3] x[4] x[5] 9 10 / 9 12 80 42

i := 2 k = x2 = 9 j = i -1 =1 x[0] := k {phần tử đứng canh} Tìm vị trí để chèn

While xj > k do Begin

Page 58: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

58

Đẩy xj sang phải (j+1) Giảm j End X[j+1] := k; 2/

x[1] x[2] x[3] x[4] x[5] 9 10 12 80 42

j = 3; k = 12; j = 2; x[0] := 12

x[0] x[1] x[2] x[3] x[4] x[5] 12 9 10 12 80 42

x3 = 12; 3/

x[0] x[1] x[2] x[3] x[4] x[5] 80 9 10 12 80 42

x4 = 80; 4/

x[0] x[1] x[2] x[3] x[4] x[5] 42 9 10 12 80 42

x5 = 80; x4 = 80;

9 10 12 42 80 Giải thuật Type Mg = Array[0..500] of integer; Procedure InsertionSort( Var X : Mg; n : integer ); Var i, j, k : integer; Begin For i := 2 to n do Begin k := x[i]; j := i-1;

x[0] := k; While x[j] > k do Begin

Page 59: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

59

X[j+1] := x[j]; j := j -1 end x[j+1] := k

End End;

II. Các phương pháp sắp xếp phức tạp 1. Phương pháp sắp xếp nhanh (Quick - Sort) Cải tiến của phương pháp nổi bọt Ở phương pháp nổi bọt ta đổi chỗ hai phần tử liên tiếp gần nhau Ở đây ta sẽ tìm cách đỗi chỗ hai phần tử xa nhau Cách làm Chọn một số bất kì trong dãy ( thường chọn x[i] hoặc x[1+n] div 2 ) làm khóa chốt. Cho dãy x1, x2, …………..xn-1, xn

Tìm từ đầu dãy xi >= k Tìm từ cuối dãy xj <= k Nếu i < j then

- Đổi chỗ xj cho xi - Tăng i giảm j

Rồi lại tiếp tục tìm vào trong cho đến khi j < i Ví dụ : 10 8 7 80 42 k = 7; i = 1; j = 5 j = 3 => đổi chỗ 7, 10 7 8 10 80 42 i = 2; j = 2; k = 8; Sau một vòng đã chia dãy thành 2 đoạn

- Đoạn đầu <= k - Đoạn sau >= k

Sau đó ta lại phân hoạch tiếp các đoạn có độ dài lớn hơn 1 cho đến khi mọi đoạn đều có độ dài = 1 coi như xong. Giải thuật : Type Mg = Array[1..500] of integer; Procedure QuickSort(d,c); {Sắp xếp các đoạn từ xd tới xc}

Page 60: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

60

var k, i, j, tg : integer; begin i := d; j := c; k := x[(i+j) div 2]; Repeat {tìm từ đầu dãy xi >= k} While x[i] < k do inc(i) {Tìm từ cuối dãy xj <= k} While x[j] > k do dec(j) If (i <= j) then Begin Đổi chỗ x[i], x[j] Inc(i), dec(j) End Until j < i; If d < j -1 then QuickSort(d,j) If i+1 < c then QuickSort(i,c) end; Giải thuật sắp xếp nhanh không đệ qui

- Ấn đoạn từ 1 -> n vào Stack - Lấy một đoạn ra từ stack rồi phân hoạch

o Nếu độ dài đoạn 1 lớn hơn 1 thì đẩy vào Stack o Nếu đội dài đoạn 2 lớn hơn 1 thì đẩy vào Stack

- Cho đến khi Stack rỗng Giải thuật

Type pt = record d,c : integer; end; Mg = Array[1..500] of integer; Procedure SXNhanh(Var X : Mg; n : integer); Var Stack : array[1..100] of pt; Sp : Integer; {Đỉnh Stack} D, c, i, j, k, tg : integer;

Page 61: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

61

Begin {Đẩy đoạn 1,n vào Stack} Sp := 1; Stack[sp].d := 1; Stack[sp].c := n; Repeat {Lấy một đoạn từ Stack} d := Stack[sp].d; c:= Stack[sp].c; Dec(sp); {Phân hoạch đoạn vừa lấy ra} i := d; j := c; Repeat While (x[i] < k) do inc(i) While (x[j] > k) do dec(j) If (i <= j) then Begin Đổi chỗ x[i], x[j] Inc(i); Dec(j) End; Until i > j; If d < j then {Đẩy đoạn d, j vào Stack} Begin Inc(Sp); Stack[sp].d := d; Stack[sp].c := j End; If c > i then {Đẩy đoạn i, c vào Stack} Until sp = 0 {Stack rỗng} End;

2. Sắp xếp vun đống (Heap - Sort) Tìm số lớn nhất trong x1, x2, ………….. xn-1, xn rồi đổi chỗ cho xn, khi đó xn đã được sắp xếp. Sau đó tìm số lớn nhất trong n-1 số x1, x2, …………..xn-1 rồi đổi chỗ cho xn-1, khi đó xn-1, xn đã được sắp xếp. ……………………………………… Nếu tìm số lớn nhất bằng cách chọn trực tiếp thì ta có giải thuật SelectionSort Tìm hiểu cách mới : Tìm số lớn nhất bằng cách vun thành đống

Page 62: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

62

Từ dãy số ban đầu : 40 10 2 6 8 16 Ta luôn có thể coi là biểu diễn kế tiếp của một cây nhị phân hoàn chỉnh theo nguyên tắc. phần tử thứ i của dãy thì biểu diễn nút thứ i của cây. Quá trình sắp xếp chia làm hai bước :

- Bước 1 : Vun cây ban đầu thành đống - Bước 2 : Sắp xếp

Bước 1 : Vun cây ban đầu thành đống Định nghĩa đống : là một cây nhị phân hoàn chỉnh mà mọi nút cha đều có trường khóa lớn hơn khóa của hai nút con. Giải thuật điều chỉnh Adjust(r,n) : Điều chỉnh cây có gốc là r, gồm n nút thành đống, giả thiết hai cây con đã là đống. Thí dụ điều chỉnh cây có gốc là 1 ( chứa giá trị 16 ) gồm 6 nút thành đống

- So sánh hai con để tìm con lớn hơn 10 < 40 : Con phải lớn hơn

- So sánh gốc với con lớn o Nếu gốc lớn hơn con lớn thì xong o Nếu gốc bé hơn con thì đẩy con lên.

Procedure Adjust(r,n: Integer); {Điều chỉnh cây có gốc r, gồm n nút thành đống, giả thiết hai cây con đã là đống } Var c : integer; k : integer; {Chứa khóa gốc} Done : Boolean;

40

10 2

6 8 16

1

2 3

4 5 6

16

10 40

6 8 2

1

2 3

4 5 6

Page 63: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

63

Begin Done := False; C:= 2 * r; While ((c <=n) and not done) do Begin {Tìm con lớn} If (c < n) then {có con phải} If (x[c] < x[c+1] ) then c := c+1; {So sánh con lớn với gốc} If x[r] > x[c] then Done := true; Else Begin {Đẩy con lên} x[r] <=> x[c]; {Xuống cây có gốc là c cũ} r := c; c := 2 * c End; End End; Từ cây ban đầu điều chỉnh thành cây n = 6; 1, 2, 3 là gốc 1/ Điều chỉnh cây có gốc i = n div 2 = 3

16

10 2

6 8 7

1

2 3

4 5 6

Dùng Adjust điều chỉnh từ dưới lên. Chú ý chỉ có các nút 1, 2, …. n div 2 ; mới là nút gốc

2

7

3

6

7

2

3

6 ==>

Page 64: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

64

2/ Điều chỉnh cây có gốc i = 2 3/ Điều chỉnh cây có gốc i =1 Vậy quá trình điều chỉnh : For i := n div 2 to 1 do Adjust(i,n) Bước 2 : Sắp xếp + Đổi chỗ x[1] cho x[n] ==> Dãy đã sắp xếp là xn

+ Vun đống

10

6 8

==>

10

6 8

16

10 7

6 8 2

1

2 3

4 5 6

==>

16

10 7

6 8 2

1

2 3

4 5 6

Dãy sắp xếp : 16

16

10 7

6 8 2

1

2 3

4 5 6

2

10 7

6 8

10

2 7

6 8

==> 10

8 7

6 2

==>

Page 65: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

65

+ Đổi chỗ x[1] cho x[5] -> dãy sắp xếp 10, 16 + Vun đống + Đổi chỗ x[1] cho x[4] -> dãy sắp xếp 8, 10, 16 + Vun đống + Đổi chỗ x[1] cho x[3] -> dãy đã sắp xếp : 7, 8, 10, 16 + Vun đống Đổi chỗ x[1] và x[2] có dãy được sắp xếp : 2, 6, 7, 8, 10, 16 => For i := n-1 downto 1 do Begin

1. Đổi chỗ x[1] <->x[i] 2. Adjust(1,i)

End; Procedure Heap-Sort( var A : mg; n : integer); Var i : integer; tg : integer; Begin For i:= n div 2 downto 1 do {vun đống}

2

8 7

6

8

2 7

6

==> 8

6 7

2

==>

2

6 7

7

6 2

==>

2

6

6

2

==>

Page 66: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

66

Adjust(i,n) {Sắp xếp} For i := n downto 2 do Begin {Đổi chỗ a[1] <-> a[i]} a[1] <-Tg-> a[i] {Vun cây có gốc là 1 và có i-1 nút thành đống} Adjust(1,i-1); End; End;

3. Sắp xếp theo kiểu hòa nhập Tách mảng T thành hai phần mà kích thước của chúng sai khác nhau càng ít càng tốt, sắp xếp các phần này bằng cách gọi đệ qui và sau đó trộn chúng lại (chú ý duy trì tính thứ tự). Để làm được điều này chúng ta cần một giải thuật hiệu quả cho việc trộn hai mảng đã được sắp U và V thành một mảng mới T mà kích thước của mảng T bằng tổng kích thước của hai mảng U và V. Vấn đề này có thể thực hiện tốt hơn nếu ta thêm vào các ô nhớ có sẵn ở cuối của mảng U và V các giá trị đứng canh (giá trị lớn hơn tất cả các giá trị trong U và V) . Procedure merge(U[1..m+1],V[1..n+1],T[1..m+n]);

(*Trộn 2 mảng U[1..m+1] và V[1..n+1] thành mảng T[1..m+n]); U[m+1],V[n+1] được dùng để chứa các giá trị cầm canh*)

Begin i:=1;j:=1; U[m+1]:=∞; V[n+1]:=∞;

For k:=1 to n+m do If U[i]<V[j] then Begin T[k]:=U[i]; i:=i+1 End Else Begin T[k]:=V[j]; j:=j+1 End End;

Giải thuật sắp xếp trộn sau đây có thể tốt hơn nếu các mảng U và V là các biến toàn cục và xem việc sắp xếp chèn Insert(T) như là giải thuật cơ bản

Page 67: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

67

Procedure mergesort(T[1..n]); Begin If n =1 then Return T

Else Begin Array U[1..1+ ⎣ ⎦2/n ], V[1..1+ ⎡ ⎤2/n ;

U[1.. ⎣ ⎦2/n ]:= T[1.. ⎣ ⎦2/n ];

V[1.. ⎡ ⎤2/n ]:= T[1+ ⎣ ⎦2/n ..n];

mergesort(U[1.. ⎣ ⎦2/n ]);

mergesort(V[1.. ⎡ ⎤2/n ]);

merge(U,V,T) End; Hình sau chỉ ra các bước của mergesort.

40 12 16 18 27 21 50 4

4 12 16 18 21 27 40 50

12 16 18 40 4 21 27 50

MergeSort(40, 12, 16, 18, 27, 21, 50, 4) - MS()

12 40 16 18 21 27 4 50

MS(40, 12, 16, 18) MS ( 27, 21, 50, 4)

MS(40, 12) MS(16, 18) MS ( 27, 21) MS ( 50, 4)

MS(40) MS(16) MS(27) MS(50) MS(12) MS(18) MS(21) MS(4)

Page 68: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

68

Chương 8 : Tìm kiếm Bài toán : Cho n bản ghi có các khóa k1, k2, ……, kn. Hãy tìm một bản ghi có khóa là X. Nếu tìm thấy thì phép tìm kiếm là thỏa mãn Ngược lại thì phép tìm kiếm là không thỏa mãn

I. Tìm kiếm tuần tự có phần tử đứng canh Đặt phần tử đứng canh ở cuối dãy kn+1 = X;

I := 1; While (x <> k[i]) do inc(i); If i = n+1 then Không thấy Else Thấy

Viết dưới dạng hàm - Nếu tìm thấy thì giá trị của hàm = chỉ số của phần tử - Nếu không tìm thấy thì giá trị của hàm = 0

Type Mg = Array[1..1000] of integer; Function Loc(Var k : Mg; X,n : integer) : integer;

Var i : integer; Begin i := 1; k[n+1] := x; While (k[i] <> x) do inc(i) If i = n+1 then loc := 0 Else loc := i End;

II. Tìm kiếm nhị phân Cho dãy khóa k1, k2, ……, kn đã được sắp xếp theo thứ tự ( giả sử thứ tự tăng) Hãy tìm vị trí của bản ghi có giá trị X Dùng giải thuật tìm kiếm nhị phân

Function Loc( Var k : Mg; X, n : integer) : integer; Var d, c, g : integer; Found : Boolean; Begin D := 1; c := n; Found := false;

Page 69: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

69

While not Found and (d <= c) do Begin G := (d+c) div 2 If k[g] = X then Found := true Else if x > k[g] then D := g + 1 Else c := g -1 End If found then Loc := g Else Loc := 0 End;

-> ĐPT : O(Logn) Viết lại thành giải thuật đệ qui Trường hợp suy biến khi x[g] := X; hoặc d > c => Giải thuật

Function Loc( Var K : Mg; X, d, c : integer) : integer; Var g : integer; Begin If (d > c) then Loc := 0 Else Begin G := (d+c) div 2 If k[g] = x then Loc := g; Else if ( k[g] > x) then Loc := Loc(k,x,d,g-1) Else Loc := Loc(k,x,g+1,c) End; End;

Thường thì - Nếu thấy thì thôi - Nếu không thấy thì bổ xung vào dãy theo đúng thứ tự

Nếu lưu trữ theo kiểu kế tiếp -> bổ xung mất công -> Khắc phục bằng cách xây dựng cây nhị phân thì việc bổ xung dễ dàng hơn.

Page 70: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

70

III. Cây tìm kiếm nhị phân - Giải thuật BST Định nghĩa : Cây tìm kiếm nhị phân Là một cây nhị phân mà mọi nút đều có tính chất Khóa của nút đó thì lớn hơn mọi khóa của các nút thuộc cây con trái và nhỏ hơn mọi khóa của các nút thuộc cây con phải.

Nhận xét : Nếu duyệt theo thứ tự giữa ta được một dãy đã sắp xếp Giải thuật tìm kiếm trên cây nhị phân tìm kiếm

Nếu cây T khác rỗng thì So sánh với gốc Nếu X = K[gốc] thì thấy Nếu X > K[gốc] thì xuống cây con phải X < K[gốc] thì xuống cây con trái

Giả thiết mỗi nút chỉ có một trường chứa thông tin là khóa

Vì tìm kiếm có bổ xung nên ta dùng hai con trỏ P := T; Cho nó trỏ vào gốc cây T PreP := Nil;

Vòng lặp While ((p <> Nil) and not found) do Begin If (x = p^.Key) then Found := True Else Begin PreP := P; If (x > P^.Key) then P := P^.Rc Else P := P^.Lc

Lc Key Rc

40

30 90

10 8631 99

Page 71: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

71

End End; {Bổ sung}

If (not found) then Begin {Tạo ra một nút chứa khóa mới} New(Temp); Temp^.Key := X; Temp^.Lc := Nil; Temp^.Rc := Nil; {Nối vào cây} If (T = Nil) then T := Temp Else {Thành con của PreP} If (x < PreP^.Key) then PreP^.Lc := temp Else PreP^.Rc := Temp End;

=> Cài đặt giải thuật procedure BST(var T, P: ptr; Var found : boolean; x : integer);

{Tìm nút có khóa = x trên cây T, nếu thấy thì found = true và p chứa địa chỉ của nút có khóa = x, nếu không thấy thì found=false, chèn nút có khóa = X vào cây, P chứa địa chỉ của khóa bổ sung }

Var TrP : ptr; Begin {1. Tìm} P := T; TrP := Nil; Found := false; While (not found) and (p <> nil) do begin if p^.Key = X then Found := true else begin trp := P; if (p^.Key < X) Then p := p^.Rc; else p := p^.Lc end end ; {Bổ sung }

Page 72: GiaoAn_CTDL_Kiendt_2

Giáo án môn : Cấu trúc dữ liệu và giải thuật

ĐỖ TRUNG KIÊN

72

If not found then Begin {Tạo ra nút chứa x} new(p); p^.Key :=X; p^.Lc := Nil; p^.Rc := Nil; {Nối vào cây T} If (T = Nil) then T := p; else If Trp^.Key > X then Trp^.Lc := P else Trp^.Rc := p End End; Thí dụ : Vào một dòng khóa 10, -2, -6, 9, 16 Tìm kiếm và bổ sung vào cây T (rỗng) Đầu tiên T = Nil + Tìm X = 10, vì T = Nil nên bổ sung nút có khóa 10 vào thành cây + Tìm 2; 2 ≠ 10; vì 2 < 10 sang cây cong trái, nhưng con trái của 10 là Nil vậy bổ sung 2 thành con trái của 10. + Tìm 6; 6 ≠ 10; vì 6 < 10 nên sang cây con trái

10T

10T

2