Academia.eduAcademia.edu
HỌC VIỆN CÔNG NGHỆ BƯU CHÍNH VIỄN THÔNG BÀI GI NG KỸ THUẬT LẬP TRÌNH Biên so n : Ths. NGUYỄN DUY PHƯƠNG Giới thiệu môn học GIỚI THIỆU MÔN HỌC I. GI I THIỆU CHUNG Sự phát triển công ngh thông tin trong nh ng nĕm vừa qua đã làm thay đổi bộ mặt kinh t xã hội toàn c u, trong đó công ngh ph n m m tr thành một ngành công nghi p quan trọng đ y ti m nĕng. Với sự hội t c a công ngh vi n thông và công ngh thông rin, t trọng v giá tr ph n m m chi m r t cao trong các h th ng vi n thông cũng nh các thi t b đ u cu i. Chính vì lý do đó, vi c nghiên c u, tìm hiểu, ti n tới phát triển cũng nh làm ch các h th ng ph n m m c a các k s đi n t vi n thông là r t c n thi t. Tài li u gi ng d y “Kỹ thuật lập trình” cho h đào t o từ xa đ c xây dựng dựa trên giáo trình “Kỹ thuật lập trình” đã đ c gi ng d y t i học vi n trong nh ng nĕm qua với m c đích cung c p cho sinh viên nh ng ki n th c c b n nh t, có tính h th ng liên quan tới l p trình. Thông qua cu n tài li u này, chúng tôi mu n giới thi u với các b n đọc v k nĕng l p trình c u trúc và một s thu t toán quan trọng, bao g m: Đ i c ng v l p trình c u trúc; Duy t và đ qui; Ngĕn x p, hàng đ i và danh sách móc n i; Cây; Đ th và cu i cùng là S p x p và tìm ki m. II. M C ĐÍCH Môn học cung c p cho sinh viên k nĕng l p trình trên các c u trúc d li u quan trọng nh : stack, queue mlink, tree & graph cùng với ph ng pháp phân tích, thi t k , đánh giá thu t toán. Sau khi học xong môn học này, sinh viên có kh nĕng vi t đ quy t nh ng bài toán trong thực t . c ch ng trình gi i III. PH M VI NGHIÊN C U Nghiên c u các thu t toán c b n đ c s d ng trong thực t nh các thu t toán tìm ki m, các thu t toán liên quan đ n đ th . Các gi i thu t l p trình dựa trên danh sách, cây… Nghiên c u cách cài đặt các thu t toán trên máy tính. Tìm hiểu các lĩnh vực ng d ng c a các thu t toán, ph ng pháp trong thực t . IV. PHƯƠNG PHÁP NGHIÊN C U Để học t t môn học này, sinh viên c n l u ý nh ng v n đ sau: 1. Ki n th c c n tr c Lời nói đầu - Sinh viên ph i có ki n th c c b n v toán học cao c p. - Thành th o ít nh t một ngôn ng l p trình. Đặc bi t trong cu n sách này đã s d ng ngôn ng l p trình C để mô t thu t toán, vì v y sinh viên ph i n m đ c ngôn ng l p trình C. 2. Các tài li u c n có: Sách h ớng d n học t p K thu t l p trình. Ths. Nguy n Duy Ph Công ngh B u chính Vi n thông, 2006. ng, Học vi n N u c n sinh viên nên tham kh o thêm: - Giáo trình K thu t l p trình. Ts. Lê H u L p, Ths. Nguy n Duy Ph vi n Công ngh B u chính Vi n thông, 2002. ng, Học - Bài gi ng đi n t môn học: “K thu t l p trình” c a Học vi n Công ngh B u chính Vi n thông. 3. Đ t ra m c tiêu, th i h n cho b n thân Đặt ra các m c tiêu t m th i và th i h n cho b n thân và c g ng thực hi n chúng Xây dựng m c tiêu trong ch ng trình nghiên c u. 4 Nghiên c u và n m nh ng ki n th c c t lõi Sinh viên nên đọc qua sách h ớng d n học t p tr ớc khi nghiên c u bài gi ng môn học và các tài li u tham kh o khác. 5. Tham gia đ y đ các buổi h ng d n học t p Thông qua các buổi h ớng d n học t p, gi ng viên s giúp sinh viên n m đ c nội dung tổng thể c a môn học và gi i đáp th c m c, đ ng th i sinh viên cũng có thể trao đổi, th o lu n với nh ng sinh viên khác v nội dung bài học. 6. Ch đ ng liên h v i b n học và gi ng viên Cách đ n gi n nh t là tham dự các di n dàn học t p trên m ng Internet, qua đó có thể trao đổi trực ti p các v n đ v ớng m c với gi ng viên hoặc các b n học khác đang online. 7. Tự ghi chép l i nh ng ý chính Vi c ghi chép l i nh ng ý chính là một ho t động tái hi n ki n th c, kinh nghi m cho th y nó giúp ích r t nhi u cho vi c hình thành thói quen tự học và t duy nghiên c u. 8. Học đi đôi v i hành Học lý thuy t đ n đâu thực hành làm bài t p và thực hành ngay đ n đó để hiểu và n m ch c lý thuy t. Sinh viên c n cài đặt trên máy tính các thu t toán trong bài học bằng các ngôn ng l p trình để từ đó có thể hiểu và n m ch c h n t t ng và nội dung c a thu t toán. Hà Nội, ngày 20 tháng 02 năm 2006 Ths. Nguy n Duy Ph 2 ng Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc CHƯƠNG 1: ĐẠI CƯƠNG VỀ KỸ THUẬT LẬP TRÌNH CẤU TRÚC Nội dung chính c a ch ng này t p chung làm sáng t nh ng nguyên lý c b n c a l p trình c u trúc. Nh ng nguyên lý này đ c coi nh n n t ng t t ng c a ph ng pháp l p trình c u trúc đã đ c tích h p trong các ngôn ng l p trình. N m v ng các nguyên lý c a l p trình c u trúc không ch giúp ng i học có cách ti p c n ngôn ng l p trình nhanh chóng mà con giúp họ cách t duy trong khi xây dựng các h th ng ng d ng. Các nguyên lý c b n đ c giới thi u trong ch ng này bao g m: 9 Nguyên lý l nh - l nh có c u trúc - c u trúc d li u. 9 Nguyên lý t i thiểu. 9 Nguyên lý đ a ph 9 Nguyên lý an toàn. ng. 9 Nguyên lý nh t quán. 9 Nguyên lý Top-Down . 9 Nguyên lý Botton-Up. B n đọc có thể tìm đ c nh ng chi ti t sâu h n và rộng h n trong tài li u [1] & [6]. 1.1. SƠ LƯ C V L CH S L P TRÌNH C U TRÚC L p trình là một trong nh ng công vi c nặng nhọc nh t c a khoa học máy tính. Có thể nói, nĕng su t xây dựng các s n ph m ph n m m là r t th p so với các ho t động trí tu khác. Một s n ph m ph n m m có thể đ c thi t k và cài đặt trong vòng 6 tháng với 3 lao động chính. Nh ng để kiểm tra tìm lỗi và ti p t c hoàn thi n s n ph m đó ph i m t thêm chừng 3 nĕm. Đây là hi n t ng phổ bi n trong tin học c a nh ng nĕm 1960 khi xây dựng các s n ph m ph n m m bằng k thu t l p trình tuy n tính. Để kh c ph c tình tr ng lỗi c a s n ph m, ng i ta che ch n nó b i một mành che mang tính ch t th ng m i đ c gọi là Version. Thực ch t, Version là vi c thay th s n ph m cũ bằng cách s a đổi nó r i công b d ới d ng một Version mới, gi ng nh : MS-DOS 4.0 ch t n t i trong th i gian vài tháng r i thay đổi thành MS-DOS 5.0, MS-DOS 5.5, MS-DOS 6.0 . . . Đây không ph i là một s n ph m mới nh ta t ng mà trong nó còn t n t i nh ng lỗi không thể b qua đ c, vì ngay MS-DOS 6.0 cũng ch là sự kh c ph c h n ch c a MS-DOS 3.3 ban đ u. Trong th i kỳ đ u c a tin học, các l p trình viên xây dựng ch ng trình bằng các ngôn ng l p trình b c th p, quá trình n p và theo dõi ho t động c a ch ng trình một cách trực ti p trong ch độ trực tuy n (on-line). Vi c tìm và s a lỗi (debbugging) nh ngày nay là không thể thực hi n đ c. Do v y, tr ớc nh ng nĕm 1960, ng i ta coi vi c l p trình 3 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc gi ng nh nh ng ho t động ngh thu t nhuộm màu s c cá nhân h n là khoa học. Một s ng i n m đ c một vài ngôn ng l p trình, cùng một s mẹo vặt t n d ng c u hình v t lý c thể c a h th ng máy tính, t o nên một s s n ph m l c a ph n m m đ c coi là một chuyên gia n m b t đ c nh ng bí n c a ngh thu t l p trình. Các h th ng máy tính trong giai đo n này có c u hình y u, bộ nhớ nh , t c độ các thi t b vào ra th p làm ch m quá trình n p và thực hi n ch ng trình. Ch ng trình đ c xây dựng bằng k thu t l p trình tuy n tính mà nổi b t nh t là ngôn ng l p trình Assembler và Fortran. Với ph ng pháp l p trình tuy n tính, l p trình viên ch đ c phép thể hi n ch ng trình c a mình trên hai c u trúc l nh, đó là c u trúc l nh tu n tự (sequential) và nh y không đi u ki n (goto). H th ng th vi n vào ra nghèo nàn làm cho vi c l p trình tr nên khó khĕn, chi phí cho các s n ph m ph n m m quá lớn, độ tin c y c a các s n ph m ph n m m không cao d n tới hàng lo t các dự án tin học b th t b i, đặc bi t là các h th ng tin học có t m c lớn. Nĕm 1973, Hoare khẳng đ nh, nguyên nhân th t b i mà ng i M gặp ph i khi phóng v tinh nhân t o v phía sao V n (Sao Kim) là do lỗi c a ch ng trình đi u khiển vi t bằng Fortran. Thay vì vi t: DO 50 I = 12, 523 (Thực hiện số 50 với I là 12, 13, ..., 523) L p trình viên (hoặc thao tác viên đ c bìa) vi t thành: DO 50 I = 12.523 (Dấu phảy đã thay bằng dấu chấm) Gặp câu l nh này, ch ng trình d ch c a Fortran đã hiểu là gán giá tr thực 12.523 cho bi n DO 50 I làm cho k t qu ch ng trình sai. Để gi i quy t nh ng v ớng m c trong k thu t l p trình, các nhà tin học lý thuy t đã đi sâu vào nghiên c u tìm hiểu b n ch t c a ngôn ng , thu t toán và ho t động l p trình, nâng nội dung c a k thu t l p trình lên thành các nguyên lý khoa học ngày nay. K t qu nổi b t nh t trong giai đo n này là Knuth xu t b n bộ 3 t p sách mang tên “Ngh thu t l p trình” giới thi u h t s c t m c s lý thuy t đ m b o toán học và các thu t toán c b n x lý d li u n a s , s p x p và tìm ki m. Nĕm 1968, Dijkstra công b lá th “V sự nguy h i c a toán t goto”. Trong công trình này, Dijkstra khẳng đ nh, có một s lỗi do goto gây nên không thể xác đ nh đ c điểm b t đ u c a lỗi. Dijkstra còn khẳng đ nh thêm: “Tay ngh c a một l p trình viên t l ngh ch với s l ng toán t goto mà anh ta s d ng trong ch ng trình”, đ ng th i kêu gọi hu b tri t để toán t goto trong mọi ngôn ng l p trình ngo i trừ ngôn ng l p trình b c th p. Dijkstra còn đ a ra khẳng đ nh, động thái c a ch ng trình có thể đ c đánh giá t ng minh qua các c u trúc lặp, r nhánh, gọi đ qui là c s c a l p trình c u trúc ngày nay. Nh ng k t qu đ c Dijikstra công b đã t o nên một cuộc cách m ng trong k thu t l p trình, Knuth li t kê một s tr ng h p có l i c a goto nh vòng lặp k t thúc gi a chừng, b t lỗi . . ., Dijkstra, Hoare, Knuth ti p t c phát triển t t ng coi ch ng trình máy tính cùng với l p trình viên là đ i t ng nghiên c u c a k thu t l p trình và ph ng pháp 4 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc làm ch sự ph c t p c a các ho t động l p trình. Nĕm 1969, Hoare đã phát biểu các tiên đ ph c v cho vi c ch ng minh tính đúng đ n c a ch ng trình, phát hi n tính b t bi n c a vòng lặp bằng cách coi ch ng trình vừa là b n mã hoá thu t toán đ ng th i là b n ch ng minh tính đúng đ n c a ch ng trình. Sau đó Dahl, Hoare, Dijiksta đã phát triển thành ngôn ng l p trình c u trúc. Để triển khai các nguyên lý l p trình c u trúc, L. Wirth đã thi t k và cài đặt ngôn ng ALGOL W là một bi n thể c a ALGOL 60. Sau này, L. Wirth ti p t c hoàn thi n để tr thành ngôn ng l p trình Pascal. Đây là ngôn ng l p trình gi n d , sáng s a v cú pháp, d minh họa nh ng v n đ ph c t p c a l p trình hi n đ i và đ c coi là một chu n mực trong gi ng d y l p trình. Nĕm 1978, Brian Barninghan cùng Denit Ritche thi t k ngôn ng l p trình C với t i thiểu các c u trúc l nh và hàm khá phù h p với t duy và tâm lý c a c a ng i l p trình. Đ ng th i, hai tác gi đã phát hành phiên b n h đi u hành UNIX vi t ch y u bằng ngôn ng C, khẳng đ nh thêm uy th c a C trong l p trình h th ng. 1.2. C U TRÚC LỆNH, LỆNH CÓ C U TRÚC, C U TRÚC D LIỆU 1.2.1. C u trúc l nh (c u trúc đi u khi n) Mỗi ch ng trình máy tính v b n ch t là một b n mã hoá thu t toán. Thu t toán đ c coi là dãy h u h n các thao tác s c p trên t p đ i t ng vào (Input) nhằm thu đ c k t qu ra (output). Các thao tác trong một ngôn ng l p trình c thể đ c đi u khiển b i các l nh hay các c u trúc đi u khiển, còn các đ i t ng ch u thao tác thì đ c mô t và biểu di n thông qua các c u trúc d li u. Trong các ngôn ng l p trình c u trúc, nh ng c u trúc l nh sau đ c s d ng để xây dựng ch ng trình. Dĩ nhiên, chúng ta s không bàn tới c u trúc nh y không đi u ki n goto mặc dù ngôn ng l p trình c u trúc nào cũng trang b c u trúc l nh goto. C u trúc r nhánh d ng đ y đ C u trúc tu n tự câu l A; nh GOTO. If (E) A; A S Đ Else B; B B B; Sau khi thực hi n l nh A thì thực hi n l nh B A N u biểu th c E có giá tr đúng (khác 0) thì thực hi n A; N u E sai thì thực hi n B; Hình 1.1: C u trúc tu n tự và c u trúc r nhánh d ng đ y đ 5 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc C u trúc lặp với đi u ki n tr ớc C u trúc lặp với đi u ki n sau While (E) A; do A E S Đ A; A S E Đ while (E); Trong khi biểu th c E còn có giá tr đúng thì thực hi n A; Thực hi n A cho tới khi nào E v n còn đúng; C u trúc lặp FOR For (E1; E2;E3) E1 A; E3 E2 S A Đ Hình 1.2. Các c u trúc l p A, B : ký hi u cho các câu l nh đ n hoặc l nh h p thành. Mỗi l nh đ n lẻ đ c gọi là một l nh đ n, l nh h p thành là l nh hay c u trúc l nh đ c ghép l i với nhau theo qui đ nh c a ngôn ng , trong Pascal là t p l nh hay c u trúc l nh đ c bao trong thân c a begin . . . end; trong C là t p các l nh hay c u trúc l nh đ c bao trong hai ký hi u { ... }. E, E1, E2, E3 là các biểu th c s học hoặc logic. Một s ngôn ng l p trình coi giá tr c a biểu th c logic hoặc đúng (TRUE) hoặc sai (FALSE), một s ngôn ng l p trình khác nh C coi giá tr c a biểu th c logic là đúng n u nó có giá tr khác 0, ng c l i biểu th c logic có giá tr sai. C n l u ý rằng, một ch ng trình đ c thể hi n bằng các c u trúc đi u khiển l nh : tu n tự, tuyển chọn if..else, switch . . case .. default, lặp với đi u ki n tr ớc while , lặp với đi u ki n sau do . . while, vòng lặp for bao gi cũng chuyển đ c v một ch ng trình, ch s d ng t i thiểu hai c u trúc l nh là tu n tự và lặp với đi u ki n tr ớc while. Ph ng pháp l p trình này còn đ c gọi là ph ng pháp l p trình h n ch . 6 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc 1.2.2. L nh có c u trúc L nh có c u trúc là l nh cho phép ch a các c u trúc đi u khiển trong nó. Khi tìm hiểu một c u trúc đi u khiển c n xác đ nh rõ v trí đ c phép đặt một c u trúc đi u khiển trong nó, cũng nh nó là một ph n c a c u trúc đi u khiển nào. Đi u này t ng nh r t t m th ng nh ng có ý nghĩa h t s c quan trọng trong khi xây dựng và kiểm tra lỗi có thể x y ra trong ch ng trình. Nguyên t c vi t ch ng trình theo c u trúc: C u trúc con ph i đ c vi t lọt trong c u trúc cha, điểm vào và điểm ra c a mỗi c u trúc ph i nằm trên cùng một hàng dọc. Ví d sau s minh họa cho nguyên t c vi t ch ng trình: if (E) while (E1) A; else do B; while(E2); Trong ví d trên, while (E1) A; là c u trúc con nằm trong thân c a c u trúc cha là if (E) ; còn do B while(E2); là c u trúc con trong thân c a else. Do v y, câu l nh while(E1); do . . . while(E2) có cùng c p với nhau nên nó ph i nằm trên cùng một cột, t ng tự nh v y với A, B và if với else. 1.2.3. C u trúc d li u Các ngôn ng l p trình c u trúc nói chung đ u gi ng nhau v c u trúc l nh và c u trúc d li u. Điểm khác nhau duy nh t gi a các ngôn ng l p trình c u trúc là ph ng pháp đặt tên, cách khai báo, cú pháp câu l nh và t p các phép toán đ c phép thực hi n trên các c u trúc d li u c thể. N m b t đ c nguyên t c này, chúng ta s d dàng chuyển đổi cách thể hi n ch ng trình từ ngôn ng l p trình này sang ngôn ng l p trình khác một cánh nhanh chóng mà không t n quá nhi u th i gian cho vi c học t p ngôn ng l p trình. Thông th ng, các c u trúc d li u đ c phân thành hai lo i: c u trúc d li u có kiểu c b n (Base type) và c u trúc d li u có kiểu do ng i dùng đ nh nghĩa (User type) hay còn gọi là kiểu d li u có c u trúc. Kiểu d li u c b n bao g m: Kiểu kí tự (char), kiểu s nguyên có d u (signed int), kiểu s nguyên không d u (unsigned int), kiểu s nguyên dài có d u (signed long), kiểu s nguyên dài không d u (unsigned long ), kiểu s thực (float) và kiểu s thực có độ chính xác g p đôi (double). Kiểu d li u do ng i dùng đ nh nghĩa bao g m kiểu xâu kí tự (string), kiểu m ng (array), kiểu t p h p (union), kiểu c u trúc (struct), kiểu file, kiểu con tr (pointer) và các kiểu d li u đ c đ nh nghĩa mới hoàn toàn nh kiểu danh sách móc n i (link list), kiểu cây (tree) . . . Kích c c a kiểu c b n đ ng nghĩa với mi n xác đ nh c a kiểu với biểu di n nh phân c a nó, và ph thuộc vào từng h th ng máy tính c thể. Để xác đ nh kích c c a kiểu nên dùng toán t sizeof( type). Ch ng trình sau s li t kê kích c c a các kiểu c b n. 7 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc Ví d 1.1. Kiểm tra kích c c a kiểu. #include <stdio.h> #include <conio.h> #include <stdlib.h> #include <io.h> void main(void) { printf(“\n Kích c kiểu kí tự:%d”, sizeof(char)); printf(“\n Kích c kiểu kí tự không d u:%d”, sizeof(unsigned char)); printf(“\n Kích c kiểu s nguyên không d u:%d”, sizeof(unsigned int)); printf(“\n Kích c kiểu s nguyên có d u:%d”, sizeof(signed int)); printf(“\n Kích c kiểu s nguyên dài không d u:%d”, sizeof(unsigned long )); printf(“\n Kích c kiểu s nguyên dài có d u:%d”, sizeof(signed long )); printf(“\n Kích c kiểu s thực có độ chính xác đ n:%d”, sizeof(float )); printf(“\n Kích c kiểu s thực có độ chính xác kép:%d”, sizeof(double )); getch(); } Kích c c a các kiểu d li u do ng i dùng đ nh nghĩa là tổng kích c c a mỗi kiểu thành viên trong nó. Chúng ta cũng v n dùng toán t sizeof(tên kiểu) để xác đ nh độ lớn tính theo byte c a các kiểu d li u này. Một điểm đặc bi t chú ý trong khi l p trình trên các c u trúc d li u là c u trúc d li u nào thì ph i kèm theo phép toán đó, vì một bi n đ c gọi là thuộc kiểu d li u nào đó n u nh nó nh n một giá tr từ mi n xác đ nh c a kiểu và các phép toán trên kiểu d li u đó. 1.3. NGUYÊN LÝ T I THI U Hãy b t đ u từ một t p nguyên t c và t i thiểu các ph ng ti n là các c u trúc l nh, kiểu d li u cùng các phép toán trên nó và thực hi n vi t ch ng trình. Sau khi n m ch c nh ng công c vòng đ u mới đặt v n đ m rộng sang h th ng th vi n ti n ích c a ngôn ng . Khi làm quen với một ngôn ng l p trình nào đó, không nh t thi t ph i l thuộc quá nhi u vào h th ng th vi n hàm c a ngôn ng , mà đi u quan trọng h n là tr ớc một bài toán c thể, chúng ta s d ng ngôn ng để gi i quy t nó th nào, và ph ng án t t nh t là l p trình bằng chính h th ng th vi n hàm c a riêng mình. Do v y, đ i với các ngôn ng l p trình, chúng ta ch c n n m v ng một s các công c t i thiểu nh sau: 1.3.1. T p các phép toán Tập các phép toán số học: + (cộng); - (trừ); * (nhân); % (l y ph n d ); / (chia). Tập các phép toán số học mở rộng: ++a --a Ù a = a +1; // tĕng giá tr bi n nguyên a lên một đ n v ; Ù a = a-1; //gi m giá tr bi n nguyên a một đ n v ; a+= n Ù a = a+n; // tĕng giá tr bi n nguyên a lên n đ n v ; 8 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc a-=n Ù a = a - n; // gi m giá tr bi n nguyên a n đ n v ); a/=n Ù a=a/n;// l y giá tr bi n a chia cho n; a%=n Ù a = a%n; // l y giá tr bi n a modul với n; a*=n Ù a = a*n; // l y giá tr bi n a nhân với n; Tập các phép toán so sánh: >, <, >=, <=, ==, != ( lớn h n, nh h n, lớn h n hoặc bằng, nh h n hoặc bằng, đúng bằng, khác). Qui t c vi t đ c thể hi n nh sau: if ( a>b) { . . } // n u a lớn h n b if ( a<b) { . . } // n u a nh h n b if ( a>=b) { . . } // n u a lớn h n hoặc bằng b if ( a<=b) { . . } // n u a nh h n hoặc bằng b if ( a==b) { . . } // n u a đúng bằng b if ( a!=b) { . . } // n u a khác b Tập các phép toán logic: &&, ||, ! (và, hoặc, ph đ nh) &&: Phép và logic ch cho giá tr đúng khi hai biểu th c tham gia đ u có giá tr đúng (giá tr đúng c a một biểu th c trong C đ c hiểu là biểu th c có giá tr khác 0). ||: Phép hoặc logic ch cho giá tr sai khi c hai biểu th c tham gia đ u có giá tr sai. !: Phép ph đ nh cho giá tr đúng n u biểu th c có giá tr sai và ng c l i cho giá tr sai khi biểu th c có giá tr đúng. Ng nghĩa c a các phép toán đ c minh họa thông qua các câu l nh sau: int a =3, b =5; if ( (a !=0) && (b!=0) ) // n u a khác 0 và b khác 0 if ((a!=0) || (b!=0)) // n u a khác 0 hoặc b khác 0 if ( !a ) // ph đ nh a khác 0 if (a==b) // n u a đúng bằng b Các toán tử thao tác bít (không s d ng cho float và double) & : Phép hội các bít. | : Phép tuyển các bít. ^ : Phép tuyển các bít có lo i trừ. << : Phép d ch trái (d ch sang trái n bít giá tr 0) >> : Phép d ch ph i (d ch sang ph i n bít có giá tr 0) ~ : Phép l y ph n bù. 9 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc Ví d 1.2: Vi t ch ng trình kiểm tra các toán t thao tác bít. #include <stdio.h> #include <conio.h> #include <stdlib.h> #include <io.h> void main(void){ unsigned int a=3, b=5, c; clrscr(); c = a & b; printf(“\n c = a & b=%d”,c); c = a | b; printf(“\n c = a | b=%d”,c); c = a ^ b; printf(“\n c = a ^ b=%d”,c); c = ~a; printf(“\n c = ~a =%d”,c); c = a << b; printf(“\n c = a << b=%d”,c); c = a >>b; printf(“\n c = a >> b=%d”,c); getch(); } Toán tử chuyển đổi kiểu: Ta có thể dùng toán t chuyển đổi kiểu để nh n đ c k t qu tính toán nh mong mu n. Qui t c chuyển đổi kiểu đ c thực hi n theo qui t c: (kiểu) bi n. Ví d 1.3: Tính giá tr phép chia hai s nguyên a và b. #include <stdio.h> #include <conio.h> #include <stdlib.h> #include <io.h> void main(voi)( int a=3, b=5; float c; c= (float) a / (float) b; printf(“\n th ng c = a / b =%6.2f”, c); getch(); } Th tự u tiên các phép toán : Khi vi t một biểu th c, chúng ta c n l u ý tới th tự u tiên tính toán các phép toán, các b ng tổng h p sau đây ph n ánh tr t tự u tiên tính toán c a các phép toán s học và phép toán so sánh. B ng tổng h p th tự u tiên tính toán các phép toán s học và so sánh TÊN TOÁN T CHI U TÍNH TOÁN ( ), [] , -> L -> R - , ++, -- , ! , ~ , sizeof() R -> L * , /, % L -> R 10 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc +, - L -> R >>, << L -> R <, <=, > , >=, L -> R == != L -> R & L -> R ^ L -> R | L -> R && L -> R || L -> R ?: R -> L =, +=, -=, *=, /=, %=, &=, ^=, |=, <<=, >>= R -> L 1.3.2. T p các l nh vào ra c b n Nh p d li u từ bàn phím: scanf(“format_string, . . .”, &parameter . . .); Nh p d li u từ t p: fscanf( file_pointer,”format_string, . . .”, &parameter, . . .); Nh n một ký tự từ bàn phím: getch(); getchar(); Nh n một ký tự từ file: fgetc(file_pointer, character_name); Nh p một string từ bàn phím: gets(string_name); Nh n một string từ file text : fgets(string_name, number_character, file_pointer); Xu t d li u ra màn hình: printf(“format_string . . .”, parameter . . .); Xu t d li u ra file : fprintf(file_pointer, “format_string . . .”, parameter. . .); Xu t một ký tự ra màn hình: putch(character_name); Xu t một ký tự ra file: fputc(file_pointer, character_name); Xu t một string ra màn hình: puts(const_string_name); Xu t một string ra file: fputs(file_pointer, const_string_name); 1.3.3. Thao tác trên các ki u d li u có c u trúc T p thao tác trên string: char *strchr(const char *s, int c) : tìm ký tự c đ u tiên xu t hi n trong xâu s; char *stpcpy(char *dest, const char *src) : copy xâu scr vào dest; 11 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc int strcmp(const char *s1, const char *s2) : so sánh hai xâu s1 và s2 theo th tự từ điển, n u s1 < s2 thì hàm tr l i giá tr nh h n 0. N u s1>s2 hàm tr l i giá tr d ng. N u s1==s2 hàm tr l i giá tr 0. char *strcat(char *dest, const char *src) : thêm xâu scr vào sau xâu dest. char *strlwr(char *s) : chuyển xâu s từ ký tự in hoa thành ký tự in th char *strupr(char *s): chuyển xâu s từ ký tự th char *strrev(char *s): đ o ng ng. ng hoa thành ký tự in hoa. c xâu s. char *strstr(const char *s1, const char *s2): tìm v trí đ u tiên c a xâu s2 trong xâu s1. int strlen(char *s): cho độ dài c a xâu ký tự s. T p thao tác trên con tr : Thao tác l y đ a ch c a bi n: & parameter_name; Thao tác l y nội dung bi n (bi n có kiểu c b n): *pointer_name; Thao tác tr tới ph n t ti p theo: ++pointer_name; Thao tác tr tới ph n t th n kể từ v trí hi n t i: pointer_name = pointer_name +n; Thao tác tr tới ph n t sau con tr kể từ v trí hi n t i: --pointer_name; Thao tác tr tới ph n t sau n ph n t kể từ v trí hi n t i: Pointer_name = pointer_name - n; Thao tác c p phát bộ nhớ cho con tr : void *malloc(size_t size); void *calloc(size_t nitems, size_t size); Thao tác c p phát l i bộ nhớ cho con tr : void *realloc(void *block, size_t size); Thao tác gi i phóng bộ nhớ cho con tr : void free(void *block); T p thao tác trên c u trúc: Đ nh nghĩa c u trúc: struct struct_name{ type_1 parameter_name_1; type_2 parameter_name_2; ...................... type_k parameter_name_k; } struct_parameter_name; Phép truy nh p tới thành ph n c u trúc: 12 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc struct_parameter_name.parameter_name. Phép gán hai c u trúc cùng kiểu: struct_parameter_name1 = struct_parameter_name2; Phép tham tr tới thành ph n c a con tr c u trúc: pointer_struct_parameter_name -> struct_parameter_name. T p thao tác trên file: Khai báo con tr file: FILE * file_pointer; Thao tác m file theo mode: FILE *fopen(const char *filename,const char *mode); Thao tác đóng file: int fclose(FILE *stream); Thao tác đọc từng dòng trong file: char *fgets(char *s, int n, FILE *stream); Thao tác đọc từng kh i trong file: size_t fread(void *ptr, size_t size,size_t n, FILE *stream); Thao tác ghi từng dòng vào file: int fputs(const char *s, FILE *stream); Thao tác ghi từng kh i vào file: size_t fwrite(const void *ptr, size_t size, size_t n, FILE *stream); Thao tác kiểm tra sự t n t i c a file: int access(const char *filename, int amode); Thao tác đổi tên file: int rename(const char *oldname,const char *newname); Thao tác lo i b file: int unlink(const char *filename); 1.4. NGUYÊN LÝ Đ A PHƯƠNG ƒ ƒ ƒ ƒ ƒ ph Các biến địa ph ơng trong hàm, thủ tục hoặc chu trình cho dù có trùng tên với biến toàn cục thì khi xử lý biến đó trong hàm hoặc thủ tục vẫn không làm thay đổi giá trị của biến toàn cục. Tên của các biến trong đối của hàm hoặc thủ tục đều là hình thức. Mọi biến hình thức truyền theo trị cho hàm hoặc thủ tục đều là các biến địa ph ơng. Các biến khai báo bên trong các ch ơng trình con, hàm hoặc thủ tục đều là biến địa ph ơng. Khi phải sử dụng biến phụ nên dùng biến địa ph ơng và hạn chế tối đa việc sử dụng biến toàn cục để tránh xảy ra các hiệu ứng phụ. Ví d hoán đổi giá tr c a hai s a và b sau đây s minh họa rõ h n v nguyên lý đ a ng. Ví d 1.4. Hoán đổi giá tr c a hai bi n a và b. 13 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc #include <stdio.h> #include <conio.h> #include <stdlib.h> #include <io.h> int a, b; // khai báo a, b là hai bi n toàn c c. void Swap(void) { int a,b, temp; // khai báo a, b là hai bi n đ a ph ng a= 3; b=5; // gán giá tr cho a và b temp=a; a=b; b=temp;// đổi giá tr c a a và b printf(“\n K t qu thực hi n trong th t c a=%5d b=%5d:,a,b); } void main(void) { a=1; b=8; // kh i đ u giá tr cho bi n toàn c c a, b. Swap(); printf(“\n K t qu sau khi thực hi n th t c a =%5d b=%5d”,a,b); getch(); } K t qu thực hi n ch ng trình: Kết quả thực hiện trong thủ tục a = 5 b=3 Kết quả sau khi thực hiện thủ tục a = 1 b =8 Trong ví d trên a, b là hai bi n toàn c c, hai bi n a, b trong th t c Swap là hai bi n c c bộ. Các thao tác trong th t c Swap gán cho a giá tr 3 và b giá tr 5 sau đó thực hi n đổi giá tr c a a =5 và b =3 là công vi c x lý nội bộ c a th t c mà không làm thay đổi giá tr c a bi n toàn c c c a a, b sau thi thực hi n xong th t c Swap. Do v y, k t qu sau khi thực hi n Swap a = 1, b =8; Đi u đó ch ng t trong th t c Swap ch a bao gi s d ng tới hai bi n toàn c c a và b. Tuy nhiên, trong ví d sau, th t c Swap l i làm thay đổi giá tr c a bi n toàn c c a và b vì nó thao tác trực ti p trên bi n toàn c c. Ví d 1.5. Đổi giá tr c a hai bi n a và b #include <stdio.h> #include <conio.h> #include <stdlib.h> #include <io.h> int a, b; // khai báo a, b là hai bi n toàn c c. void Swap(void) { int temp; // khai báo a, b là hai bi n đ a ph ng a= 3; b=5; // gán giá tr cho a và b temp=a; a=b; b=temp;// đổi giá tr c a a và b printf(“\n K t qu thực hi n trong th t c a=%5d b=%5d:,a,b); } void main(void) { 14 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc a=1; b=8; // kh i đ u giá tr cho bi n toàn c c a, b. Swap(); printf(“\n K t qu sau khi thực hi n th t c a =%5d b=%5d”,a,b); getch(); } K t qu thực hi n ch ng trình: Kết quả thực hiện trong thủ tục a = 8 b=1 Kết quả sau khi thực hiện thủ tục a = 1 b =8 1.5. NGUYÊN LÝ NH T QUÁN ƒ Dữ liệu thế nào thì phải thao tác thế ấy. Cần sớm phát hiện những mâu thuẫn giữa cấu trúc dữ liệu và thao tác để kịp thời khắc phục. Nh chúng ta đã bi t, kiểu là một tên ch t p các đ i t ng thuộc mi n xác đ nh cùng với nh ng thao tác trên nó. Một bi n khi đ nh nghĩa bao gi cũng thuộc một kiểu xác đ nh nào đó hoặc là kiểu c b n hoặc kiểu do ng i dùng đ nh nghĩa. Thao tác với bi n ph thuộc vào nh ng thao tác đ c phép c a kiểu. Hai kiểu khác nhau đ c phân bi t b i tên, mi n xác đ nh và các phép toán trên kiểu d li u. Tuy nhiên, trên thực t có nhi u lỗi nh p nhằng gi a phép toán và c u trúc d li u mà chúng ta c n hiểu rõ. Đ i với kiểu ký tự, v nguyên t c chúng ta không đ c phép thực hi n các phép toán s học trên nó, nh ng ngôn ng C luôn đ ng nh t gi a ký tự với s nguyên có độ lớn 1 byte. Do v y, nh ng phép toán s học trên các ký tự thực ch t là nh ng phép toán s học trên các s nguyên. Chẳng h n, nh ng thao tác nh trong khai báo d ới đây là đ c phép: char x1=’A’, x2 =’z’; x1 = (x1 + 100) % 255; x2 = (x2-x1) %255; Mặc dù x1, x2 đ c khai báo là hai bi n kiểu char, nh ng trong thao tác x1 = (x1 + 100) % 255; x2 = (x2 +x1) %255; ch ng trình d ch s tự động chuyển đổi x1 thành mã c a ký tự ‘A’ là 65, x2 thành mã ký tự ‘z’ là 122 để thực hi n phép toán. K t qu nh n đ c x1 là một ký tự có mã là (65+100)%255 = 165; x2 là ký tự có mã là 32 ng với mã c a ký tự space. Chúng ta có thể thực hi n đ c các phép toán s học trên kiểu int, long, float, double. Nh ng đ i với int và long, chúng ta c n đặc bi t chú ý phép chia hai s nguyên cho ta một s nguyên, tích hai s nguyên cho ta một s nguyên, tổng hai s nguyên cho ta một s nguyên mặc dù th ng hai s nguyên là một s thực, tích hai s nguyên hoặc tổng hai s nguyên có thể là một s long int. Do v y, mu n nh n đ c k t qu đúng, chúng ta c n ph i chuyển đổi các bi n thuộc cùng một kiểu tr ớc khi thực hi n phép toán. Ng c l i, ta không 15 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc thể l y modul c a hai s thực hoặc thực hi n các thao tác d ch chuyển bít trên nó, vì nh ng thao tác đó không nằm trong đ nh nghĩa c a kiểu. Đi u t ng tự cũng x y ra với các string. Trong Pascal, phép toán so sánh hai string hoặc gán trực ti p hai Record cùng kiểu với nhau là đ c phép, ví d : Str1>Str2, Str1 := Str2; Nh ng trong C thì các phép toán trên l i không đ c đ nh nghĩa, n u mu n thực hi n nó, chúng ta ch có cách đ nh nghĩa l i hoặc thực hi n nó thông qua các l i gọi hàm. 1.6. NGUYÊN LÝ AN TOÀN ƒ ƒ Lỗi nặng nhất nằm ở mức cao nhất (mức ý đồ thiết kế) và ở mức thấp nhất thủ tục phải chịu tải lớn nhất. Mọi lỗi, dù là nhỏ nhất cũng phải đ ợc phát hiện ở một b ớc nào đó của ch ơng trình. Quá trình kiểm tra và phát hiện lỗi phải đ ợc thực hiện tr ớc khi lỗi đó hoành hành. Các lo i lỗi th ng x y ra trong khi vi t ch ng trình có thể đ c tổng k t l i nh sau: L i đ c thông báo bởi t khoá error (l i cú pháp): lo i lỗi này th ng x y ra trong khi so n th o ch ng trình, chúng ta có thể vi t sai các từ khoá ví d thay vì vi t là int chúng ta so n th o sai thành Int (lỗi ch in th ng thành in hoa), hoặc vi t sai cú pháp các biểu th c nh thi u các d u ngoặc đ n, ngoặc kép hoặc d u ch m ph y khi k t thúc một l nh, hoặc ch a khai báo nguyên m u cho hàm . L i đ c thông báo bởi t khoá Warning (l i c nh báo): lỗi này th ng x y ra khi ta khai báo bi n trong ch ng trình nh ng l i không s d ng tới chúng, hoặc lỗi trong các biểu th c kiểm tra khi bi n đ c kiểm tra không xác đ nh đ c giá tr c a nó, hoặc lỗi do th tự u tiên các phép toán trong biểu th c. Hai lo i lỗi error và warning đ c thông báo ngay khi d ch ch ng trình thành file *.OBJ. Quá trình liên k t (linker) các file *.OBJ để t o nên file ch ng trình mã máy *.EXE ch đ c ti p t c khi chúng ta hi u đính và kh b mọi lỗi error. L i x y ra trong quá trình liên k t: lỗi này th ng xu t hi n khi ta s d ng tới các l i gọi hàm, nh ng nh ng hàm đó mới ch t n t i d ới d ng nguyên m u (function prototype) mà ch a đ c mô t chi ti t các hàm, hoặc nh ng l i hàm gọi ch a đúng với tên c a nó. Lỗi này đ c kh c ph c khi ta bổ sung đo n ch ng trình con mô t chi ti t cho hàm hoặc s a đổi l i nh ng l i gọi hàm t ng ng. Ta quan ni m, lỗi cú pháp (error), lỗi c nh báo (warning) và lỗi liên k t (linker) là lỗi t m th ng vì nh ng lỗi này đã đ c Compiler c a các ngôn ng l p trình phát hi n đ c. Để kh c ph c các lỗi lo i này, chúng ta ch c n ph i đọc và hiểu đ c nh ng thông báo lỗi th ng đ c vi t bằng ti ng Anh. Cũng c n ph i l u ý rằng, do m c độ ph c t p c a ch ng trình d ch nên không ph i lỗi nào cũng đ c ch ra một cách t ng minh và chính xác hoàn toàn t i n i xu t hi n lỗi. 16 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc Lo i lỗi cu i cùng mà các compiler không thể phát hi n nổi đó là lỗi do chính l p trình viên gây nên trong khi thi t k ch ng trình và x lý d li u. Nh ng lỗi này không đ c compiler thông báo mà nó ph i tr giá bằng quá trình tự test hoặc ch ng minh đ c tính đúng đ n c a ch ng trình. Lỗi có thể nằm chính ý đ thi t k , hoặc lỗi do không l ng tr ớc đ c tính ch t c a mỗi lo i thông tin vào. Ví d sau minh họa cho lỗi th ng x y ra thuộc lo i này. Ví d 1.6. Tính tổng hai đa th c A b c n, đa th c B b c m. #include <stdio.h> #include <conio.h> #include <stdlib.h> #define MAX 100 typedef float dathuc[MAX]; void In(dathuc A, int n, char c){ int i; printf("\n Da thuc %c:", c); for(i=0;i<n; i++) printf("%6.2f", A[i]); } void Init( dathuc A, int *n, dathuc B, int *m){ int i;float temp; printf("\n Nhap n="); scanf("%d", n);*n=*n+1; printf("\n Nhap m="); scanf("%d",m); *m=*m+1; printf("\n Nhap he so da thuc A:"); for(i=0; i<*n;i++){ printf("\n A[%d]=", i); scanf("%f", &temp); A[i]=temp; } printf("\n Nhap he so da thuc B:"); for(i=0; i<*m;i++){ printf("\n B[%d]=",i); scanf("%f", &temp); B[i] = temp; } In(A,*n,'A'); In(B,*m,'B'); } void Tong(dathuc A, int n, dathuc B, int m, dathuc C){ int i, k; if (n>= m ){ k =n; for(i=0; i<m; i++) C[i] = A[i]+B[i]; for (i=m; i<n; i++) 17 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc C[i]=A[i]; In(C,k,'C'); } else { k = m; for(i=0; i<n; i++) C[i] = A[i]+B[i]; for (i=n; i<m; i++) C[i]=B[i]; In(C,k,'C'); } } void main(void){ dathuc A, B, C; int n, m; Init(A, &n, B, &m); Tong(A, n, B, m, C); } Trong ví d trên, chúng ta s d ng đ nh nghĩa MAX =100 để gi i quy t bài toán. MAX đ c hiểu là b c c a đa th c lớn nh t mà chúng ta c n x lý. Nh v y, b n thân vi c đ nh nghĩa MAX đã h n ch tới ph m vi bài toán, h n ch đó cũng có thể xu t phát từ ý đ thi t k . Do v y, n u ng i s d ng nh p n>MAX thì ch ng trình s gặp lỗi. N u chúng ta kh c ph c bằng cách đ nh nghĩa BAC đ lớn thì trong tr ng h p x lý các đa th c có b c n nh s gây nên hi n t ng lãng phí bộ nhớ, và trong nhi u tr ng h p không đ bộ nhớ để đ nh nghĩa đa th c. Gi i pháp kh c ph c các lỗi lo i này là chúng ta s d ng con tr thay cho các hằng, k thu t này s đ c th o lu n k trong Ch ng 2. 1.7. PHƯƠNG PHÁP TOP-DOWN ƒ Quá trình phân tích bài toán đ ợc thực hiện từ trên xuống d ới. Từ vấn đề chung nhất đến vấn đề cụ thể nhất. Từ mức trừu t ợng mang tính chất tổng quan tới mức đơn giản nhất là đơn vị ch ơng trình. Một trong nh ng nguyên lý quan trọng c a l p trình c u trúc là ph ng pháp phân tích từ trên xu ng (Top - Down) với quan điểm “th y cây không bằng th y rừng”, ph i đ ng cao h n để quan sát tổng thể khu rừng ch không thể đ ng trong rừng quan sát chính nó. Quá trình phân rã bài toán đ c thực hi n theo từng m c khác nhau. M c th p nh t đ c gọi là m c tổng quan (level 0), m c tổng quan cho phép ta nhìn tổng thể h th ng thông qua các ch c nĕng c a nó, nói cách khác m c 0 s tr l i thay cho câu h i “H th ng có thể thực hi n đ c nh ng gì ?”. M c ti p theo là m c các ch c nĕng chính. m c này, nh ng ch c nĕng c thể đ c mô t . Một h th ng có thể đ c phân tích thành nhi u m c khác nhau, m c th p đ c phép s d ng các d ch v c a m c cao. Quá trình phân tích ti p 18 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc t c phân rã h th ng theo từng ch c nĕng ph cho tới khi nào nh n đ c m c các đ n thể ( UNIT, Function, Procedure), khi đó chúng ta ti n hành cài đặt h th ng. Chúng ta s làm rõ h n từng m c c a quá trình Top-Down thông qua bài toán sau: Bài toán: Cho hai s nguyên có biểu di n nh phân là a=(a1, a2, . . ., an), b = (b1, b2, .., bn); ai, bi =0, 1, i=1, 2, . . .n. Hãy xây dựng t p các thao tác trên hai s nguyên đó. M c tổng quan (level 0): Hình dung toàn bộ nh ng thao tác trên hai s nguyên a=(a1, a2, . . ., an), b=(b1,b2,..,bn) với đ y đ nh ng ch c nĕng chính c a nó. Gi s nh ng thao tác đó bao g m: F1- Chuyển đổi a, b thành các số nhị phân; F2- Tính tổng hai số nguyên: a + b; F3- Tính hiệu hai số nguyên: a - b; F4 Tính tích hai số nguyên: a *b; F5- Th ơng hai số nguyên : a/b; F6- Phần d hai số nguyên: a % b; ớc số chung lớn nhất của hai số nguyên. F7- M c 1. M c các ch c nĕng chính: mỗi ch c nĕng c n mô t đ y đ thông tin vào (Input), thông tin ra (Output), khuôn d ng (Format) và các hành động (Actions). Ch c nĕng F1: Chuyển đổi a, b thành các s h nh phân Input : a : integer; Output : a=(a1, a2, . . ., an)b; (*khai triển c s b b t kỳ*) Format : Binary(a); Actions { Q = n; k=0; While ( Q≠ 0 ) { ak = q mod b; q = q div b; k = k +1; } < Khai triển c s b c a a là (ak-1, ak-2, . ., a1, a0) >; } Ch c nĕng F2: Tính tổng hai s nguyên a, b. Input: a=(a1, a2, . . ., an), b = (b1, b2, .., bn); Output: 19 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc c = a + b; Format: Addition(a, b); Actions { c = 0; for (j = 0; j< n; j++){ d = (aj + bj + c) div 2; sj = aj + bj + c - 2d; c = d; } sn = c; < Khai triển nh phân c a tổng là (snsn-1. . .s1,s0)2> } Ch c nĕng F3: Hi u hai s nguyên a, b. Input: a=(a1, a2, . . ., an), b = (b1, b2, .., bn); Output: c = a - b; Format: Subtraction(a, b); Actions { b = -b; c = Addition(a, b); return(c); } Ch c nĕng F4: Tích hai s nguyên a, b. Input: a=(a1, a2, . . ., an), b = (b1, b2, .., bn); Output: c = a * b; Format: Multual(a, b); Actions { For (j =0; j< n; j++){ If ( bj =1) cj = a<<j; Else cj = 0; } (* c0, c1, . . ., cn-1 là các tích riêng*) p=0; 20 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc for( j=0; j< n; j++) { p = Addition(p, cj); } return(p); } Ch c nĕng F5: Th ng hai s nguyên a, b. Input: a=(a1, a2, . . ., an), b = (b1, b2, .., bn); Output: c = a div b; Format: Division(a, b); Actions { c = 0; while ( a>= b ) { c = c +1; a = Subtraction(a, b); } return(c); } Ch c nĕng F6: Modul hai s nguyên a, b. Input: a=(a1, a2, . . ., an), b = (b1, b2, .., bn); Output: c = a mod b; Format: Modulation(a, b); Actions { while ( a>= b ) a = Subtraction(a, b); return(a); } Ch c nĕng F7: ớc s chung lớn nh t hai s nguyên a, b. Input: a=(a1, a2, . . ., an), b = (b1, b2, .., bn); Output: c = USCLN(a,b); Format: USCLN(a, b); Actions { 21 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc while ( a≠ b ) { if (a > b) a = Subtraction(a, b) else b = Subtraction(b,a); } return(a); } Để ý rằng, sau khi phân rã bài toán m c 1, chúng ta ch c n xây dựng hai phép toán cộng và phép tính nhân các s nh phân c a a, b. Vì hi u hai s a và b chính là tổng s c a (a,-b). T ng tự nh v y, tích hai s a và b đ c biểu di n bằng tổng c a một s l n phép nhân một bít nh phân c a với a. Phép chia và l y ph n d hai s a và b chính là phép trừ nhi u l n s a. Phép tìm USCLN cũng t ng tự nh v y. Đ i với các h th ng lớn, quá trình còn đ c mô t ti p t c cho tới khi nh n đ c m c đ n v ch ng trình. Trong ví d đ n gi n này, m c đ n v ch ng trình xu t hi n ngay t i m c 1 nên chúng ta không c n phân rã ti p n a mà dừng l i để cài đặt h th ng. 1.8. PHƯƠNG PHÁP BOTTOM-UP ƒ Đi từ cái riêng tới cái chung, từ các đối t ợng thành phần ở mức cao tới các đối t ợng thành phần ở mức thấp, từ mức đơn vị ch ơng trình tới mức tổng thể, từ những đơn vị đã biết lắp đặt thành những đơn vị mới. N u nh ph ng pháp Top-Down là ph ng pháp phân rã v n đ một cách có h th ng từ trên xu ng, đ c ng d ng ch y u cho quá trình phân tích và thi t h th ng, thì ph ng pháp Bottom- Up th ng đ c s d ng cho quá trình cài đặt h th ng. Trong ví d trên, chúng ta s không thể xây dựng đ c ch ng trình một cách hoàn ch nh n u nh ta ch a xây dựng đ c các hàm Binary(a), Addition(a,b), Subtraction(a,b), Multial(a,b), Division(a,b), Modulation(a,b), USCLN(a,b). Ch ng trình sau thể hi n quá trình cài đặt ch ng trình theo nguyên lý Botton-Up: #include <stdio.h> #include <math.h> #include <conio.h> #include <stdlib.h> #include <alloc.h> #include <dos.h> void Init(int *a, int *b){ printf("\n Nhap a=");scanf("%d", a); printf("\n Nhap b=");scanf("%d", b); } void Binary(int a){ int i, k=1; 22 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc for(i=15; i>=0; i--){ if ( a & (k<<i)) printf("%2d",1); else printf("%2d",0); } printf("\n");delay(500); } int bit(int a, int k){ int j=1; if (a & (j<<k)) return(1); return(0); } int Addition(int a, int b){ int du, d, s, j, c=0; du=0; for ( j=0; j<=15; j++){ d =( bit(a,j) + bit(b, j) +du)/2; s = bit(a,j)+bit(b,j)+ du - 2*d; c = c | (s <<j); du = d; } return(c); } int Multial(int a, int b) { int c,j, p=0; for(j=0; j<=15; j++){ c = bit(b, j); if (c==1) { c = a<<j; p= Addition(p, c); } else c=0; } return(p); } int Subtraction(int a, int b){ int c; b=-b; c=Addition(a,b);return(c); 23 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc } int Modul(int a, int b){ while(a>=b) a = Subtraction(a,b); return(a); } int Division(int a, int b){ int d=0; while(a>=b) { a= Subtraction(a,b); d++; } return(d); } int USCLN(int a, int b){ while(a!=b){ if(a>b) a = Subtraction(a,b); else b = Subtraction(b,a); } return(a); } void main(void){ int a, b, key, control=0; do { clrscr(); printf("\n Tap thao tac voi so nguyen"); printf("\n 1- Nhap hai so a,b"); printf("\n 2- So nhi phan cua a, b"); printf("\n 3- Tong hai so a,b"); printf("\n 4- Hieu hai so a,b"); printf("\n 5- Tich hai so a,b"); printf("\n 6- Thuong hai so a,b"); printf("\n 7- Phan du hai so a,b"); printf("\n 8- USCLN hai so a,b"); printf("\n 0- Tro ve"); key=getch(); switch(key){ case '1': Init(&a, &b); control=1; break; case '2': if (control){ Binary(a); Binary(b); 24 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc } break; case '3': if (control) printf("\n Tong a+b = %d", Addition(a, b)); break; case '4': if (control) printf("\n Hieu a-b =%d", Subtraction(a, b)); break; case '5': if(control) printf("\n Tich a*b =%d", Multial(a,b)); break; case '6': if(control) printf("\n Chia nguyen a div b=%d",Division(a,b)); break; case '7': if(control) printf("\n Phan du a mod b =%d", Modul(a,b)); break; case '8': if(control) printf("\n Uoc so chung lon nhat:%d",USCLN(a,b)); break; } delay(1000); } while(key!='0'); } 25 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc NH NG N I DUNG C N GHI NH 9 Một ngôn ng l p trình b t kỳ đ u dựa trên t p các c u trúc l nh đi u khiển (tu n tự, tuyển chọn & lặp), các c u trúc d li u (d li u kiểu c b n & d li u có c u trúc) cùng với các phép toán trên nó. 9 Khi mới b t đ u học l p trình, hãy l p trình từ t p t i thiểu các công c mà ngôn ng l p trình trang b (Nguyên lý t i thiểu). 9 Khi nào dùng bi n đ a ph ng, khi nào dùng bi n toàn c c là nội dung chính c a nguyên lý đ a ph ng. N m v ng nguyên lý này giúp cho ta s d ng cách truy n tham bi n và cách truy n tham tr cho hàm. 9 D li u kiểu nào thì phép toán đó là nội dung chính c a nguyên lý nh t quán. 9 Mọi lỗi dù nh nh t cũng ph i l ng tr ớc mỗi m c cài đặt c a ch ng trình. 9 Cách phân rã một v n đ lớn thành nh ng v n đ nh h n là nội dung chính c a nguyên lý Top-Down. 9 Cách cài đặt một v n đ đ c thực hi n từ m c đ n v ch ng trình (hàm, th t c) đ n m c l p ghép các đ n v ch ng trình thành h th ng hoàn thi n là nội dung chính c a nguyên lý Botton-Up. 26 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc BÀI T P CHƯƠNG 1 Bài 1. Tìm các nghi m nguyên d ng c a h ph ng trình: X + Y + Z = 100 5X + 3Y + Z/3 = 100 Bài 2. Cho s tự nhiên n. Hãy tìm t t c các bộ 3 các s tự nhiên a, b, c sao cho a2+b2 = c2 trong đó a<= b< =c <=n . Bài 3. Cho s tự nhiên n. Hãy tìm các s Fibonaci nh h n n. Trong đó các s Fibonai đ c đ nh nghĩa nh sau: U0 = 0; U1 = 1; Uk = Uk-1 + Uk-2 ; k=1, 2, . . . Bài 4. Ch ng minh rằng, với mọi s nguyên d ng N, 0<N<=39 thì N2 + N + 41 là một s nguyên t . Đi u khẳng đ nh trên không còn đúng với N>39. Bài 5. Cho s tự nhiên n. Hãy li t kê t t c các s nguyên t nh h n n. Bài 6. Cho s tự nhiên n. Hãy tìm t t c các s nguyên t nh h n n bằng ph sàng Estheven. ng pháp Bài 7. Cho s tự nhiên n. Dùng ph ng pháp sàng Estheven để tìm 4 s nguyên t bé h n n nằm trong cùng b c ch c ( ví d : 11, 13, 15, 17). Bài 8. Cho s tự nhiên n. Hãy li t kê t t c các cặp s p, 4p+1 đ u là s nguyên t nh h n n. Trong đó p cũng là s nguyên t nh h n n. Bài 9. Hãy li t kê t t c các s nguyên t có 5 ch s sao cho tổng s các ch s trong s nguyên t đó đúng bằng S cho tr ớc 1≤S≤45. Bài 10. Một s đ c gọi là s Mersen n u nó là s nguyên t đ c biểu di n d ới d ng 2P 1 trong đó P cũng là một s nguyên t . Cho s tự nhiên n, tìm t t c các s Mersen nh h n n. Bài 11. Cho s tự nhiên n. Hãy phân tích n thành tích các thừa s nguyên t . Ví d 12 = 2*2*3. Bài 12. Hai s tự nhiên a, b đ c gọi là “h u ngh ” n u tổng các ớc s thực sự c a a (kể c 1) bằng b và ng c l i. Cho hai s tự nhiên P , Q. Hãy tìm t t c các cặp s h u ngh trong kho ng [P, Q]. Bài 13. Cho s tự nhiên n. Hãy tìm t t c các s 1, 2, .., n sao cho các s trùng với ph n cu i bình ph ng chính nó (Ví d : 62 = 36, 252 = 625). Bài 14. Một s tự nhiên đ c gọi là s amstrong n u tổng các lũy thừa b c n c a các ch s c a nó bằng chính s đó. Trong đó n là s các ch s ( Ví d 153 = 13 + 23 + 33 ). Hãy tìm t t c các s amstrong g m 2, 3, 4 ch s . 27 Ch ơng 1: Đại c ơng về kỹ thuật lập trình cấu trúc Bài 15. Một s tự nhiên là Palindrom n u các ch s c a nó vi t theo th tự ng t o thành là chính s đó ( Ví d : 4884, 393). Hãy tìm: T t c các s tự nhiên nh h n 100 mà khi bình ph Palindrom. T t c các s Palindrom bé h n 100 mà khi bình ph Palindrom. c l i thì s ng lên thì cho một ng lên chúng cho một Bài 16. Để ghi nhãn cho nh ng chi c gh trong một gi ng đ ng, ng i ta s d ng 4 ký tự, ký tự đ u tiên là một ch cái in hoa, ba ký tự ti p theo là một s nguyên d ng không v t quá 100. H i bằng cách đó có nhi u nh t bao nhiêu chi c gh đ c đánh nhãn và đó là nh ng nhãn nào. Bài 17. Dự án đánh s đi n tho i c a Bang Florida đ c qui đ nh nh sau. Trong dự án đánh s đi n tho i g m 10 ch s đ c chia thành nhóm: mã vùng g m 3 ch s , nhóm mã chi nhánh g m 3 ch s và nhóm mã máy g m 4 ch s . Vì nh ng nguyên nhân k thu t nên có một s h n ch đ i với các ch s , gi s X biểu th các ch s nh n giá tr từ 0 . .9, Y là các ch s nh n giá tr hoặc 0 hoặc là 1, N là các ch s nh n giá tr từ 2 . .9. H i với cách đánh s d ng NYX NNX XXXX và NXX NXX XXXX s có bao nhiêu s đi n tho i khác nhau. In ra màn hình và ghi l i vào File DT.TXT s đi n tho i c a vùng có mã 200, mã chi nhánh 250 và s đi n tho i b t đ u là s 9. Mỗi s đi n tho i đ c ghi trên một dòng, mỗi dòng đ c ghi làm 3 ph n (Mã vùng, mã chi nhánh, s đi n tho i) mỗi ph n phân bi t với nhau b i một hoặc và d u tr ng. Bài 18. Cho File d li u TEXT.TXT đ c tổ ch c thành từng dòng, độ dài t i đa c a mỗi dòng là 80 kí tự. K thu t mã hoá tuy n tính là ph ng pháp bi n đổi mã c a các kí tự từ [a . .z], [A . .Z] thành một kí tự mới mà mã c a nó cộng thêm với một hằng s k cho tr ớc. Quá trình gi i mã đ c thực hi n ng c l i. Hãy vi t ch ng trình mô t ph ng pháp mã hoá và gi i mã tuy n tính File d li u TEXT.TXT. Quá trình mã hoá đ c ghi l i trong File MAHOA.TXT, quá trình gi i mã ghi l i trong File GIAIMA.TXT. Bài 19. Cho File d li u TEXT.TXT đ c tổ ch c thành từng dòng, độ dài t i đa c a mỗi dòng là 80 kí tự. K thu t mã hoá chẵn lẻ là ph ng pháp bi n đổi mã c a các kí tự [a . .z], [A . . Z]. Trong đó, n u kí tự có s các bít 1 là lẻ ta bổ xung thêm một bít có giá tr một vào bít s 7 c a kí tự để kí tự tr thành chẵn. Quá trình gi i mã đ c thực hi n ng c l i. Hãy vi t ch ng trình mô t k thu t mã hoá chẵn lẻ File d li u TEXT.TXT. Quá trình mã hoá đ c ghi l i trong File MAHOA.TXT, quá trình gi i mã ghi l i trong File GIAIMA.TXT. 28 Ch ơng 2: Duyệt và đệ qui CHƯƠNG 2: DUYỆT VÀ ĐỆ QUI Duy t toàn bộ là ph ng pháp phổ d ng nh t trong khi gi i quy t một bài toán trên máy tính. Các k thu t duy t cũng r t phong phú đa d ng n u nh ta chúng ta l i d ng đ c nh ng mẹo mực không mang tính tổng quát hoá nh ng h n ch đ c không gian tìm ki m l i gi i bài toán. Đ qui đ c s d ng nhi u trong các k thu t duy t. S d ng đ qui th ng cho ta một l i gi i t ng đ i ng n gọn, d hiểu nh ng n ch a trong nó nhi u bí n khó l ng. Tuy nhiên, nó v n đ c coi là một m u hình để vét c n t t c các kh nĕng c a bài toán. Các k thu t đ qui đ c đ c p đây bao g m: 9 Các đ nh nghĩa bằng đ qui, các c u trúc d li u đ nh nghĩa bằng đ qui & gi i thu t đ qui. 9 Thu t toán sinh k ti p gi i quy t bài toán duy t. 9 Thu t toán quay lui gi iquy t bài toán duy t. 9 Thu t toán nhánh c n gi iquy t bài toán duy t. B n đọc có thể tìm th y nhi u h n nh ng ng d ng và cài đặt c thể ph duy t trong tài li u [1]. ng pháp 2.1. Đ NH NGHƾA B NG ĐỆ QUI Trong thực t , chúng ta gặp r t nhi u đ i t ng mà khó có thể đ nh nghĩa nó một cách t ng minh, nh ng l i d dàng đ nh nghĩa đ i t ng qua chính nó. K thu t đ nh nghĩa đ i t ng qua chính nó đ c gọi là k thu t đ qui (recursion). Đ qui đ c s d ng rộng rãi trong khoa học máy tính và lý thuy t tính toán. Các gi i thu t đ qui đ u đ c xây dựng thông qua hai b ớc: b ớc phân tích và b ớc thay th ng c l i. Ví d 2.1. Để tính tổng S(n) = 1 + 2 + . . .+ n, chúng ta có thể thực hi n thông qua hai b ớc nh sau: B c phân tích: ƒ ƒ ƒ ƒ ƒ B Để tính toán đ c S(n) tr ớc tiên ta ph i tính toán tr ớc S(n-1) sau đó tính S(n) = S(n-1) +n. Để tính toán đ S(n-2) + n-1. c S(n-1), ta ph i tính toán tr ớc S(n-2) sau đó tính S(n-1) = ...................................................... Để tính toán đ c S(2), ta ph i tính toán tr ớc S(1) sau đó tính S(2) = S(1) + 2. Và cu i cùng S(1) chúng ta có ngay k t qu là 1. c thay th ng c l i: 29 Ch ơng 2: Duyệt và đệ qui Xu t phát từ S(1) thay th ng ƒ c l i chúng ta xác đ nh S(n): S(1) = 1 ƒ S(2) = S(1) + 2 ƒ S(3) = S(2) + 3 ƒ ............ ƒ S(n) = S(n - 1) + n Ví d 2.2. Đ nh nghĩa hàm bằng đ qui Hàm f(n) = n! D th y f(0) = 1. Vì (n+1) ! = 1 . 2.3 . . . n(n+1) = n! (n+1), nên ta có: f(n+1) = ( n+1) . f(n) với mọi n nguyên d ng. Ví d 2.3. T p h p đ nh nghĩa bằng đ qui đ Đ nh nghĩa đ qui t p các xâu : Gi s Σ* là t p các xâu trên bộ ch cái Σ. Khi đó Σ* c đ nh nghĩa bằng đ qui nh sau: λ ∈ Σ*, trong đó λ là xâu rỗng ƒ wx ∈ Σ* n u w ∈ Σ* và x ∈ Σ ƒ Ví d 2.4. C u trúc tự tr đ c đ nh nghĩa bằng đ qui struct node { int infor; struct node *left; struct node *right; }; 2.2. GI I THU T ĐỆ QUI Một thu t toán đ c gọi là đ qui n u nó gi i bài toán bằng cách rút gọn bài toán ban đ u thành bài toán t ng tự nh v y sau một s h u h n l n thực hi n. Trong mỗi l n thực hi n, d li u đ u vào ti m c n tới t p d li u dừng. Ví d : để gi i quy t bài toán tìm ớc s chung lớn nh t c a hai s nguyên d ng a và b với b> a, ta có thể rút gọn v bài toán tìm ớc s chung lớn nh t c a (b mod a) và a vì USCLN(b mod a, a) = USCLN(a,b). Dãy các rút gọn liên ti p có thể đ t đ c cho tới khi đ t đi u ki n dừng USCLN(0, a) = USCLN(a, b) = a. Sau đây là ví d v một s thu t toán đ qui thông d ng. Thu t toán 1: Tính an bằng gi i thu t đ qui, với mọi s thực a và s tự nhiên n. double power( float a, int n ){ if ( n ==0) 30 Ch ơng 2: Duyệt và đệ qui return(1); return(a *power(a,n-1)); } Thu t toán 2: Thu t toán đ qui tính ớc s chung lớn nh t c a hai s nguyên d a và b. ng int USCLN( int a, int b){ if (a == 0) return(b); return(USCLN( b % a, a)); } Thu t toán 3: Thu t toán đ qui tính n! long factorial( int n){ if (n ==1) return(1); return(n * factorial(n-1)); } Thu t toán 4: Thu t toán đ qui tính s fibonacci th n int fibonacci( int n) { if (n==0) return(0); else if (n ==1) return(1); return(fibonacci(n-1) + fibonacci(n-2)); } 2.3. THU T TOÁN SINH K TI P Ph ng pháp sinh k ti p dùng để gi i quy t bài toán li t kê c a lý thuy t tổ h p. Thu t toán sinh k ti p ch đ c thực hi n trên lớp các bài toán th a mãn hai đi u ki n sau: ƒ ƒ Có thể xác định đ ợc một thứ tự trên tập các cấu hình tổ hợp cần liệt kê, từ đó xác định đ ợc cấu hình đầu tiên và cấu hình cuối cùng. Từ một cấu hình bất kỳ ch a phải là cuối cùng, đều có thể xây dựng đ ợc một thuật toán để suy ra cấu hình kế tiếp. Tổng quát, thu t toán sinh k ti p có thể đ c mô t bằng th t c genarate, trong đó Sinh_Kế_Tiếp là th t c sinh c u hình k ti p theo thu t toán sinh đã đ c xây dựng. N u c u hình hi n t i là c u hình cu i cùng thì th t c Sinh_Kế_Tiếp s gán cho stop giá tr true, ng c l i c u hình k ti p s đ c sinh ra. Procedure generate{ <Xây dựng c u hình ban đ u>; stop =false; while (! stop) { <Đ a ra c u hình đang có >; Sinh_K _Ti p; 31 Ch ơng 2: Duyệt và đệ qui } } D ới đây là một ví d điển hình minh họa cho thu t toán sinh k ti p. Bài toán li t kê các t p con c a t p n ph n t Một t p h p h u h n g m n ph n t đ u có thể biểu di n t ng đ ng với t p các s tự nhiên 1, 2, . . . n. Bài toán đ c đặt ra là: Cho một t p h p g m n ph n t X = { X1, X2, . ., Xn }, hãy li t kê t t c các t p con c a t p h p X. Để li t kê đ c t t c các t p con c a X, ta làm t ng ng mỗi t p Y⊆ X với một xâu nh phân có độ dài n là B = { B1, B2, . . , Bn } sao cho Bi = 0 n u Xi ∉ Y và Bi = 1 n u Xi ∈ Y; nh v y, phép li t kê t t c các t p con c a một t p h p n ph n t t ng đ ng với phép li t kê t t c các xâu nh phân có độ dài n. S các xâu nh phân có độ dài n là 2n. Bây gi ta đi xác đ nh th tự các xâu nh phân và ph ng pháp sinh k ti p. d N u xem các xâu nh phân b = { b1, b2, . . , bn } nh là biểu di n c a một s nguyên ng p(b). Khi đó th tự hiển nhiên nh t là th tự tự nhiên đ c xác đ nh nh sau: Ta nói xâu nh phân b = { b1, b2, . . , bn } có th tự tr ớc xâu nh phân b’ = { b’1, b’2, . . , b’n } và kí hi u là b<b’ n u p(b) < p(b’). Ví d với n= 4: chúng ta có 24 = 16 xâu nh phân (t ng ng với 16 t p con c a t p g m n ph n t ) đ c li t kê theo th tự từ điển nh sau: b p(b) 0000 0 0001 1 0010 2 0011 3 0100 4 0101 5 0110 6 0111 7 1000 8 1001 9 1010 10 1011 11 1100 12 1101 13 1110 14 1111 15 32 Ch ơng 2: Duyệt và đệ qui Từ đây ta xác đ nh đ c xâu nh phân đ u tiên là 00. .00 và xâu nh phân cu i cùng là 11..11. Quá trình li t kê dừng khi ta đ c xâu nh phân 1111. Xâu nh phân k ti p là biểu di n nh phân c a giá tr xâu nh phân tr ớc đó cộng thêm 1 đ n v . Từ đó ta nh n đ c qui t c sinh k ti p nh sau: Tìm ch s i đ u tiên theo th tự i = n, n-1, . ., 1 sao cho bi = 0. Gán l i bi = 1 và bj = 0 với t t c j>i. Dãy nh phân thu đ c là dãy c n tìm Thu t toán sinh xâu nh phân k ti p void Next_Bit_String( int *B, int n ){ i = n; while (bi ==1 ) { bi = 0; i = i-1; } bi = 1; } Sau đây là vĕn b n ch ng trình li t kê các xâu nh phân có độ dài n: #include <stdio.h> #include <alloc.h> #include <stdlib.h> #include <conio.h> #define MAX 100 #define TRUE 1 #define FALSE 0 int Stop, count; void Init(int *B, int n){ int i; for(i=1; i<=n ;i++) B[i]=0; count =0; } void Result(int *B, int n){ int i;count++; printf("\n Xau nhi phan thu %d:",count); for(i=1; i<=n;i++) printf("%3d", B[i]); } void Next_Bits_String(int *B, int n){ int i = n; while(i>0 && B[i]){ B[i]=0; i--; 33 Ch ơng 2: Duyệt và đệ qui } if(i==0 ) Stop=TRUE; else B[i]=1; } void Generate(int *B, int n){ int i; Stop = FALSE; while (!Stop) { Result(B,n); Next_Bits_String(B,n); } } void main(void){ int i, *B, n;clrscr(); printf("\n Nhap n=");scanf("%d",&n); B =(int *) malloc(n*sizeof(int)); Init(B,n);Generate(B,n);free(B);getch(); } 2.4. THU T TOÁN QUAY LUI (BACK TRACK) Ph ng pháp sinh k ti p có thể gi i quy t đ c các bài toán li t kê khi ta nh n bi t đ c c u hình đ u tiên c a bài toán. Tuy nhiên, không ph i c u hình sinh k ti p nào cũng đ c sinh một cách đ n gi n từ c u hình hi n t i, ngay kể c vi c phát hi n c u hình ban đ u cũng không ph i d tìm vì nhi u khi chúng ta ph i ch ng minh sự t n t i c a c u hình. Do v y, thu t toán sinh k ti p ch gi i quy t đ c nh ng bài toán li t kê đ n gi n. Để gi i quy t nh ng bài toán tổ h p ph c t p, ng i ta th ng dùng thu t toán quay lui (Back Track) s đ c trình bày d ới đây. Nội dung chính c a thu t toán này là xây dựng d n các thành ph n c a c u hình bằng cách th t t c các kh nĕng. Gi s c n ph i tìm một c u hình c a bài toán x = (x1, x2, . ., xn) mà i-1 thành ph n x1, x2, . ., xi-1 đã đ c xác đ nh, bây gi ta xác đ nh thành ph n th i c a c u hình bằng cách duy t t t c các kh nĕng có thể có và đánh s các kh nĕng từ 1 . .ni. Với mỗi kh nĕng j, kiểm tra xem j có ch p nh n đ c hay không. Khi đó có thể x y ra hai tr ng h p: ƒ ƒ Nếu chấp nhận j thì xác định xi theo j, nếu i=n thì ta đ ợc một cấu hình cần tìm, ng ợc lại xác định tiếp thành phần xi+1. Nếu thử tất cả các khả năng mà không có khả năng nào đ ợc chấp nhận thì quay lại b ớc tr ớc đó để xác định lại xi-1. 34 Ch ơng 2: Duyệt và đệ qui Điểm quan trọng nh t c a thu t toán là ph i ghi nhớ l i mỗi b ớc đã đi qua, nh ng kh nĕng nào đã đ c th để tránh sự trùng lặp. Để nhớ l i nh ng b ớc duy t tr ớc đó, ch ng trình c n ph i đ c tổ ch c theo c ch ngĕn x p (Last in first out). Vì v y, thu t toán quay lui r t phù h p với nh ng phép gọi đ qui. Thu t toán quay lui xác đ nh thành ph n th i có thể đ c mô t bằng th t c Try(i) nh sau: void Try( int i ) { int j; for ( j = 1; j < ni; j ++) { if ( <Ch p nh n j >) { <Xác đ nh xi theo j> if (i==n) <Ghi nh n c u hình>; else Try(i+1); } } } Có thể mô t quá trình tìm ki m l i gi i theo thu t toán quay lui bằng cây tìm ki m l i gi i sau: G c Kh nĕng chọn x1 Kh nĕng chọn x2 với x1 đã chọn Kh nĕng chọn x3 với x1, x2 đã chọn Hình 2.1. Cây li t kê l i gi i theo thu t toán quay lui. Ví d : Bài toán X p H u. Li t kê t t c các cách x p n quân h u trên bàn c n x n sao cho chúng không ĕn đ c nhau. 35 Ch ơng 2: Duyệt và đệ qui Bàn c có n hàng đ c đánh s từ 0 đ n n-1, n cột đ c đánh s từ 0 đ n n-1; Bàn c có n*2 -1 đ ng chéo xuôi đ c đánh s từ 0 đ n 2*n -2, 2 *n -1 đ ng chéo ng c đ c đánh s từ 2*n -2. Ví d : với bàn c 8 x 8, chúng ta có 8 hàng đ c đánh s từ 0 đ n 7, 8 cột đ c đánh s từ 0 đ n 7, 15 đ ng chéo xuôi, 15 đ ng chéo ng c đ c đánh s từ 0 . .15. Vì trên mỗi hàng ch x p đ c đúng một quân h u, nên chúng ta ch c n quan tâm đ n quân h u đ c x p cột nào. Từ đó d n đ n vi c xác đ nh bộ n thành ph n x1, x2, . ., xn, trong đó xi = j đ c hiểu là quân h u t i dòng i x p vào cột th j. Giá tr c a i đ c nh n từ 0 đ n n-1; giá tr c a j cũng đ c nh n từ 0 đ n n-1, nh ng tho mãn đi u ki n ô (i,j) ch a b quân h u khác chi u đ n theo cột, đ ng chéo xuôi, đ ng chéo ng c. Vi c kiểm soát theo hàng ngang là không c n thi t vì trên mỗi hàng ch x p đúng một quân h u. Vi c kiểm soát theo cột đ c ghi nh n nh dãy bi n logic aj với qui ớc aj=1 n u cột j còn tr ng, cột aj=0 n u cột j không còn tr ng. Để ghi nh n đ ng chéo xuôi và đ ng chéo ng c có chi u tới ô (i,j) hay không, ta s d ng ph ng trình i + j = const và i - j = const, đ ng chéo th nh t đ c ghi nh n b i dãy bi n bj, đ ng chéo th 2 đ c ghi nh n b i dãy bi n cj với qui ớc n u đ ng chéo nào còn tr ng thì giá tr t ng ng c a nó là 1 ng c l i là 0. Nh v y, cột j đ c ch p nh n khi c 3 bi n aj, bi+j, ci+j đ u có giá tr 1. Các bi n này ph i đ c kh i đ u giá tr 1 tr ớc đó, gán l i giá tr 0 khi x p xong quân h u th i và tr l i giá tr 1 khi đ a ra k t qu . #include <stdio.h> #include <stdlib.h> #include <conio.h> #include <dos.h> #define N 8 #define D (2*N-1) #define SG (N-1) #define TRUE 1 #define FALSE 0 void hoanghau(int); void inloigiai(int int loigiai[]);FILE *fp; A[N], B[D], C[D], loigiai[N]; int soloigiai =0; void hoanghau(int i){ int j; for (j=0; j<N;j++){ if (A[j] && B[i-j+SG] && C[i+j] ) { loigiai[i]=j; A[j]=FALSE; B[i-j+SG]=FALSE; C[i+j]=FALSE; if (i==N-1){ soloigiai++; 36 Ch ơng 2: Duyệt và đệ qui inloigiai(loigiai); delay(500); } else hoanghau(i+1); A[j]=TRUE; B[i-j+SG]=TRUE; C[i+j]=TRUE; } } } void inloigiai(int *loigiai){ int j; printf("\n L i gi i %3d:",soloigiai); fprintf(fp,"\n L i gi i %3d:",soloigiai); for (j=0;j<N;j++){ printf("%3d",loigiai[j]); fprintf(fp,"%3d",loigiai[j]); } } void main(void){ int i;clrscr();fp=fopen("loigiai.txt","w"); for (i=0;i<N;i++) A[i]=TRUE; for(i=0;i<D; i++){ B[i]=TRUE; C[i]=TRUE; } hoanghau(0);fclose(fp); } 2.5. THU T TOÁN NHÁNH C N min{ f ( x) : x ∈ D} Gi s , chúng ta c n gi i quy t bài toán t i u tổ h p với mô hình tổng quát nh sau: Trong đó, D là t p h u h n ph n t . Ta gi thi t D đ c mô t nh sau: D = { x =( x1, x2, . . ., xn) ∈ A1× A2 × . . .× An ; x thoả mãn tính chất P }, với A1× A2 × . . .× An là các t p h u h n, P là tính ch t cho trên tích đ các A1× A2 × . . .× An . Với gi thi t v t p D nh trên, chúng ta có thể s d ng thu t toán quay lui để li t kê các ph ng án c a bài toán. Trong quá trình li t kê theo thu t toán quay lui, ta s xây dựng 37 Ch ơng 2: Duyệt và đệ qui d n các thành ph n c a ph ng án. Một bộ ph n g m k thành ph n (a1, a2, . . ., ak) xu t hi n trong quá trình thực hi n thu t toán s đ c gọi là ph ng án bộ ph n c p k. Thu t toán nhánh c n có thể đ c áp d ng gi i bài toán đặt ra, n u nh có thể tìm đ c một hàm g xác đ nh trên t p t t c các ph ng án bộ ph n c a bài toán tho mãn b t đẳng th c sau: g (a1 , a 2 ,.., a k ) ≤ min{ f ( x) : x ∈ D, xi = ai , i = 1,2,..., k } (*) với mọi l i gi i bộ ph n (a1, a2, . ., ak), và với mọi k = 1, 2, . . . B t đẳng th c (*) có nghĩa là giá tr c a hàm t i ph ng án bộ ph n (a1, a2, . ., ak) không v t quá giá tr nh nh t c a hàm m c tiêu bài toán trên t p con các ph ng án. D(a1, a2, . ., ak) { x ∈ D: xi = ai, 1 = 1, 2, . ., k }, nói cách khác, g(a1, a2, .., ak) là c n d ới c a t p D(a1, a2, . ., ak). Do có thể đ ng nh t t p D(a1, a2, . . ., ak) với ph ng án bộ ph n (a1, a2, . . , ak), nên ta cũng gọi giá tr g(a1, a2, . ., ak) là c n d ới c a ph ng án bộ ph n (a1, a2, . ., ak). Gi s , ta đã có đ c hàm g. Ta xét cách s d ng hàm này để h n ch kh i l ng duy t trong quá trình duy t t t c các ph ng án theo thu t toán quay lui. Trong quá trình li t kê các ph ng án, có thể đã thu đ hàm m c tiêu nh nh t trong s các ph ph c một s ph ng án c a bài toán. Gọi x là giá tr ng án đã duy t, ký hi u f = f (x) . Ta gọi x là ng án t t nh t hi n có, còn f là k l c. Gi s , ta có đ c f , khi đó n u g(a1, a2, .., ak) > f thì từ b t đẳng th c (*) ta suy ra f < g(a1, a2, . . ., ak) ≤ min { f(x): x ∈ D, xi = ai, i=1, 2, . . ., k }, vì th t p con các ph ng án c a bài toán D(a1, a2, . . ., ak) ch c ch n không ch a ph ng án t i u. Trong tr ng h p này, ta không c n ph i phát triển ph ng án bộ ph n (a1, a2, . . ., ak). Nói cách khác, ta có thể lo i b các ph ng án trong t p D(a1, a2, . ., an) kh i quá trình tìm ki m. Thu t toán quay lui li t kê các ph ng án c n s a đổi l i nh sau: void Try(int k) { (*Phát triển ph ng án bộ ph n (a1, a2, . . ., ak-1 theo thu t toán quay lui có kiểm tra c n d ới tr ớc khi ti p t c phát triển ph ng án*) for ak ∈ Ak { if ( ch p nh n ak ) { xk = ak; if (k== n) < c p nh t k l c>; else if (g(a1, a2, . . ., ak) ≤ } } } 38 f )) Try (k+1); Ch ơng 2: Duyệt và đệ qui Khi đó, thu t toán nhánh c n đ c thực hi n nh th t c sau: void Nhanh_Can(){ f = +∞; (* N u bi t một ph ng án x nào đó thì có thể đặt f = f ( x) *) Try(1); if( f ≤ +∞ ) < f là giá tr t i u , x là ph ng án t i u >; else < bài toán không có ph ng án>; } Chú ý rằng, n u trong th t c Try ta thay th câu l nh if( k== n) < c p nh t k l c >; else if (g(a1, a2, . ., ak) ≤ f )) Try(k+1); b i if (k == n) < c p nh t k l c >; else Try(k+1); thì th t c Try s li t kê toàn bộ các ph ng án c a bài toán, và ta l i thu đ c thu t toán duy t toàn bộ. Vi c xây dựng hàm g ph thuộc vào từng bài toán t i u tổ h p c thể. Nh ng chúng ta c g ng xây dựng sao cho vi c tính giá tr c a g ph i đ n gi n và giá tr c a g(a1, a2, . ., ak) ph i sát với giá tr c a hàm m c tiêu. Ví d . Gi i bài toán ng i du l ch bằng thu t toán nhánh c n Bài toán Ng i du l ch. Một ng i du l ch mu n đi thĕm quan n thành ph T1, T2, . . . , Tn. Xu t phát từ một thành ph nào đó, ng i du l ch mu n đi qua t t c các thành ph còn l i, mỗi thành ph đi qua đúng một l n, r i quay tr l i thành ph xu t phát. Bi t cij là chi phí đi từ thành ph Ti đ n thành ph Tj (i, j = 1, 2, . ., n), hãy tìm hành trình với tổng chi phí là nh nh t (một hành trình là một cách đi tho mãn đi u ki n). Gi i: C đ nh thành ph xu t phát là T1. Bài toán Ng toán: Tìm cực tiểu c a phi m hàm: i du l ch đ c đ a v bài f ( x1 , x 2 ,..., x n ) = c[1, x 2 ] + c[ x 2 , x3 ] + ... + c[ x n −1 , x n ] + c[ x n , x1 ] → min với đi u ki n c min = min{c[i, j ], i, j = 1,2,..., n; i ≠ j} là chi phí đi l i gi a các thành ph . Gi s ta đang có ph ng án bộ ph n (u1, u2, . . ., uk). Ph trình bộ ph n qua k thành ph : ng án t ng ng với hành T1 → T (u 2 ) → ... → T (u k −1 ) → T (u k ) Vì v y, chi phí ph i tr theo hành trình bộ ph n này s là tổng các chi phí theo từng node c a hành trình bộ ph n. 39 Ch ơng 2: Duyệt và đệ qui ∂ =c[1,u2] + c[u2,u3] + . . . + c[uk-1, uk] . Để phát triển hành trình bộ ph n này thành hành trình đ y đ , ta còn ph i đi qua n-k thành ph còn l i r i quay tr v thành ph T1, t c là còn ph i đi qua n-k+1 đo n đ ng n a. Do chi phí ph i tr cho vi c đi qua mỗi trong n-k+1 đo n đ ng còn l i đ u không nhi u h n cmin, nên c n d ới cho ph ng án bộ ph n (u1, u2, . . ., uk) có thể đ c tính theo công th c g(u1, u2, . . ., uk) = ∂ +(n - k +1) *cmin. Chẳng h n, gi i bài toán ng C= i du l ch với ma tr n chi phí nh sau 0 3 14 18 15 3 0 4 22 20 17 9 0 16 4 6 2 7 0 12 9 15 11 5 0 đ Ta có cmin = 2. Quá trình thực hi n thu t toán đ c thể hi n trong hình 2.2. t Thông tin v một ph ng ng theo th tự sau: ƒ đ u tiên là các thành ph n c a ph ƒ g là c n d ới ƒ ph ng án bộ ph n trên cây đ c mô t b i cây tìm ki m l i gi i c ghi trong các ô trên hình v ng án ti p đ n ∂ là chi phí theo hành trình bộ ph n K t thúc thu t toán, ta thu đ ng án t i u với hành trình c ph ng án t i u ( 1, 2, 3, 5, 4, 1) t T1 → T2 → T3 → T5 → T4 → T1 và chi phí nh nh t là 22 40 ng ng với Ch ơng 2: Duyệt và đệ qui f = +∞ (2) ∂=3; g=15 (2,3) ∂=7; g=16 (2,3,4) g=29 (2,3,4,5) g=44 (2,4) ∂=25; g=34 ∂=23; f = 53 Các nhánh này b lo i vì có c n d ới g > f = 22 Hành trình ( 1, 2, 3, 5,4, 1) chi phí 25(K l c mới) . Đặt f = 22 Hình 2.2. Cây tìm ki m l i gi i bài toán ng Ch (5) ∂=15; g=27 (2,5)∂=23; g=32 ∂=16; (2,3,5,4) g=19 Hành trình ( 1, 2, 3,4, 5,1) (4) ∂=18; g=30 ∂=11; (2,3,5) g=17 ∂=41; chi phí 53. Đặt (3) ∂=14; g=26 ng trình gi i bài toán theo thu t toán nhánh c n đ #include <stdio.h> #include <stdlib.h> #include <conio.h> #include <io.h> #define MAX 20 int n, P[MAX], B[MAX], C[20][20], count=0; int A[MAX], XOPT[MAX]; int can, cmin, fopt; void Read_Data(void){ int i, j;FILE *fp; fp = fopen("dulich.in","r"); fscanf(fp,"%d", &n); printf("\n So thanh pho: %d", n); printf("\n Ma tran chi phi:"); 41 i du l ch. c thể hi n nh sau: Ch ơng 2: Duyệt và đệ qui for (i=1; i<=n; i++){ printf("\n"); for(j=1; j<=n; j++){ fscanf(fp,"%d",&C[i][j]); printf("%5d", C[i][j]); } } } int Min_Matrix(void){ int min=1000, i, j; for(i=1; i<=n; i++){ for(j=1; j<=n; j++){ if (i!=j && min>C[i][j]) min=C[i][j]; } } return(min); } void Init(void){ int i; cmin=Min_Matrix(); fopt=32000;can=0; A[1]=1; for (i=1;i<=n; i++) B[i]=1; } void Result(void){ int i; printf("\n Hanh trinh toi uu %d:", fopt); printf("\n Hanh trinh:"); for(i=1; i<=n; i++) printf("%3d->", XOPT[i]); printf("%d",1); } void Swap(void){ int i; for(i=1; i<=n;i++) XOPT[i]=A[i]; } void Update_Kyluc(void){ int sum; sum=can+C[A[n]][A[1]]; 42 Ch ơng 2: Duyệt và đệ qui if(sum<fopt) { Swap(); fopt=sum; } } void Try(int i){ int j; for(j=2; j<=n;j++){ if(B[j]){ A[i]=j; B[j]=0; can=can+C[A[i-1]][A[i]]; if (i==n) Update_Kyluc(); else if( can + (n-i+1)*cmin< fopt){ count++; Try(i+1); } B[j]=1;can=can-C[A[i-1]][A[i]]; } } } void main(void){ clrscr();Read_Data();Init(); Try(2);Result(); getch(); } 43 Ch ơng 2: Duyệt và đệ qui NH NG N I DUNG C N GHI NH 9 Khi không còn cách nào để gi i quy t v n đ thì có thể s d ng cách duy t để gi i quy t. 9 Tuy ph ng pháp đ nh nghĩa bằng đ qui & gi i thu t đ qui t ng đ i ng n gọn và d hiểu nh ng không nên quá l m d ng nó trong khi vi t ch ng trình. 9 C n ph i hiểu rõ khi nào thì phép sinh k ti p mới đ 9 Quá trình quay lui ch thực sự đúng khi ta kiểm soát đ 9 Để h n ch các phép duy t nên s d ng ph 44 c áp d ng. c các b ớc tr ớc đó. ng pháp nhánh c n (n u có thể). Ch ơng 2: Duyệt và đệ qui BÀI T P CHƯƠNG 2 Bài 1. Duy t mọi t p con c a t p h p 1, 2, . . ., n. D li u vào cho b i file tapcon.in, k t qu ghi l i trong file bai11.out. Ví d sau s minh họa cho file tapcon.in và tapcon.out. tapcon.in tapcon.out 3 1 2 2 1 3 3 1 3 2 3 2 1 Bài 2. Tìm t p con dài nh t có th tự tĕng d n, gi m d n. Cho dãy s a1, a2, . . ., an. Hãy tìm dãy con dài nh t đ c s p x p theo th tự tĕng hoặc gi m d n. D li u vào cho b i file tapcon.in, dòng đ u tiên ghi l i s tự nhiên n (n≤100), dòng k ti p ghi l i n s , mỗi s đ c phân bi t với nhau b i một hoặc vài ký tự rỗng. K t qu ghi l i trong file tapcon.out. Ví d sau s minh họa cho file tapcon.in và tapcon.out. tapcon.in tapcon.out 5 5 7 1 3 8 9 6 12 1 3 8 9 12 Bài 3. Duy t các t p con tho mãn đi u ki n. Cho dãy s a1, a2, . . ., an và s M. Hãy tìm t t c các dãy con dãy con trong dãy s a1, a2, . . ., an sao cho tổng các ph n t trong dãy con đúng bằng M. D li u vào cho b i file tapcon.in, dòng đ u tiên ghi l i hai s tự nhiên N và s M (N≤100), dòng k ti p ghi l i N s mỗi s đ c phân bi t với nhau b i một và d u tr ng. K t qu ghi l i trong file tapcon.out. Ví d sau s minh họa cho file tapcon.in và tapcon.out tapcon.in 7 50 5 10 15 20 25 tapcon.out 20 30 45 30 35 Ch ơng 2: Duyệt và đệ qui 15 35 10 15 25 5 20 25 5 15 30 5 10 35 5 10 15 20 Bài 4. Cho l i hình ch nh t g m (n×m) hình vuông đ n v . Hãy li t kê t t c các đ ng đi từ điểm có tọa độ (0, 0) đ n điểm có tọa độ (n×m). Bi t rằng, điểm (0, 0) đ c coi là đ nh d ới c a hình vuông d ới nh t góc bên trái, mỗi b ớc đi ch đ c phép thực hi n hoặc lên trên hoặc xu ng d ới theo c nh c a hình vuông đ n v . D li u vào cho b i file bai14.inp, k t qu ghi l i trong file bai14.out. Ví d sau s minh họa cho file bai14.in và bai14.out. bai14.in 2 2 bai14.out 0 0 1 1 0 1 0 1 0 1 1 0 1 0 0 1 1 0 1 0 1 1 0 0 Bài 5. Duy t mọi t p con k ph n t t t p g m n ph n t . D li u vào cho b i file tapcon.in, k t qu ghi l i trong file tapcon.out. Ví d sau s minh họa cho tapcon.in và tapcon.out. tapcon.in 5 3 tapcon.out 1 2 3 1 2 4 1 2 5 1 3 4 1 3 5 1 4 5 46 Ch ơng 2: Duyệt và đệ qui 2 3 4 2 3 5 2 4 5 3 4 5 Bài 6. Duy t các t p con k ph n t th a mãn đi u ki n. Cho dãy s a1, a2, . . ., an và s M. Hãy tìm t t c các dãy con dãy con k ph n t trong dãy s a1, a2, . . ., an sao cho tổng các ph n t trong dãy con đúng bằng M. D li u vào cho b i file tapcon.in, dòng đ u tiên ghi l i s tự nhiên n , k và s M, hai s đ c vi t cách nhau b i một vài ký tự tr ng, dòng k ti p ghi l i n s mỗi s đ c vi t cách nhau b i một hoặc vài ký tự tr ng. K t qu ghi l i trong file tapcon.out. Ví d sau s minh họa cho file tapcon.in và tapcon.out. tapcon.in 7 3 50 5 10 15 20 25 30 35 tapcon.out 5 10 35 5 15 35 5 20 25 10 15 25 Bài 7. Duy t mọi hoán v c a từ COMPUTER. D li u vào cho b i file hoanvi.in, k t qu ghi l i trong file hoanvi.out. Bài 8. Duy t mọi ma tr n các hoán v . Cho hình vuông g m n × n (n ≥ 5, n lẻ) hình vuông đ n v . Hãy đi n các s từ 1, 2, . . ., n vào các hình vuông đ n v sao cho nh ng đi u ki n sau đ c tho mãn: Đọc theo hàng ta nh n đ Đọc theo cột ta nh n đ Đọc theo hai đ c n hoán v khác nhau c a 1, 2, . . ., n; c n hoán v khác nhau c a 1, 2, . . ., n; ng chéo ta nh n đ c 2 hoán v khác nhau c a 1, 2, . . ., n; Hãy tìm ít nh t 1 (hoặc t t c ) các hình vuông tho mãn 3 đi u ki n trên. D li u vào cho b i file hoanvi.in, k t qu ghi l i trong file hoanvi.out. Ví d sau s minh họa cho file input & output c a bài toán. hoanvi.in 5 hoanvi.out 5 3 4 1 2 47 Ch ơng 2: Duyệt và đệ qui 1 2 5 3 4 3 4 1 2 5 2 5 3 4 1 4 1 2 5 3 Bài 9. Duy t mọi cách chia s tự nhiên n thành tổng các s nguyên nh h n. D li u vào cho b i file chiaso.in, k t qu ghi l i trong file chiaso.out. Ví d sau s minh họa cho file input & output c a bài toán. chiaso.in 4 chiaso.out 4 3 1 2 2 2 1 1 1 1 1 1 Bài 10. Duy t mọi b giá tr trong t p các giá tr r i r c. Cho k t p h p các s thực A1, A2, . . ., Ak(k≤ 10) có s các ph n t t ng ng là N1, N2, . . ., Nk ( các t p có thể có nh ng ph n t gi ng nhau). Hãy duy t t t c các bộ k ph n t a=(a1, a2, . . ., ak) sao cho ai∈Ai(i=1, 2, . . ., k). D li u vào cho b i file chiaso.in, dòng đ u tiên ghi l i k+1 s tự nhiên, mỗi s đ c phân bi t với nhau b i một vài d u tr ng là giá tr c a n, N1, N2, . . ., Nk; k dòng k ti p ghi l i các ph n t c a t p h p A1, A2, . . ., Ak. K t qu ghi l i trong file chiaso.out, mỗi ph n t đ c phân bi t với nhau b i một vài d u tr ng. Ví d sau s minh họa cho file input & output c a bài toán. Chiaso.inp 3 3 2 1 2 3 4 5 6 7 2 chiaso.out 1 4 6 1 4 7 1 5 6 1 5 7 48 Ch ơng 2: Duyệt và đệ qui 2 4 6 2 4 7 2 5 6 2 5 7 3 4 6 3 4 7 3 5 6 3 5 7 Bài 11. Tìm bộ giá tr r i r c trong bài 21 để hàm m c tiêu sin(x1+x2 + . . .+ xk) đ t giá tr lớn nh t. D li u vào cho b i file bai22.inp, k t qu ghi l i trong file bai22.out. Bài 12. Duy t mọi phép toán trong tính toán giá tr biểu th c. Vi t ch ng trình nh p từ bàn phím hai s nguyên M, N. Hãy tìm cách thay các d u ? trong biểu th c sau b i các phép toán +, -, *, %, / (chia nguyên) sao cho giá tr c a biểu th c nh n đ c bằng đúng N: ( (((M?M) ?M)?M)?M)?M)?M N u không đ c hãy đ a ra thông báo là không thể đ c. Bài 13. Bài toán cái túi với s l ng đ v t không h n ch . Một nhà thám hiểm đem theo một cái túi có trọng l ng không quá b. Có n đ v t c n đem theo, đ v t th i có trọng l ng t ng ng là một s ai và giá tr s d ng ci (1≤i≤n). Hãy tìm cách b các đ v t vào túi sao cho tổng giá tr s d ng các đ v t là lớn nh t. Bi t rằng s l ng các đ v t là không h n ch . D li u vào cho b i file caitui.in, dòng đ u tiên ghi l i s tự nhiên n và s thực b hai s đ c vi t cách nhau b i một d u tr ng, hai dòng k ti p ghi n s trên mỗi dòng, t ng ng với vector giá tr s d ng ci và vector trọng l ng ai. K t qu ghi l i trong file caitui.out trên 3 dòng, dòng đ u ghi l i giá tr s d ng t i u, dòng k ti p ghi l i lo i đ v t c n đem theo, dòng cu i cùng ghi l i s l ng c a mỗi lo i đ v t. Ví d sau s minh họa cho file input & output c a bài toán. caitui.in 4 8 10 5 3 6 5 3 2 4 1 1 0 0 1 1 0 0 caitui.out 15 49 Ch ơng 2: Duyệt và đệ qui Bài 14. Bài toán cái túi với s l ng đ v t h n ch . Một nhà thám hiểm đem theo một cái túi có trọng l ng không quá b. Có n đ v t c n đem theo, đ v t th i có trọng l ng t ng ng là một s ai và giá tr s d ng ci (1≤i≤n). Hãy tìm cách b các đ v t vào túi sao cho tổng giá tr s d ng các đ v t là lớn nh t. Bi t rằng s l ng mỗi đ v t là 1. D li u vào cho b i file caitui.in, dòng đ u tiên ghi l i s tự nhiên n và s thực b hai s đ c vi t cách nhau b i một d u tr ng, hai dòng k ti p ghi n s trên mỗi dòng, t ng ng với vector giá tr s d ng ci và vector trọng l ng ai. K t qu ghi l i trong file caitui.out trên 2 dòng, dòng đ u ghi l i giá tr s d ng t i u, dòng k ti p ghi l i lo i đ v t c n đem theo. Ví d sau s minh ho cho file input & output c a bài toán. caitui.in 4 8 8 5 3 1 4 3 2 1 1 0 1 caitui.out 14 1 Bài 15. Bài toán ng i du l ch. Một ng i du l ch mu n đi tham quan t i n thành ph khác nhau. Xu t phát t i một thành ph nào đó, ng i du l ch mu n đi qua t t c các thành ph còn l i mỗi thành ph đúng một l n r i quay tr l i thành ph ban đ u. Bi t Cij là chi phí đi l i từ thành ph th i đ n thành ph th j. Hãy tìm hành trình có chi phí th p nh t cho ng i du l ch. D li u vào cho b i file dulich.in, dòng đ u tiên ghi l i s tự nhiên n, n dòng k ti p ghi l i ma tr n chi phí Cij. K t qu ghi l i trong file dulich.out, dòng đ u tiên ghi l i chi phí t i u, dòng k ti p ghi l i hành trình t i u. Ví d sau s minh họa cho file input & output c a bài toán. dulich.in 5 00 48 43 54 31 20 00 30 63 22 29 64 00 04 17 06 19 02 00 08 01 28 07 18 00 5 3 4 2 dulich.out 81 1 50 1 Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối CHƯƠNG 3: NGĂN XẾP, HÀNG ĐỢI VÀ DANH SÁCH MÓC NỐI (STACK, QUEUE, LINK LIST) Nội dung chính c a ch ng này nhằm làm rõ các ph ng pháp, k thu t biểu di n, phép toán và ng d ng c a các c u trúc d li u trừu t ng. C n đặc bi t l u ý, ng d ng các c u trúc d li u này không ch riêng cho l p trình ng d ng mà còn ng d ng trong biểu di n bộ nhớ để gi i quy t nh ng v n đ bên trong c a các h đi u hành. Các k thu t l p trình trên c u trúc d li u trừu t ng đ c đ c p đây bao g m: 9 K thu t l p trình trên ngĕn x p. 9 K thu t l p trình trên hàng đ i. 9 K thu t l p trình trên danh sách liên k t đ n. 9 K thu t l p trình trên danh sách liên k t kép. B n đọc có thể tìm th y nh ng cài đặt và ng d ng c thể trong tài li u [1]. 3.1. KI U D LIỆU NGĔN X P VÀ NG D NG 3.1.1. Đ nh nghƿa và khai báo Ngĕn x p (Stack) hay bộ x p ch ng là một kiểu danh sách tuy n tính đặc bi t mà phép bổ xung ph n t và lo i b ph n t luôn luôn đ c thực hi n một đ u gọi là đ nh (top). Có thể hình dung stack nh một ch ng đĩa đ c x p vào hộp hoặc một bĕng đ n đ c n p vào kh u súng liên thanh. Quá trình x p đĩa hoặc n p đ n ch đ c thực hi n một đ u, chi c đĩa hoặc viên đ n cu i cùng l i chi m v trí đ nh đ u tiên còn đĩa đ u hoặc viên đ n đ u l i đáy c a hộp (bottom), khi l y ra thì đĩa cu i cùng hoặc viên đ n cu i cùng l i đ c l y ra tr ớc tiên. Nguyên t c vào sau ra tr ớc c a stack còn đ c gọi d ới một tên khác LIFO (Last- In- First- Out). Stack có thể rỗng hoặc bao g m một s ph n t . Có hai thao tác chính trên stack là thêm một nút vào đ nh stack (push) và lo i b một nút t i đ nh stack (pop). Khi mu n thêm một nút vào stack thì tr ớc đó ta ph i kiểm tra xem stack đã đ y (full) hay ch a, n u ta mu n lo i b một nút c a stack thì ta ph i kiểm *tra stack có rỗng hay không. Hình 4.1 minh họa sự thay đổi c a stack thông qua các thao tác thêm và bớt đ nh trong stack. Gi s ta có một stack S l u tr các kí tự. Tr ng thái b t đ u c a stack đ trong hình a là tr ng thái rỗng, hình e mô t tr ng thái đ y. Các thao tác: push(S,’A’) (hình b) push(S,’B’) (hình c) 51 c mô t Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối push(S,’C’) (hình d) push(S,’D’) (hình e) pop(S) (hình f) pop(S) (hình g) B A A (a) (b) C B A D C B A (d) (e) (c) C B A B A (f) (g) Hình 3.1. Các thao tác trên Stack Có thể l u tr stack d ới d ng một vector S g m n thành ph n liên ti p nhau. N u T là đ a ch c a ph n t đ nh stack thì T s có giá tr bi n đổi khi stack ho t động. Ta gọi ph n t đ u tiên c a stack là ph n t th 0, nh v y stack rỗng khi T có giá tr nh h n 0 ta qui ớc là -1. Stack tràn khi T có giá tr là n-1. Mỗi khi một ph n t đ c thêm vào stack, giá tr c a T đ c tĕng lên 1 đ n v , khi một ph n t b lo i b kh i stack giá tr c a T s gi m đi một đ n v . TOP T S1 S2 S3 ... ST BOOTTOM ... Hình 3.2. Vector S l u tr Stack Để khai báo một stack, chúng ta có thể dùng một m ng một chi u. Ph n t th 0 là đáy stack, ph n t cu i c a m ng là đ nh stack. Một stack tổng quát là một c u trúc g m hai tr ng, tr ng top là một s nguyên ch đ nh stack. Tr ng node: là một m ng một chi u g m MAX ph n t trong đó mỗi ph n t là một nút c a stack. Một nút c a stack có thể là một bi n đ n hoặc một c u trúc ph n ánh t p thông tin v nút hi n t i. Ví d , khai báo stack dùng để l u tr các s nguyên. #define TRUE 1 #define FALSE 0 #define MAX typedef struct 100 { int top; int nodes[MAX]; } stack; 52 Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối 3.1.2. Các thao tác v i stack Trong khi khai báo một stack dùng danh sách tuy n tính, chúng ta c n đ nh nghĩa MAX đ lớn để có thể l u tr đ c mọi đ nh c a stack. Một stack đã b tràn (TOP = MAX1) thì nó không thể thêm vào ph n t trong stack, một stack rỗng thì nó không thể đ a ra ph n t . Vì v y, chúng ta c n xây dựng thêm các thao tác kiểm tra stack có b tràn hay không (full) và thao tác kiểm tra stack có rỗng hay không (empty). Thao tác Empty: Kiểm tra stack có rỗng hay không: int Empty(stack *ps) { if (ps ->top == -1) return(TRUE); return(FALSE); } Thao tác Push: Thêm nút mới x vào đ nh stack và thay đổi đ nh stack. void Push (stack *ps, int x) { if ( ps ->top == -1) { printf(“\n stack full”); return; } ps -> top = ps ->top + 1; ps -> nodes[ps->top] = x; } Thao tác Pop : Lo i b nút t i đ nh stack. int Pop ( stack *ps) { if (Empty(ps) { printf(“\n stack empty”); return(0); } return( ps -> nodes[ps->top --]); } 3.1.3. ng d ng c a stack Stack đ c nng d ng để biểu di n nhi u thu t gi i ph c t p khác nhau, đặc bi t đ i với nh ng bài toán c n s d ng đ n các l i gọi đ qui. D ới đây là một s các ví d điển hình c a vi c ng d ng stack. Đ o ng c xâu kí tự: Quá trình đ o ng c một xâu kí tự gi ng nh vi c đ a vào (push) từng kí tự trong xâu vào stack, sau đó đ a ra (pop) các kí tự trong stack ra cho tới khi stack rỗng ta đ c một xâu đ o ng c. Chuy n đổi s t h th p phân sang h c s b t kỳ: Để chuyển đổi một s h th p phân thành s h c s b t kỳ, chúng ta l y s đó chia cho c s c n chuyển đổi, l u 53 Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối tr l i ph n d c a phép chia, sau đó đ o ng c l i dãy các s d ta nh n đ chuyển đổi, vi c làm này gi ng nh c ch LIFO c a stack. cs c n Tính giá tr m t bi u th c d ng h u t :Xét một biểu th c d ng h u t ch ch a các phép toán cộng (+), trừ (-), nhân (*), chia (/), lũy thừa ($). C n ph i nh c l i rằng, nhà logic học Lewinski đã ch ng minh đ c rằng, mọi biểu th c đ u có thể biểu di n d ới d ng h u t mà không c n dùng thêm các kí hi u ph . 23+5*2$ = ( (2 + 3) *5 ) 2 = 625 Ví d : Để tính giá tr c a biểu th c d ng h u t , chúng ta s d ng một stack l u tr biểu th c quá trình tính toán đ c thực hi n nh sau: L y toán h ng 1 ( 2 ) -> L y toán h ng 2 ( 3 ) -> L y phép toán ‘+’ -> L y toán h ng 1 cộng toán h ng 2 và đ y vào stack (5) -> L y toán h ng ti p theo (5), l y phép toán ti p theo (*), nhân với toán h ng 1 r i đ y vào stack (25), l y toán h ng ti p theo (2), l y phép toán ti p theo ($) và thực hi n, l y lu thừa r i đ y vào stack. Cu i cùng ta nh n đ c 25 2= 625. D ới đây là ch ng trình đ o ng c xâu kí tự s d ng stack. Nh ng ví d khác, b n đọc có thể tìm th y trong các tài li u [1], [2]. Ví d 3.1. Ch ng trình đ o ng c xâu kí tự. #include <stdio.h> #include <stdlib.h> #include <conio.h> #include <dos.h> #include <string.h> #define MAX 100 #define TRUE 1 #define FALSE 0 typedef struct{ int top; char node[MAX]; } stack; /* nguyen mau cua ham*/ int Empty(stack *); void Push(stack *, char); char Pop(stack *); /* Mo ta ham */ int Empty(stack *ps){ if (ps->top==-1) return(TRUE); return(FALSE); } void Push(stack *ps, char x){ 54 Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối if (ps->top==MAX-1 ){ printf("\n Stack full"); delay(2000); return; } (ps->top)= (ps->top) + 1; ps->node[ps->top]=x; } char Pop(stack *ps){ if (Empty(ps)){ printf("\n Stack empty"); delay(2000);return(0); } return( ps ->node[ps->top--]); } void main(void){ stack s; char c, chuoi[MAX]; int i, vitri,n;s.top=-1;clrscr(); printf("\n Nhap String:");gets(chuoi); vitri=strlen(chuoi); for (i=0; i<vitri;i++) Push(&s, chuoi[i]); while(!Empty(&s)) printf("%c", Pop(&s)); getch(); } 3.2. HÀNG Đ I (QUEUE) 3.2.1. Đ nh nghƿa và khai báo Khác với stack, hàng đ i (queue) là một danh sách tuy n tính mà thao tác bổ sung ph n t đ c thực hi n một đ u gọi là l i vào (rear). Phép lo i b ph n t đ c thực hi n một đ u khác gọi là l i ra (front). Nh v y, c ch c a queue gi ng nh một hàng đ i, đi vào một đ u và đi ra một đ u hay FIFO (First- In- First- Out). Ta có thể khai báo hàng đ i nh một danh sách tuy n tính g m MAX ph n t mỗi ph n t là một c u trúc, hai bi n front, rear tr l i vào và l i ra trong queue. Ví d d ới đây đ nh nghĩa một hàng đ i c a các s n ph m g m hai thuộc tính mã hàng (mahang) và tên hàng (ten). typedef struct{ int mahang; 55 Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối char ten[20]; } hang; typedef struct { int front, rear; hang node[MAX]; } q; Để truy nh p vào hàng đ i, chúng ta s d ng hai bi n con tr front ch l i tr ớc và rear ch l i sau. Khi l i tr ớc trùng với l i sau (q.rear = q.rear) thì queue tr ng thái rỗng (hình a), để thêm d li u vào hàng đ i các ph n t A, B, C đ c thực hi n thông qua thao tác insert(q,A), insert(q,B), insert(q,C) đ c mô t hình b, thao tác lo i b ph n t kh i hàng đ i Remove(q) đ c mô t hình c, nh ng thao tác ti p theo đ c mô t t i hình d, e. Hình a. Tr ng thái rỗng c a hàng đ i. q.rear=2 C q.front=0 B A q.rear=2 q.front=1 C q.rear=3 D q.rear=3 D Hình c. remove(q). B q.front=1 C Hình b. insert(Q,A);insert(Q,B), insert(Q,C) Hình d. insert(q,D). B q.front=2 Hình e. remove(q). C Hình 3.3. Các thao tác trên Hàng đ i (Queue) Cách tổ ch c này s d n tới tr ng h p các ph n t di chuyển kh p không gian nhớ khi thực hi n bổ sung và lo i b . Trong nhi u tr ng h p, khi thực hi n thêm hoặc lo i b ph n t c a hàng đ i chúng ta c n xét tới một th tự u tiên nào đó, khi đó hàng đ i đ c gọi là hàng đ i có độ u tiên ( Priority Queue ). Với priority queue, thì nút nào có độ u tiên cao nh t đ c thực hi n lo i b tr ớc nh t, còn với thao tác thêm ph n t vào hàng đ i tr thành thao tác thêm ph n t vào hàng đ i có xét tới độ u tiên. 56 Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối ng d ng hàng đ i 3.2.2. Mọi v n đ c a thực t liên quan tới c ch FIFO nh c ch g i ti n, rút ti n trong ngân hàng, đặt vé máy bay đ u có thể ng d ng đ c bằng hàng đ i. Hàng đ i còn có nh ng ng d ng trong vi c gi i quy t các bài toán c a H đi u hành và ch ng trình d ch nh bài toán đi u khiển các quá trình, đi u khiển n p ch ng trình vào bộ nhớ hay bài toán l p l ch. B n đọc có thể tham kh o thêm trong các tài li u [1], [2]. D ới đây, chúng ta đ a ra một ng d ng c a hàng đ i để gi i quy t bài toán “Nhà sản xuất và Ng ời tiêu dùng”. Ví d 3.2- Gi i quy t bài toán ”Người sản xuất và nhà tiêu dùng “ với s các vùng đ m h n ch . Chúng ta mô t quá trình s n xu t và tiêu dùng nh hai quá trình riêng bi t và thực hi n song hành, ng i s n xu t có thể s n xu t t i đa n mặt hàng. Ng i tiêu dùng ch đ c phép s d ng trong s n mặt hàng. Tuy nhiên, ng i s n xu t ch có thể l u tr vào kho khi và ch khi kho ch a b đ y. Ng c l i, n u kho hàng không rỗng (kho có hàng) ng i tiêu dùng có thể tiêu dùng nh ng mặt hàng trong kho theo nguyên t c hàng nào nh p vào kho tr ớc đ c tiêu dùng tr ớc gi ng nh c ch FIFO c a queue. Sau đây là nh ng thao tác ch y u trên hàng đ i để gi i quy t bài toán: Ta xây dựng hàng đ i nh một danh sách tuy n tính g m MAX ph n t mỗi ph n t là một c u trúc, hai bi n front, rear tr đ n l i vào và l i ra trong queue: typedef struct{ int mahang; char ten[20]; } hang; typedef struct { int front, rear; hang node[MAX]; } queue; Thao tác Initialize: thi t l p tr ng thái ban đ u c a hàng đ i. và rear có cùng một giá tr MAX-1. tr ng thái này, font void Initialize ( queue *pq){ pq->front = pq->rear = MAX -1; } Thao tác Empty: kiểm tra hàng đ i có khi front == rear. int Empty(queue *pq){ if (pq->front==pq->rear) return(TRUE); return(FALSE); } 57 tr ng thái rỗng hay không. Hàng đ i rỗng Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối Thao tác Insert: thêm X vào hàng đ i Q. N u vi c thêm X vào hàng đ i đ c thực hi n đ u hàng, khi đó rear có giá tr 0, n u rear không ph i đ u hàng đ i thì giá tr c a nó đ c tĕng lên 1 đ n v . void Insert(queue *pq, hang x){ if (pq->rear==MAX-1 ) pq->rear=0; else (pq->rear)++; if (pq->rear ==pq->front){ printf("\n Queue full"); delay(2000);return; } else pq->node[pq->rear]=x; } Thao tác Remove: lo i b ph n t v trí front kh i hàng đ i. N u hàng đ i tr ng thái rỗng thì thao tác Remove không thể thực hi n đ c, trong tr ng h p khác front đ c tĕng lên một đ n v . hang Remove(queue *pq){ if (Empty(pq)){ printf("\n Queue Empty"); delay(2000); } else { if (pq->front ==MAX-1) pq->front=0; else pq->front++; } return(pq->node[pq->front]); } Thao tác Traver: Duy t t t c các nút trong hàng đ i. void Traver( queue *pq){ int i; if(Empty(pq)){ printf("\n Queue Empty"); return; } if (pq->front ==MAX-1) i=0; 58 Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối else i = pq->front+1; while (i!=pq->rear){ printf("\n %11d % 15s", pq->node[i].mahang, pq->node[i].ten); if(i==MAX-1) i=0; else i++; } printf("\n %11d % 15s", pq->node[i].mahang, pq->node[i].ten); } D ới đây là toàn bộ vĕn b n ch ng trình: #include <stdio.h> #include <stdlib.h> #include <conio.h> #include <dos.h> #include <string.h> #include <math.h> #define MAX 50 #define TRUE 1 #define FALSE 0 typedef struct{ int mahang; char ten[20]; } hang; typedef struct { int front, rear; hang node[MAX]; } queue; /* nguyen mau cua ham*/ void Initialize( queue *pq); int Empty(queue *); void Insert(queue *, hang x); hang Remove(queue *); void Traver(queue *); /* Mo ta ham */ void Initialize ( queue *pq){ pq->front = pq->rear = MAX -1; } int Empty(queue *pq){ if (pq->front==pq->rear) 59 Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối return(TRUE); return(FALSE); } void Insert(queue *pq, hang x){ if (pq->rear==MAX-1 ) pq->rear=0; else (pq->rear)++; if (pq->rear ==pq->front){ printf("\n Queue full"); delay(2000);return; } else pq->node[pq->rear]=x; } hang Remove(queue *pq){ if (Empty(pq)){ printf("\n Queue Empty"); delay(2000); } else { if (pq->front ==MAX-1) pq->front=0; else pq->front++; } return(pq->node[pq->front]); } void Traver( queue *pq){ int i; if(Empty(pq)){ printf("\n Queue Empty"); return; } if (pq->front ==MAX-1) i=0; else i = pq->front+1; while (i!=pq->rear){ printf("\n %11d % 15s", pq->node[i].mahang, pq->node[i].ten); if(i==MAX-1) 60 Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối i=0; else i++; } printf("\n %11d % 15s", pq->node[i].mahang, pq->node[i].ten); } void main(void){ queue q; char chucnang, front1; char c; hang mh; clrscr(); Initialize(&q); do { clrscr(); printf("\n NGUOI SAN XUAT/ NHA TIEU DUNG"); printf("\n 1- Nhap mot mat hang"); printf("\n 2- Xuat mot mat hang"); printf("\n 3- Xem mot mat hang"); printf("\n 4- Xem hang moi nhap"); printf("\n 5- Xem tat ca"); printf("\n 6- Xuat toan bo"); printf("\n Chuc nang chon:");chucnang=getch(); switch(chucnang){ case ‘1’: printf("\n Ma mat hang:"); scanf("%d", &mh.mahang); printf("\n Ten hang:");scanf("%s", mh.ten); Insert(&q,mh);break; case ‘2’: if (!Empty(&q)){ mh=Remove(&q); printf("\n %5d %20s",mh.mahang, mh.ten); } else { printf("\n Queue Empty"); delay(1000); } break; case ‘3’: front1=(q.front==MAX-1)?0:q.front+1; printf("\n Hang xuat"); printf("\n%6d%20s",q.node[front1].mahang,q.node[front1].ten); break; 61 Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối case ‘4’: printf("\n Hang moi nhap"); printf("\n%5d%20s",q.node[q.rear].mahang,q.node[q.rear].ten); break; case ‘5’: printf("\ Hang trong kho"); Traverse(&q);delay(2000);break; } } while(chucnang!=’0’); } 3.3. DANH SÁCH LIÊN K T ĐƠN 3.3.1. Gi i thi u và đ nh nghƿa Một danh sách móc n i, hoặc ng n gọn h n, một danh sách, là một dãy có th tự các ph n t đ c gọi là đ nh. Danh sách có điểm b t đ u, gọi là tiêu đ hay đ nh đ u, một điểm cu i cùng gọi là đ nh cu i. Mọi đ nh trong danh sách đ u có cùng kiểu ngay c khi kiểu này có nhi u d ng khác nhau. B n ch t động là một trong nh ng tính ch t chính c a danh sách móc n i. Có thể thêm hoặc bớt đ nh trong danh sách vào mọi lúc, mọi v trí. Vì s đ nh c a danh sách không thể dự ki n tr ớc đ c, nên khi thực hi n, chúng ta ph i dùng con tr mà không dùng đ c m ng để b o đ m vi c thực hi n hi u qu và tin c y. Mỗi đ nh trong danh sách đ u g m hai ph n. Ph n th nh t ch a d li u. D li u có thể ch là một bi n đ n hoặc là một c u trúc (hoặc con tr c u trúc) có kiểu nào đó. Ph n th hai c a đ nh là một con tr ch vào đ a ch c a đ nh ti p theo trong danh sách. Vì v y có thể d dàng s d ng các đ nh c a danh sách qua một c u trúc tự tr hoặc đ qui. Danh sách móc n i đ n gi n d ới đây xây dựng mỗi đ nh c a danh sách ch l u gi một bi n nguyên. /*đ nh c a danh sách đ n ch ch a một s nguyên*/ struct don { int phantu; struct don *tiep; }; typedef struct don don_t; Trong tr ng h p này, bi n nguyên phantu c a từng đ nh ch a d li u còn bi n con tr tiep ch a đ a ch c a đ nh ti p theo. S đ biểu di n danh sách móc n i đ n đ c biểu di n nh hình d ới đây: Ph n_t Ph n_t Ph n_t Hình 3.4. Danh sách móc n i đ n 62 .... Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối Tổng quát h n, mỗi đ nh c a danh sách có thể ch a nhi u ph n t d li u. Trong tr ng h p này, h p lý h n c là đ nh nghĩa một kiểu c u trúc t ng ng với d li u c n l u gi t i mỗi đ nh. Ph ng pháp này đ c s d ng trong đ nh nghĩa kiểu sau đây: /*đ nh c a danh sách tổng quát */ struct tq { thtin_t phantu; struc tq*tiep; }; typedef struct tq tq_t; Kiểu c u trúc thtin_t ph i đ c đ nh nghĩa tr ớc đó để t ng ng với các d li u s đ c l u tr t i từng đ nh. Danh sách đ c t o nên từ kiểu đ nh này gi ng nh s đ trong Hình 3.4, ngo i trừ vi c mỗi phantu là một bi n nguyên. 3.3.2. Các thao tác trên danh sách móc n i Các thao tác trên danh sách móc n i bao g m vi c c p phát bộ nhớ cho các đ nh và gán d li u cho con tr . Để danh sách đ c t o nên đúng đ n, ta biểu di n ph n t cu i danh sách là một con tr NULL. Con tr NULL là tín hi u thông báo không còn ph n t nào ti p theo trong danh sách n a. Ti n h n c là chúng ta đ nh nghĩa một con tr tới danh sách nh sau: struct node { int infor; struct node *next; }; typedef struct node *NODEPTR; // Con tr tới node C p phát b nh cho m t node: NODEPTR Getnode(void) { NODEPTR p; P = (NODEPTR) malloc(sizeof( struct node)); Return(p); } Gi i phóng b nh c a m t node” NODEPTR Freenode( NODEPTR p){ free(p); } Chèn m t ph n t m i vào đ u danh sách: Các b ớc để chèn một ph n t mới vào đ u danh sách c n thực hi n là: 9 C p không gian bộ nhớ đ l u gi một đ nh mới; 9 Gán các giá tr con tr thích h p cho đ nh mới; 63 Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối 9 Thi t l p liên k t với đ nh mới. S đ biểu di n phép thêm một đ nh mới vào đ u danh sách đ hình 3.5. infor next infor infor next c thể hi n nh trên infor next next Node c n chèn vào đ u danh sách móc n i. Hình 3.5. Thêm đ nh m i vào đ u danh sách móc n i đ n void Push_Top( NODEPTR *plist, int x) { NODEPTR p; p= Getnode(); // c p không gian nhớ cho đ nh mới p -> infor = x; // gán giá tr thích h p cho đ nh mới p ->next = *plist; *plist = p; // thi t l p liên k t } Thêm m t ph n t m i vào cu i danh sách: Để thêm một node vào cu i danh sách, ta c n thực hi n qua các b ớc sau: 9 C p phát bộ nhớ cho node mới; 9 Gán giá tr thích h p cho node mới; 9 Di chuyển con tr tới ph n t cu i danh sách; 9 Thi t l p liên k t cho node mới. S đ thể hiên phép thêm một ph n t mới vào cu i danh sách đ trong hình 3.6 infor next infor next infor infor next Hình 3.6. Thêm node m i vào cu i danh sách. 64 c thể hi n nh next NULL Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối void Push_Bottom( NODEPTR *plist, int x) { NODEPTR p, q; p= Getnode(); // c p phát bộ nhớ cho node mới p->infor = x; // gán giá tr thông tin thích h p q = *plist; // chuyển con tr tới cu i danh sách while (q-> next != NULL) q = q -> next; // q là node cu i cùng c a danh sách liên k t q -> next = p; //node cu i bây gi là node p; p ->next = NULL; // liên k t mới c a p } Thêm node m i q vào gi a danh sách tr c node p: Để thêm node q vào tr ớc node p, chúng ta c n l u ý node p ph i có thực trong danh sách. Gi s node p là có thực, khi đó x y ra hai tình hu ng: hoặc node p là node cu i cùng c a danh sách liên k t t c p->next =NULL, hoặc node p ch a ph i là cu i cùng hay p->next != NULL. Tr ng h p th nh t, chúng ta ch c n gọi tới thao tác Push_Bottom(). Tr ng h p th 2, chúng ta thực hi n theo các b ớc nh sau: 9 C p phát bộ nhớ cho node mới; 9 Gán giá tr thích h p cho node; 9 Thi t l p liên k t node q với node k ti p p; 9 Thi t l p liên k t node node p với node q; void Push_Before( NODEPTR p, int x ){ NODEPTR q; if (p->next==NULL) Push_Bottom(p, x); else { q= Getnode(); // c p phát bộ nhớ cho node mới q -> infor = x; // gán giá tr thông tin thích h p q-> next = p-> next; // thi t l p liên k t node q với node k ti p p; p->next = q; // thi t l p liên k t node p với node k ti p q; } } S đ thêm node vào gi a danh sách đ c thể hi n nh sau: 65 Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối p infor next infor next infor next q infor NULL next Hình 3.7. Phép thêm ph n t vào gi a danh sách liên k t đ n. Xoá m t node ra kh i đ u danh sách: Khi lo i b node kh i đ u danh sách liên k t, chúng ta c n chú ý rằng n u danh sách đang rỗng thì không thể thực hi n vi c lo i b . Trong tr ng h p còn l i, ta thực hi n nh sau: 9 Dùng node p tr tới đ u danh sách; 9 D ch chuyển v trí đ u danh sách tới node ti p theo; 9 Lo i b liên k t với p; 9 Gi i phóng node p; void Del_Top( NODEPTR *plist) { NODEPTR p; p = *plist; // node p tr tới đ u danh sách; if (p==NULL) return; // danh sách rỗng (*plist) = (*plist) -> next; // d ch chuyển node g c lên node k ti p p-> next = NULL; //lo i b liên k t với p Freenode(p); // gi i phóng p; } Lo i b node ở cu i danh sách: Một node cu i danh sách có thể x y ra ba tình hu ng sau: 9 Danh sách rỗng: ta không c n thực hi n lo i b ; 9 Danh sách ch có đúng một node: ng với tr ng h p lo i b node g c; Tr ng h p còn l i danh sách có nhi u h n một node, khi đó ta ph i d ch chuyển tới node g n node cu i cùng nh t để thực hi n lo i b . void Del_Bottom(NODEPTR *plist) { NODEPTR p, q; if (*plist==NULL) return; //không làm gì else if ( (*plist)->next==NULL)) // danh sách có một node Del_Top(plist); else { 66 Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối p = *plist; while (p->next!=NULL){ q = p; p = p->next; // q là node sau node p; } // p là node cu i danh sách; q->next =NULL; //node cu i cùng là q Freenode(p); //gi i phóng p; } } Lo i b node ở gi a danh sách (tr c node p): C n để ý rằng, n u tr ớc node p là NULL (p->next==NULL) thì ta không thực hi n lo i b đ c. Tr ng h p còn l i chúng ta thực hi n nh sau: 9 Dùng node q tr tới node tr ớc node p; 9 Lo i b liên k t c a q; 9 Gi i phóng q. void Del_before(NODEPTR p){ NODEPTR q; if (p->next==NULL) return; // không làm gì q = p ->next; p->next = q->next; Freenode(q); } B n đọc có thể tìm th y nh ng cài đặt c thể c a danh sách liên k t đ n trong các tài li u [1], [2]. 3.4. DANH SÁCH LIÊN K T KÉP Mỗi khi thao tác trên danh sách, vi c duy t danh sách theo c hai chi u t ra thu n ti n h n cho ng i s d ng. Đôi khi chúng ta ph i di chuyển trong danh sách từ node cu i lên node đ u hoặc ng c l i bằng cách đi qua một lo t các con tr . Đi u này có thể d dàng gi i quy t đ c n u ta tĕng thông tin ch a t i từng đ nh c a danh sách. Ngoài con tr ch a đ a ch đ nh ti p theo, ta thêm con tr tr ớc để ch a đ a ch đ ng sau đ nh này. Làm nh v y, chúng ta thu đ c một c u trúc d li u mới gọi là danh sách liên k t kép. struct node { int infor; struct node *right;// con tr tới node sau struct node *left; // con tr tới node k ti p }; typedef struct node *NODEPTR; // đ nh nghĩa con tr tới node 67 Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối L Null I R L I R L I R Null Hình 3.8. Mô t m t danh sách liên k t kép. Các thao tác trên danh sách liên k t kép cũng t ng tự nh danh sách liên k t đ n. Nh ng c n chú ý rằng, mỗi node p c a danh sách liên k t kép có hai đ ng liên k t là p-> left và p->right; Thao tác thêm node m i vào đ u danh sách liên k t kép: 9 C p phát bộ nhớ cho node mới; 9 Gán giá tr thích h p cho node mới; 9 Thi t l p liên k t cho node mới; void Push_Top(NODEPTR *plist, int x){ NODEPTR p; p = Getnode(); //c p phát bộ nhớ cho node p ->infor = x; //gán giá tr thích h p; p -> right = *plist; // thi t l p liên k t ph i (*plist) ->left = p; // thi t liên k t với *plist p-> left = NULL;// thi t l p liên k t trái *plist = p; } Thao tác thêm node vào cu i danh sách: 9 N u danh sách rỗng thì thao tác này trùng với thao tác thêm node mới vào đ u danh sách. 9 N u danh sách không rỗng chúng ta thực hi n nh sau: ƒ ƒ ƒ ƒ ƒ C p phát bộ nhớ cho node; Gán giá tr thích h p cho node; Chuyển con tr tới node cu i trong danh sách; Thi t l p liên k t trái; Thi t l p liên k t ph i; void Push_Bottom(NODEPTR *plist, int x){ NODEPTR p, q; if (*plist ==NULL) Push_Top(plist, x); else { p= Getnode();// c p phát bộ nhớ cho node p->infor =x; //gán giá tr thích h p //chuyển con tr tới node cu i danh sách 68 Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối q = *plist; while (q->right!=NULL) q = q->right; //q là node cu i cùng trong danh sách q -> right =p; // liên k t ph i p->left = q; // liên k t trái p->right =NULL; //liên k t ph i } } Thêm node vào tr c node p: Mu n thêm node vào tr ớc node p thì node p ph i t n t i trong danh sách. N u node p t n t i thì có thể x y ra hai tr ng h p: hoặc node p là node cu i cùng c a danh sách hoặc node p là node ch a ph i là cu i cùng. Tr ng h p th nh t ng với thao tác Push_Bottom. Tr ng h p th hai, chúng ta làm nh sau: 9 C p phát bộ nhớ cho node; 9 Gán giá tr thích h p; 9 Thi t l p liên k t trái cho node mới; 9 Thi t l p liên k t ph i cho node mới; Quá trình đ c mô t b i th t c sau: void Push_Before(NODEPTR p, int x){ NODEPTR q; if (p==NULL) return; //không làm gì else if (p->next==NULL) Push_Bottom(p, x); else { q = Getnode(); // c p phát bộ nhớ cho node mới q ->infor = x; //gán giá tr thông tin thích h p q ->right = p->right; //thi t l p liên k t ph i (p ->right) ->left =q; q -> left = p; //thi t l p liên k t trái p ->right = q; } } Lo i b node đ u danh sách: 9 N u danh sách rỗng thì không c n lo i b ; 9 Dùng node p tr tới đ u danh sách; 9 Chuyển g c lên node k ti p; 69 Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối 9 Lo i b liên k t với node p; 9 Gi i phóng p; void Del_Top(NODEPTR *plist){ NODEPTR p; if ( (*plist)==NULL) return; //không làm gì p = *plist; //p là node đ u tiên trong danh sách (*plist) = (*plist) -> right; // chuyển node g c tới node k ti p p ->right =NULL; // ng t liên k t ph i c a p; (*plist) ->left ==NULL;//ng t liên k t trái với p Freenode(p); //gi i phóng p } Lo i b node ở cu i danh sách: 9 N u danh sách rỗng thì không c n lo i b ; 9 N u danh sách có một node thì nó là tru ng h p lo i ph n t sách; 9 N u danh sách có nhi u h n một node thì: ƒ ƒ ƒ ƒ Chuyển con tr tới node cu i cùng; Ng t liên k t trái c a node; Ng t liên k t ph i c a node; Gi i phóng node. void Del_Bottom(NODEPTR *plist) { NODEPTR p, q; if ((*plist)==NULL) return; //không làm gì else if ( (*plist) ->right==NULL) Del_Top(plist); else { p = *plist; // chuyển con tr tới node cu i danh sách while (p->right!=NULL) p =p->right; // p là node cu i c a danh sách q = p ->left; //q là node sau p; q ->right =NULL; //ng t liên k t ph i c a q p -> left = NULL; //ng t liên k t trái c a p Freenode(p); //gi i phóng p } } Lo i node tr c node p 9 N u node p không có thực thì không thể lo i b ; 70 đ u danh Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối 9 N u node p là node cu i thì cũng không thể lo i b ; 9 Tr ƒ ƒ ƒ ng h p còn l i đ c thực hi n nh sau: Ng t liên k t trái với node p đ ng th i thi t l p liên k t ph i với node (pÆright)Æright; Ng t liên k t ph i với node p đ ng th i thi t l p liên k t trái với node (pÆright)Æright; Gi i phóng node pÆright. void Del_Before(NODEPTR p){ NODEPTR q, r; if (p==NULL || p->right==NULL) return; /*không làm gì n u node p là không có thực hoặc là node cu i cùng */ q = (p->right)->right; //q là node tr ớc node p ->right r = p->right; // r là node c n lo i b r -> left =NULL; //ng t liên k t trái c a r r->right ==NULL;//ng t liên k t ph i c a r p->right =q; //thi t l p liên k t ph i mới cho p q ->left = p; // thi t l p liên k t trái mới cho p Freenode(r); //gi i phóng node } 71 Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối NH NG N I DUNG C N GHI NH 9 Các ph ng pháp đ nh nghĩa stack, khi nào dùng stack & vai trò c a stack đ i với các gi i thu t đ qui. 9 Ph ng pháp đ nh nghĩa hàng đ i, các thao tác trên hàng đ i và ng d ng c a hàng đ i. 9 B n ch t động là tính ch t c b n nh t c a danh sách liên k t đ n và liên k t kép. 9 Sự khác bi t c b n c a danh sách liên k t đ n và danh sách liên k t kép là các con tr left và right. 9 Nh ng ng d ng lớn th ng đ c cài đặt trên các c u trúc d li u động. 9 Chú ý gi i phóng bộ nhớ cho con tr trong khi l p trình. 72 Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối BÀI T P CHƯƠNG 3 Bài 1. Xâu thu n ngh ch độc là xâu bít nh phân có độ dài n mà khi đ o xâu ta v n nh n đ c chính xâu đó. Hãy li t kê t t c các xâu thu n ngh ch độc có độ dài n và ghi l i nh ng xâu đó vào File thuang.out theo từng dòng, dòng đ u tiên ghi l i giá tr c a n, các dòng ti p theo là nh ng xâu thu n ngh ch độc có độ dài n. Ví d : với n=4, ta có đ c nh ng xâu thu n ngh ch độc có d ng sau: 4 0 0 0 0 0 1 1 0 1 0 0 1 1 1 1 1 Bài 2. Vi t ch ng trình qu n lý điểm thi c a sinh viên bằng single (double) link list bao g m nh ng thao tác sau: - Nh p d li u; - Hiển th d li u theo lớp, x p lo i . . .; - S p x p d li u; - Tìm ki m d li u; - In n k t qu . Trong đó, thông tin v mỗi sinh viên đ c đ nh nghĩa thông qua c u trúc sau: typedef struct { int masv; // mã sinh viên; char malop[12]; //mã lớp char hoten[30]; //họ tên sinh viên float diemki; // điểm tổng k t kỳ 1 float diemkii;// điểm tổng k t kỳ 2 float diemtk; // điểm tổng k t c nĕm char xeploai[12]; // x p lo i } sinhvien; Bài 3. Biểu di n biểu th c theo cú pháp Ba Lan. Biểu th c nguyên là một dãy đ c thành l p từ các bi n kiểu nguyên n i với nhau bằng các phép toán hai ngôi ( cộng: + , trừ : , nhân : *) và các d u m ngoặc đ n ‘(‘, đóng ngoặc đ n ‘)’. Nguyên t c đặt tên bi n và th tự thực hi n các phép toán đ c thực hi n nh sau: - Qui t c đặt tên bi n: Là dãy các kí tự ch in th ng hoặc kí tự s độ dài không quá 8, kí tự b t đ u ph i là một ch cái. 73 Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối - Qui t c thực hi n phép toán: Biểu th c trong ngoặc đ n đ c tính tr ớc, phép toán nhân ‘*’ có độ u tiên cao h n so với hai phép toán cộng và trừ. Hai phép toán cộng ‘+’ và trừ có cùng độ u tiên. Ví d : a * b + c ph i đ c hiểu là: (a * b) + c. D ng vi t không ngoặc Ba Lan cho biểu th c nguyên đ c đ nh nghĩa nh sau: - N u e là tên bi n thì d ng vi t Ba Lan c a nó chính là e, - N u e1 và e2 là hai biểu th c có d ng vi t Ba Lan t ng ng là d1 và d2 thì d ng vi t Ba Lan c a e1 + e2 là d1 d2+, c a e1 - e2 là d1 d2-, c a e1*e2 là d1 d2* ( Gi a d1 và d2 có đúng một d u cách, tr ớc d u phép toán không có d u cách), - N u e là biểu th c có d ng vi t Ba Lan là d thì d ng vi t Ba Lan c a biểu th c có ngoặc đ n (e) chính là d ( không còn d u ngoặc n a) . Ví d : Biểu th c (c+b*(f-d)) có d ng vi t Ba Lan là : c b f d-*+. Cho file d li u balan.in đ c tổ ch c thành từng dòng, mỗi dòng không dài quá 80 ký tự là biểu di n c a biểu th c nguyên A. Hãy d ch các biểu th c nguyên A thành d ng vi t Ba Lan c a A ghi vào file balan.out theo từng dòng. Ví d : với file balan.in d ới đây s cho ta k t qu nh sau: balan.in balan.out a+b a b+ a-b a b- a*b a b* (a - b) +c a b- c+ (a + b) * c a b+ c* (a + (b-c)) a b c-+ ( a + b*(c-d)) a b c d-*+ ( (a + b) *c- ( d + e) * f) a b+c* d e+f*- Bài 4. Tính toán giá tr biểu th c Ba Lan. Cho file d li u balan.in g m 2 * n dòng trong đó, dòng có s th tự lẻ (1, 3, 5, . . ) ghi l i một xâu là biểu di n Ba Lan c a biểu th c nguyên A, dòng có s th tự chẵn (2,4,6, . .) ghi l i giá tr c a các bi n xu t hi n trong A. Hãy tính giá tr c a biểu th c A, ghi l i giá tr c a A vào file balan.out từng dòng theo th tự: Dòng có th tự lẻ ghi l i biểu th c Ba Lan c a A sau khi đã thay th các giá tr t ng ng c a bi n trong A, dòng có th tự chẵn ghi l i giá tr c a biểu th c A. Ví d với file balan.in d ới đây s cho ta k t qu nh sau: balan.in balan.out a b+ 3 5+ 74 Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối 35 8 a b- 7 3- 73 4 a b* 43* 43 12 c a b-+ 3 4 5-+ 345 2 Bài 5. L p l ch với m c độ u tiên. Để l p l ch cho CPU đáp ng cho các quá trình đang đ i c a h th ng, ng i ta biểu di n mỗi quá trình bằng một b n ghi bao g m nh ng thông tin : s quá trình(Num) là một s tự nhiên nh h n 1024, tên quá trình (Proc) là một xâu ký tự độ dài không quá 32 không ch a d u tr ng gi a, độ u tiên quá trình là một s nguyên d ng (Pri) nh h n 10, th i gian thực hi n c a quá trình (Time) là một s thực. Các quá trình đang đ i trong h đ c CPU đáp ng thông qua một hàng đ i đ c gọi là hàng đ i các quá trình, hàng đ i các quá trình với độ u tiên đ c xây dựng sao cho nh ng đi u ki n sau đ c tho mãn: - Các quá trình đ - Đ i với nh ng quá trình có cùng độ u tiên thì quá trình nào có th i gian thực hi n ít nh t đ c x p lên tr ớc nh t. Cho file d li u lich.in đ c s p theo th tự u tiên; c tổ ch c nh sau: - Dòng đ u tiên ghi l i một s tự nhiên n là s các quá trình; - n dòng k ti p, mỗi dòng ghi l i thông tin v một quá trình đang đ i. Hãy xây dựng hàng đ i các quá trình với độ u tiên. Ghi l i th tự các quá trình mà CPU đáp ng trên một dòng c a file lich.out, mỗi quá trình đ c phân bi t với nhau b i một hoặc vài ký tự tr ng, dòng k ti p ghi l i s gi c n thi t mà CPU c n đáp ng cho các quá trình. Ví d với file lich.in d ới đây s cho ta k t qu nh sau: lich.in 7 1 Data_Processing 1 10 2 Editor_Program 1 20 3 System_Call 3 0.5 4 System_Interative 3 1 5 System_Action 3 2 6 Writing_Data 2 7 Reading_Data 2 10 20 75 Ch ơng 3: Ngăn xếp, hàng đợi và danh sách móc nối lich.out 3 4 5 7 6 1 2 63.5 Bài 6. Thu t toán RR (Round Robin): Thu t toán SJF đáp ng đ c t i đa các quá trình ho t động trong h , tuy nhiên s có nhi u quá trình có chi phí th i gian lớn ph i đ i nhi u quá trình có chi phí th i gian nh thực hi n. Với thu t toán SJF , tính công bằng c a h b vi ph m. Để kh c ph c đi u trên, thu t toán Round Robin thực hi n chọn một l ng t th i gian thích h p, sau đó đáp ng cho mỗi quá trình theo từng vòng với l ng t th i gian đã chọn. u điểm c a RR là tính công bằng c a h đ c đ m b o, s các quá trình đ c CPU đáp ng trên một đ n v th i gian ch p nh n đ c. Nh c điểm lớn nh t c a thu t toán là vi c lựa chọn l ng t th i gian đáp ng cho mỗi quá trình sao cho t i u không ph i là đ n gi n. Hãy vi t ch ng trình mô ph ng thu t toán l p l ch RR. 76 Ch ơng 4: Cấu trúc dữ liệu cây (Tree) CHƯƠNG 4: CẤU TRÚC DỮ LIỆU CÂY (TREE) Cây là một trong nh ng c u trúc d li u r i r c có ng d ng quan trọng trong biểu di n tính toán, biểu di n tri th c & biểu di n các đ i t ng d li u ph c t p. Trọng tâm chính c a ch ng này nhằm cung c p cho b n đọc nh ng khái ni m và thao tác c b n trên cây nh phân, bao g m: 9 Khái ni m v cây, cây nh phân, cây nh phân tìm ki m. 9 Khái ni m node g c (root), node lá (leaf), m c (level) & độ sâu c a cây. 9 Ph ng pháp biểu di n và các thao tác trên cây nh phân. 9 Ph ng pháp biểu di n và các thao tác trên cây nh phân tìm ki m. 9 Các thao tác duy t cây: duy t theo th tự tr ớc, duy t theo th tự gi a & duy t theo th tự sau. B n đọc có thể tìm hiểu sâu h n v cây nhi u nhánh, cây cân bằng và cây nh phân hoàn toàn cân bằng trong tài li u [1]. 4.1. Đ NH NGHƾA VÀ KHÁI NIỆM Cây là một t p h p h u h n các node có cùng chung một kiểu d li u, trong đó có một node đặc bi t gọi là node g c (root). Gi a các node có một quan h phân c p gọi là “quan h cha con”. Có thể đ nh nghĩa một cách đ qui v cây nh sau: ƒ ƒ Một node là một cây. Node đó cũng là g c (root) c a cây y. N u n là một node và T1, T2, . .., Tk là các cây với n1, n2, . . , nk l n l t là g c thì một cây mới T s đ c t o l p bằng cách cho node n tr thành cha c a các node n1, n2, . . , nk hay node n tr thành g c và T1, T2, . ., Tk là các cây con (subtree) c a g c. Ví d : c u trúc tổ ch c th m c (directory) c a dos là một c u trúc cây. 4 4.1 4.1.1 4.1.2 4.2 4.3 4.3.1 4.4 4.3.2 Hình 4.1. Ví d v m t cây th m c 77 4.4.1 4.4.2 Ch ơng 4: Cấu trúc dữ liệu cây (Tree) Một cây đ c gọi là rỗng n u nó không có b t kỳ một node nào. S các node con c a một node đ c gọi là c p (degree) c a node đó. Ví d : trong cây 4.2 sau, c p c a node A là 3, c p c a node B là 2, c p c a node D là 3, c p c a node H là 2. A B E D C F G I H J K Hình 4.2. mô t c p c a cây Node có c p bằng 0 đ c gọi là lá (leaf) hay node t n cùng (terminal node). Ví d : các node E, F, C, G, I, J, K đ c gọi là lá. Node không là lá đ c gọi là node trung gian hay node nhánh (branch node). Ví d node B, D, H là các node nhánh. C p cao nh t c a node trên cây gọi là c p c a cây, trong tr 4.2 c p c a cây là 3. ng h p cây trong hình G c c a cây có s m c là 1. N u node cha có s m c là i thì node con có s m c là i+1. Ví d g c A có s m c là 1, D có s m c là 2, G có s m c là 3, J có s m c là 4. Chi u cao (height) hay chi u sâu (depth) c a một cây là s m c lớn nh t c a node trên cây đó. Cây 4.2 có chi u cao là 4. Đ ng đi từ node n1 đ n nk là dãy các node n1, n2, . ., nk sao cho ni là node cha c a node ni+1 (1<=i<k), độ dài c a đ ng đi (path length) đ c tính bằng s các node trên đ ng đi trừ đi 1 vì nó ph i tính từ node b t đ u và node k t thúc. Ví d : trong cây 4.2 đ ng đi từ node A tới node G là 2, đ ng đi từ node A đ n node K là 3. Một cây đ c gọi là có th tự n u chúng ta xét đ n th tự các cây con trong cây (ordered tree), ng c l i là cây không có th tự (unordered tree). Thông th ng các cây con đ c tính theo th tự từ trái sang ph i. 4.2. CÂY NH PHÂN Cây nh phân là một d ng quan trọng c a c u trúc cây có đặc điểm là mọi node trên cây ch có t i đa là hai node con. Cây con bên trái c a cây nh phân đ c gọi là left subtree, cây con bên ph i c a cây đ c gọi là right subtree. Đ i với cây nh phân, bao gi cũng đ c phân bi t cây con bên trái và cây con bên ph i. Nh v y, cây nh phân là một cây có th tự. Ví d trong hình 4.3 đ u là các cây nh phân: 78 Ch ơng 4: Cấu trúc dữ liệu cây (Tree) A A A B B B C D C D E C D E E Hình 4.3. Cây nh phân Các cây nh phân có d ng đ c bi t bao g m: ƒ ƒ ƒ ƒ ƒ Cây nh phân l ch trái (hình 4.4a): là cây nh phân ch có các node bên trái. Cây nh phân l nh ph i (hình 4.4b): là cây ch bao g m các node ph i. Cây nh phân zic z c (hình 4.4 c, 4.4d): node trái và node ph i c a cây đan xen nhau thành một hình zic z c. Cây nh phân hoàn ch nh ( strictly binary tree: hình 4.4e) : Một cây nh phân đ c gọi là hoàn ch nh n u nh node g c và t t c các node trung gian đ u có hai con. Cây nh phân đ y đ (complete binary tree : hình 4.4f): Một cây nh phân đ c gọi là đ y đ với chi u sâu d thì nó ph i là cây nh phân hoàn ch nh và t t c các node lá đ u có chi u sâu là d. A B A A A B B C B C C C D D D D E E E Hình 4.4a Hình 4.4b Hình 4.4c 79 E Hình 4.4d Ch ơng 4: Cấu trúc dữ liệu cây (Tree) A A B C B D E F D H C G E G F I Hình 4.4 e Hình 4.4f Cây nh phân hoàn toàn cân b ng (hình 4.5): là cây nh phân mà t t c các node c a nó s node trên nhánh cây con bên trái và s node trên nhánh cây con bên ph i chênh l nh nhau không quá 1. N u ta gọi Nl là s node c a nhánh cây con bên trái và Nr là s node c a nhánh cây con bên ph i, khi đó cây nh phân hoàn toàn cân bằng ch có thể là một trong 3 tr ng h p: ƒ ƒ ƒ S node nhánh cây con bên trái bằng s node nhánh cây con bên ph i bằng (Nl = Nr ) (hình 4.5a). S node nhánh cây con bên trái bằng s node nhánh cây con bên ph i cộng 1 (Nl = Nr+1) (hình 4.5b) S node nhánh cây con bên trái bằng s node nhánh cây con bên ph i trừ 1 (Nl = Nr-1) (hình 4.5c). A B D A E Hình 4.5a C B C D A E Hình 4.5b B F D C E F Hình 4.5c Cây nh phân tìm ki m: là một cây nh phân hoặc b rỗng hoặc t t c các node trên cây th a mãn đi u ki n sau: ƒ ƒ Nội dung c a t t c các node thuộc nhánh cây con bên trái đ u nh h n nội dung c a node g c. Nội dung c a t t c các node thuộc nhánh cây con bên ph i đ u lớn h n nội dung c a node g c. 80 Ch ơng 4: Cấu trúc dữ liệu cây (Tree) ƒ Cây con bên trái và cây con bên ph i cũng tự nhiên hình thành hai cây nh phân tìm ki m. 10 20 12 8 6 9 30 25 15 10 22 37 10 19 29 8 40 28 39 6 Hình 4.6. Ví d v cây nh phân tìm ki m 4.3. BI U DIỄN CÂY NH PHÂN 4.3.1. Bi u di n cây nh phân b ng danh sách tuy n tính Trong tr ng h p cây nh phân đ y đ , ta có thể d dàng biểu di n cây nh phân bằng một m ng l u tr k ti p. Trong đó node g c là ph n t đ u tiên c a m ng (ph n t th 1), node con th i>=1 c a cây nh phân là ph n t th 2i, 2i + 1 hay cha c a node th j là [j/2]. Với qui t c đó, cây nh phân có thể biểu di n bằng một vector V sao cho nội dung c a node th i đ c l u tr trong thành ph n V[i] c a vector V. Ng c l i, n u bi t đ a ch c a ph n t th i trong vector V chúng ta cũng hoàn toàn xác đ nh đ c ng c l i đ a ch c a node cha, đ a ch node g c trong cây nh phân. Ví d : cây nh phân trong hình 4.7 s đ 30 25 22 c l u tr k ti p nh sau: V[0] V[1] V[2] 30 25 37 V[3] 22 V[4] V[5] V[6] 28 35 40 37 28 35 40 Hình 4.7. L u tr k ti p c a cây nh phân Đ i với cây nh phân không đ y đ , vi c l u tr bằng m ng t ra không hi u qu vì chúng ta ph i b tr ng quá nhi u ph n t gây lãng phí bộ nhớ nh trong ví d sau: 81 Ch ơng 4: Cấu trúc dữ liệu cây (Tree) 30 37 25 22 V[0] V[1] V[2] V[3] V[4] V[5] V[6] 30 25 37 22 35 φ φ 35 Hình 4.8- L u tr k ti p c a cây nh phân không đ y đ 4.3.2. Bi u di n cây nh phân b ng danh sách móc n i Trong cách l u tr cây nh phân bằng danh sách móc n i, mỗi node đ c mô t bằng ba lo i thông tin chính : left là một con tr tr tới node bên trái c a cây nh phân; infor : là thông tin v node, infor có thể là một bi n đ n hoặc một c u trúc; right là một con tr tr tới node bên ph i c a cây nh phân. Trong tr ng h p node là node lá thì con tr left và con tr right đ c tr tới con tr NULL. Đ i với node l ch trái, con tr right s tr tới con tr NULL, ng c l i đ i với node l ch ph i, con tr left cũng s tr tới con tr NULL. C u trúc c a một node đ c mô t trong hình 4.9. Left Infor Right Hình 4.9. mô t m t node c a cây nh phân. Ví d : cây nh phân trong hình 4.10 s đ c biểu di n bằng danh sách liên k t nh sau: Left 30 right 30 25 37 Left 25 right Left 37 NULL 22 35 NULL 22 NULL NULL Hình 4.10. Bi u di n cây nh phân b ng danh sách móc n i . 82 35 NULL Ch ơng 4: Cấu trúc dữ liệu cây (Tree) 4.4. CÁC THAO TÁC TRÊN CÂY NH PHÂN 4.4.1. Đ nh nghƿa cây nh phân b ng danh sách tuy n tính Mỗi node trong cây đ c khai báo nh một c u trúc g m 3 tr ng: infor, left, right. Toàn bộ cây có thể coi nh một m ng mà mỗi ph n t c a nó là một node. Tr ng infor tổng quát có thể là một đ i t ng d li u kiểu c b n hoặc một c u trúc. Ví d : đ nh nghĩa một cây nh phân l u tr danh sách các s nguyên: #define MAX 100 #define TRUE 1 #define FALSE 0 struct node { int infor; int left; int right; typedef struct node }; node[MAX]; 4.4.2. Đ nh nghƿa cây nh phân theo danh sách liên k t: struct node { int infor; struct node *left; struct node *right; struct node *NODEPTR } typedef 4.4.3. Các thao tác trên cây nh phân C p phát b nh cho m t node m i c a cây nh phân: NODEPTR Getnode(void) { NODEPTR p; p= (NODEPTR) malloc(sizeof(struct node)); return(p); } Gi i phóng node đã đ void c c p phát Freenode( NODEPTR p){ free(p); } Khởi đ ng cây nh phân void Initialize(NODEPTR *ptree){ *ptree=NULL; } 83 Ch ơng 4: Cấu trúc dữ liệu cây (Tree) Ki m tra tính r ng c a cây nh phân: int Empty(NODEPTR *ptree){ if (*ptree==NULL) return(TRUE); return(FALSE); } T o m t node lá cho cây nh phân: ƒ ƒ ƒ C p phát bộ nhớ cho node; Gán giá tr thông tin thích h p cho node; T o liên k t cho node lá; NODEPTR Makenode(int NODEPTR x){ p; p= Getnode();// c p phát bộ nhớ cho node p ->infor = x; // gán giá tr thông tin thích h p p ->left = NULL; // t o liên k t trái c a node lá p ->right = NULL;// t o liên k t ph i c a node lá return(p); } T o node con bên trái c a cây nh phân: Để t o đ ƒ ƒ ƒ c node con bên trái là node lá c a node p, chúng ta thực hi n nh sau: N u node p không có thực (p==NULL), ta không thể t o đ trái c a node p; c node con bên N u node p đã có node con bên trái (p->left!=NULL), thì chúng ta cũng không thể t o đ c node con bên trái node p; N u node p ch a có node con bên trái, thì vi c t o node con bên trái chính là thao tác make node đã đ c xây dựng nh trên; void Setleft(NODEPTR p, int x ){ if (p==NULL){ // n u node p không có thực thì không thể thực hi n đ c printf(“\n Node p không có thực”); delay(2000); return; } // n u node p có thực và t n t i lá con bên trái thì cũng không thực hi n đ else if ( p ->left !=NULL){ printf(“\n Node p đã có node con bên trái”); delay(2000); return; } // n u node có thực và ch a có node trái 84 c Ch ơng 4: Cấu trúc dữ liệu cây (Tree) else p ->left = Makenode(x); } T o node con bên ph i c a cây nh phân: Để t o đ ƒ ƒ ƒ c node con bên ph i là node lá c a node p, chúng ta làm nh sau: N u node p không có thực (p==NULL), thì ta không thể thực hi n đ tác thêm node lá vào node ph i node p; c thao N u node p có thực (p!=NULL) và đã có node con bên ph i thì thao tác cũng không thể thực hi n đ c; N u node p có thực và ch a có node con bên ph i thì vi c t o node con bên ph i node p đ c thực hi n thông qua thao tác Makenode(); void Setright(NODEPTR p, int x ){ if (p==NULL){ // N u node p không có thực printf(“\n Node p không có thực”); delay(2000); return; } // N u node p có thực & đã có node con bên ph i else if ( p ->right !=NULL){ printf(“\n Node p đã có node con bên ph i”); delay(2000); return; } // N u node p có thực & ch a có node con bên ph i else p ->right = Makenode(x); } Thao tác xoá node con bên trái cây nh phân Thao tác lo i b node con bên trái node p đ ƒ ƒ c thực hi n nh sau: N u node p không có thực thì thao tác không thể thực hi n; N u node p có thực (p==NULL) thì kiểm tra xem p có node lá bên trái hay không; 9 N u node p có thực và p không có node lá bên trái thì thao tác cũng không thể thực hi n đ c; 9 N u node p có thực (p!=NULL) và có node con bên trái là q thì: - N u node q không ph i là node lá thì thao tác cũng không thể thực hi n đ c (q->left!=NULL || q->right!=NULL); - N u node q là node lá (q->left==NULL && q->right==NULL) thì: o Gi i phóng node q; 85 Ch ơng 4: Cấu trúc dữ liệu cây (Tree) o Thi t l p liên k t mới cho node p; Thu t toán đ int c thể hi n bằng thao tác Delleft() nh d ới đây: Delleft(NODEPTR p) { NODEPTR q; int x; if ( p==NULL) printf(“\n Node p không có thực”);delay(2000); exit(0); } q = p ->left; // q là node c n xoá; x = q->infor; //x là nội dung c n xoá if (q ==NULL){ // kiểm tra p có lá bên trái hay không printf(“\n Node p không có lá bên trái”); delay(2000); exit(0); } if (q->left!=NULL || q->right!=NULL) { // kiểm tra q có ph i là node lá hay không printf(“\n q không là node lá”); delay(2000); exit(0); } p ->left =NULL; // t o liên k t mới cho p Freenode(q); // gi i phóng q return(x); } Thao tác xoá node con bên ph i cây nh phân: Thao tác lo i b node con bên ph i node p đ ƒ ƒ c thực hi n nh sau: N u node p không có thực thì thao tác không thể thực hi n; N u node p có thực (p==NULL) thì kiểm tra xem p có node lá bên ph i hay không; 9 N u node p có thực và p không có node lá bên ph i thì thao tác cũng không thể thực hi n đ c; 9 N u node p có thực (p!=NULL) và có node con bên ph i là q thì: - N u node q không ph i là node lá thì thao tác cũng không thể thực hi n đ c (q->left!=NULL || q->right!=NULL); - N u node q là node lá (q->left==NULL && q->right==NULL) thì: o Gi i phóng node q; o Thi t l p liên k t mới cho node p; Thu t toán đ c thể hi n bằng thao tác Delright() nh d ới đây: 86 Ch ơng 4: Cấu trúc dữ liệu cây (Tree) int Delright(NODEPTR p) { NODEPTR q; int x; if ( p==NULL) printf(“\n Node p không có thực”);delay(2000); exit(0); } q = p ->right; // q là node c n xoá; x = q->infor; //x là nội dung c n xoá if (q ==NULL){ // kiểm tra p có lá bên ph i hay không printf(“\n Node p không có lá bên ph i”); delay(2000); exit(0); } if (q->left!=NULL || q->right!=NULL) { // kiểm tra q có ph i là node lá hay không printf(“\n q không là node lá”); delay(2000); exit(0); } p ->right =NULL; // t o liên k t cho p Freenode(q); // gi i phóng q return(x); } Thao tác tìm node có n i dung là x trên cây nh phân: Để tìm node có nội dung là x trên cây nh phân, chúng ta có thể xây dựng bằng th t c đ qui nh sau: ƒ ƒ ƒ N u node g c (proot) có nội dung là x thì proot chính là node c n tìm; N u proot =NULL thì không có node nào trong cây có nội dung là x; N u nội dung node g c khác x (proot->infor!=x) và proot!=NULL thì: 9 Tìm node theo nhánh cây con bên trái (proot = proot->left); 9 Tìm theo nhánh cây con bên ph i; Thu t toán tìm một node có nội dung là x trong cây nh phân đ NODEPTR Search( NODEPTR proot, int x) { NODEPTR p; if ( proot ->infor ==x) // đi u ki n dừng return(proot); if (proot ==NULL) return(NULL); p = Search(proot->left, x); // tìm trong nhánh con bên trái if (p ==NULL) // Tìm trong nhánh con bên ph i Search(proot->right, x); 87 c thể hi n nh sau: Ch ơng 4: Cấu trúc dữ liệu cây (Tree) return(p); } 4.5. CÁC PHÉP DUYỆT CÂY NH PHÂN (TRAVERSING BINARY TREE) Phép duy t cây là ph ng pháp vi ng thĕm (visit) các node một cách có h th ng sao cho mỗi node ch đ c thĕm đúng một l n. Có ba ph ng pháp để duy t cây nh phân đó là: ƒ ƒ ƒ Duy t theo th tự tr ớc (Preorder Travesal); Duy t theo th tự gi a (Inorder Travesal); Duy t theo th tự sau (Postorder Travesal). A B D C E Hình 4.11. mô t ph 4.5.1. Duy t theo th tự tr ƒ ƒ F G ng pháp duy t cây nh phân c (Preorder Travesal) N u cây rỗng thì không làm gì; N u cây không rỗng thì : 9 Thĕm node g c c a cây; 9 Duy t cây con bên trái theo th tự tr ớc; 9 Duy t cây con bên ph i theo th tự tr ớc; Ví d : với cây trong hình 4.11 thì phép duy t Preorder cho ta k t qu duy t theo th tự các node là :A -> B -> D -> E -> C -> F -> G. Với ph ng pháp duy t theo th tự tr ớc, chúng ta có thể cài đặt cho cây đ nghĩa trong m c 4.4 bằng một th t c đ qui nh sau: void Pretravese ( NODEPTR proot ) { if ( proot !=NULL) { // n u cây không rỗng printf(“%d”, proot->infor); // duy t node g c Pretravese(proot ->left); // duy t nhánh cây con bên trái Pretravese(proot ->right); // Duy t nhánh con bên ph i } } 88 c đ nh Ch ơng 4: Cấu trúc dữ liệu cây (Tree) 4.5.2. Duy t theo th tự gi a (Inorder Travesal) ƒ ƒ N u cây rỗng thì không làm gì; N u cây không rỗng thì : 9 Duy t cây con bên trái theo th tự gi a; 9 Thĕm node g c c a cây; 9 Duy t cây con bên ph i theo th tự gi a; Ví d : cây trong hình 4.11 thì phép duy t Inorder cho ta k t qu duy t theo th tự các node là :D -> B -> E -> A -> F -> C -> G. Với cách duy t theo th tự gi a, chúng ta có thể cài đặt cho cây đ trong m c 4.4 bằng một th t c đ qui nh sau: void c đ nh nghĩa Intravese ( NODEPTR proot ) { if ( proot !=NULL) { // n u cây không rỗng Intravese(proot ->left); // duy t nhánh cây con bên trái printf(“%d”, proot->infor); // duy t node g c Intravese(proot ->right); // Duy t nhánh con bên ph i } } 4.5.3. Duy t theo th tự sau (Postorder Travesal) ƒ N u cây rỗng thì không làm gì; ƒ N u cây không rỗng thì : 9 Duy t cây con bên trái theo th tự sau; 9 Duy t cây con bên ph i theo th tự sau; 9 Thĕm node g c c a cây; Ví d : cây trong hình 4.11 thì phép duy t Postorder cho ta k t qu duy t theo th tự các node là :D -> E -> B -> F -> G-> C -> A . Với cách duy t theo th tự gi a, chúng ta có thể cài đặt cho cây đ trong m c 4.4 bằng một th t c đ qui nh sau: void Posttravese ( NODEPTR proot ) { if ( proot !=NULL) { // n u cây không rỗng Posttravese(proot ->left); // duy t nhánh cây con bên trái Posttravese(proot ->right); // duy t nhánh con bên ph i printf(“%d”, proot->infor); // duy t node g c } } 89 c đ nh nghĩa Ch ơng 4: Cấu trúc dữ liệu cây (Tree) 4.6. CÀI Đ T CÂY NH PHÂN TÌM KI M Nh ng cài đặt c thể cho cây nh phân và cây nh phân đ y đ đã đ c trình bày trong [1]. D ới đây là một cài đặt c thể cho cây nh phân tìm ki m bằng danh sách móc n i. Vì cây nh phân tìm ki m là một d ng đặc bi t c a cây nên các thao tác nh thi t l p cây, duy t cây v n nh cây nh phân thông th ng riêng, các thao tác tìm ki m , thêm node và lo i b node có thể đ c thực hi n nh sau: Thao tác tìm ki m node (Search): Gi s ta c n tìm ki m node có giá tr x trên cây nh phân tìm ki m, tr ớc h t ta b t đ u từ g c: ƒ ƒ ƒ ƒ N u cây rỗng: phép tìm ki m không tho mãn; N u x trùng với khoá g c: phép tìm ki m tho mãn; N u x nh h n khoá g c thì tìm sang cây bên trái; N u x lớn h n khoá g c thì tìm sang cây bên ph i; NODEPTR Search( NODEPTR proot, int x){ NODEPTR p; p=proot; if ( p!=NULL){ if (x <p->infor) Search(proot->left, x); if (x >p->infor) Search(proot->right, x); } return(p); } Thao tác chèn thêm node (Insert): để thêm node x vào cây nh phân tìm ki m, ta thực hi n nh sau: ƒ ƒ ƒ N u x trùng với g c thì không thể thêm node N u x < g c và ch a có lá con bên trái thì thực hi n thêm node vào nhánh bên trái. N u x > g c và ch a có lá con bên ph i thì thực hi n thêm node vào nhánh bên ph i. void Insert(NODEPTR proot, int x){ if (x==proot->infor){ printf("\n Noi dung bi trung"); delay(2000);return; } else if(x<proot->infor && proot->left==NULL){ Setleft(proot,x);return; 90 Ch ơng 4: Cấu trúc dữ liệu cây (Tree) } else if (x>proot->infor && proot->right==NULL){ Setright(proot,x);return; } else if(x<proot->infor) Insert(proot->left,x); else Insert(proot->right,x); } Thao tác lo i b node (Remove): Vi c xoá node trên cây nh phân tìm ki m khá ph c t p. Vì sau khi xoá node, chúng ta ph i đi u ch nh l i cây để nó v n là cây nh phân tìm ki m. Khi xoá node trên cây nh phân tìm ki m thì node c n xoá b có thể một trong 3 tr ng h p sau: Tr ng h p 1: n u node p c n xoá là node lá hoặc node g c thì vi c lo i b đ thực hi n ngay. c Tr ng h p 2: n u node p c n xoá có một cây con thì ta ph i l y node con c a node p thay th cho p. Tr ng h p 3: node p c n xoá có hai cây con. N u node c n xoá phía cây con bên trái thì node bên trái nh t s đ c chọn làm node th m ng, n u node c n xoá phía cây con bên ph i thì node bên ph i nh t s đ c chọn làm node th m ng. Thu t toán lo i b node trên cây nh phân tìm ki m đ c thể hi n nh sau: NODEPTR Remove(NODEPTR p){ NODEPTR rp,f; if(p==NULL){ printf("\n Nut p khong co thuc"); delay(2000);return(p); } if(p->right==NULL) rp=p->left; else { if (p->left==NULL) rp = p->right; else { f=p; rp=p->right; while(rp->left!=NULL){ f=rp; rp=rp->left; } if(f!=p){ f->left =rp->right; 91 Ch ơng 4: Cấu trúc dữ liệu cây (Tree) rp->right = p->right; rp->left=p->left; } else rp->left = p->left; } } Freenode(p); return(rp); } Cài đặt c thể các thao tác trên cây nh phân tìm ki m đ #include <stdio.h> #include <stdlib.h> #include <conio.h> #include <alloc.h> #include <string.h> #include <dos.h> #define TRUE 1 #define FALSE 0 #define MAX struct node 100 { int infor; struct node *left; struct node *right; }; typedef struct node *NODEPTR; NODEPTR Getnode(void){ NODEPTR p; p=(NODEPTR)malloc(sizeof(struct node)); return(p); } void Freenode(NODEPTR p){ free(p); } void Initialize(NODEPTR *ptree){ *ptree=NULL; } NODEPTR Makenode(int x){ NODEPTR p; p=Getnode(); p->infor=x; 92 c thể hi n nh d ới đây. Ch ơng 4: Cấu trúc dữ liệu cây (Tree) p->left=NULL; p->right=NULL; return(p); } void Setleft(NODEPTR p, int x){ if (p==NULL) printf("\n Node p khong co thuc"); else { if (p->left!=NULL) printf("\n Node con ben trai da ton tai"); else p->left=Makenode(x); } } void Setright(NODEPTR p, int x){ if (p==NULL) printf("\n Node p khong co thuc"); else { if (p->right!=NULL) printf("\n Node con ben phai da ton tai"); else p->right=Makenode(x); } } void Pretrav(NODEPTR proot){ if (proot!=NULL){ printf("%5d", proot->infor); Pretrav(proot->left); Pretrav(proot->right); } } void Intrav(NODEPTR proot){ if (proot!=NULL){ Intrav(proot->left); printf("%5d", proot->infor); Intrav(proot->right); } } void Postrav(NODEPTR proot){ if (proot!=NULL){ Postrav(proot->left); 93 Ch ơng 4: Cấu trúc dữ liệu cây (Tree) Postrav(proot->right); printf("%5d", proot->infor); } } void Insert(NODEPTR proot, int x){ if (x==proot->infor){ printf("\n Noi dung bi trung"); delay(2000);return; } else if(x<proot->infor && proot->left==NULL){ Setleft(proot,x);return; } else if (x>proot->infor && proot->right==NULL){ Setright(proot,x);return; } else if(x<proot->infor) Insert(proot->left,x); else Insert(proot->right,x); } NODEPTR Search(NODEPTR proot, int x){ NODEPTR p;p=proot; if (p!=NULL) { if (x <proot->infor) p=Search(proot->left,x); else if(x>proot->infor) p=Search(proot->right,x); } return(p); } NODEPTR Remove(NODEPTR p){ NODEPTR rp,f; if(p==NULL){ printf("\n Nut p khong co thuc"); delay(2000);return(p); } if(p->right==NULL) rp=p->left; else { if (p->left==NULL) rp = p->right; else { 94 Ch ơng 4: Cấu trúc dữ liệu cây (Tree) f=p; rp=p->right; while(rp->left!=NULL){ f=rp; rp=rp->left; } if(f!=p){ f->left =rp->right; rp->right = p->right; rp->left=p->left; } else rp->left = p->left; } } Freenode(p); return(rp); } void Cleartree(NODEPTR proot){ if(proot!=NULL){ Cleartree(proot->left); Cleartree(proot->right); Freenode(proot); } } void main(void){ NODEPTR ptree, p; int noidung, chucnang; Initialize(&ptree); do { clrscr(); printf("\n CAY NHI PHAN TIM KIEM"); printf("\n 1-Them nut tren cay"); printf("\n 2-Xoa node goc"); printf("\n 3-Xoa node con ben trai"); printf("\n 4-Xoa node con ben phai"); printf("\n 5-Xoa toan bo cay"); printf("\n 6-Duyet cay theo NLR"); printf("\n 7-Duyet cay theo LNR"); printf("\n 8-Duyet cay theo LRN"); printf("\n 9-Tim kiem tren cay"); 95 Ch ơng 4: Cấu trúc dữ liệu cây (Tree) printf("\n 0-Thoat khoi chuong trinh"); printf("\n Lua chon chuc nang:"); scanf("%d", &chucnang); switch(chucnang){ case 1: printf("\n Noi dung nut moi:"); scanf("%d",&noidung); if(ptree==NULL) ptree=Makenode(noidung); else Insert(ptree,noidung); break; case 2: if (ptree==NULL) printf("\n Cay bi rong"); else ptree=Remove(ptree); break; case 3: printf("\n Noi dung node cha:"); scanf("%d", &noidung); p=Search(ptree,noidung); if (p!=NULL) p->left = Remove(p->left); else printf("\n Khong co node cha"); break; case 4: printf("\n Noi dung node cha:"); scanf("%d", &noidung); p=Search(ptree,noidung); if (p!=NULL) p->right = Remove(p->right); else printf("\n Khong co node cha"); break; case 5: Cleartree(ptree); break; case 6: printf("\n Duyet cay theo NLR"); 96 Ch ơng 4: Cấu trúc dữ liệu cây (Tree) if(ptree==NULL) printf("\n Cay rong"); else Pretrav(ptree); break; case 7: printf("\n Duyet cay theo LNR"); if(ptree==NULL) printf("\n Cay rong"); else Intrav(ptree); break; case 8: printf("\n Duyet cay theo NRN"); if(ptree==NULL) printf("\n Cay rong"); else Postrav(ptree); break; case 9: printf("\n Noi dung can tim:"); scanf("%d",&noidung); if(Search(ptree,noidung)) printf("\n Tim thay"); else printf("\n Khong tim thay"); break; } delay(1000); } while(chucnang!=0); Cleartree(ptree); ptree=NULL; } 97 Ch ơng 4: Cấu trúc dữ liệu cây (Tree) NH NG N I DUNG C N GHI NH 9 9 9 9 9 Đ nh nghĩa cây, cây nh phân, cây cân bằng và cây hoàn toàn cân bằng. Các khái ni m m c, độ sâu c a cây. Các ph ng pháp duy t cây: duy t theo th tự tr ớc, duy t theo th tự gi a và duy t theo th tự sau. Phân bi t đ c nh ng thao tác gi ng nhau và khác nhau cây nh phân tìm ki m và cây nh phân thông th ng. Tìm hiểu thêm v cây nhi u nhánh trong các tài li u [1], [2]. Tìm hiểu thêm v cây quy t đ nh và ng d ng c a nó trong biểu di n tri th c. 98 Ch ơng 4: Cấu trúc dữ liệu cây (Tree) BÀI T P CHƯƠNG 4 Bài 1. Một cây nh phân đ c gọi là cây nh phân đúng n u node g c c a cây và các node trung gian đ u có hai node con (ngo i trừ node lá). Ch ng minh rằng, n u cây nh phân đúng có n node lá thì cây này có t t c 2n-1 node. Hãy t o l p một cây nh phân b t kỳ, sau đó kiểm tra xem n u cây không ph i là cây nh phân đúng hãy tìm cách bổ sung vào một s node để cây tr thành cây hoàn toàn đúng. Làm t ng tự nh trên với thao tác lo i b node. Bài 2. Một cây nh phân đ c gọi là cây nh phân đ y với chi u sâu d (d nguyên d ng) khi và ch khi m c i (0≤i≤d) cây có đúng 2i node. Hãy vi t ch ng trình kiểm tra xem một cây nh phân có ph i là một cây đ y hay không? N u cây ch a ph i là cây nh phân đ y, hãy tìm cách bổ xung một s node vào cây nh phân để nó tr thành cây nh phân đ y. Bài 3. Một cây nh phân đ c gọi là cây nh phân g n đ y với độ sâu d n u với mọi m c i (0≤i≤d-1) nó có đúng 2i node. Cho cây nh phân b t kỳ, hãy kiểm tra xem nó có ph i là cây nh phân g n đ y hay không ? Bài 4. Hãy xây dựng các thao tác sau trên cây nh phân: - T o l p cây nh phân; - Đ m s node c a cây nh phân; - Xác đ nh chi u sâu c a cây nh phân; - Xác đ nh s node lá c a cây nh phân; - Xác đ nh s node trung gian c a cây nh phân; - Xác đ nh s node trong từng m c c a cây nh phân; - Xây dựng t p thao tác t - Thêm một node vào node ph i c a một node; - Thêm node vào node trái c a một node; - Lo i b node ph i c a một node; - Lo i b node trái c a một node; - Lo i b c cây; - Duy t cây theo th tự tr ớc; - Duy t cây theo th gi a; - Duy t cây theo th tự sau; ng tự nh trên đ i với các nhánh cây con; 99 Ch ơng 4: Cấu trúc dữ liệu cây (Tree) Bài 5. Cho file d li u cay.in đ c tổ ch c thành từng dòng, trên mỗi dòng ghi l i một từ là nội dung node c a một cây nh phân tìm ki m. Hãy xây dựng các thao tác sau cho cây nh phân tìm ki m: - T o l p cây nh phân tìm ki m với node g c là từ đ u tiên trong file d li u cay.in. - Xác đ nh s node trên cây nh phân tìm ki m; - Xác đ nh chi u sâu c a cây nh phân tìm ki m; - Xác đ nh s node nhánh cây bên trái; - Xác đ nh s node nhánh cây con bên ph i; - Xác đ nh s node trung gian; - Xác đ nh s node lá; - Tìm node có độ dài lớn nh t; - Thêm node; - Lo i b node; - Lo i b c cây; - Duy t cây theo th tự tr ớc; - Duy t cây theo th tự gi a; - Duy t cây theo th tự sau; - Cho cây nh phân b t kỳ hãy xây dựng ch - Cây có ph i là cây nh phân đúng hay không? - Cây có ph i là cây nh phân đ y hay không ? - Cây có ph i là cây nh phân g n đ y hay không? - Cây có ph i là cây nh phân hoàn toàn cân bằng hay không? - Cây có ph i là cây nh phân tìm ki m hay không ? ng trình xác đ nh xem: Bài 6. Cho tam giác s đ c biểu di n nh hình d ới đây. Hãy vi t ch ng trình tìm dãy các s có tổng lớn nh t trên con đ ng từ đ nh và k t thúc t i đâu đó đáy. Bi t rằng, mỗi b ớc đi có thể đi chéo xu ng phía trái hoặc chéo xu ng phía ph i. S l ng hàng trong tam giác là lớn h n 1 nh ng nh h n 100; các s trong tam giác đ u là các s từ 0 . .99. 100 Ch ơng 4: Cấu trúc dữ liệu cây (Tree) 7 3 8 8 1 2 4 0 7 4 5 4 2 6 5 D li u vào cho b i file cay.in, dòng đ u tiên ghi l i s tự nhiên n là s l ng hàng trong tam giác, n hàng ti p theo ghi l i từng hàng mỗi ph n t đ c phân bi t với nhau b i một hoặc vài d u tr ng. K t qu ghi l i trong file cay.out dòng đ u tiên ghi l i tổng s lớn nh t tìm đ c, dòng k ti p ghi l i dãy các s có tổng lớn nh t. Ví d với hình trên file input & output nh sau: cay.in 5 7 2 8 8 1 0 2 7 4 4 4 5 2 6 5 3 8 7 5 cay.out 30 7 Bài 7. Cho cây nh phân s hoàn toàn cân bằng:(s node bên nhánh cây con bên trái đúng bằng s node nhánh cây con bên ph i, m c th i có đúng 2i node) nh hình sau: 7 9 0 2 6 3 1 Bài 8. Hãy tìm dãy các node xu t phát từ g c tới một node lá nào đó sao cho tổng giá tr c a các node là lớn nh t, bi t rằng mỗi b ớc đi ch đ c phép đi chéo sang node trái hoặc chéo theo node ph i. D li u vào cho b i file cay.in, dòng đ u tiên ghi l i s tự nhiên n ≤50 là s các m c c a cây, n dòng k ti p mỗi dòng ghi l i dãy các s là các 101 Ch ơng 4: Cấu trúc dữ liệu cây (Tree) node trên mỗi m c. K t qu ghi l i trong file cay.out theo th tự, dòng đ u là tổng lớn nh t c a hành trình, dòng k ti p là dãy các node trong hành trình. Ví d : với hình trên file input & output đ c tổ ch c nh sau: cay.in 3 7 9 2 0 6 3 9 6 1 cay.out 22 7 102 Ch ơng 5: Đồ thị (Graph) CHƯƠNG 5: ĐỒ THỊ (GRAPH) Đ th là một c u trúc d li u r i r c nh ng l i có ng d ng hi n đ i. Đ th có thể dùng để biểu di n các s đ c a một m ch đi n, biểu di n đ ng đi c a h th ng giao thông hay các lo i m ng máy tính. N m b t đ c nh ng thu t toán trên đ th giúp chúng ta gi i quy t đ c nhi u bài toán t i u quan trọng nh bài toán qui ho ch m ng, bài toán phân lu ng trên m ng hay phân lu ng giao thông, bài toán tìm đ ng đi ng n nh t hoặc cực tiểu hoá chi phí cho các ho t động s n xu t kinh doanh. Nh ng nội dung đ c trình bày bao g m: 9 Đ nh nghĩa đ th , phân lo i d th và nh ng khái ni m c b n liên quan. 9 Các ph ng pháp biểu di n đ th trên máy tính. 9 Các thu t toán tìm ki m trên đ th . 9 Đ th Euler & đ th hamilton. 9 Bài toán tìm cây bao trùm nh nh t. 9 Bài toán tìm đ ng đi ng n nh t B n đọc có thể tìm th y nh ng cài đặt c thể và nh ng ki n th c sâu h n v Lý thuy t đ th trong tài li u [1] & [3]. 5.1. NH NG KHÁI NIỆM CƠ B N C A Đ TH 5.1.1. Các lo i đ th Lý thuy t đ th là lĩnh vực nghiên c u đã t n t i từ nh ng nĕm đ u c a th k 18 nh ng l i có nh ng ng d ng hi n đ i. Nh ng t t ng c b n c a lý thuy t đ th đ c nhà toán học ng i Thuỵ Sĩ Leonhard Euler đ xu t và chính ông là ng i dùng lý thuy t đ th gi i quy t bài toán nổi ti ng “C u Konigsberg”. Đ th đ c s d ng để gi i quy t nhi u bài toán thuộc các lĩnh vực khác nhau. Chẳng h n, ta có thể dùng đ th để biểu di n nh ng m ch vòng c a một m ch đi n, dùng đ th biểu di n quá trình t ng tác gi a các loài trong th giới động thực v t, dùng đ th biểu di n nh ng đ ng phân c a các h p ch t polyme hoặc biểu di n m i liên h gi a các lo i thông tin khác nhau. Có thể nói, lý thuy t đ th đ c ng d ng rộng rãi trong t t c các lĩnh vực khác nhau c a thực t cũng nh nh ng lĩnh vực trừu t ng c a lý thuy t tính toán. Đ th (Graph) là một c u trúc d li u r i r c bao g m các đ nh và các c nh n i các cặp đ nh này. Chúng ta phân bi t đ th thông qua kiểu và s l ng c nh n i gi a các cặp đ nh c a đ th . Để minh ch ng cho các lo i đ th , chúng ta xem xét một s ví d v các 103 Ch ơng 5: Đồ thị (Graph) lo i m ng máy tính bao g m: mỗi máy tính là một đ nh, mỗi c nh là nh ng kênh đi n tho i đ c n i gi a hai máy tính với nhau. Hình 5.1 là s đ c a m ng máy tính lo i 1. San Francisco Detroit Chicago New York Denver Los Angeles Washington Hình 5.1. M ng máy tính đ n kênh tho i. Trong m ng máy tính này, mỗi máy tính là một đ nh c a đ th , mỗi c nh vô h ớng biểu di n các đ nh n i hai đ nh phân bi t, không có hai cặp đ nh nào n i cùng một cặp đ nh. M ng lo i này có thể biểu di n bằng một đ n đ th vô h ng. Đ nh nghƿa 1. Đơn đồ thị vô h ớng G = <V, E> bao gồm V là tập các đỉnh, E là tập các cặp có thứ tự gồm hai phần tử khác nhau của V gọi là các cạnh. Trong tr ng h p gi a hai máy tính nào đó th ng xuyên truy n t i nhi u thông tin, ng i ta n i hai máy tính b i nhi u kênh tho i khác nhau. M ng máy tính đa kênh tho i có thể đ c biểu di n nh hình 5.2. San Francisco Detroit Chicago New York Denver Los Angeles Washington Hình 5.2. M ng máy tính đa kênh tho i. Trên hình 5.2, gi a hai máy tính có thể đ c n i với nhau b i nhi u h n một kênh tho i. Với m ng lo i này, chúng ta không thể dùng đ n đ th vô h ớng để biểu di n. Đ th lo i này là đa đ th vô h ớng. Đ nh nghƿa 2. Đa đồ thị vô h ớng G = <V, E> bao gồm V là tập các đỉnh, E là họ các cặp không có thứ tự gồm hai phần tử khác nhau của V gọi là tập các cạnh. e1, e2 đ ợc gọi là cạnh lặp nếu chúng cùng t ơng ứng với một cặp đỉnh. 104 Ch ơng 5: Đồ thị (Graph) Rõ ràng, mọi đ n đ th đ u là đa đ th , nh ng không ph i đa đ th nào cũng là đ n đ th vì gi a hai đ nh có thể có nhi u h n một c nh n i gi a chúng với nhau. Trong nhi u tr ng h p, có máy tính có thể n i nhi u kênh tho i với chính nó. Với lo i m ng này, ta không thể dùng đa đ th để biểu di n mà ph i dùng gi đ th vô h ớng. Gi đ th vô h ớng đ c mô t nh trong hình 5.3. Đ nh nghƿa 3. Giả đồ thị vô h ớng G = <V, E> bao gồm V là tập đỉnh, E là họ các cặp không có thứ tự gồm hai phần tử (hai phần tử không nhất thiết phải khác nhau) trong V đ ợc gọi là các cạnh. Cạnh e đ ợc gọi là khuyên nếu có dạng e =(u, u), trong đó u là đỉnh nào đó thuộc V. San Francisco Detroit Chicago New York Denver Los Angeles Washington Hình 5.3. M ng máy tính đa kênh tho i có khuyên. Trong nhi u m ng, các kênh tho i n i gi a hai máy tính có thể ch đ c phép truy n tin theo một chi u. Chẳng h n máy tính đặt t i San Francisco đ c phép truy nh p tới máy tính đặt t i Los Angeles, nh ng máy tính đặt t i Los Angeles không đ c phép truy nh p ng c l i San Francisco. Hoặc máy tính đặt t i Denver có thể truy nh p đ c tới máy tính đặt t i Chicago và ng c l i máy tính đặt t i Chicago cũng có thể truy nh p ng c l i máy tính t i Denver. Để mô t m ng lo i này, chúng ta dùng khái ni m đ n đ th có h ớng. Đ n đ th có h ớng đ c mô t nh trong hình 5.4. San Francisco Detroit Chicago New York Denver Los Angeles Washington Hình 5.4. M ng máy tính có h ng. Đ nh nghƿa 4. Đơn đồ thị có h ớng G = <V, E> bao gồm V là tập các đỉnh, E là tập các cặp có thứ tự gồm hai phần tử của V gọi là các cung. 105 Ch ơng 5: Đồ thị (Graph) Đ th có h ớng trong hình 5.4 không ch a các c nh bội. Nên đ i với các m ng đa kênh tho i một chi u, đ th có h ớng không thể mô t đ c mà ta dùng khái ni m đa đ th có h ớng. M ng có d ng đa đ th có h ớng đ c mô t nh trong hình 5.5. San Francisco Detroit Chicago New York Denver Los Angeles Washington Hình 5.5. M ng máy tính đa kênh tho i m t chi u. Đ nh nghƿa 5. Đa đồ thị có h ớng G = <V, E> bao gồm V là tập đỉnh, E là cặp có thứ tự gồm hai phần tử của V đ ợc gọi là các cung. Hai cung e1, e2 t ơng ứng với cùng một cặp đỉnh đ ợc gọi là cung lặp. Từ nh ng d ng khác nhau c a đ th kể trên, chúng ta th y sự khác nhau gi a các lo i đ th đ c phân bi t thông qua các c nh c a đ th có th tự hay không có th tự, các c nh bội, khuyên có đ c dùng hay không. 5.1.2. M t s thu t ng c b n c a đ th Đ nh nghƿa 1. Hai đỉnh u và v của đồ thị vô h ớng G =<V, E> đ ợc gọi là kề nhau nếu (u,v) là cạnh thuộc đồ thị G. Nếu e =(u, v) là cạnh của đồ thị G thì ta nói cạnh này liên thuộc với hai đỉnh u và v, hoặc ta nói cạnh e nối đỉnh u với đỉnh v, đồng thời các đỉnh u và v sẽ đ ợc gọi là đỉnh đầu của cạnh (u,v). Đ nh nghƿa 2. Ta gọi bậc của đỉnh v trong đồ thị vô h ớng là số cạnh liên thuộc với nó và ký hiệu là deg(v). a b c d f e g Hình 5.6 Đ th vô h ng G. Ví d 1. Xét đ th trong hình 5.6, ta có deg(a) = 2, deg(b) =deg(c) = deg(f) = 4, deg(e) = 3, deg(d) = 1, deg(g)=0. 106 Ch ơng 5: Đồ thị (Graph) Đ nh b c 0 đ c gọi là đ nh cô l p. Đ nh b c 1 đ trên, đ nh g là đ nh cô l p, đ nh d là đ nh treo. c gọi là đ nh treo. Trong ví d Đ nh nghƿa 3. N u e=(u,v) là cung c a đ th có h ớng G thì ta nói hai đ nh u và v là k nhau, và nói cung (u, v) n i đ nh u với đ nh v hoặc cũng nói cung này đi ra kh i đ nh u và đi vào đ nh v. Đ nh u (v) s đ c gọi là đ nh đ u (cu i) c a cung (u,v). 5.1.3. Đ ng đi, chu trình, đ th liên thông Đ nh nghƿa 1. Đ ng đi độ dài n từ đ nh u đ n đ nh v trên đ th vô h ớng G=<V,E> là dãy x0, x1, . . ., xn-1, xn trong đó n là s nguyên d ng, x0=u, xn =v, (xi, xi+1)∈E, i =0, 1, 2,. . ., n-1. Đ ng đi nh trên còn có thể biểu di n thành dãy các c nh (x0, x1), (x1,x2) , . . ., (xn-1, xn). Ta gọi đ nh u là đ nh đ u, đ nh v là đ nh cu i c a đ ng đi. Đ ng đi có đ nh đ u trùng với đ nh cu i (u=v) đ c gọi là chu trình. Đ ng đi hay chu trình đ c gọi là đ n n u nh không có c nh nào lặp l i. Ví d 3. Tìm các đ ng đi, chu trình trong đ th vô h ớng nh trong hình 5.7. D dàng nh n th y (a, d, c, f, e) là đ ng đi đ n độ dài 4, (d, e, c, a) không là đ ng đi vì (e,c) không ph i là c nh c a đ th . Dãy (b, c, f, e, b) là chu trình độ dài 4. Đ ng đi (a, b, e, d, a, b) có độ dài 5 không ph i là đ ng đi đ n vì c nh (a,b) có mặt hai l n. a b c d e f Hình 5.7. Đ ng đi trên đ th . t Khái ni m đ ng đi và chu trình trên đ th có h ớng đ c đ nh nghĩa hoàn toàn ng tự, ch có đi u khác bi t duy nh t là ta ph i chú ý tới các cung c a đ th . đ Đ nh nghƿa 3. Đ th vô h ớng (có h ớng) đ ng đi gi a hai đ nh b t kỳ c a nó. 5.2. BI U DIỄN Đ c gọi là liên thông n u luôn tìm đ c TH TRÊN MÁY TÍNH 5.2.1. Ma tr n k , ma tr n trọng s Để l u tr đ th và thực hi n các thu t toán khác nhau, ta c n ph i biểu di n đ th trên máy tính, đ ng th i s d ng nh ng c u trúc d li u thích h p để mô t đ th . Vi c chọn c u trúc d li u nào để biểu di n đ th có tác động r t lớn đ n hi u qu thu t toán. Vì v y, lựa chọn c u trúc d li u thích h p biểu di n đ th s ph thuộc vào từng bài toán c thể. 107 Ch ơng 5: Đồ thị (Graph) Xét đ n đ th vô h ớng G =<V, E>, với t p đ nh V = {1, 2, . . ., n}, t p c nh E = {e1, e2, . . ., em}. Ta gọi ma tr n k c a đ th G là ma tr n có các ph n t hoặc bằng 0 hoặc bằng 1 theo qui đ nh nh sau: A = { aij: aij = 1 nếu (i, j) ∈E, aij = 0 nếu (i,j) ∉E; i, j =1, 2, . . ., n}. Ví d 1. Biểu di n đ th trong hình 5.8 d ới đây bằng ma tr n k . 2 4 1 6 3 5 Hình 5.8. Đ th vô h ng G 1 1 0 2 1 3 1 4 0 5 0 6 0 2 1 0 1 1 0 0 3 1 1 0 0 1 0 4 0 1 0 0 1 1 5 0 0 1 1 0 1 6 0 0 0 1 1 0 Ma tr n k có nh ng tính ch t sau: a. Ma tr n k c a đ th vô h ớng là ma tr n đ i x ng A[i,j] = A[j, i]; i, j = 1, 2, . . . n. Ng c l i, mỗi (0, 1) ma tr n c p n đẳng c u với một đ n đ th vô h ớng n đ nh; b. Tổng các ph n t theo dòng i ( cột j) c a ma tr n k chính bằng b c đ nh i (đỉnh j); c. N u ký hi u aijp , i, j = 1,2,..., n là các ph n t c a ma tr n Ap = A.A. . . A (p lần) khi đó aijp , i, j = 1,2,..., n , cho ta s đ ng đi khác nhau từ đ nh i đ n đ nh j qua p-1 đ nh trung gian. Ma tr n k c a đ th có h ớng cũng đ c đ nh nghĩa hoàn toàn t ng tự, chúng ta ch c n l u ý tới h ớng c a c nh. Ma tr n k c a đ th có h ớng là không đ i x ng. Ví d 2. Tìm ma tr n k c a đ th có h ớng trong hình 5.9. 1 2 3 4 Hình 5.9. Đ th có h 1 2 3 4 5 1 2 0 0 1 0 1 0 0 1 0 1 3 0 0 0 1 0 5 4 0 0 0 0 0 5 1 0 0 0 0 ng G 108 Ch ơng 5: Đồ thị (Graph) Trong r t nhi u ng d ng khác nhau c a lý thuy t đ th , mỗi c nh e =(u,v) c a nó đ c gán b i một s c(e) = d(u,v) gọi là trọng s c a c nh e. Đ th trong tr ng h p nh v y gọi là đ th trọng s . Trong tr ng h p đó, ma tr n k c a đ th đ c thay b i ma tr n trọng s c= { c[i,j], i, j= 1, 2, . . ., n. c[i,j] = d(i,j) nếu (i, j) ∈E, c[i,j] = θ nếu (i, j) ∉E. Trong đó, θ nh n các giá tr : 0, ∞, -∞ tuỳ theo từng tình hu ng c thể c a thu t toán. Ví d 3. Ma tr n k c a đ th có trọng s trong hình 5.10. 2 1 2 3 4 5 6 1 0 3 7 0 0 0 6 2 3 0 6 6 0 0 3 7 6 0 0 3 0 4 0 6 0 0 8 5 Hình 5.10. Đ th trọng s G. 5 0 0 3 8 0 9 6 0 0 0 5 9 0 3 6 6 4 8 5 1 7 9 3 3 5 u điểm c a ph ng pháp biểu di n đ th bằng ma tr n k (hoặc ma tr n trọng s ) là ta d dàng tr l i đ c câu h i: Hai đ nh u, v có k nhau trên đ th hay không và chúng ta ch m t đúng một phép so sánh. Nh c điểm lớn nh t c a nó là b t kể đ th có bao nhiêu c nh ta đ u m t n2 đ n v bộ nhớ để l u tr đ th . 5.2.2. Danh sách c nh (cung ) Trong tr ng h p đ th th a (đ th có s c nh m ≤ 6n), ng i ta th ng biểu di n đ th d ới d ng danh sách c nh. Trong phép biểu di n này, chúng ta s l u tr danh sách t t c các c nh (cung) c a đ th vô h ớng (có h ớng). Mỗi c nh (cung) e(x, y) đ c t ng ng với hai bi n dau[e], cuoi[e]. Nh v y, để l u tr đ th , ta c n 2m đ n v bộ nhớ. Nh c điểm lớn nh t c a ph ng pháp này là để nh n bi t nh ng c nh nào k với c nh nào chúng ta c n m phép so sánh trong khi duy t qua t t c m c nh (cung) c a đ th . N u là đ th có trọng s , ta c n thêm m đ n v bộ nhớ để l u tr trọng s c a các c nh. Ví d 4. Danh sách c nh (cung) c a đ th vô h ớng, đ th có h ớng, đ th trọng s : Dau 1 1 2 2 3 4 4 5 Cuoi 2 3 3 4 5 5 6 6 Danh sách c nh cung hình Dau 1 1 2 2 3 5 Cuoi 2 3 4 5 4 1 Đ th có h ớng 109 Dau 1 1 2 2 3 4 4 5 Cuoi 2 3 3 4 5 5 6 6 Trongso 3 7 6 6 3 8 5 9 Danh sách trọng s Ch ơng 5: Đồ thị (Graph) 5.2.3. Danh sách k Trong r t nhi u ng d ng, cách biểu di n đ th d ới d ng danh sách k th ng đ c s d ng. Trong biểu di n này, với mỗi đ nh v c a đ th chúng ta l u tr danh sách các đ nh k với nó mà ta ký hi u là Ke(v), nghĩa là Ke(v) = { u∈ V: (u, v)∈E}, Với cách biểu di n này, mỗi đ nh i c a đ th , ta làm t ng ng với một danh sách t t c các đ nh k với nó và đ c ký hi u là List(i). Để biểu di n List(i), ta có thể dùng các kiểu d li u kiểu t p h p, m ng hoặc danh sách liên k t. Ví d 5. Danh sách k c a đ th vô h ớng trong hình 5.8, đ th có h ớng trong hình 5.9 đ c biểu di n bằng danh sách k nh sau: List(i) Đ nh List(i) 1 2 3 2 1 3 3 1 4 Đ nh 1 3 2 4 2 4 5 2 5 3 4 2 5 6 5 1 5 3 4 6 6 4 5 5.3. CÁC THU T TOÁN TÌM KI M TRÊN Đ TH 5.3.1 Thu t toán tìm ki m theo chi u sâu R t nhi u thu t toán trên đ th đ c xây dựng dựa trên vi c duy t t t c các đ nh c a đ th sao cho mỗi đ nh đ c vi ng thĕm đúng một l n. Nh ng thu t toán nh v y đ c gọi là thu t toán tìm ki m trên đ th . Chúng ta cũng s làm quen với hai thu t toán tìm ki m c b n, đó là duy t theo chi u sâu (Depth First Search) và duy t theo chi u rộng (Breath First Search). T t ng c b n c a thu t toán tìm ki m theo chi u sâu là b t đ u t i một đ nh v0 nào đó, chọn một đ nh u b t kỳ k với v0 và l y nó làm đ nh duy t ti p theo. Cách duy t ti p theo đ c thực hi n t ng tự nh đ i với đ nh v0. Để kiểm tra vi c duy t mỗi đ nh đúng một l n, chúng ta s d ng một m ng g m n ph n t (t ng ng với n đ nh), n u đ nh th i đã đ c duy t, ph n t t ng ng trong m ng có giá tr FALSE. Ng c l i, n u đ nh ch a đ c duy t, ph n t t ng ng trong m ng có giá tr TRUE. Thu t toán tìm ki m theo chi u sâu b t đ u từ đ nh v nào đó s duy t t t c các đ nh liên thông với v. Thu t toán có thể đ c mô t bằng th t c đ qui DFS() trong đó: chuaxet - là m ng các giá tr logic đ c thi t l p giá tr TRUE void DFS(int v){ 110 Ch ơng 5: Đồ thị (Graph) Thĕm_Đ nh(v); chuaxet[v] = FALSE; for u ∈ke(v) { if (chuaxet[u] ) DFS( v); } } Th t c DFS() s thĕm t t c các đ nh cùng thành ph n liên thông với v mỗi đ nh đúng một l n. Để đ m b o duy t t t c các đ nh c a đ th (có thể có nhi u thành ph n liên thông), chúng ta ch c n thực hi n : for( i=1; i≤n; i++) chuaxet[i] = TRUE; for( i:=1;i≤ n; i++) if (chuaxet[i] ) DFS( i); Chú ý: Thu t toán tìm ki m theo chi u sâu d dàng áp d ng cho đ th có h ớng. Đ i với đ th có h ớng, chúng ta ch c n thay các c nh vô h ớng bằng các cung c a đ th có h ớng. Ví d 1. Áp d ng thu t toán tìm ki m theo chi u sâu với đ th trong hình sau: 2 6 8 7 1 4 5 3 10 11 12 9 13 Hình 5.11. Đ th vô h K t qu duy t: ng G 1, 2, 4 , 3, 6, 7, 8, 10, 5, 9, 13, 11, 12 5.3.2. Thu t toán tìm ki m theo chi u r ng (Breadth First Search) Để ý rằng, với thu t toán tìm ki m theo chi u sâu, đ nh thĕm càng muộn s tr thành đ nh sớm đ c duy t xong. Đó là k t qu t t y u vì các đ nh thĕm đ c n p vào stack trong th t c đ qui. Khác với thu t toán tìm ki m theo chi u sâu, thu t toán tìm ki m theo chi u rộng thay th vi c s d ng stack bằng hàng đ i queue. Trong th t c này, đ nh đ c n p vào hàng đ i đ u tiên là v, các đ nh k với v là v1, v2, . . ., vk đ c n p vào queue k ti p. Quá trình đ c thực t ng tự với các đ nh trong hàng đ i. Thu t toán dừng khi ta đã duy t h t các đ nh k với đ nh trong hàng đ i. Chúng ta có thể mô t thu t toán bằng th t c BFS nh d ới đây. 111 Ch ơng 5: Đồ thị (Graph) chuaxet- m ng kiểm tra các đ nh đã xét hay ch a; queue – hàng đ i l u tr các đ nh s đ c duy t c a đ th ; void BFS(int u){ queue = φ; u <= queue; (*nạp u vào hàng đợi*) chuaxet[u] = false; while (queue ≠ φ ){ queue<=p; (* lấy p ra từ stack*) Thăm_Đỉnh(p); for v ∈ ke(p) { if (chuaxet[v] ) { v<= queue; (*nạp v vào hàng đợi*) chuaxet[v] = false; } } } Th t c BFS s thĕm t t c các đ nh dùng thành ph n liên thông với u. Để thĕm t t c các đ nh c a đ th , chúng ta ch c n thực hi n gọi tới th t c BFS(). for( u=1; u≤n; u++) chuaxet[u] = TRUE; for(u=1; u≤n; u++) if (chuaxet[u]) BFS(u); đ Ví d . Áp d ng thu t toán tìm ki m theo chi u rộng với đ th trong hình 5.11 ta nh n c k t qu nh sau: 1, 2, 3, 11, 4, 6, 12, 13, 7, 8, 9, 10, 5; 5.3.3. Ki m tra tính liên thông c a đ th Một đ th có thể liên thông hoặc có thể không liên thông. N u đ th là liên thông (s thành ph n liên thông là 1), chúng ta ch c n gọi tới th t c DFS() hoặc BFS() một l n. N u đ th là không liên thông, khi đó s thành ph n liên thông c a đ th chính bằng s l n gọi tới th t c BFS() hoặc DFS(). Để xác đ nh s các thành ph n liên thông c a đ th , chúng ta s d ng một bi n mới solt để nghi nh n các đ nh cùng một thành ph n liên thông trong m ng chuaxet: - N u đ nh i ch a đ - N u đ nh i đ chuaxet[i]=solt; c duy t, chuaxet[i] có giá tr 0; c duy t thuộc thành ph n liên thông th j=solt, ta ghi nh n - Các đ nh cùng thành ph n liên thông n u chúng có cùng giá tr trong m ng chuaxet. void BFS(int u){ 112 Ch ơng 5: Đồ thị (Graph) queue= φ; u <= queue; (*n p u vào hàng đ i*) solt = solt+1; chuaxet[u] = solt;(*solt là bi n toàn c c thi t l p giá tr 0*) while (queue ≠ φ ) { queue<=p; (* l y p ra từ stack*) Thĕm_Đ nh(p); for v ∈ ke(p) { if (chuaxet[v] ){ v<= queue; (*n p v vào hàng đ i*) chuaxet[v] = solt; } } } } 5.3.4. Tìm đ ng đi gi a hai đ nh b t kỳ c a đ th Th t c BFS(s) hoặc DFS(s) cho phép ta duy t các đ nh cùng một thành ph n liên thông với đ nh s. Nh v y, n u trong s các đ nh liên thông với s ch a t thì ch c ch n có đ ng đi từ đ nh s đ n đ nh t. N u trong s các đ nh liên thông với đ nh s không ch a đ nh t thì không t n t i đ ng đi từ đ nh s đ n đ nh t. Do v y, chúng ta ch c n gọi tới th t c DFS(s) hoặc BFS(s) và kiểm tra xem đ nh t có thuộc thành ph n liên thông với s hay không. D ới đây là toàn vĕn ch ng trình tìm đ ng đi gi a hai đ nh c a đ th . #include <stdio.h> #include <conio.h> #include <io.h> #include <stdlib.h> #include <dos.h> #define MAX 100 #define TRUE 1 #define FALSE 0int n, truoc[MAX], chuaxet[MAX], queue[MAX]; int A[MAX][MAX]; int s, t; /* Breadth First Search */ void Init(void){ FILE *fp; int i, j; fp=fopen("lienth.IN", "r"); if(fp==NULL){ printf("\n Khong co file input"); delay(2000);return; } fscanf(fp,"%d", &n); printf("\n So dinh do thi:%d",n); 113 Ch ơng 5: Đồ thị (Graph) printf("\n Ma tran ke cua do thi:"); for(i=1; i<=n;i++){ printf("\n"); for(j=1; j<=n;j++){ fscanf(fp,"%d", &A[i][j]); printf("%3d", A[i][j]); } } for(i=1; i<=n;i++){ chuaxet[i]=TRUE; truoc[i]=0; } } void Result(void){ printf("\n\n"); if(truoc[t]==0){ printf("\n Khong co duong di tu %d den %d",s,t); getch(); return; } printf("\n Duong di tu %d den %d la:",s,t); int j = t;printf("%d<=", t); while(truoc[j]!=s){ printf("%3d<=",truoc[j]); j=truoc[j]; } printf("%3d",s); } void In(void){ printf("\n\n"); for(int i=1; i<=n; i++) printf("%3d", truoc[i]); } void BFS(int s) { int dauQ, cuoiQ, p, u;printf("\n"); dauQ=1;cuoiQ=1; queue[dauQ]=s;chuaxet[s]=FALSE; while (dauQ<=cuoiQ){ u=queue[dauQ]; dauQ=dauQ+1; printf("%3d",u); for (p=1; p<=n;p++){ 114 Ch ơng 5: Đồ thị (Graph) if(A[u][p] && chuaxet[p]){ cuoiQ=cuoiQ+1;queue[cuoiQ]=p; chuaxet[p]=FALSE;truoc[p]=u; } } } } void duongdi(void){ int chuaxet[MAX], truoc[MAX], queue[MAX]; Init();BFS(s);Result(); } void main(void){ clrscr(); printf("\n Dinh dau:"); scanf("%d",&s); printf("\n Dinh cuoi:"); scanf("%d",&t); Init();printf("\n");BFS(s); n();getch(); Result();getch(); } 5.4. ĐƯ NG ĐI VÀ CHU TRÌNH EULER Chu trình đ n trong đ th G đi qua mỗi c nh c a đ th đúng một l n đ c gọi là chu trình Euler. Đ ng đi đ n trong G đi qua mỗi c nh c a nó đúng một l n đ c gọi là đ ng đi Euler. Đ th đ c gọi là đ th Euler n u nó có chu trình Euler. Đ th có đ ng đi Euler đ c gọi là n a Euler. Rõ ràng, mọi đ th Euler đ u là n a Euler nh ng đi u ng c l i không đúng. Ví d 1. Xét các đ th G1, G2, G3 trong hình 5.12. a b a e d a b c c d e c G1 b d G2 e G3 Hình 5.12. Đ th vô h ng G1, G2, G3. Đ th G1 là đ th Euler vì nó có chu trình Euler a, e, c, d, e, b, a. Đ th G3 không có chu trình Euler nh ng ch a đ ng đi Euler a, c, d, e, b, d, a, b vì th G3 là n a Euler. G2 không có chu trình Euler cũng nh đ ng đi Euler. 115 Ch ơng 5: Đồ thị (Graph) Đ nh lý. Đ th vô h ớng liên thông G=<V, E> là đ th Euler khi và ch khi mọi đ nh c a G đ u có b c chẵn. Đ th vô h ớng liên thông G=<V, E> là đ th n a Euler khi và ch khi nó không có quá hai đ nh b c lẻ. Để tìm một chu trình Euler, ta thực hi n theo thu t toán sau: ƒ ƒ ƒ T o một m ng CE để ghi đ ng đi và một stack để x p các đ nh ta s xét. X p vào đó một đ nh tuỳ ý u nào đó c a đ th , nghĩa là đ nh u s đ c xét đ u tiên. Xét đ nh trên cùng c a ngĕn x p, gi s đ nh đó là đ nh v; và thực hi n: 9 N u v là đ nh cô l p thì l y v kh i ngĕn x p và đ a vào CE; 9 N u v là liên thông với đ nh x thì x p x vào ngĕn x p sau đó xoá b c nh (v, x); Quay l i b ớc 2 cho tới khi ngĕn x p rỗng thì dừng. K t qu đ đ c ch a trong CE theo th tự ng c l i. Th t c Euler_Cycle sau s cho phép ta tìm chu trình Euler. void Euler_Cycle(int u){ Stack=φ; CE=φ; u=>Stack; { n p u vào stack}; while (Stack≠φ) { x= top(Stack); { x là ph n t đ u stack } if (ke(x) ≠ φ) { y = Đ nh đ u trong danh sách ke(x); Stack<=y; { n p y vào Stack}; Ke(x) = Ke(x) \{y}; Ke(y) = Ke(y)\{x}; {lo i c nh (x,y) kh i đ th }; } else { x<= Stack; {l y x ra kh i stack}; CE <=x; { n p x vào CE;} } } Ví d . Tìm chu trình Euler trong hình 5.13. a b 4 f 1 2 8 c 3 5 6 7 d 10 Hình 5.13. Đ th vô h ng G. 9 116 e ng đi Euler Ch ơng 5: Đồ thị (Graph) Các b ớc thực hi n theo thu t toán s cho ta k t qu sau: B c Giá tr trong stack Giá tr trong CE C nh còn l i 1 F 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 2 f, a 2, 3, 4, 5, 6, 7, 8, 9, 10 3 f, a, c 3, 4, 5, 6, 7, 8, 9, 10 4 f,a,c,f 3, 4, 5, 6, 7, 9, 10 5 f, a, c f 3, 4, 5, 6, 7, 9, 10 6 f, a, c, b f 3, 4, 6, 7, 9, 10 7 f, a, c, b, d f 3, 4, 7, 9, 10 8 f, a, c, b, d,c f 3, 4, 7, 10 9 f, a, c, b, d f, c 3, 4, 7, 10 10 f, a, c, b, d, e f, c 3, 4, 7 11 f, a, c, b, d, e, b f, c 3, 4 12 f, a, c, b, d, e, b, a f, c 3 13 f, a, c, b, d, e, b, a, d f, c 14 f, a, c, b, d, e, b, a f, c, d 15 f, a, c, b, d, e, b f,c,d,a 16 f, a, c, b, d, e f,c,d,a,b 17 f, a, c, b, d f,c,d,a,b,e 18 f, a, c, b f,c,d,a,b,e,d 19 f, a, c f,c,d,a,b,e,d,b 20 f, a f,c,d,a,b,e,d,b,c 21 f f,c,d,a,b,e,d,b,c,a 22 f,c,d,a,b,e,d,b,c,a,f Một đ th không có chu trình Euler nh ng v n có thể có đ ng đi Euler. Khi đó, đ th có đúng hai đ nh b c lẻ, t c là tổng các s c nh xu t phát từ một trong hai đ nh đó là s lẻ. Một đ ng đi Euler ph i xu t phát từ một trong hai đ nh đó và k t thúc đ nh kia. Nh v y, thu t toán tìm đ ng đi Euler ch khác với thu t toán tìm chu trình Euler chỗ ta ph i xác đ nh điểm xu t phát c a đ ng đi. Để tìm t t c các đ thu t đ qui nh sau: ng đi Euler c a một đ th n đ nh, m c nh, ta có thể dùng k ƒ B ớc 1. T o m ng b có độ dài m + 1 nh một ngĕn x p ch a đ i=1 (xét đ nh th nh t c a đ ng đi); ng đi. Đặt b[0]=1, ƒ B ớc 2. L n l t cho b[i] các giá tr là đ nh k với b[i-1] mà c nh (b[i-1],b[i]) không trùng với nh ng c nh đã dùng từ b[0] đ n b[i-1]. Với mỗi giá tr c a b[i], ta kiểm tra: 9 N u i<m thì tĕng i lên 1 đ n v (xét đ nh ti p theo) và quay l i b ớc 2. 9 N u i=m thì dãy b chính là một đ 117 ng đi Euler. Ch ơng 5: Đồ thị (Graph) 5.5. ĐƯ NG ĐI VÀ CHU TRÌNH HAMILTON Với đ th Euler, chúng ta quan tâm tới vi c duy t các c nh c a đ th mỗi c nh đúng một l n, thì trong m c này, chúng ta xét đ n một bài toán t ng tự nh ng ch khác nhau là ta ch quan tâm tới các đ nh c a đ th , mỗi đ nh đúng một l n. Sự thay đổi này t ng nh không đáng kể, nh ng thực t có nhi u sự khác bi t trong khi gi i quy t bài toán. Đ nh nghƿa. Đ ng đi qua t t c các đ nh c a đ th mỗi đ nh đúng một l n đ c gọi là đ ng đi Hamilton. Chu trình b t đ u t i một đ nh v nào đó qua t t c các đ nh còn l i mỗi đ nh đúng một l n sau đó quay tr l i v đ c gọi là chu trình Hamilton. Đ th đ c gọi là đ th Hamilton n u nó ch a chu trình Hamilton. Đ th ch a đ ng đi Hamilton đ c gọi là đ th n a Hamilton. Nh v y, một đ th Hamilton bao gi cũng là đ th n a Hamilton nh ng đi u ng l i không luôn luôn đúng. Ví d sau s minh họa cho nh n xét này. c Ví d . Đ th đ thi hamilton G3, n a Hamilton G2 và G1. a b c a b c d G1 a c G2 b d G3 Hình 5.14. Đ th đ thi hamilton G3, n a Hamilton G2 và G1. Cho đ n nay, vi c tìm ra một tiêu chu n để nh n bi t đ th Hamilton v n còn m , mặc dù đây là v n đ trung tâm c a lý thuy t đ th . H n th n a, cho đ n nay cũng v n ch a có thu t toán hi u qu để kiểm tra một đ th có ph i là đ th Hamilton hay không. Để li t kê t t c các chu trình Hamilton c a đ th , chúng ta có thể s d ng thu t toán sau: void Hamilton(int k){ (* Li t kê các chu trình Hamilton c a đ th bằng cách phát triển dãy đ nh (X[1], X[2], . . ., X[k-1] ) c a đ th G = (V, E) *) for y∈ Ke(X[k-1]) { if ((k==n+1) && (y == v0)) Ghinhan(X[1], X[2], . . ., X[n], v0); else { X[k]=y; chuaxet[y] = false; Hamilton(k+1); chuaxet[y] = true; }; } 118 Ch ơng 5: Đồ thị (Graph) 5.6. CÂY BAO TRÙM 5.6.1. Khái ni m và đ nh nghƿa Đ nh nghƿa 1. Ta gọi cây là đồ thị vô h ớng liên thông không có chu trình. Đồ thị không có chu trình đ ợc gọi là rừng. Nh v y, rừng là đ th mà mỗi thành ph n liên thông c a nó là một cây. Ví d . Rừng g m 3 cây trong hình 5.15. T1 T2 T3 Hình 5.15 . R ng g m 3 cây T1, T2, T3. Cây đ c coi là d ng đ th đ n gi n nh t c a đ th . Đ nh lý sau đây cho ta một s tính ch t c a cây. Đ nh lý. Giả sử G=<V, E> là đồ thị vô h ớng n đỉnh. Khi đó những khẳng định sau là t ơng đ ơng a. G là một cây. b. G là đ th vô h ớng liên thông không có chu trình. c. G liên thông và có đúng n-1 c nh. d. Gi a hai đ nh b t kỳ c a G có đúng một đ ng đi. e. G liên thông và mỗi c nh c a nó đ u là c u. f. G không ch a chu trình nh ng h c thêm vào nó một c nh ta thu đ c đúng một chu trình. Đ nh nghƿa 2. Cho G là đồ thị vô h ớng liên thông. Ta gọi đồ thị con T của G là một cây bao trùm hay cây khung nếu T thoả mãn hai điều kiện: g. T là một cây; h. T p đ nh c a T bằng t p đ nh c a G. Đ i với cây bao trùm, chúng ta quan tâm tới nh ng bài toán c b n sau: Bài toán 1. Cho G=<V, E> là đ th vô h ớng liên thông. Hãy xây dựng một cây bao trùm c a G. 119 Ch ơng 5: Đồ thị (Graph) Bài toán 2. Cho G = <V, E> là đ th vô h ớng liên thông có trọng s . Hãy tìm cây bao trùm nh nh t c a G. 5.6.2. Tìm m t cây bao trùm trên đ th Để tìm một cây bao trùm trên đ th vô h ớng liên thông, có thể s d ng k thu t tìm ki m theo chi u rộng hoặc tìm ki m theo chi u sâu để thực hi n. Gi s ta c n xây dựng một cây bao trùm xu t phát t i đ nh u nào đó. Trong c hai tr ng h p, mỗi khi ta đ n đ c đ nh v t c (chuaxet[v] = true) từ đ nh u thì c nh (u,v) đ c k t n p vào cây bao trùm. Hai k thu t này đ c thể hi n trong hai th t c STREE_DFS(u) và STREE_BFS(v) nh sau: void STREE_DFS( int u){ /* Tìm ki m theo chi u sâu, áp d ng cho bài toán xây dựng cây bao trùm c a đ th vô h ớng liên thông G=(V, E); các bi n chuaxet, Ke, T là toàn c c */ chuaxet[u] = true; for v∈ Ke(u) { if (chuaxet[v]){ T = T ∪ (u,v); STREE_DFS(v); } } } void STREE_BFS(int u) { QUUE=φ; QUEUE<= u; /* đ a u vào hàng đ i*/ chuaxet[u] = false; while (QUEUE≠ φ ) { v<= QUEUE; /* l y v kh i hàng đ i */ for p ∈ Ke(v) { if (chuaxet[u]) { QUEUE<= u; chuaxet[u] = false; T = T∪(v, p); } } } } /* Main program */ for u ∈ V { chuaxet[u] = true; T = φ; STREE_BFS(root); } 120 Ch ơng 5: Đồ thị (Graph) 5.6.3. Tìm cây bao trùm ng n nh t Bài toán tìm cây bao trùm nh nh t là một trong nh ng bài toán t i u trên đ th có ng d ng trong nhi u lĩnh vực khác nhau c a thực t . Bài toán đ c phát biểu nh sau: Cho G=<V, E> là đ th vô h ớng liên thông với t p đ nh V = {1, 2, . . ., n } và t p c nh E g m m c nh. Mỗi c nh e c a đ th đ c gán với một s không âm c(e) đ c gọi là độ dài c a nó. Gi s H = <V, T> là một cây bao trùm c a đ th G. Ta gọi độ dài c(H) c a cây bao trùm H là tổng độ dài các c nh c a nó: c( H ) = ∑ c(e) . Bài toán đ e∈T c đặt ra là, trong s các cây khung c a đ th hãy tìm cây khung có độ dài nh nh t c a đ th . Để gi i bài toán cây bao trùm nh nh t, chúng ta có thể li t kê toàn bộ cây bao trùm và chọn trong s đó một cây nh nh t. Ph ng án nh v y thực sự không kh thi vì s cây bao trùm c a đ th là r t lớn c nn-2, đi u này không thể thực hi n đ c với đ th với s đ nh c ch c. Để tìm một cây bao trùm chúng ta có thể thực hi n theo các b ớc nh sau: ƒ ƒ ƒ B ớc 1. Thi t l p t p c nh c a cây bao trùm là φ . Chọn c nh e = (i, j) có độ dài nh nh t bổ sung vào T. B ớc 2. Trong s các c nh thuộc E \ T, tìm c nh e = (i1, j1) có độ dài nh nh t sao cho khi bổ sung c nh đó vào T không t o nên chu trình. Để thực hi n đi u này, chúng ta ph i chọn c nh có độ dài nh nh t sao cho hoặc i1∈ T và j1∉ T, hoặc j1∈ T và i1∉ T. B ớc 3. Kiểm tra xem T đã đ n-1 c nh hay ch a? N u T đ n-1 c nh thì nó chính là cây bao trùm ng n nh t c n tìm. N u ch a đ n-1 c nh thì thực hi n l i b ớc 2. Ví d . Tìm cây bao trùm nh nh t c a đ th trong hình 5.16. 2 20 4 33 1 8 18 16 9 6 17 14 3 ƒ ƒ 4 5 Hình 5.16. Đ th vô h ng liên thông G=(V, E) B ớc 1. Đặt T=φ. Chọn c nh (3, 5) có độ dài nh nh t bổ sung vào T. Buớc 2. Sau ba l n lặp đ u tiên, ta l n l t bổ sung vào các c nh (4,5), (4, 6). Rõ ràng, n u bổ sung vào c nh (5, 6) s t o nên chu trình vì đ nh 5, 6 đã có mặt trong T. Tình hu ng t ng tự cũng x y ra đ i với c nh (3, 4) là c nh ti p theo c a dãy. Ti p đó, ta bổ sung hai c nh (1, 3), (2, 3) vào T. 121 Ch ơng 5: Đồ thị (Graph) ƒ Buớc 3. T p c nh trong T đã đ n-1 c nh: T={ (3, 5 (2,3)} chính là cây bao trùm ng n nh t. ), (4,6), (4,5), (1,3), 5.6.4. Thu t toán Kruskal Thu t toán xây dựng t p c nh T c a cây khung nh nh t H=<V, T> theo từng b ớc nh sau: a. b. c. S p x p các c nh c a đ th G theo th tự tĕng d n c a trọng s c nh; Xu t phát từ t p c nh T=φ, mỗi b ớc, ta s l n l t duy t trong danh sách các c nh đã đ c s p x p, từ c nh có trọng s nh đ n c nh có trọng s lớn để tìm ra c nh mà khi bổ sung nó vào T không t o thành chu trình trong t p các c nh đã đ c bổ sung vào T tr ớc đó; Thu t toán s k t thúc khi ta thu đ Thu t toán đ void c t p T g m n-1 c nh. c mô t thông qua th t c Kruskal nh sau: Kruskal(void) { T = φ; While( | T | < (n-1) and (E≠ φ )) { Chọn c nh e ∈E là c nh có độ dài nh nh t; E = E\ {e}; if (T ∪ {e} không t o nên chu trình ) T = T ∪ {e}; } if ( | T | <n-1) <Đ th không liên thông>; } 5.6.5. Thu t toán Prim Thu t toán Kruskal làm vi c kém hi u qu đ i với nh ng đ th có s c nh kho ng m=n(n-1)/2. Trong nh ng tình hu ng nh v y, thu t toán Prim t ra hi u qu h n. Thu t toán Prim còn đ c mang tên là ng i láng gi ng g n nh t. Trong thu t toán này, b t đ u t i một đ nh tuỳ ý s c a đ th , n i s với đ nh y sao cho trọng s c nh c[s, y] là nh nh t. Ti p theo, từ đ nh s hoặc y tìm c nh có độ dài nh nh t, đi u này d n đ n đ nh th ba z và ta thu đ c cây bộ ph n g m 3 đ nh 2 c nh. Quá trình đ c ti p t c cho tới khi ta nh n đ c cây g m n-1 c nh, đó chính là cây bao trùm nh nh t c n tìm. Trong quá trình thực hi n thu t toán, mỗi b ớc, ta có thể nhanh chóng chọn đ nh và c nh c n bổ sung vào cây khung, các đ nh c a đ th đ c s đ c gán các nhãn. Nhãn c a một đ nh v g m hai ph n, [d[v], near[v]]. Trong đó, ph n th nh t d[v] dùng để ghi nh n độ dài c nh nh nh t trong s các c nh n i đ nh v với các đ nh c a cây khung đang xây dựng. Ph n th hai, near[v] ghi nh n đ nh c a cây khung g n v nh t. Thu t toán Prim đ c mô t thông qua th t c sau: 122 Ch ơng 5: Đồ thị (Graph) void Prim(void) { (*b ớc kh i t o*) Chọn s là một đ nh nào đó c a đ th ; VH = { s }; T = φ; d[s] = 0; near[s] = s; For v∈ V\VH { D[v] = C[s, v]; near[v] = s; } (* B ớc lặp *) Stop = False; While (! stop) { Tìm u∈ V\VH tho mãn : d[u] = min { d[v] với u∈V\VH}; VH = VH∪ {u}; T = T ∪ {u, near[u] }; If (| VH | == n ) { H = (VH, T) là cây khung nh nh t c a đ th ; Stop := TRUE; } Else For (v ∈ V\VH ) { If (d[v] > C[u, v]) { D[v] = C[u, v]; Near[v] = u; } } } 5.7. BÀI TOÁN TÌM ĐƯ NG ĐI NG N NH T 5.7.1. Phát bi u bài toán Xét đ th có h ớng G=<V, E>; trong đó | V| = n, | E | = m. Với mỗi cung (u,v)∈E, ta đặt t ng ng với nó một s thực A(u,v) đ c gọi là trọng s c a cung. Ta s đặt A[u,v]=∞ n u (u,v)∉E. N u dãy v0, v1, . . . , vk là một đ đ c gọi là độ dài c a đ ng đi. ng đi trên G thì ∑ p i =1 A[vi −1 , vi ] Bài toán tìm đ ng đi ng n nh t trên đ th có h ớng d ới d ng tổng quát có thể đ c phát biểu d ới d ng sau: tìm đ ng đi ng n nh t từ một đ nh xu t phát s∈V (đ nh ngu n) đ n đ nh cu i t∈V (đ nh đích). Đ ng đi nh v y đ c gọi là đ ng đi ng n nh t từ s đ n t, độ dài c a đ ng đi d(s,t) đ c gọi là kho ng cách ng n nh t từ s đ n t (trong tr ng h p tổng quát d(s,t) có thể âm). N u nh không t n t i đ ng đi từ s đ n t thì độ dài đ ng đi d(s,t)=∞. N u nh mỗi chu trình trong đ th đ u có độ dài d ng thì trong đ ng đi ng n nh t s không có đ nh nào b lặp l i, đ ng đi nh v y đ c gọi là đ ng đi c b n. N u nh đ th t n t i một chu trình nào đó có độ dài âm , thì đ ng đi ng n nh t có thể 123 Ch ơng 5: Đồ thị (Graph) không xác đ nh, vì ta có thể đi qua chu trình âm đó một s l n đ lớn để độ dài c a nó nh h n b t kỳ một s thực cho tr ớc nào. 5.7.2. Thu t toán Dijkstra Thu t toán tìm đ ng đi ng n nh t từ đ nh s đ n các đ nh còn l i đ c Dijkstra đ ngh áp d ng cho tr ng h p đ th có trọng s không âm. Thu t toán đ c thực hi n trên c s gán nhãn t m th i cho các đ nh. Nhãn c a mỗi đ nh cho bi t c n trên c a độ dài đ ng đi ng n nh t tới đ nh đó. Các nhãn này s đ c bi n đổi (tính l i) nh một th t c lặp, mà mỗi b ớc lặp một s đ nh s có nhãn không thay đổi, nhãn đó chính là độ dài đ ng đi ng n nh t từ s đ n đ nh đó. Thu t toán có thể đ c mô t bằng th tực Dijkstra nh sau: void Dijkstra(void) { (*Đ u vào G=(V, E) với n đ nh có ma tr n trọng s A[u,v]≥ 0; s∈V *) (*Đ u ra là kho ng cách nh nh t từ s đ n các đ nh còn l i d[v]: v∈V. Truoc[v] ghi l i đ nh tr ớc v trong đ ng đi ng n nh t từ s đ n v*) (* B ớc 1: Kh i t o nhãn t m th i cho các đ nh*) for (v=1; v≤n; v++){ d[v] = A[s,v]; truoc[v]=s; } d[s]=0; T = V\{s};(*T là t p đ nh có nhãn t m th i*) while (T!=φ ) { (* b ớc lặp *) Tìm đ nh u∈T sao cho d[u] = min { d[z] : z∈T} T= T\{u}; (*c đ nh nhãn đ nh u*); For (v∈T) { (* Gán l i nhãn cho các đ nh trong T*) If ( d[v] > d[u] + A[u, v] ) { d[v] = d[u] + A[u, v]; truoc[v] =u; } } } } 5.7.3. Thu t toán Floy Để tìm đ ng đi ng n nh t gi a t t c các cặp đ nh c a đ th , chúng ta có thể s d ng n l n thu t toán Ford_Bellman hoặc Dijkstra (trong tr ng h p trọng s không âm). Tuy nhiên, trong c hai thu t toán đ c s d ng đ u có độ ph c t p tính toán lớn (chí ít là O(n3)). Trong tr ng h p tổng quát, ng i ta th ng dùng thu t toán Floy đ c mô t nh sau: void Floy(){ (* Tìm đ ng đi ng n nh t gi a t t c các cặp đ nh 124 Ch ơng 5: Đồ thị (Graph) Input: Đ th cho b i ma tr n trọng s a[i, j], i, j = 1, 2, . . ., n. Output:- Ma tr n đ ng đi ng n nh t gi a các cặp đ nh d[i, j], i, j = 1, 2, . . .,n; d[i,j] là độ dài ng n nh t từ i đ n j. Ma tr n ghi nh n đ ng đi p[i, j], i, j = 1, 2, . . ., n p[i, j] ghi nh n đ nh đi tr ớc đ nh j trong đ ng đi ng n nh t;*) (*b ớc kh i t o*) for( i=1; i≤; i++) for( j =1; j≤n; j++) { d[i,j] = a[i, j]; p[i,j] = i; } (*b ớc lặp *) for (k=1; k≤n; k++) for( i=1; i≤n; i++) for (j =1; j≤n; j++) if (d[i,j] > d[i, k] + d[k, j]) { d[i, j] = d[i, k] + d[k, j]; p[i,j] = p[k, j]; } } B n đọc có thể tìm th y nh ng cài đặt c thể các thu t toán trên đ th thông qua các tài li u [1], [5]. 125 Ch ơng 5: Đồ thị (Graph) NH NG N I DUNG C N GHI NH 9 N m v ng nh ng khái ni m và đ nh nghĩa c b n c a đ th . 9 Hiểu đ c các ph ng pháp biểu di n đ th trên máy tính. 9 N m v ng đ c các thu t toán tìm ki m trên đ th : thu t toán tìm ki m theo chi u rộng, thu t toán tìm ki m theo chi u sâu và ng d ng c a nó trong bài toán tìm đ ng đi gi a hai đ nh c a đ th cũng nh trong tính toán các thành ph n liên thông c a đ th . 9 N m v ng sự khác bi t gi a đ th Euler và đ th Hamilton cùng với thu t toán tìm đ ng đi và chu trình trên các đ th Euler & Hamilton. 9 N m v ng bài toán tìm cây bao trùm & tìm cây bao trùm nh nh t. 9 Hiểu & cài đặt nhu n nhuy n các thu t toán tìm đ đ nh c a đ th trọng s . 126 ng đi ngẵn nh t gi a các cặp Ch ơng 5: Đồ thị (Graph) BÀI T P CHƯƠNG 5 Bài 1. Cho tr ớc ma tr n k c a đ th . Hãy vi t ch th đó. ng trình t o ra danh sách k c a đ Bài 2. Cho tr ớc danh sách k c a đ th , hãy t o nên ma tr n k c a đ th . Bài 3. Một bàn c 8×8 đ c đánh s theo cách sau: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 Mỗi ô có thể coi là một đ nh c a đ th . Hai đ nh đ c coi là k nhau n u một con vua đặt ô này có thể nh y sang ô kia sau một b ớc đi. Ví d : ô 1 k với ô 2, 9, 10, ô 11 k với 2, 3, 4, 10, 12, 18, 19, 20. Hãy vi t ch ng trình t o ma tr n k c a đ th , k t qu in ra file king.out. Bài 4. Bàn c 8×8 đ c đánh s nh bài trên. Mỗi ô có thể coi là một đ nh c a đ th . Hai đ nh đ c gọi là k nhau n u một con mã đặt ô này có thể nh y sang ô kia sau một n ớc đi. Ví d ô 1 k với 11, 18, ô 11 k với 1, 5, 17, 21, 26, 28. Hãy vi t ch ng trình l p ma tr n k c a đ th , k t qu ghi vào file ma.out. Bài 5. Hãy l p ch ng trình tìm một đ đ c nh p từ bàn phím). ng đi c a con mã trên bàn c từ ô s đ n ô t (s, t Bài 6. Cho C s d li u ghi l i thông tin v N Tuyến bay (N<=100) c a một hãng hàng không. Trong đó, thông tin v mỗi tuy n bay đ c mô t b i: Điểm kh i hành (departure), điểm đ n (destination), kho ng cách (lenght). Departure, destination là một xâu kí tự độ dài không quá 32, không ch a d u tr ng gi a, Length là một s nh h n 32767. Ta gọi “Hành trình bay” từ điểm kh i hành A tới điểm đ n B là dãy các hành trình [A, A1, n1], [A1, A2, n2] . . .[Ak, B,nk] với Ai là điểm đ n c a tuy n i nh ng l i là điểm kh i 127 Ch ơng 5: Đồ thị (Graph) hành c a tuy n i +1, ni là kho ng cách c a tuy n bay th i (1<=i<k). Trong đó, kho ng cách c a hành trình là tổng kho ng cách c a các tuy n mà hành trình đi qua (n1+n2+. .+nk). Cho file d li u kiểu text hanhtrinh.in đ c ghi theo từng dòng, s các dòng trong file d li u không v t quá N, trên mỗi dòng ghi l i thông tin v một tuy n bay, trong đó departure, destination, length đ c phân bi t với nhau b i một hoặc vài d u tr ng. Hãy tìm gi i pháp để tho mãn nhu c u c a khách hàng đi từ A đ n B theo một s tình hu ng sau: Tìm hành trình có kho ng cách bé nh t từ A đ n B. In ra màn hình từng điểm mà hành trình đã qua và kho ng cách c a hành trình. N u hành trình không t n t i hãy đ a ra thông báo “Hành trình không t n t i”. Ví d v C s d li u hanhtrinh.in New_York Chicago 1000 Chicago Denver 1000 New_York Toronto 800 New_York Denver 1900 Toronto Calgary 1500 Toronto Los_Angeles 1800 Toronto Chicago 500 Denver Urbana 1000 Denver Houston 1500 Houston Los_Angeles 1500 Denver Los_Angeles 1000 Với điểm đi : New_York, điểm đ n : Los_Angeles ; chúng ta s có k t qu sau: Hành trình ng n nh t: New_York to Toronto to Los_Angeles; Kho ng cách: 2600. Bài 7. K t c thành công với kh i l p ph ng th n bí, Rubik sáng t o ra d ng phẳng c a trò ch i này gọi là trò ch i các ô vuông th n bí. Đó là một b ng g m 8 ô vuông bằng nhau nh hình 1. Chúng ta qui đ nh trên mỗi ô vuông có một màu khác nhau. Các màu đ c kí hi u b i 8 s nguyên t ng ng với tám màu c b n c a màn hình EGA, VGA nh hình 1. Tr ng thái c a b ng các màu đ c cho b i dãy kí hi u màu các ô đ c vi t l n l t theo chi u kim đ ng h b t đ u từ ô góc trên bên trái và k t thúc ô góc d ới bên trái. Ví d : tr ng thái trong hình 1 đ c cho b i dãy các màu t ng ng với dãy s (1, 2, 3, 4, 5 , 6, 7, 8). Tr ng thái này đ c gọi là tr ng thái kh i đ u. Bi t rằng ch c n s d ng 3 phép bi n đổi c b n có tên là ‘A’, ‘B’, ‘C’ d ới đây bao gi cũng chuyển đ c từ tr ng thái kh i đ u v tr ng thái b t kỳ: 128 Ch ơng 5: Đồ thị (Graph) ‘A’ : đổi chỗ dòng trên xu ng dòng d ới. Ví d sau phép bi n đổi A, hình 1 s tr thành hình 2: ‘B’ : thực hi n một phép hoán v vòng quanh từ trái sang ph i trên từng dòng. Ví d sau phép biển đổi B hình 1 s tr thành hình 3: ‘C’ : quay theo chi u kim đ ng h b n ô tr thành hình 4: Hình 1 gi a. Ví d sau phép bi n đổi C hình 1 Hình 2 Hình 3 Hình 4 1 2 3 4 8 7 6 5 4 1 2 3 1 7 2 4 8 7 6 5 1 2 3 4 5 8 7 6 8 6 3 5 Cho file d li u Input.txt ghi l i 8 s nguyên trên một dòng, mỗi s đ c phân bi t với nhau b i một d u tr ng ghi l i tr ng thái đích. Hãy tìm dãy các phép bi n đổi s b n để đ a tr ng thái kh i đ u v tr ng thái đích sao cho s các phép bi n đổi là ít nh t có thể đ c. D li u ra đ c ghi l i trong file Output.txt, dòng đ u tiên ghi l i s các phép bi n đổi, nh ng dòng ti p theo ghi l i tên c a các thao tác c b n đã thực hi n, mỗi thao tác c b n đ c vi t trên một dòng. B n s đ c thêm 20 điểm n u s d ng b ng màu thích h p c a màn hình để mô t l i các phép bi n đổi tr ng thái c a trò ch i. Ví d với tr ng thái đích d ới đây s cho ta k t qu nh sau: Input.txt Output.txt 2 6 8 4 5 7 3 1 7 B C A B C C B Bài 8. Cho một m ng thông tin g m N nút. Trong đó, đ ng truy n tin hai chi u trực ti p từ nút i đ n nút j có chi phí truy n thông t ng ng là một s nguyên A[i,j] = A[j,i], với A[i,j]>=0, i ≠ j. N u đ ng truy n tin từ nút i1 đ n nút ik ph i thông qua các nút i2, . . ik-1 thì chi phí truy n thông đ c tính bằng tổng các chi phí truy n thông A[i1,i2], A[i2,i3], . . . A[ik-1,ik]. Cho tr ớc hai nút i và j. Hãy tìm một đ ng truy n tin từ nút i đ n nút j sao cho chi phí truy n thông là th p nh t. 129 Ch ơng 5: Đồ thị (Graph) D li u vào đ c cho b i file TEXT có tên INP.NN. Trong đó, dòng th nh t ghi ba s N, i, j, dòng th k + 1 ghi k-1 s A[k,1], A[k,2], . . , A[k,k-1], 1<=k<=N. K t qu thông báo ra file TEXT có tên OUT.NN. Trong đó, dòng th nh t ghi chi phí truy n thông th p nh t từ nút i đ n nút j, dòng th 2 ghi l n l t các nút trên đ ng truy n tin có chi phí truy n thông th p nh t từ nút i tới nút j. Bài 9. Cho một m ng thông tin g m N nút. Trong đó, đ ng truy n tin hai chi u trực ti p từ nút i đ n nút j có chi phí truy n thông t ng ng là một s nguyên A[i,j] = A[j,i], với A[i,j]>=0, i ≠ j. N u đ ng truy n tin từ nút i1 đ n nút ik ph i thông qua các nút i2, . . ik-1 thì chi phí truy n thông đ c tính bằng tổng các chi phí truy n thông A[i1,i2], A[i2,i3], . . . A[ik-1,ik]. Bi t rằng, gi a hai nút b t kỳ c a m ng thông tin đ u t n t i ít nh t một đ ng truy n tin. Để ti t ki m đ ng truy n, ng i ta tìm cách lo i b đi một s đ ng truy n tin mà v n đ m b o đ c tính liên thông c a m ng. Hãy tìm một ph ng án lo i b đi nh ng đ ng truy n tin, sao cho ta nh n đ c một m ng liên thông có chi phí t i thiểu nh t có thể đ c. D li u vào đ c cho b i file TEXT có tên INP.NN. Trong đó, dòng th nh t ghi s N, dòng th k + 1 ghi k-1 s A[k,1], A[k,2], . . , A[k,k-1], 1<=k<=N. K t qu thông báo ra file TEXT có tên OUT.NN trong đó dòng th nh t ghi chi phí truy n thông nh nh t trong toàn m ng. Từ dòng th 2 ghi l n l t các nút trên đ ng truy n tin, mỗi đ ng truy n ghi trên một dòng. Bài 10. Cho file d li u đ điểm s đ n t. c tổ ch c gi ng nh bài 6.6. Hãy tìm t t c các hành trình đi từ Bài 11. Cho file d li u đ c tổ ch c gi ng nh bài 6.6. Hãy tìm hành trình đi từ điểm s đ n t sao cho hành trình đi qua nhi u node nh t. Bài 12. Cho file d li u đ c tổ ch c gi ng nh bài 6.6. Hãy tìm hành trình đi từ điểm s đ n t sao cho hành trình đi qua ít node nh t. Bài 13. Tìm hiểu thu t toán leo đ i trên đ th và ng d ng c a nó trong lĩnh vực trí tu nhân t o. 130 Ch ơng 6: Sắp xếp và tìm kiếm (sorting and searching) CHƯƠNG 6: SẮP XẾP VÀ TÌM KIẾM (SORTING AND SEARCHING) S p x p & tìm ki m là bài toán c b n nh t c a tin học. Có thể nói, mọi t ng tác gi a con ng i và h th ng máy tính v b n ch t đ u là tìm ki m và thu th p thông tin. n sau các quá trình tìm ki m là vi c s p x p các đ i t ng theo một tr t tự nào đó để quá trình tìm ki m di n ra nhanh nh t, chính xác và hi u qu nh t đó là ý nghĩa c b n c a quá trình s p x p. Nội dung chính c a ch ng này t p chung vào các gi i thu t s p x p và tìm ki m c b n d ới đây: 9 Gi i thu t Selection Sort, Gi i thu t Insert Sort, Gi i thu t Bubble Sort, Gi i thu t Shaker Sort, Gi i thu t Quick Sort, Gi i thu t Heap Sort, và gi i thu t Merge Sort. 9 Tìm ki m tu n tự (Sequential), tìm ki m nh phân (Binary Search) & tìm ki m trên cây nh phân (Binary Search). B n đọc có thể tìm th y nh ng cài đặt c thể và nh ng ki n th c sâu h n trong tài li u [1] & [6]. 6.1. Đ T BÀI TOÁN S p x p là quá trình b trí l i các ph n t c a một t p đ i t ng nào đó theo một th tự n đ nh tĕng d n (increasing), hoặc gi m d n (decreasing). Bài toán s p x p xu t hi n trong b t kỳ lĩnh vực nào c a tin học, ph c v nh ng ng d ng riêng c a h th ng, từ nh ng ng d ng n bên trong c a H đi u hành nh bài toán đi u khiển quá trình ( Proccess Control Problem), bài toán l p l ch cho CPU (CPU Schedulling), bài toán qu n lý bộ nhớ (Memory Management) . . . cho tới nh ng ng d ng thông th ng nh s p x p dãy s , s p x p các từ, các câu, các b n ghi theo th tự đ u có liên quan tới quá trình s p x p. T p đ i t ng c n đ c s p x p có thể xu t hi n d ới nhi u d ng khác nhau, các đ i t ng đó có thể là các đ i t ng d li u kiểu c b n nh s p x p dãy s , s p x p kí tự, s p x p string hoặc là các đ i t ng tổng quát nh một c u trúc bao g m một s tr ng thông tin ph n ánh đ i t ng. Chúng ta qui ớc đ i t ng c n đ c s p x p là các c u trúc, và quá trình s p x p đ c thực hi n trên một tr ng nào đó gọi là tr ng khoá. Có nhi u thu t toán s p x p khác nhau để s p x p các đ i t ng. Tuy nhiên, để lựa chọn một thu t toán s p x p t t, chúng ta c n đánh giá thu t toán theo các hai khía c nh: đó là sự chi m d ng bộ nhớ khi áp d ng gi i thu t và th i gian thực hi n gi i thu t. Đ i với th i gian thực hi n gi i thu t, chúng ta cũng c n đánh giá chi phí th i gian trong tr ng h p t t nh t, trung bình và x u nh t đ i với ngu n d li u vào. Chúng ta cũng ch đ a ra nh ng 131 Ch ơng 6: Sắp xếp và tìm kiếm (sorting and searching) k thu t l p trình, thông qua gi i thu t và k t qu đánh giá thu t toán mà không ch ng minh l i nh ng k t qu đó, vì nó đã đ c trình bày trong một chuyên đ khác c a tin học. Nh ng thu t toán s p x p và tìm ki m s đ c bàn lu n trong ch ng này bao g m các thu t toán s p x p đ n gi n nh : chọn trực ti p (Selection), thu t toán s i bọt (Bubble), thu t toán chèn trực ti p (Insertion), các thu t toán s p x p nhanh nh quick sort, merge sort, heap sort. Trong t t c các ví d minh họa cho gi i thu t s p x p và tìm ki m, chúng ta s s d ng t p các s nguyên d ới đây làm ví d s p x p. Dãy s nguyên này s không đ c nh c l i trong khi gi i thích mỗi thu t toán s p x p. 42 23 74 11 65 58 94 36 99 87 6.2. GI I THU T SELECTION SORT Nội dung c a Selection Sort là l n l t chọn ph n t nh nh t trong dãy ch s k1, k2,. . ., kn với i = 0, 1, . .,n; ki< k i+1 < . . ., kn và đổi chỗ cho ph n t th ki. Nh v y, sau j =n-1 l n chọn, chúng ta s só dãy khoá đ c s p x p theo th tự tĕng d n. Đ i với dãy s trên, chúng ta s thực hi n nh sau: ƒ L n chọn th 0: Tìm trong kho ng từ 0 đ n n-1 bằng cách thực hi n n- 1 l n so v trí 0. sánh để xác đ nh ph n t min0 và đổi chỗ cho ph n t ƒ L n chọn th 1: Tìm trong kho ng từ 1 đ n n-1 bằng cách thực hi n n- 2 l n so v trí 1. sánh để xác đ nh ph n t min1 và đổi chỗ cho ph n t ƒ .......................................................... ƒ L n chọn th i: Tìm trong kho ng từ i đ n n-1 bằng cách thực hi n n- i l n so v trí i. sánh để xác đ nh ph n t mini và đổi chỗ cho ph n t ƒ L n chọn th n-2: Tìm trong kho ng từ n-2 đ n n-1 bằng cách thực hi n 1 l n so v trí n-2. sánh để xác đ nh ph n t minn-2 và đổi chỗ cho ph n t Độ ph c t p tính toán c a gi i thu t Selection Sort là: Cmin=Cmax=Ctb = n (n-1)/2 Quá trình s p x p dãy s đ c minh họa thông qua b ng sau: i ki 1 2 3 4 5 6 7 8 9 0 42 11 11 11 11 11 11 11 11 11 1 23 23 23 23 23 23 23 23 23 23 2 74 74 74 36 36 36 36 36 36 36 3 11 42 42 42 42 42 42 42 42 42 4 65 65 65 65 65 58 58 58 58 58 5 58 58 58 58 58 65 65 65 65 65 132 Ch ơng 6: Sắp xếp và tìm kiếm (sorting and searching) 6 94 94 94 94 94 94 74 74 74 74 7 36 36 36 74 74 74 94 87 87 87 8 99 99 99 99 99 99 99 99 94 94 9 87 87 87 87 87 87 87 94 99 99 Ch ng trình đ c cài đặt nh sau: #include <stdio.h> #include <conio.h> #include <stdlib.h> #include <alloc.h> #include <dos.h> void Select(int *, int); void Init(int *, int); void In(int *, int); void Init(int *A, int n){ int i; printf("\n Tao lap day so:"); for (i=0; i<n;i++){ A[i]=random(1000); printf("%5d",A[i]); } delay(1000); } void Select(int *A, int n){ register i,j,temp; for(i=0;i<n-1;i++){ for (j=i+1;j<n;j++){ if(A[i]>A[j]){ temp=A[i]; A[i]=A[j]; A[j]=temp; } } In(A,n); } } void In(int *A, int n){ register int i; for(i=0;i<n;i++) printf("%5d",A[i]); 133 Ch ơng 6: Sắp xếp và tìm kiếm (sorting and searching) delay(1000); } void main(void){ int *A,n;clrscr(); printf("\n Nhap n="); scanf("%d",&n); A=(int *) malloc(n*sizeof(int)); Init(A,n);Select(A,n); free(A); } 6.3. GI I THU T INSERTION SORT Gi i thu t Insert Sort đ c thực hi n dựa trên kinh nghi m c a nh ng ng i ch i bài. Khi có i-1 lá bài đã đ c s p x p đang trên tay, nay ta thêm lá bài th i thì lá bài đó đ c so sánh với lá bài i-1, i-2, . . để tìm đ c v trí thích h p và chèn vào quân bài th i. V i nguyên t c s p bài nh v y, gi i thu t đ ƒ ƒ ƒ ƒ L y ph n t đ u tiên i0, đ c thực hi n nh sau: ng nhiên t p một ph n t là t p đã đ c s p x p. L y ti p ph n t th i1 chọn v trí thích h p c a ph n t th i1 trong t p hai ph n t và thực hi n đổi chỗ. ........................................................... L y ti p ph n t th ik chọn v trí thích h p c a ph n t th ik trong t p hai ikc s p x p hoàn toàn sau n-1 l n 1 ph n t và thực hi n đổi chỗ, dãy s đ chèn ph n t vào v trí thích h p. Độ ph c t p bé nh t c a thu t toán là: Cmin = ( n-1); Độ ph c t p lớn nh t c a thu t toán là: n(n-1)/2 = O(n2) Độ ph c t p trung bình c a thu t toán là: (n2 +n- 2)/4 = O(n2) Quá trình s p x p theo Insertion Sort đ L c mô t nh sau: t 1 2 3 4 ... 8 9 10 Khoá 42 23 74 11 ... 36 99 87 1 42 23 23 11 ... 11 11 11 42 42 23 ... 23 23 23 74 42 ... 42 36 36 74 ... 58 42 42 5 ... 65 58 58 6 ... 74 65 65 2 3 4 134 Ch ơng 6: Sắp xếp và tìm kiếm (sorting and searching) 7 ... 8 74 74 ... 94 87 9 ... 99 95 10 ... Thu t toán đ c cài đặt nh sau: #include <stdio.h> #include <conio.h> #include <stdlib.h> #include <alloc.h> #include <dos.h> void Insert(int *, int); void Init(int *, int); void In(int *, int); void Init(int *A, int n){ int i; printf("\n Tao lap day so:"); for (i=0; i<n;i++){ A[i]=random(1000); printf("%5d",A[i]); } delay(1000); } void Insert(int *A, int n){ register i,j,temp; for (i=1;i<n;i++){ temp=A[i]; for(j=i-1;j>=0 && temp<A[j];j--) A[j+1]=A[j]; A[j+1]=temp; printf("\n"); In(A,i+1); } } void In(int *A, int n){ register int i; for(i=0;i<n;i++) printf("%5d",A[i]); delay(1000); } 135 94 99 Ch ơng 6: Sắp xếp và tìm kiếm (sorting and searching) void main(void){ int *A,n;clrscr(); printf("\n Nhap n="); scanf("%d",&n); A=(int *) malloc(n*sizeof(int)); Init(A,n);Insert(A,n); free(A); } 6.4. GI I THU T BUBBLE SORT Gi i thu t Bubble Sort đ c thực hi n bằng cách đổi chỗ liên ti p hai ph n t k c n khi chúng ng c th tự. Quá trình thực hi n đ c duy t từ đáy lên đ nh. Nh v y, sau l n duy t th nh t, ph n t lớn nh t s đ c x p đúng v trí th n-1, l n duy t th k thì k ph n t lớn nh t đã đ c x p đúng v trí n-1, n-2, . ., n-k+1. Sau l n duy t th n-1, toàn bộ n ph n t s đ c s p x p. Với ph ng pháp này, các ph n t có giá tr nh đ c nổi d n lên nh n ớc s i bọt nh đó nó có tên gọi “ph ng pháp s i bọt”. Độ ph c t p c a thu t toán Bubble Sort là: Cmin = Cmax = Ctb = n(n-1)/2. Ch ng trình mô t thu t toán Bubble Sort đ #include <stdio.h> #include <conio.h> #include <stdlib.h> #include <alloc.h> #include <dos.h> void Bubble(int *, int); void Init(int *, int); void In(int *, int); void Init(int *A, int n){ int i; printf("\n Tao lap day so:"); for (i=0; i<n;i++){ A[i]=random(1000); printf("%5d",A[i]); } delay(1000); } void Bubble(int *A, int n){ register i,j,temp; for (i=1; i<n; i++){ for (j=n-1; j>=i; j--){ if (A[j-1]>A[j]){ 136 c cài đặt nh sau: Ch ơng 6: Sắp xếp và tìm kiếm (sorting and searching) temp=A[j-1]; A[j-1]=A[j]; A[j]=temp; } } printf("\n Ket qua lan:%d", i); In(A,n); } } void In(int *A, int n){ register int i; for(i=0;i<n;i++) printf("%5d",A[i]); delay(1000); } void main(void){ int *A,n;clrscr(); printf("\n Nhap n="); scanf("%d",&n); A=(int *) malloc(n*sizeof(int)); Init(A,n);Bubble(A,n); free(A); } 6.5. GI I THU T SHARER SORT Thu t toán Shaker Sort là c i ti n c a thu t toán Bubble Sort. Trong đó, sau mỗi l n duy t đi để x p đúng v trí ph n t lớn nh t, chúng ta thực hi n duy t l i để s p đúng v trí ph n t nh nh t. Dãy s đ c s p sau [n/2] + 1 l n duy t. Ch ng trình mô t thu t toán Shaker Sort đ c thực hi n nh sau: #include <stdio.h> #include <conio.h> #include <stdlib.h> #include <alloc.h> #include <dos.h> void Shaker(int *, int); void Init(int *, int); void In(int *, int); void Init(int *A, int n){ int i; printf("\n Tao lap day so:"); for (i=0; i<n;i++){ A[i]=random(1000); 137 Ch ơng 6: Sắp xếp và tìm kiếm (sorting and searching) printf("%5d",A[i]); } delay(1000); } void Shaker(int *A, int n){ register i,j,temp, exchange; do { exchange=0; for (i=n-1; i>0; i--){ if (A[i-1]>A[i]){ temp=A[i-1]; A[i-1]=A[i]; A[i]=temp; exchange=1; } } for(j=1; j<n;j++){ if (A[j-1]>A[j]){ temp=A[j-1]; A[j-1]=A[j]; A[j]=temp; exchange=1; } } printf("\n Ket qua lan:"); In(A,n); }while(exchange); } void In(int *A, int n){ register int i; for(i=0;i<n;i++) printf("%5d",A[i]); delay(1000); } void main(void){ int *A,n;clrscr(); printf("\n Nhap n="); scanf("%d",&n); A=(int *) malloc(n*sizeof(int)); Init(A,n);Shaker(A,n); free(A); } 138 Ch ơng 6: Sắp xếp và tìm kiếm (sorting and searching) 6.6. GI I THU T QUICK SORT Ph ng pháp s p x p kiểu phân đo n là một c i ti n c a ph ng pháp Selection Sort. Đây là một ph ng pháp t t do C.A.R. Hoare đ a ra và đặt tên cho nó là gi i thu t Quick Sort. Nội dung ch đ o c a ph ng pháp này là chọn ng u nhiên một ph n t nào đó c a dãy làm khoá ch t. Tính từ khoá ch t, các ph n t nh h n khoá ph i đ c x p vào tr ớc ch t (đ u dãy), mọi ph n t sau ch t đ c x p vào sau ch t (cu i dãy). Để làm đ c vi c đó, các ph n t trong dãy s đ c so sánh với khoá ch t và tráo đổi v trí cho nhau, hoặc cho khoá ch t n u ph n t đó lớn h n ch t mà l i nằm tr ớc ch t hoặc nh h n ch t nh ng l i nằm sau ch t. Khi vi c đổi chỗ l n đ u tiên đã thực hi n xong thì dãy hình thành hai đo n: một đo n bao g m các ph n t nh h n ch t, một đo n g m các ph n t lớn h n ch t, còn ch t chính là v trí c a ph n t trong dãy đ c s p x p. Áp d ng k thu t nh trên cho mỗi đo n tr ớc ch t và sau ch t cho tới khi các đo n còn l i hai ph n t thì vi c ghi nhớ không còn c n thi t n a. Dãy s đ c s p x p khi t t c các đo n đ c x lý xong. Ví d với dãy : 42 23 74 11 65 58 94 36 99 87 Ta chọn ch t đ u tiên là 42. Để phát hi n ra hai khoá c n đổi chỗ cho nhau, ta dùng hai bi n i, j với giá tr ban đ u i=2, j=10. N u ki < 42 thì ti p t c tĕng i và lặp l i cho tới khi gặp ph n t th ki >42. Duy t các ph n t th kj với 42 n u kj > 42 thì j gi m đi một, cho tới khi gặp ph n t th kj <42 thì ph n t th ki và kj đ c đổi chỗ cho nhau. Quá trình s đ c lặp l i với ki và kj cho tới khi i=j chính là v trí dành cho khoá 42. Cu i cùng chúng ta đổi chỗ 42 cho khoá cho kj. 42 23 74 11 65 58 94 36 99 87 42 23 74 11 65 58 94 36 99 87 42 23 36 11 65 58 94 74 99 87 42 23 36 11 65 58 94 74 99 87 42 23 36 11 65 58 94 74 99 87 (i>j) 11 23 36 42 65 58 94 74 99 87 Nh v y, k t thúc l n th nh t, chúng ta đ nh sau: (11 23 36) [42] (65 58 c hai đo n đ 94 74 c phân bi t b i khoá 42 99 87) Quá trình đ c lặp l i t ng tự cho từng phân đo n cho tới khi dãy đ c s p x p hoàn toàn. Chúng ta có thể cài đặt gi i thu t bằng vi c s d ng stack hoặc đ qui. Độ ph c t p tính toán c a gi i thu t Quick Sort: Tr ng h p t t nh t Cmax = Ctb = O (n log2n) Tru ng h p x u nh t Cmin= k.O(n2) 139 Ch ơng 6: Sắp xếp và tìm kiếm (sorting and searching) Sau đây là ch ng trình cài đặt gi i thu t Quick Sort bằng ph #include <stdio.h> #include <conio.h> #include <stdlib.h> #include <alloc.h> #include <dos.h> void qs(int *, int ,int); void Quick(int *,int ); void Init(int *, int); void In(int *, int); void Init(int *A, int n){ int i; printf("\n Tao lap day so:"); for (i=0; i<n;i++){ A[i]=random(1000); printf("%5d",A[i]); } delay(1000); } void Quick(int *A, int n){ qs(A,0,n-1); } void qs(int *A, int left,int right) { register i,j;int x,y; i=left; j=right; x= A[(left+right)/2]; do { while(A[i]<x && i<right) i++; while(A[j]>x && j>left) j--; if(i<=j){ y=A[i];A[i]=A[j];A[j]=y; i++;j--; } } while (i<=j); if (left<j) qs(A,left,j); if (i<right) qs(A,i,right); } void In(int *A, int n){ register int i; for(i=0;i<n;i++) printf("%5d",A[i]); 140 ng pháp đ qui. Ch ơng 6: Sắp xếp và tìm kiếm (sorting and searching) delay(1000); } void main(void){ int *A,n;clrscr(); printf("\n Nhap n="); scanf("%d",&n); A=(int *)malloc(n*sizeof(int)); Init(A,n);Quick(A,n);printf("\n"); In(A,n);getch(); free(A); } 6.7. GI I THU T HEAP SORT Heap là một cây nh phân đ c biểu di n bằng một m ng, m ng đó biểu di n một cây nh phân hoàn ch nh sao cho khóa node cha bao gi cũng lớn h n khoá c a node con c a nó. S p x p kiểu Heap Sort đ c ti n hành qua hai giai đo n. Giai đo n đ u tiên cây nh phân biểu di n b ng khoá đ c bi n đổi để đ a v một heap. Nh v y, đ i với heap, n u j là ch s c a node con thì [j/2] là ch s c a node cha. Theo đ nh nghĩa c a heap thì node con bao gi cũng nh h n node cha. Nh v y, node g c c a heap là khóa có giá tr lớn nh t trong mọi node. Ví d cây ban đ u là cây 6.1a thì heap c a nó là 6.1b. 42 23 11 36 99 74 65 58 99 94 87 94 36 87 23 11 Hình 6.1a 65 58 74 42 Hình 6.1b Để chuyển cây nh phân 6.1a thành cây nh phân 6.1b là một heap, chúng ta thực hi n duy t từ d ới lên (bottom up). Node lá đ ng nhiên là một heap. N u cây con bên trái và cây con bên ph i đ u là một heap thì toàn bộ cây cũng là một heap. Nh v y, để t o thành heap, chúng ta thực hi n so sánh nội dung node bên trái, nội dung node bên ph i với node cha c a nó, node nào có giá tr lớn h n s đ c thay đổi làm nội dung c a node cha. Quá trình l n ng c l i cho tới khi gặp node g c, khi đó nội dung node g c chính là khoá có giá tr lớn nh t. Giai đo n th hai c a gi i thu t là đ a nội dung c a node g c v v trí cu i cùng và nội dung c a node cu i cùng đ c thay vào v trí node g c, sau đó coi nh node cu i cùng nh đã b lo i b vì thực t node cu i cùng là giá tr lớn nh t trong dãy s . 141 Ch ơng 6: Sắp xếp và tìm kiếm (sorting and searching) Cây mới đ c t o ra (không kể ph n t lo i b ) không ph i là một heap, chúng ta l i thực hi n vun thành đ ng và thực hi n t ng tự nh trên cho tới khi đ ng còn một ph n t là ph n t bé nh t c a dãy. Độ ph c t p thu t toán c a Heap Sort Cmax = Ctb = O (n log2n ) Gi i thu t Heap Sort đ c cài đặt nh sau: #include <stdio.h> #include <conio.h> #include <stdlib.h> #include <alloc.h> #include <dos.h> void Heap(int *, int ); void Init(int *, int); void In(int *, int); void Init(int *A, int n){ int i; printf("\n Tao lap day so:"); for (i=0; i<n;i++){ A[i]=random(1000); printf("%5d",A[i]); } delay(1000); } void Heap(int *A, int n) { int k,x,s,f,ivalue; for(k=1;k<n;k++){ x=A[k]; s=k; f=(s-1)/2; while(s>0 && A[f]<x){ A[s]=A[f]; s=f; f=(s-1)/2; } A[s]=x; } for(k=n-1;k>0;k--){ ivalue=A[k]; A[k]=A[0]; f=0; if(k==1) s=-1; 142 Ch ơng 6: Sắp xếp và tìm kiếm (sorting and searching) else s=1; if(k>2 && A[2]>A[1]) s=2; while(s>=0 && ivalue<A[s]){ A[f]=A[s]; f=s;s=2*f +1; if (s+1<=k-1 && A[s]<A[s+1]) s=s+1; if (s>k-1) s=-1; } A[f]=ivalue; } } void In(int *A, int n){ register int i; for(i=0;i<n;i++) printf("%5d",A[i]); delay(1000); } void main(void){ int *A,n;clrscr(); printf("\n Nhap n="); scanf("%d",&n); A=(int *) malloc(n*sizeof(int)); Init(A,n);Heap(A,n);printf("\n"); In(A,n);getch(); free(A); } 6.8. GI I THU T MERGE SORT S p x p theo Merge Sort là ph ng pháp s p x p bằng cách trộn hai danh sách đã đ c s p x p thành một danh sách đã đ c s p x p. Ph ng pháp Merge Sort đ c ti n hành thông qua các b ớc nh sau: ƒ B ớc 1: Coi danh sách là n danh sách con mỗi danh sách con g m một ph n t , nh v y các danh sách con đã đ c s p x p. Trộn từng cặp hai danh sách con k c n thành một danh sách có hai ph n t đã đ c s p x p, chúng ta nh n đ c n/2 danh sách con đã đ c s p x p. ƒ B ớc 2: Xem danh sách c n s p x p nh n/2 danh sách con đã đ c s p x p. Trộn cặp hai danh sách k c n thành từng danh sách có 4 ph n t đã đ c s p x p, chúng ta nh n đ c n/4 danh sách con. 143 Ch ơng 6: Sắp xếp và tìm kiếm (sorting and searching) ƒ ...................................................... ƒ B ớc th i: Làm t ng tự nh b ớc i- 1. Quá trình đ c ti p t c khi chúng ta nh n đ c danh sách có n ph n t đã đ c s p x p. Ví d với dãy: 42 23 74 11 68 58 94 36 l n 1: [23 42] [11 74] [58 68] [94 36] l n 2: [11 23 42 74] [36 58 68 94] l n 3: [11 23 42 36 58 68 74 94] Ch ơng trình cài đặt giải thuật Merge Sort đ ợc thực hiện nh sau: #include <stdio.h> #include <conio.h> #include <stdlib.h> #include <alloc.h> #include <dos.h> #define MAX 10 void Merge(int *, int ); void Init(int *, int); void In(int *, int); void Init(int *A, int n){ int i; printf("\n Tao lap day so:"); for (i=0; i<n;i++){ A[i]=random(1000); printf("%5d",A[i]); } delay(1000); } void Merge(int *A, int n) { int i,j,k,low1,up1,low2,up2,size; int *dstam;size=1;dstam=(int *) malloc(n*sizeof(int)); while(size<n){ low1=0;k=0; while(low1 +size <n){ low2=low1+size; up1=low2-1; if (low2+size-1< n) up2=low2+size-1; else up2=n-1; for(i=low1, j=low2; i<=up1 && j<=up2; k++){ if(A[i]<=A[j]) 144 Ch ơng 6: Sắp xếp và tìm kiếm (sorting and searching) dstam[k]=A[i++]; else dstam[k] =A[j++]; } for(;i<=up1;k++) dstam[k]=A[i++]; for(;j<=up2;k++) dstam[k]=A[j++]; low1=up2+1; } for (i=low1; k<n;i++) dstam[k++]=A[i]; for(i=0;i<n;i++) A[i]=dstam[i]; size*=2; } printf("\n Ket qua:"); In(A,n);free(dstam); } void In(int *A, int n){ register int i; for(i=0;i<n;i++) printf("%5d",A[i]); delay(1000); } void main(void){ int *A,n;clrscr(); printf("\n Nhap n="); scanf("%d",&n); A=(int *) malloc(n*sizeof(int)); Init(A,n);Merge(A,n);printf("\n"); free(A); } 6.9. TÌM KI M (SEARCHING) Tìm ki m là công vi c quan trọng đ i với các h th ng tin học và có liên quan m t thi t với quá trình s p x p d li u. Bài toán tìm ki m tổng quát có thể đ c phát biểu nh sau: “Cho một b ng g m n b n ghi R1, R2, . ., Rn. Với mỗi b n ghi Ri đ c t ng ng với một khoá ki (tr ng th i trong record). Hãy tìm b n ghi có giá tr c a khoá bằng X cho tr ớc”. 145 Ch ơng 6: Sắp xếp và tìm kiếm (sorting and searching) N u chúng ta tìm đ c b n ghi có giá tr khóa là X thì phép tìm ki m đ c tho (successful). N u không có giá tr khóa nào là X thì quá trình tìm ki m là không tho (unsuccessful). Sau quá trình tìm ki m, có thể xu t hi n yêu c u bổ xung thêm b n ghi mới có giá tr khóa là X thì gi i thu t đ c gọi là gi i thu t tìm ki m bổ sung. 6.9.1. Tìm ki m tu n tự (Sequential Searching) Tìm ki m tu n tự là k thu t tìm ki m cổ điển trên một danh sách ch a đ c s p x p. Nội dung c b n c a ph ng pháp tìm ki m tu n tự là duy t từ b n ghi th nh t cho tới b n ghi cu i cùng, và so sánh l n l t giá tr c a khoá với giá tr X c n tìm. Trong quá trình duy t, n u có b n ghi trùng với giá tr X thì chúng ta đ a ra v trí c a b n ghi trong dãy, n u duy t tới cu i dãy mà không có b n ghi nào có giá tr c a khoá trùng với X thì quá trình tìm ki m tr l i giá tr -1 (-1 đ c hiểu là giá tr khoá X không thuộc dãy). Ch ng trình cài đặt ph ng pháp tìm ki m tu n tự đ c thực hi n nh sau: #include <stdio.h> #include <conio.h> #include <stdlib.h> #include <alloc.h> #include <dos.h> int Sequential(int *, int, int); void Init(int *, int); void Init(int *A, int n){ int i; printf("\n Tao lap day so:"); for (i=0; i<n;i++){ A[i]=random(1000); printf("%5d",A[i]); } delay(1000); } int Bubble(int *A, int x, int n){ register i,temp; for (i=0; i<n ; i ++){ if (A[i] == X) return(i); } return(-1); } void main(void){ int *A,n, x, k;clrscr(); printf("\n Nhap n="); scanf("%d",&n); printf(“\n S x c n tìm:”); scanf(“%d”, &x); 146 Ch ơng 6: Sắp xếp và tìm kiếm (sorting and searching) A=(int *) malloc(n*sizeof(int)); k= Sequential(A,x,n); if ( k>=0) printf(“\n %d v trí %d”, x,k); else printf(“\n %d không thuộc dãy”); free(A); getch(); } 6.9.2. Tìm ki m nh phân (Binary Searching) Tìm ki m nh phân là ph ng pháp tìm ki m phổ bi n đ c thực hi n trên một dãy đã đ c s p th tự. Nội dung c a gi i thu t đ c thực hi n nh sau: l y khóa c n tìm ki m X so sánh với nội dung c a khóa c a ph n t gi a, v trí c a ph n t gi a là mid = (low + hight )/ 2, trong đó c n d ới low =0, c n trên hight = n-1. Vì dãy đã đ c s p x p nên n u nội dung c a khóa t i v trí gi a lớn h n X thì ph n t c n tìm thuộc kho ng [mid+1, hight], n u nội dung c a khóa t i v trí gi a nh h n X thì ph n t c n tìm thuộc kho ng [low, mid1], n u nội dung c a khóa t i v trí gi a trùng với X thì đó chính là ph n t c n tìm. b ớc ti p theo, n u nội dung c a khóa t i v trí gi a lớn h n X thì ta d ch chuyển c n d ới low lên v trí mid+ 1, n u nội dung c a khóa t i v trí gi a nh h n X thì ta d ch chuyển c n trên v v trí mid- 1. Quá trình đ c lặp l i cho tới khi gặp khóa có nội dung trùng với X hoặc c n d ới v t quá c n trên hay X không thuộc dãy. Thu t toán tìm ki m nh phân đ c minh họa nh sau: int Binary_Search( int *A, int X, int n){ int mid, low=0, hight = n-1; while ( low<=hight ){ // lặp n u c n d ới v n nh h n c n trên mid = (low + hight) /2; // xác đ nh v trí ph n t gi a if (X > A[mid] ) low = mid +1; // X thuộc [mid+1, hight] else if (X < A[mid] ) hight = mid- 1; // X thuộc [low, mid-1] else return(mid); } return(-1); // X không thuộc dãy } Ch ng trình c thể đ c cài đặt nh sau: #include <stdio.h> #include <conio.h> #include <stdlib.h> #include <alloc.h> #include <dos.h> int Binary_Search( int *, int, int); void Bubble(int *, int); void Init(int *, int); 147 Ch ơng 6: Sắp xếp và tìm kiếm (sorting and searching) int Binary_Search( int *A, int X, int n) { int mid, low = 0, hight = n-1; while (low<=hight){ mid = (low +hight)/2; if (X >A[mid] ) low = mid +1; else if (X<A[mid] ) hight = mid -1; else return (mid); } return(-1); } void Init(int *A, int n){ int i; printf("\n Tao lap day so:"); for (i=0; i<n;i++){ A[i]=random(1000); printf("%5d",A[i]); } delay(1000); } void Bubble(int *A, int n){ register i,j,temp; for (i=1; i<n; i++){ for (j=n-1; j>=i;j--){ if (A[j-1]>A[j]){ temp=A[j-1]; A[j-1]=A[j]; A[j]=temp; } } printf("\n Ket qua lan:%d", i); In(A,n); } } void In(int *A, int n){ register int i; for(i=0;i<n;i++) printf("%5d",A[i]); delay(1000); } void main(void){ int *A,n, X, k;clrscr(); 148 Ch ơng 6: Sắp xếp và tìm kiếm (sorting and searching) printf("\n Nhap n="); scanf("%d",&n); printf(“\n S c n tìm X=”); scanf(“%d”,&X); A=(int *) malloc(n*sizeof(int)); Init(A,n);Bubble(A,n); k= Binary_Search(A, X, n); if ( k>0) printf (“\n %d v trí s %d”, X, k); else printf(“\n %d không thuộc dãy”); getch(); free(A); } 149 Ch ơng 6: Sắp xếp và tìm kiếm (sorting and searching) NH NG N I DUNG C N GHI NH 9 Hiểu đ c ý nghĩa vai trò c a bài toán s p x p và tìm ki m trong tin học. 9 Cài đặt nhu n nhuy n các gi i thu t s p x p và tìm ki m trên các c u trúc d li u khác nhau. 9 Gi i quy t các bài t p thực hành kèm theo làm thĕng ti n k nĕng gi i quy t bài toán s p x p & tìm ki m. 150 Ch ơng 6: Sắp xếp và tìm kiếm (sorting and searching) BÀI T P CHƯƠNG 6 Bài 1. Cài đặt ch ng trình theo thu t toán Quick Sort không dùng ph dùng c u trúc stack. Bài 2. Tìm hiểu v gi i thu t Shell-Sort là ph ng pháp đ qui mà ng pháp c i ti n c a Insertion Sort. Bài 3. Cài đặt l i gi i thu t Bubble Sort sao cho các node nh đ c đ y d n v phía tr ớc. Bài 4. Một Ternary Heap là cây tam phân g n đ y đ c cài đặt bằng m ng một chi u, mỗi node có ba node con. Nội dung c a node cha bao gi cũng lớn h n hoặc bằng nội dung c a node con, các node đ c đánh s từ 0 đ n n-1, node i có 3 con là 3i+1, 3i+2, 3i+3. Hãy cài đặt gi i thu t Ternary Heap. Bài 5. Cài đặt gi i thu t Bubble Sort trên file. Bài 6. Cài đặt gi i thu t Insertion Sort trên file. Bài 7. Cài đặt gi i thu t Quick Sort trên file. Bài 8. Cài đặt các gi i thu t s p x p theo nhi u khoá khác nhau. Bài 9. Nghiên c u và cài đặt thu t toán tìm ki m tam phân. Bài 10. Nghiên c u và cài đặt thu t toán s p x p kiểu hoà nh p thực hi n trên file. Bài 11. Vi t ch ng trình chuyển đổi một file d li u đ c tổ ch c theo khuôn d ng *.DBF thành file kiểu text. Ng c l i, chuyển đổi file d li u kiểu text thành một file d li u theo khuôn d ng DBF. Bài 12. Tìm hiểu cách s p x p và tìm ki m theo kiểu index c a các h qu n tr c s d li u nh foxprol hoặc access. 151 Tài liệu tham khảo TÀI LIỆU THAM KHẢO [1] Lê H u L p - Nguy n Duy Ph Đi n, 2002. ng. Giáo trình Kỹ thuật lập trình. NXB B u [2] Đỗ Xuân Lôi. Cấu trúc dữ liệu và giải thuật. NXB Khoa Học K Thu t, 2000. [3] Đặng Huy Ru n. Lý thuyết đồ thị. NXB Khoa Học K Thu t, 2003 [4] William Ford, William Topp. Data Structures with C++. Prentice Hall, 1996. [5] Mark Allen Weiss. Data Structures and Algorithm Analysis In C. Prentice Hall, 1996. [6] Phan Đĕng C u. Cấu trúc dữ liệu và Giải thuật (Tài li u gi ng d y–Học Vi n Công ngh BCVT), 2003. 152 Mục lục MỤC LỤC Ch ng 1: Đ I C 1.1. S l 1.2. C u trúc l nh, l nh có c u trúc, c u trúc d li u ............................................5 c v l ch s l p trình c u trúc...............................................................3 1.2.1. C u trúc l nh (c u trúc đi u khiển) ........................................................5 1.2.2. L nh có c u trúc .....................................................................................7 1.2.3. C u trúc d li u......................................................................................7 1.3. Ch NG V K THU T L P TRÌNH C U TRÚC ..................................3 Nguyên lý t i thiểu .........................................................................................8 1.3.1. T p các phép toán ..................................................................................8 1.3.2. T p các l nh vào ra c b n...................................................................11 1.3.3. Thao tác trên các kiểu d li u có c u trúc............................................11 1.4. Nguyên lý đ a ph 1.5. Nguyên lý nh t quán.....................................................................................15 1.6. Nguyên lý an toàn.........................................................................................16 1.7. Ph ng pháp Top-Down ..............................................................................18 1.8. Ph ng pháp Bottom - Up............................................................................22 ng ..................................................................................13 ng 2: DUY T VÀ Đ QUI ..........................................................................................29 2.1. Đ nh nghĩa bằng đ qui ................................................................................29 2.2. Gi i thu t đ qui ...........................................................................................30 2.3. Thu t toán sinh k ti p .................................................................................31 2.4. Thu t toán quay lui (Back track) .................................................................34 2.5. Thu t toán nhánh c n ...................................................................................37 Ch ng 3: NGĔN X P, HÀNG Đ I VÀ DANH SÁCH MÓC N I (STACK, QUEUE, LINK LIST)...........................................................................................................................51 3.1. Kiểu d li u ngĕn x p và ng d ng..............................................................51 3.1.1. Đ nh nghĩa và khai báo ........................................................................51 3.1.2. Các thao tác với stack ..........................................................................53 3.1.3. ng d ng c a stack..............................................................................53 153 Mục lục 3.2. 3.2.1. Đ nh nghĩa và khai báo ........................................................................55 3.2.2. ng d ng hàng đ i...............................................................................57 3.3. Giới thi u và đ nh nghĩa.......................................................................62 3.3.2. Các thao tác trên danh sách móc n i....................................................63 Danh sách liên k t kép..................................................................................67 ng 4: C U TRÚC D LI U CÂY (TREE).................................................................77 4.1. Đ nh nghĩa và khái ni m ..............................................................................77 4.2. Cây nh phân.................................................................................................78 4.3. Biểu di n cây nh phân .................................................................................81 4.3.1. Biểu di n cây nh phân bằng danh sách tuy n tính ..............................81 4.3.2. Biểu di n cây nh phân bằng danh sách móc n i .................................82 4.4. Các thao tác trên cây nh phân......................................................................83 4.4.1. Đ nh nghĩa cây nh phân bằng danh sách tuy n tính............................83 4.4.2. Đ nh nghĩa cây nh phân theo danh sách liên k t:...............................83 4.4.3. Các thao tác trên cây nh phân .............................................................83 4.5. Các phép duy t cây nh phân (Traversing Binary Tree)...............................88 4.5.1. Duy t theo th tự tr ớc (Preorder Travesal)........................................88 4.5.2. Duy t theo th tự gi a (Inorder Travesal) ...........................................89 4.5.3. Duy t theo th tự sau (Postorder Travesal) .........................................89 4.6. Ch Danh sách liên k t đ n .................................................................................62 3.3.1. 3.4. Ch Hàng đ i (Queue) .........................................................................................55 Cài đặt cây nh phân tìm ki m......................................................................90 ng 5: Đ TH (GRAPH) ............................................................................................103 5.1. Nh ng khái ni m c b n c a đ th ............................................................103 5.1.1. Các lo i đ th ....................................................................................103 5.1.2. Một s thu t ng c b n c a đ th ....................................................106 5.1.3. Đ 5.2. ng đi, chu trình, đ th liên thông................................................107 Biểu di n đ th trên máy tính ....................................................................107 5.2.1. Ma tr n k , ma tr n trọng s ..............................................................107 5.2.2. Danh sách c nh (cung )......................................................................109 5.2.3. Danh sách k ......................................................................................110 154 Mục lục 5.3. 5.3.1. Thu t toán tìm ki m theo chi u sâu ...................................................110 5.3.2. Thu t toán tìm ki m theo chi u rộng (Breadth First Search).............111 5.3.3. Kiểm tra tính liên thông c a đ th .....................................................112 5.3.4. Tìm đ ng đi gi a hai đ nh b t kỳ c a đ th ....................................113 5.4. Đ ng đi và chu trình Euler .......................................................................115 5.5. Đ ng đi và chu trình Hamilton.................................................................118 5.6. Cây bao trùm ..............................................................................................119 5.6.1. Khái ni m và đ nh nghĩa ....................................................................119 5.6.2. Tìm một cây bao trùm trên đ th .......................................................120 5.6.3. Tìm cây bao trùm ng n nh t...............................................................121 5.6.4. Thu t toán Kruskal.............................................................................122 5.6.5. Thu t toán Prim..................................................................................122 5.7. Ch Các thu t toán tìm ki m trên đ th ............................................................110 Bài toán tìm đ ng đi ng n nh t ................................................................123 5.7.1. Phát biểu bài toán...............................................................................123 5.7.2. Thu t toán Dijkstra.............................................................................124 5.7.3. Thu t toán Floy ..................................................................................124 ng 6: S P X P VÀ TÌM KI M (SORTING AND SEARCHING)..........................131 6.1. Đặt bài toán ................................................................................................131 6.2. Gi i thu t Selection Sort.............................................................................132 6.3. Gi i thu t Insertion Sort .............................................................................134 6.4. Gi i thu t Bubble Sort................................................................................136 6.5. Gi i thu t Shaker Sort ................................................................................137 6.6. Gi i thu t Quick Sort..................................................................................139 6.7. Gi i thu t Heap Sort ...................................................................................141 6.8. Gi i thu t Merge Sort .................................................................................143 6.9. Tìm ki m (Searching).................................................................................145 6.9.1. Tìm ki m tu n tự (Sequential Searching) ..........................................146 6.9.2. Tìm ki m nh phân (Binary Searching)..............................................147 TÀI LI U THAM KH O .........................................................................................152 M C L C .................................................................................................................153 155