ỦY BAN NHÂN DÂN TỈNH AN GIANG
TRƯỜNG CAO ĐẲNG NGHỀ AN GIANG
GIÁO TRÌNH
Cấu trúc dữ liệu và giải thuật
NGHỀ LẬP TRÌNH MÁY TÍNH & TIN
ỨNG DỤNG
TRÌNH ĐỘ CAO ĐẲNG NGHỀ & TRUNG CẤP NGHỀ
(Ban hành theo Quyết định số:
/QĐ-CĐN ngày tháng năm 20
của Hiệu trưởng trường Cao đẳng nghề An Giang)
Tên tác giả : Trần Thị Kim Ngọc
Năm ban hành: 2018
TUYÊN BỐ BẢN QUYỀN
Tài liệu này thuộc loại sách giáo trình nên các nguồn thơng tin có thể đƣợc
phép dùng nguyên bản hoặc trích dùng cho các mục đích về đào tạo và tham khảo.
Mọi mục đích khác mang tính lệch lạc hoặc sử dụng với mục đích kinh
doanh thiếu lành mạnh sẽ bị nghiêm cấm.
1
LỜI GIỚI THIỆU
Bài giảng cấu trúc dữ liệu và giải thuật đƣợc viết nhằm để giảng dạy cho sinh
viên chuyên ngành CNTT trƣờng Cao Đẳng Nghề An giang. Bài giảng đƣợc thiết
kế theo chƣơng trình mơn học cấu trúc dữ liệu và giải thuật của Bộ ban hành. Bài
giảng này bao gồm 4 chƣơng, nội dung các chƣơng đƣợc trình bày các cấu trúc dữ
liệu và các giải thuật đơn giản nhất của tin học. Trƣớc tiên bài giảng trình bày về
các cấu trúc dữ liệu cơ bản nhƣ: mảng, con trỏ, cấu trúc, tập tin; tiếp theo là các cấu
trúc dữ liệu nâng cao nhƣ: danh sách, ngăn xếp, hàng đợi và một số ứng dụng của
danh sách. Sau đó bài giảng trình bày tiếp về giải thuật sắp xếp và tìm kiếm, với
các phƣơng pháp sắp xếp: xen, chọn, nổi bọt, quick sort và tìm kiếm nhƣ: tuần tự
và nhị phân. Thêm vào đó, cuối chƣơng sẽ có các bài tập tƣơng ứng để sinh viên có
thể ơn lại lý thuyết và tùy vào mỗi chƣơng mà có một số bài tập nâng cao để
khuyến khích sinh viên tự học và nghiên cứu.
Cuốn tài liệu giảng dạy này vẫn cịn nhiều thiếu sót và hạn chế. Rất mong
nhận đƣợc ý kiến đóng góp của sinh viên và các bạn đọc để bài giảng ngày càng
hoàn thiện hơn.
Chân thành cảm ơn quý Thầy Cô trong Hội đồng thẩm định của trƣờng Cao
Đẳng Nghề An Giang để bài giảng Cấu trúc dữ liệu và giải thuật đƣợc hoàn chỉnh.
An Giang, ngày tháng năm 2018
Tham gia biên soạn
Trần Thị Kim Ngọc
2
MỤC LỤC
TUYÊN BỐ BẢN QUYỀN ................................................................................... 1
LỜI GIỚI THIỆU ................................................................................................... 2
MỤC LỤC .............................................................................................................. 3
GIÁO TRÌNH MƠN HỌC CẤU TRÚC DỮ LIỆU VÀ GIẢI THUẬT ............... 6
CHƢƠNG 1: GIỚI THIỆU CẤU TRÚC DỮ LIỆU VÀ GIẢI THUẬT .............. 7
I. MỐI LIÊN HỆ GIẢI THUẬT VÀ CẤU TRÚC DỮ LIỆU ........................... 7
II. ĐÁNH GIÁ ĐỘ PHỨC TẠP CỦA GIẢI THUẬT ....................................... 9
1. Sự cần thiết phải phân tích giải thuật: ......................................................... 9
2. Thời gian thực hiện của giải thuật:............................................................ 10
a. Thời gian thực hiện chƣơng trình: ......................................................... 10
b. Đơn vị đo thời gian thực hiện ................................................................ 11
c. Thời gian thực hiện trong trƣờng hợp xấu nhất: .................................... 11
3. Tỷ suất tăng và độ phức tạp của giải thuật ................................................ 11
a. Tỷ suất tăng: ........................................................................................... 11
b. Khái niệm độ phức tạp của giải thuật .................................................... 12
4. Cách tính độ phức tạp: .............................................................................. 12
a. Qui tắc cộng: .......................................................................................... 12
b. Qui tắc nhân: .......................................................................................... 13
c. Qui tắc tổng qt để phân tích một chƣơng trình: ................................. 13
d. Độ phức tạp của chƣơng trình có gọi chƣơng trình con khơng đệ quy: 13
5. Phân tích các chƣơng trình đệ quy: ........................................................... 14
a. Thành lập phƣơng trình đệ quy: ............................................................. 15
b. Giải phƣơng trình đệ quy: ...................................................................... 15
III. BÀI TẬP ..................................................................................................... 20
CHƢƠNG 2: CÁC KIỂU DỮ LIỆU NÂNG CAO ............................................. 22
I. MẢNG: .......................................................................................................... 22
1. Mảng 1 chiều: ............................................................................................ 22
2. Mảng nhiều chiều: ..................................................................................... 24
II. CON TRỎ: ................................................................................................... 25
3
1. Cấp phát tĩnh, cấp phát động và con trỏ .................................................... 25
2. Sự cài đặt: .................................................................................................. 26
III. CẤU TRÚC: ............................................................................................... 26
1. Định nghĩa kiểu cấu trúc: .......................................................................... 26
2. Khai báo biến cấu trúc:.............................................................................. 28
IV. BÀI TẬP ..................................................................................................... 28
CHƢƠNG 3: DANH SÁCH ................................................................................ 30
I. KHÁI NIỆM DANH SÁCH ......................................................................... 30
1. Các phép toán trên danh sách .................................................................... 30
2. Cài đặt danh sách bằng mảng (danh sách đặc): ........................................ 32
II. CÀI ĐẶT DANH SÁCH BẰNG CON TRỎ (DANH SÁCH LIÊN KẾT) 36
III. NGĂN XẾP (STACK) ............................................................................... 41
1. Định nghĩa ngăn xếp ................................................................................. 41
2. Các phép toán trên ngăn xếp ..................................................................... 41
3. Cài đặt ngăn xếp: ....................................................................................... 42
IV. HÀNG ĐỢI (QUEUE) ............................................................................... 45
1. Định Nghĩa ................................................................................................ 45
2. Các phép toán cơ bản trên hàng ................................................................ 45
3. Cài đặt hàng ............................................................................................... 45
V. MỘT SỐ ỨNG DỤNG CỦA DANH SÁCH .............................................. 53
1. Đảo ngƣợc xâu ký tự ................................................................................. 53
2. Tính giá trị của biểu thức dạng hậu tố ....................................................... 54
3. Chuyển đổi biểu thức dạng trung tố sang hậu tố ....................................... 56
VI. BÀI TẬP ..................................................................................................... 59
1. Bài tập cơ bản: ........................................................................................... 59
2. Bài tập nâng cao: ....................................................................................... 60
CHƢƠNG 4: SẮP XẾP VÀ TÌM KIẾM ............................................................. 61
I. GIỚI THIỆU VỀ SẮP XẾP VÀ TÌM KIẾM ................................................ 61
II. CÁC PHƢƠNG PHÁP SẮP XẾP ............................................................... 62
1. Sắp xếp kiểu chọn (Selection Sort) ........................................................... 62
4
2. Sắp xếp kiểu chèn (Insertion Sort) ............................................................ 63
3. Sắp xếp nổi bọt (Bubble Sort) ................................................................... 65
4. Quick sort .................................................................................................. 67
a. Giới thiệu................................................................................................ 67
b. Các bƣớc thực hiện giải thuật ................................................................ 67
c. Cài đặt giải thuật .................................................................................... 68
5. Heap sort.................................................................................................... 70
a. Giới thiệu................................................................................................ 70
b. Các bƣớc thực hiện giải thuật ................................................................ 70
III. CÁC PHƢƠNG PHÁP TÌM KIẾM ........................................................... 73
1. Tìm kiếm tuần tự ....................................................................................... 73
2. Tìm kiếm nhị phân .................................................................................... 74
3. Tìm kiếm tam phân ................................................................................... 75
IV. BÀI TẬP ..................................................................................................... 77
CÁC THUẬT NGỮ CHUYÊN MÔN ................................................................. 78
TÀI LIỆU THAM KHẢO .................................................................................... 79
5
GIÁO TRÌNH MƠN HỌC CẤU TRÚC DỮ LIỆU VÀ GIẢI THUẬT
Tên môn học: CẤU TRÚC DỮ LIỆU VÀ GIẢI THUẬT
Mã môn học: MH 16
Thời gian thực hiện môn học: 60 giờ (Lý thuyết: 20 giờ, thực hành, thí
nghiệm, thảo luận: 36 giờ, kiểm tra: 4 giờ).
Vị trí, tính chất, ý nghĩa và vai trị của mơn học:
- Vị trí: Thuộc nhóm mơn: Mơn học lý thuyết chun nghành, đƣợc bố trí sau
các mơn: Tin học căn bản, Lập trình căn bản
- Tính chất: Mơn học này u cầu phải có tƣ duy logic và các kiến thức về lập
trình căn bản.
-Ý nghĩa và vai trị của mơn học: giúp các em tƣ duy, vận động suy nghĩ làm
nền tảng cho các môn học sau.
Mục tiêu của môn học:
- Về kiến thức
Hiểu đƣợc nội dung của: dữ liệu, giải thuật, mối quan hệ giữa cấu trúc dữ
liệu và giải thuật.
Phân tích và xác định đƣợc dữ liệu, giải thuật, sự kết hợp của dữ liệu và giải
thuật trong một chƣơng trình máy tính.
Áp dụng thuật tốn hợp lý đối với cấu trúc dữ liệu tƣơng thích để giải quyết
bài toán đơn giản.
Áp dụng đƣợc các phƣơng pháp sắp xếp, tìm kiếm đơn giản, các cấu trúc dữ
liệu động (danh sách liên kết) vào giải quyết các bài toán.
- Về kỹ năng: Thực hành (cài đặt và biên dịch) các bài toán sử dụng các cấu
trúc và giải thuật đã học.
- Về năng lực tự chủ và trách nhiệm:
Nghiêm túc trong học tập và thực hiện tốt các yêu cầu đƣợc giao.
Luôn động não suy nghĩ. thƣờng xuyên luyện tập tƣ duy trong việc học
Rèn luyện tính cẩn thận, khoa học trong cơng việc học tập, nghiên cứu.
6
CHƢƠNG 1: GIỚI THIỆU CẤU TRÚC DỮ LIỆU VÀ GIẢI THUẬT
Mục tiêu:
Nhằm cung cấp những kiến thức cơ bản về cấu trúc dữ liệu, giải thuật, kiểu dữ
liệu, mơ hình dữ liệu và các kiến thức về thiết kế, phân tích giải thuật cũng nhƣ các
phƣơng pháp phân tích, thiết kế giải thuật.
Nội dung chính:
I. MỐI LIÊN HỆ GIẢI THUẬT VÀ CẤU TRÚC DỮ LIỆU
Theo quan điểm của phân tích thiết kế hƣớng đối tƣợng, mỗi lớp sẽ đƣợc xây
dựng với một số chức năng nào đó và các đối tƣợng của nó sẽ tham gia vào hoạt
động của chƣơng trình. Điểm mạnh của hƣớng đối tƣợng là tính đóng kín và tính sử
dụng lại của các lớp. Mỗi phần mềm biên dịch cho một ngơn ngữ lập trình nào đó
đều chứa rất nhiều thƣ viện các lớp nhƣ vậy. Chúng ta thử điểm qua một số lớp mà
ngƣời lập trình thƣờng hay sử dụng: các lớp có nhiệm vụ đọc/ ghi để trao đổi dữ
liệu với các thiết bị ngoại vi nhƣ đĩa, máy in, bàn phím,…; các lớp đồ họa cung cấp
các chức năng vẽ, tô màu cơ bản; các lớp điều khiển cho phép xử lý việc giao tiếp
với ngƣời sử dụng thơng qua bàn phím, chuột, màn hình; các lớp phục vụ các giao
dịch truyền nhận thông tin qua mạng;…Các lớp cấu trúc dữ liệu mà chúng ta sắp
bàn đến cũng không là một trƣờng hợp ngoại lệ. Có thể chia tất cả các lớp này
thành hai nhóm chính:
- Các lớp dịch vụ.
- Các lớp có khả năng lƣu trữ và xử lý lƣợng dữ liệu lớn.
Nhóm thứ hai muốn nói đến các lớp cấu trúc dữ liệu. Vậy có gì giống và khác
nhau giữa các lớp cấu trúc dữ liệu và các lớp khác?
- Điểm giống nhau giữa các lớp cấu trúc dữ liệu và các lớp khác: Mỗi lớp đều
phải thực hiện một số chức năng thông qua các hành vi của các đối tƣợng của nó.
Một khi chúng ta đã xây dựng xong một lớp cấu trúc dữ liệu nào đó, chúng ta hồn
tồn tin tƣởng rằng nó sẽ hồn thành xuất sắc những nhiệm vụ mà chúng ta đã thiết
kế và đã giao phó cho nó. Điều này rất khác biệt so với những tài liệu viết về cấu
trúc dữ liệu theo quan điểm hƣớng thủ tục trƣớc đây: việc xử lý dữ liệu khơng hề
có tính đóng kín và tính sử dụng lại. Tuy về mặt thực thi thì các chƣơng trình nhƣ
thế có khả năng chạy nhanh hơn, nhƣng chúng bộc lộ rất nhiều nhƣợc điểm: thời
gian phát triển giải thuật chính rất chậm gây khó khăn nhiều cho ngƣời lập trình,
chƣơng trình thiếu tính trong sáng, rất khó sửa lỗi và phát triển.
- Đặc trƣng riêng của các lớp cấu trúc dữ liệu: Nhiệm vụ chính của các lớp cấu
trúc dữ liệu là nắm giữ dữ liệu sao cho có thể đáp ứng mỗi khi đƣợc chƣơng trình
u cầu trả về một dữ liệu cụ thể nào đó mà chƣơng trình cần đến. Những thao tác
cơ bản đối với một cấu trúc dữ liệu thƣờng là: thêm dữ liệu mới, xóa bỏ dữ liệu đã
7
có, tìm kiếm, truy xuất.
Ngồi các thao tác dữ liệu cơ bản, các cấu trúc dữ liệu khác nhau sẽ khác nhau
về các thao tác bổ sung khác. Chính vì điều này mà khi thiết kế những giải thuật để
giải quyết các bài toán lớn, ngƣời ta sẽ lựa chọn cấu trúc dữ liệu nào là thích hợp
nhất.
Chúng ta thử xem xét một ví dụ thật đơn giản sau đây:
Giả sử chúng ta cần viết một chƣơng trình nhận vào một dãy các con số, và in
chúng ra theo thứ tự ngƣợc với thứ tự nhập vào ban đầu.
Để giải quyết bài toán này, nếu chúng ta nghĩ đến việc phải khai báo các biến
để lƣu các giá trị nhập vào nhƣ thế nào, và sau đó là thứ tự in ra sao để đáp ứng u
cầu bài tốn, thì dƣờng nhƣ là chúng ta đã quên áp dụng nguyên tắc lập trình hƣớng
đối tƣợng: chúng ta đã phải bận tâm đến những việc quá chi tiết. Đây chỉ là một ví
dụ vơ cùng đơn giản, nhƣng nó có thể minh họa cho vai trò của cấu trúc dữ liệu.
Nếu chúng ta nhớ rằng, việc tổ chức và lƣu dữ liệu nhƣ thế nào là một việc
quá chi tiết và tỉ mỉ khơng nên thực hiện vào lúc này, thì đó chính là lúc chúng ta
đã bƣớc đầu hiểu đƣợc vai trị của các lớp cấu trúc dữ liệu.
Mơn cấu trúc dữ liệu và giải thuật sẽ giúp chúng ta hiểu rõ về các lớp cấu trúc
dữ liệu có sẵn trong các phần mềm. Hơn thế nữa, trong khi học cách xây dựng các
lớp cấu trúc dữ liệu từ đơn giản đến phức tạp, chúng ta sẽ nắm đƣợc các phƣơng
pháp cũng nhƣ các kỹ năng thông qua một số ngun tắc chung. Từ đó, ngồi khả
năng hiểu rõ để có thể lựa chọn một cách đúng đắn nhất những cấu trúc dữ liệu có
sẵn, chúng ta cịn có khả năng xây dựng những lớp cấu trúc dữ liệu phức tạp hơn,
tinh tế và thích hợp hơn trong mỗi bài toán mà chúng ta cần giải quyết. Khả năng
thừa kế các cấu trúc dữ liệu có sẵn để phát triển thêm các tính năng mong muốn
cũng là một điều đáng lƣu ý.
Với ví dụ trên, những ai đã từng tiếp xúc ít nhiều với việc lập trình đều khơng
xa lạ với khái niệm “ngăn xếp”. Đây là một cấu trúc dữ liệu đơn giản nhất nhƣng
lại rất thông dụng, và dĩ nhiên chúng ta sẽ có dịp học kỹ hơn về nó. Ở đây chúng ta
muốn mƣợn nó để minh họa, và cũng nhằm giúp cho ngƣời đọc làm quen với một
phƣơng pháp tiếp cận hoàn toàn nhất quán trong suốt giáo trình này.
Giả sử cấu trúc dữ liệu ngăn xếp của chúng ta đã đƣợc giao cho một nhiệm vụ
là cất giữ những dữ liệu và trả về khi có yêu cầu, theo một quy định bất di bất dịch
là dữ liệu đƣa vào sau phải đƣợc lấy ra trƣớc. Bằng cách sử dụng cấu trúc dữ
liệu ngăn xếp, chƣơng trình trở nên hết sức đơn giản và đƣợc trình bày bằng ngơn
ngữ giả nhƣ sau:
Lặp cho đến khi nhập đủ các con số mong muốn
{
Nhập 1 con số.
8
Cất vào ngăn xếp con số vừa nhập.
}
Lặp trong khi mà ngăn xếp vẫn còn dữ liệu
{
Lấy từ ngăn xếp ra một con số. In số vừa lấy đƣợc.
}
Chúng ta sẽ có dịp gặp nhiều bài tốn phức tạp hơn mà cũng cần sử dụng đến
đặc tính này của ngăn xếp. Tính đóng kín của các lớp giúp cho chƣơng trình vơ
cùng trong sáng. Đoạn chƣơng trình trên khơng hề cho chúng ta thấy ngăn xếp đã
làm việc với các dữ liệu đƣợc đƣa vào nhƣ thế nào, đó là nhiệm vụ mà chúng ta đã
giao phó cho nó và chúng ta hoàn toàn yên tâm về điều này. Bằng cách này, khi đã
có những cấu trúc dữ liệu thích hợp, ngƣời lập trình có thể dễ dàng giải quyết các
bài tốn lớn. Họ có thể n tâm tập trung vào những điểm mấu chốt để xây dựng,
tinh chế giải thuật và kiểm lỗi.
Trên đây chúng ta chỉ vừa mới giới thiệu về phần cấu trúc dữ liệu nằm trong
nội dung của môn học “cấu trúc dữ liệu và giải thuật”. Vậy giải thuật là gì? Đứng
trên quan điểm thiết kế và lập trình hƣớng đối tƣợng, chúng ta đã hiểu vai trị của
các lớp. Vậy khi đã có các lớp rồi thì ngƣời ta cần xây dựng kịch bản cho các đối
tƣợng hoạt động nhằm giải quyết bài toán chính. Chúng ta cần một cấu trúc chƣơng
trình để tạo ra kịch bản đó: việc gì làm trƣớc, việc gì làm sau; việc gì chỉ làm trong
những tình huống đặc biệt nào đó; việc gì cần làm lặp lại nhiều lần. Chúng ta nhắc
đến giải thuật chính là quay về với khái niệm của “lập trình thủ tục” trƣớc kia.
Ngồi ra, chúng ta cũng cần đến giải thuật khi cần hiện thực cho mỗi lớp: xong
phần đặc tả các phƣơng thức - phƣơng tiện giao tiếp của lớp với bên ngồi - chúng
ta cần đến khái niệm “lập trình thủ tục” để giải quyết phần hiện thực bên trong của
các phƣơng thức này. Đó là việc chúng ta phải xử lý những dữ liệu bên trong của
chúng nhƣ thế nào mới có thể hồn thành đƣợc chức năng mà phƣơng thức phải
đảm nhiệm.
Nhƣ vậy, về phần giải thuật trong môn học này, chủ yếu chúng ta sẽ tìm hiểu
các giải thuật mà các phƣơng thức của các lớp Cấu trúc dữ liệu dùng đến, một số
giải thuật sắp xếp tìm kiếm, và các giải thuật trong các ứng dụng minh họa việc sử
dụng các lớp cấu trúc dữ liệu để giải quyết một số bài tốn đó.
II. ĐÁNH GIÁ ĐỘ PHỨC TẠP CỦA GIẢI THUẬT
1. Sự cần thiết phải phân tích giải thuật:
Trong khi giải một bài tốn chúng ta có thể có một số giải thuật khác nhau,
vấn đề là cần phải đánh giá các giải thuật đó để lựa chọn một giải thuật tốt (nhất).
Thơng thƣờng thì ta sẽ căn cứ vào các tiêu chuẩn sau:
9
(1) Giải thuật đúng đắn
(2) Giải thuật đơn giản
(3) Giải thuật thực hiện nhanh
Với yêu cầu (1), để kiểm tra tính đúng đắn của giải thuật chúng ta có thể cài
đặt giải thuật đó và cho thực hiện trên máy với một số bộ dữ liệu mẫu rồi lấy kết
quả thu đƣợc so sánh với kết quả đã biết. Thực ra thì cách làm này khơng chắc chắn
bởi vì có thể giải thuật đúng với tất cả các bộ dữ liệu chúng ta đã thử nhƣng lại sai
với một bộ dữ liệu nào đó. Vả lại cách làm này chỉ phát hiện ra giải thuật sai chứ
chƣa chứng minh đƣợc là nó đúng. Tính đúng đắn của giải thuật cần phải đƣợc
chứng minh bằng toán học. Tất nhiên điều này không đơn giản và do vậy chúng ta
sẽ không đề cập đến ở đây.
Khi chúng ta viết một chƣơng trình để sử dụng một vài lần thì yêu cầu (2) là
quan trọng nhất. Chúng ta cần một giải thuật dễ viết chƣơng trình để nhanh chóng
có đƣợc kết quả, thời gian thực hiện chƣơng trình khơng đƣợc đề cao vì dù sao thì
chƣơng trình đó cũng chỉ sử dụng một vài lần mà thơi.
Tuy nhiên khi một chƣơng trình đƣợc sử dụng nhiều lần thì yêu cầu tiết kiệm
thời gian thực hiện chƣơng trình lại rất quan trọng đặc biệt đối với những chƣơng
trình mà khi thực hiện cần dữ liệu nhập lớn do đó yêu cầu (3) sẽ đƣợc xem xét một
cách kỹ càng. Ta gọi nó là hiệu quả thời gian thực hiện giải thuật.
2. Thời gian thực hiện của giải thuật:
Một phƣơng pháp hiệu quả để xác định hiệu quả thời gian thực hiện của một
giải thuật là lập trình nó và đo lƣờng thời gian thực hiện của hoạt động trên một
máy tính xác định đối với tập hợp đƣợc chọn lọc các dữ liệu vào.
Thời gian thực hiện không chỉ phụ thuộc vào giải thuật mà còn phụ thuộc vào
tập các chỉ thị của máy tính, chất lƣợng của máy tính và kỹ xảo của ngƣời lập trình.
Sự thi hành cũng có thể điều chỉnh để thực hiện tốt trên tập đặc biệt các dữ liệu vào
đƣợc chọn. Để vƣợt qua các trở ngại này, các nhà khoa học máy tính đã chấp nhận
độ phức tạp của thời gian đƣợc tiếp cận nhƣ một sự đo lƣờng cơ bản sự thực thi của
giải thuật. Thuật ngữ tính hiệu quả sẽ đề cập đến sự đo lƣờng này và đặc biệt đối
với độ phức tạp thời gian trong trƣờng hợp xấu nhất.
a. Thời gian thực hiện chƣơng trình:
Thời gian thực hiện một chƣơng trình là một hàm của kích thƣớc dữ liệu vào,
ký hiệu T(n), trong đó n là kích thƣớc (độ lớn) của dữ liệu vào.
Ví dụ:
Chƣơng trình tính tổng của n số có thời gian thực hiện là T(n) = cn trong đó c
là một hằng số.
10
Thời gian thực hiện chƣơng trình là một hàm khơng âm, tức là T(n) 0 n
0.
b. Đơn vị đo thời gian thực hiện
Đơn vị của T(n) không phải là đơn vị đo thời gian bình thƣờng nhƣ giờ, phút
giây … mà thƣờng đƣợc xác định bởi số các lệnh đƣợc thực hiện trong một máy
tính lý tƣởng.
Ví dụ:
Khi ta nói thời gian thực hiện của một chƣơng trình là T(n) = cn thì có nghĩa
là chƣơng trình ấy cần cn chỉ thị thực thi.
c. Thời gian thực hiện trong trƣờng hợp xấu nhất:
Nói chung thì thời gian thực hiện chƣơng trình khơng chỉ phụ thuộc vào kích
thƣớc mà cịn phụ thuộc vào tính chất của dữ liệu vào. Nghĩa là dữ liệu vào có cùng
kích thƣớc nhƣng thời gian thực hiện chƣơng trình có thể khác nhau. Chẳng hạn
chƣơng trình sắp xếp dãy số nguyên tăng dần, khi ta cho vào dãy có thứ tự thì thời
gian thực hiện khác với khi ta cho vào dãy chƣa có thứ tự, hoặc khi ta cho vào một
dãy đã có thứ tự tăng thì thời gian thực hiện cũng khác so với khi ta cho vào một
dãy đã có thứ tự giảm
Vì vậy thƣờng ta coi T(n) là thời gian thực hiện chƣơng trình trong trƣờng hợp
xấu nhất trên dữ liệu vào có kích thƣớc n, tức là: T(n) là thời gian lớn nhất để thực
hiện chƣơng trình đối với mọi dữ liệu vào có cùng kích thƣớc n.
3. Tỷ suất tăng và độ phức tạp của giải thuật
a. Tỷ suất tăng:
Ta nói rằng hàm khơng âm T(n) có tỷ suất tăng (growth rate) f(n) nếu tồn tại
các hằng số c và no sao cho T(n) cf(n) với mọi n no
Ta có thể chứng minh đƣợc rằng “ Cho một hàm không âm T(n) bất kỳ, ta
luôn tìm đƣợc tỷ suất tăng f(n) của nó”
Ví dụ:
Giả sử T(0) = 1, T(1) = 4 và tổng quát T(n) = (n+1)2. Đặt no = 1 và c = 4 thì
với mọi n 1 chúng ta dễ dàng chứng minh rằng T(n) = (n+1)2 4n2 với mọi n 1,
tức là tỷ suất tăng của T(n) là n2
Ví dụ:
Tỷ suất tăng của hàm T(n) = 3n3 +2n2 là n3. Thực vậy, cho no = 0 và c = 5 ta
dễ dàng chứng minh rằng với mọi n 0 thì 3n3 + 2n2 5n3
11
b. Khái niệm độ phức tạp của giải thuật
Giả sử ta có hai giải thuật P1 và P2 với thời gian thực hiện tƣơng ứng là T1(n)
= 100n2 (với tỷ suất là n2) và T2(n) = 5n3 (với tỷ suất tăng là n3). Giải thuật nào sẽ
thực hiện nhanh hơn? Câu trả lời phụ thuộc vào kích thƣớc dữ liệu vào. Với n < 20
thì P2 sẽ nhanh hơn P1 (T2 < T1), do hệ số của 5n3 nhỏ hơn hệ số của 100n2
(5<100). Nhƣng khi n>20 thì ngƣợc lại do số mũ của 100n2 nhỏ hơn số mũ của 5n3
(2 < 3). Ở đây chúng ta chỉ nên quan tâm đến trƣờng hợp n > 20 vì khi n < 20 thì
thời gian thực hiện của cả P1 và P2 đều không lớn và sự khác biệt giữa T1 và T2 là
không đáng kể.
Nhƣ vậy một cách hợp lý là ta xét tỷ suất tăng của hàm thời gian thực hiện
chƣơng trình thay vì xét chính bản thân thời gian thực hiện
Cho một hàm T(n), T(n) gọi là có độ phức tạp f(n) nếu tồn tại các hằng c, No
sao cho T(n) cf(n) với mọi n No (tức là T(n) có tỷ suất tăng là f(n)) và ký hiệu
T(n) là O(f(n)) (đọc là “ơ của f(n)”)
Ví dụ:
T(n) = (n+1)2 có tỷ suẩt tăng là n2 nên T(n) = (n+1)2 là O(n2)
Chú ý: O(c.f(n)) = O(f(n)) với c là hằng số. Đặc biệt O(c) = O(1)
Nói cách khác độ phức tạp tính tốn của giải thuật là một hàm chặn trên của
hàm thời gian. Vì hằng nhân tử c trong hàm chặn trên khơng có ý nghĩa nên ta có
thể bỏ qua vì vậy hàm thể hiện độ phức tạp có các dạng thƣờng gặp sau: log2n, n,
nlog2n, n2, n3, 2n, n!, nn. Ba hàm cuối cùng ta gọi là dạng hàm mũ, các hàm khác
gọi là hàm đa thức. Một giải thuật mà thời gian thực hiện có độ phức tạp là một
hàm đa thức thì chấp nhận đƣợc tức là có thể cài đặt để thực hiện, cịn các giải thuật
có độ phức tạp hàm mũ thì phải tìm cách cải tiến giải thuật.
Khi nói đến độ phức tạp của giải thuật là ta muốn nói đến hiệu quả của thời
gian thực hiện của chƣơng trình nên ta có thể xem việc xác định thời gian thực hiện
của chƣơng trình chính là xác định độ phức tạp của giải thuật.
4. Cách tính độ phức tạp:
Cách tính độ phức tạp của một giải thuật bất kỳ là một vấn đề khơng đơn giản.
Tuy nhiên ta có thể tuân theo một số nguyên tắc sau:
a. Qui tắc cộng:
Nếu T1(n) và T2(n) là thời gian thực hiện của hai đoạn chƣơng trình P1 và P2
và T1(n) = O(f(n)), T2(n) = O(g(n)) thì thời gian thực hiện của đọan hai chƣơng
trình đó nối tiếp nhau là T(n) = O(max(f(n),g(n)))
Ví dụ:
Lệnh gán x:=15 tốn một hằng thời gian hay O(1)
12
Lệnh đọc dữ liệu READ(x) tốn một hằng thời gian hay O(1)
Vậy thời gian thực hiện cả hai lệnh trên nối tiếp nhau là O(max(1,1)) = O(1)
b. Qui tắc nhân:
Nếu T1(n) và T2(n) là thời gian thực hiện của hai đọan chƣơng trình P1 và P2
và T1(n) = O(f(n)), T2(n) = O(g(n)) thì thời gian thực hiện của đọan hai đọan
chƣơng trình đó lồng nhau là T(n) = O(f(n).g(n))
c. Qui tắc tổng qt để phân tích một chƣơng trình:
- Thời gian thực hiện của mỗi lệnh gán, đọc, ghi là O(1)
- Thời gian thực hiện của một chuỗi tuần tự các lệnh đƣợc xác định bằng qui
tắc cộng. Nhƣ vậy thời gian này là thời gian thi hành một lệnh nào đó lâu nhẩt
trong chuỗi lệnh.
- Thời gian thực hiện cấu trúc IF là thời gian lớn nhất thực hiện lệnh sau
THEN hoặc sau ELSE và thời gian kiểm tra điều kiện. Thƣờng thời gian kiểm tra
điều kiện là O(1)
- Thời gian thực hiện vòng lặp là tổng (trên tất cả các lần lặp) thời gian thực
hiện thân vòng lặp. Nếu thời gian thực hiện thân vịng lặp khơng đổi thì thời gian
thực hiện vịng lặp là tích của số lần lặp với thời gian thực hiện thân vòng lặp
d. Độ phức tạp của chƣơng trình có gọi chƣơng trình con khơng đệ quy:
Nếu chúng ta có một chƣơng trình với các chƣơng trình con khơng đệ quy, để
tính thời gian thực hiện của chƣơng trình, trƣớc hểt chúng ta tính thời gian thực
hiện của các chƣơng trình con khơng gọi các chƣơng trình con khác. Sau đó chúng
ta tính thời gian thực hiện của các chƣơng trình con chỉ gọi các chƣơng trình con
mà thời gian thực hiện của chúng đã đƣợc tính. Chúng ta tiếp tục q trình đánh giá
thời gian thực hiện của mỗi chƣơng trình con sau khi thời gian thực hiện của tất cả
các chƣơng trình con mà nó gọi đã đƣợc đánh giá. Cuối cùng ta tính thời gian cho
chƣơng trình chính.
Giả sử ta có một hệ thống các chƣơng trình gọi theo sơ đồ sau:
Chƣơng trình A gọi hai chƣơng trình con là B và C, chƣơng trình B gọi hai
chƣơng trình con là B1 và B2, chƣơng trình B1 gọi hai chƣơng trình con là B11 và
B12
A
B
B1
C
B2
B12
B11
Sơ đồ gọi thực hiện các chƣơng trình con khơng đệ quy
13
Để tính thời gian thực hiện của A ta tính theo các bƣớc sau:
- Tính thời gian thực hiện của C, B2, B11 và B12
- Tính thời gian thực hiện của B1
- Tính thời gian thực hiện của B
- Tính thời gian thực hiện của A
Ví dụ:
void BubbleSort(int M[], int N)
{1}
{ for (int I = 0; I < N-1; I++)
{2}
for (int J = N-1; J > I; J--)
{3}
if (M[J] < M[J-1])
Swap(M[J], M[J-1]);
return;
}
void Swap(int &X, int &Y)
{ int Temp = X;
X = Y;
Y = Temp;
return;
}
Trong cách viết trên, chƣơng trình Bubble gọi chƣơng trình con Swap, do đó
để tính thời gian thực hiện của bubble, trƣớc hểt ta cần tính thời gian thực hiện của
swap. Dễ thấy thời gian thực hiện của swap là O(1) vì nó bao gồm chỉ 3 lệnh gán.
Trong Bubble, lệnh {3} gọi Swap nên chỉ tốn O(1), lệnh {2} thực hiện n-i lần,
mỗi lần tốn O(1) nên chỉ tốn O(n-i). Lệnh {1} thực hiện n-1 lần nên T(n) =
n 1
(n i )
i 1
n(n 1)
O(n 2 )
2
5. Phân tích các chương trình đệ quy:
Với các chƣơng trình có gọi các chƣơng trình con đệ quy, ta khơng thể áp
dụng cách tính nhƣ vừa trình bày ở mục 3.4 bởi vì một chƣơng trình đệ quy sẽ gọi
chính bản thân nó.
Với các chƣơng trình đệ quy, trƣớc hết ta cần thành lập các phƣơng trình đệ
quy, sau đó giải phƣơng trình đệ quy, nghiệm của phƣơng trình đệ quy sẽ là thời
gian thực hiện của chƣơng trình đệ quy.
14
a. Thành lập phƣơng trình đệ quy:
Phƣơng trình đệ quy là một phƣơng trình biểu diễn mối liên hệ giữa T(n) và
T(k), trong đó T(n) là thời gian thực hiện chƣơng trình với kích thƣớc dữ liệu nhập
là n, T(k) thời gian thực hiện chƣơng trình với kích thƣớc dữ liệu là k, với k
thành lập đƣợc phƣơng trình đệ quy, ta phải căn cứ vào chƣơng trình đệ quy.
Ví dụ:
int giaithua (int n)
{
if (n= =0)
giaithua=1
else
giaithua= n*giaithua(n-1)
}
Gọi T(n) là thời gian thực hiện việc tính n giai thừa, thì T(n-1) là thời gian
thực hiện việc tính n-1 giai thừa. Trong trƣờng hợp n=0 thì chƣơng trình chỉ thực
hiện một lệnh gán giaithua = 1, nên tốn O(1), do đó ta có T(0) = C1. Trong trƣờng
hợp n>0 chƣơng trình phải gọi đệ quy giaithua (n-1), việc gọi đệ quy này tốn T(n1), sau khi có kết quả của việc gọi đệ quy, chƣơng trình phải nhân kết quả đó với n
và gán cho giaithua. Thời gian để thực hiện phép nhân và phép gán là một hằng C2.
Vậy ta có:
C1 nếu n=0
T(n) = T(n-1) +C2 nếu n>0
Đây là phƣơng trình đệ quy để tính thời gian thực hiện của chƣơng trình đệ
quy giaithua.
b. Giải phƣơng trình đệ quy:
Có 3 phƣơng pháp giải phƣơng trình đệ quy
1. Phƣơng pháp truy hồi
2. Phƣơng pháp đoán nghiệm
3. Lời giải tổng quát của một lớp các phƣơng trình đệ quy
Phƣơng pháp truy hồi:
Dùng đệ quy để thay thế bất kỳ T(m) với m
cho đến khi tất cả T(m) với m>1 đƣợc thay thế bởi biểu thức của các T(1). Vì T(1)
ln là hằng số nên chúng ta có cơng thức của T(n) chứa các số hạng chỉ liên quan
đến n và các hằng số
15
Ví dụ:
Giải phƣơng trình
C1 nếu n = 1
T(n) = 2T(n/2) +C2n nếu n>1
n
2n
Ta có T(n) 2T( ) C 2 n
n
n
T(n) 2[2T( ) C 2 ] C 2 n 4T ( ) 2C 2 n
2
4n
n4
n
T(n) 4[2T( ) C 2 ] 2C 2 n 8T ( ) 3C 2 n
8n
4
8
T(n) 2 i 8T ( i ) iC 2 n
2k
Giả sử n= 2 , quá trình suy rộng sẽ kết thúc khi i= k, khi đó ta có:
T(n) 2 k T (1) kC2 n
Vì 2k = n nên k = logn và với T(1) =C1 nên ta có:
T(n) = C1n + C2nlogn
Hay T(n) là O(nlogn)
Đốn nghiệm (Sinh viên có thể xem tham khảo)
Ta đóan một nghiệm f(n) và dùng chứng minh quy nạp để chứng tỏ rằng T(n)
f(n) với mọi n.
Thông thƣờng f(n) là một trong các hàm quen thuộc nhƣ logn, n, nlogn, n 2, n3,
2n, n!, nn
Đơi khi chúng ta chỉ đóan dạng của f(n) trong đó có một vài tham số chƣa xác
định (chẳng hạn f(n) = an2 với a chƣa xác định) và trong quá trình chứng minh quy
nạp ta sẽ suy diễn ra giá trị thích hợp của các tham số.
Ví dụ:
Giải phƣơng trình
C1 nếu n = 1
T(n) = 2T(n/2) +C2n nếu n>1
Giả sử chúng ta đóan f(n) = anlogn. Với n = 1 ta thấy rằng cách đóan nhƣ vậy
khơng đƣợc bởi vì anlogn có giá trị 0 khơng phụ thuộc vào giá trị của a. Vì thế ta
thử tiếp theo f(n) = anlogn +b
Với n=1 ta có, T(1) = C1 và f(1)=b, muốn T(1) f(1) thì b C1 (*)
Giả sử rằng T(k) aklogk +b với mọi k
+b
Giả sử n 2, từ (I.1) ta có T(n) = 2T(n/2)+C2n
Áp dụng (I.2) với k= n/2
16
T(n) = 2T(n/2)+ C2n 2[an/2log(n/2)+b]+C2n
T(n) anlogn –an +2b +C2n
T(n) (anlogn +b) + [b+(C2-a)n]. Nếu lấy a C2 +b (**) ta đƣợc
T(n) (anlogn +b) + [b+(C2-C2-b)n]
T(n) (anlogn +b) + (1-n)b
T(n) anlogn +b
Nếu ta lấy a và b sao cho cả (*) và (**) đều thỏa mãn thì T(n) anlogn +b với
mọi nb
b C1
để đơn giản, ta giải hệ
a C 2 b
Ta phải giải hệ
b C1
a C 2 b
Dễ dàng ta có b =C1 và a = C1 + C2 ta đƣợc T(n) (C1 +C2) nlogn +C1 với mọi
n
Hay nói cách khác T(n) là O(nlogn)
Lời giải tổng quát cho một lớp các phƣơng trình đệ quy
Để giải bài tóan kích thƣớc n, ta chia bài tóan đã cho thành a bài tóan con, mỗi
bài tóan con có kích thƣớc n/b. Giải các bài tóan con này và tổng hợp kết quả lại để
đƣợc kết quả của bài tóan đã cho. Với các bài tóan con ta cũng làm nhƣ vậy. Kỹ
thuật này sẽ dẫn chúng ta đến một chƣơng trình đệ quy
Giả thuyết rằng mỗi bài tốn con kích thƣớc 1 lấy một đơn vị thời gian và thời
gian để chia bài tóan kích thƣớc n thành các bài tóan con kích thƣớc n/b và tổng
hợp kết quả từ các bài tóan con để đƣợc lời giải của bài tóan ban đầu là d(n).
Gọi T(n) là thời gian để giải bài tóan kích thƣớc n thì ta có phƣơng trình đệ
quy
T (1) 1
T (n) aT ( n ) d (n) (I.1)
b
Ta sử dụng phƣơng pháp truy hồi để giải phƣơng trình này
T(n) = aT(n/b) + d(n)
n
T (n) a[aT 2
b
n
2 n
d ] d (n) a T 2
b
b
n
ad d (n)
b
n
n
n
n
n
n
T (n) a 2 [aT 3 d 2 ] ad d (n) a 3T 3 a 2 d 2 ad d (n)
b
b
b
b
b
b
17
=…
i n
= a T i
b
i 1 j n
a d j
j 0
b
Giả sử n = bk ta đƣợc: T(n/bk) = T(1) = 1. Thay vào trên với i= k ta có:
k 1
T ( n) a k a j d b k j
j 0
(I.2)
Hàm tiến triển, nghiệm thuần nhất và nghiệm riêng
Trong phƣơng trình đệ quy (I.1) hàm thời gian d(n) đƣợc gọi là hàm tiến triển
(driving function)
Trong công thức (I.2), ak = alogba đƣợc gọi là nghiệm thuần nhất (homogeneous
solutions)
Nghiệm thuần nhất là nghiệm chính xác khi d(n) = 0 với mọi n. Nói cách
khác, nghiệm thuần nhất biểu diễn thời gian để giải tất cả các bài tóan con.
a d b
k 1
Trong cơng thức (I.2),
j
k j
đƣợc gọi là nghiệm riêng (particular
j 0
solutions)
Nghiệm riêng biểu diễn thời gian phải trả để tạo ra các bài tóan con và tổng
hợp các kết quả của chúng. Nhìn vào cơng thức ta thấy nghiệm riêng phụ thuộc vào
hàm tiến triển, số lƣợng và kích thƣớc các bài tóan con.
Khi tìm nghiệm của phƣơng trình (I.1), chúng ta phải tìm nghiệm riêng và so
sánh với nghiệm thuần nhất. Nếu nghiệm riêng nào lớn hơn, ta lấy nghiệm đó làm
nghiệm của phƣơng trình (I.1)
Việc xác định nghiệm riêng khơng đơn giản chút nào, tuy vậy chúng ta cũng
tìm đƣợc một lớp các hàm tiến triển có thể dễ dàng xác định nghiệm riêng.
Hàm nhân
Một hàm f(n) đƣợc gọi là hàm nhân (multiplicative function) nếu
f(m.n)=f(m).f(n) với mọi số nguyên dƣơng m và n.
Ví dụ:
Hàm f(n) = nk là một hàm nhân, vì f(m.n) =(m.n)k = mk.nk = f(m).f(n)
Nếu d(n) trong (I.1) là một hàm nhân thì theo tính chất của hàm nhân ta có
d(b ) = (d(b))k-j và nghiệm riêng của (I.2) là:
k-j
18
k
a
j
d (b) 1
k 1
k 1
a
a k d (b) k
j
k j
k
k
a
d
(
b
)
d
(
b
)
d
(
b
)
(I.3)
a
a
j 0
j 0 d (b)
1
1
d (b)
d (b)
Xét 3 trƣờng hợp sau:
1. Nếu a> d(b) thì nghiệm riêng là O(ak) = O(nlogb a). Nhƣ vậy nghiệm riêng và
nghiệm thuần nhất bằng nhau do đó T(n) = O(nlogba)
Ta cũng thấy thời gian thực hiện chỉ phụ thuộc vào a, b mà không phụ thuộc
vào hàm tiến triển d(n). Vì vậy để cải tiến giải thuật ta cần giảm a hoặc tăng b
2. Nếu a
này nghiệm riêng lớn hơn nghiệm thuần nhất nên T(n) = O(nlogb d(b))
Để cải tiến giải thuật chúng ta cần để ý đến cả d(n), a và b cùng mức độ nhƣ
nhau
Trƣờng hợp đặc biệt quan trọng khi d(n) = n. Khi đó d(b) = b và logb(b) =.
Vì thế nghiệm riêng là O(n) và do vậy T(n) là O(n)
3. Nếu a = d(b) thì cơng thức (I.5) khơng xác định nên ta tính trực tiếp nghiệm
riêng:
Nghiệm
riêng
=
j
k 1
k 1
a
j
k j
k
k
a
(
d
(
b
))
d
(
b
)
d
(
b
)
1 d (b) k k n logb d (b) log b n
d (b)
j 0
j 0
j 0
k 1
Vì a = d(b) nên nghiệm riêng là n log d (b) log b n và nghiệm này lớn gấp logbn lần
nghiệm thuần nhất. Do đó T(n)= O(nlogb alogbn)
b
Trong trƣờng hợp đặc biệt d(n) =n ta đƣợc T(n) =O(nlogn)
Chú ý khi giải một phƣơng trình đệ quy cụ thể, ta phải xem phƣơng trình đó
có thuộc dạng phƣơng trình tổng qt hay khơng. Nếu có thì phải xem xét hàm tiến
triển có phải là hàm nhân khơng. Nếu có thì ta xác định a, d(b) và dựa vào sự so
sánh giữa a và d(b) mà vận dụng một trong ba trƣờng hợp nói trên.
Ví dụ:
Giải các phƣơng trình đệ quy sau với T(1) =1 và
1. T(n) = 4T(n/2) + n
2. T(n) = 4T(n/2) + n2
3. T(n) = 4T(n/2) +n3
19
Trong mỗi trƣờng hợp, a = 4, b = 2 và nghiệm thuần nhất là n 2. Với d(n) = n ta
có d(b) = 2 vì a = 4 > d(b) nên nghiệm riêng cũng là n2 và T(n) = O(n2) trong
phƣơng trình (1)
Trong phƣơng trình (3), d(n) = n3, d(b) = 8 và a < d(b). Vì vậy nghiệm riêng
là O(nlogb d(b)) = O(n3) và T(n) của (3) là O(n3)
Trong phƣơng trình (2) chúng ta có d(b) = 4 = a nên T(n) = O(n2logn)
Các hàm tiến triển khác
Ta xét hai trƣờng hợp dƣới dạng hai ví dụ, trƣờng hợp 1 là tổng quát hóa của
hàm bất kỳ là tích của một hàm nhân với một hằng lớn hơn hoặc bằng 1. Trƣờng
hợp thứ 2 là hàm tiến triển khơng phải là một hàm nhân.
Ví dụ:
Giải phƣơng trình đệ quy sau:
T(1) = 1
T(n) = 3T(n/2) + 2n1.5
Ở đây 2n1.5 không phải là hàm nhân nhƣng n1.5 là hàm nhân. Đặt U(n) = T(n)/2
với mọi n thì:
U(1) = ½
U(n) = 3U(n/2) + n1.5
Nghiệm thuần nhất khi U(1) = 1 là nlog3 = n1.59; vì U(1) = ½ nên nghiệm thuần
nhẩt là n1.59/2 là O(n1.59). Vì T(n) = 2U(n) nên T(n) = O(n1.59) hay T(n) = O(nlog3)
Ví dụ:
Giải phƣơng trình đệ quy sau:
T(1) = 1
T(n) = 2 T(n/2) +nlogn
Vì a = b = 2 nên nghiệm thuần nhất là n. Tuy nhiên, d(n) = nlogn khơng là
hàm nhân ta phải tính nghiệm riêng bằng cách xét trực tiếp:
k 1
2 2
j
k j
log( 2
k j
)2
j 0
k 1
k
(k j ) 2
k 1
k (k 1)
j 0
Vì k= logn chúng ta có nghiệm riêng là O(nlog2n), nghiệm này lớn hơn
nghiệm thuần nhất và T(n) = O(nlog2n)
III. BÀI TẬP
Bài 1: Tính thời gian thực hiện của các đọan chƣơng trình sau:
a) Tính tổng của các số
20
…
sum = 0;
for (int i = 1; i < N; i++)
{ scanf(&n);
sum = sum + x; }
b) Tính tích hai ma trận vuông cấp n C = A*B
…
for (int i = 0; i < n; i++)
for (int j = 1; j < n; j++)
{C[i, j] = 0;
for (int k = 1; k < n; k++)
C[i, j] = C[i, j] + A[i, k] * B[k, j];
}
Bài 2: Giải các phƣơng trình đệ quy sau với T(1) = 1 và:
a)
T(n) = T(n/2) +1
b)
T(n) = 2T(n/2) + logn
c)
T(n) = 2T(n/2) + n
Bài 3: Giải các phƣơng trình đệ quy sau với T(1) = 1 và:
a)
T(n) = 4T(n/3) + n
b)
T(n) = 4T(n/3) + n2
Bài 4: Giải các phƣơng trình đệ quy sau với T(1) = 1 và:
a)
T(n) = 3T(n/2) + n
b)
T(n) = 3T(n/2) + n2
c)
T(n) = 8T(n/2) + n3
Bài 5: Xét định nghĩa số tổ hợp chập k của n nhƣ sau:
1 nếu k = 0 hoặc k =
k
C n C Cn k 1
n 1
n 1
nếu 0 < k < n
k
a) Viết một hàm đệ quy để tính số tổ hợp chập k của n
b) Tính thời gian thực hiện của giải thuật nói trên
21
CHƢƠNG 2: CÁC KIỂU DỮ LIỆU NÂNG CAO
Mục tiêu:
Nhằm cung cấp các kiến thức và kỹ năng về các cấu trúc dữ liệu nâng cao nhƣ:
mảng, cấu trúc và hợp, con trỏ và tập tin. Đồng thời, dùng các kiến thức này để giải
qêt các bài tóan ví dụ và bài tập.
Nội dung:
I. MẢNG:
1. Mảng 1 chiều:
Mảng 1 chiều là một cấu trúc dữ liệu bao gồm một số cố định các phần tử có kiểu
giống nhau đƣợc tổ chức thành một dãy tuần tự các phần tử. Nhƣ vậy mảng một chiều là
một cấu trúc dữ liệu có kích thƣớc cố định và đồng nhất.
a. Sự đặc tả cú pháp:
Các thuộc tính của 1 mảng là:
- Số lƣợng các phần tử, luôn đƣợc chỉ rõ bằng cách cho miền giá trị của các chỉ số.
Miền giá trị này thông thƣờng đƣợc cho bởi một miền con các số nguyên, trong trƣờng
hợp đó số lƣợng các phần tử bằng số nguyên cuối cùng - số nguyên đầu tiên + 1
- Kiểu dữ liệu của mỗi một phần tử
- Chỉ số đƣợc sử dụng để lựa chọn mỗi một phần tử. Nếu tập chỉ số đƣợc cho bởi
một miền con của tập các số nguyên đầu tiên chỉ định phần tử đầu tiên số nguyên thứ 2
chỉ định phần tử thứ 2…
Khai báo mảng trong C là: tên kiểu tên mảng [số phần tử]
Ví dụ:
int msn[10]
Khai báo này xác định mảng msn có 10 phần tử là các số nguyên. Các phần tử này
đƣợc lựa chọn bởi các chỉ số từ 0 đến 9
Miền giá trị của chỉ số không nhất thiết bắt đầu từ 1
Miền giá trị của chỉ số khơng nhất thiết là miền con của số ngun, nó có thể là một
liệt kê bất kỳ (hoặc 1 miền con của một liệt kê).
Các phép tóan trên mảng bao gồm:
Phép toán lựa chọn một phần tử của mảng là phép lấy chỉ số, đƣợc viết bằng tên của
mảng theo sau là chỉ số của phần tử đƣợc lựa chọn, ví dụ V[1] hoặc X[ba]. Chỉ số có thể
là một hằng họăc một biến (nói chung là một biểu thức), ví dụ: v[i] hay v[i+2]. Nhờ chỉ
số là một biểu thức nên việc lập trình trở nên đơn giản hơn nhiều nhờ tính khái quát của
chỉ số. Ví dụ: để in ra giá trị của 10 phần tử trong mảng v, thay vì ta phải viết 10 lệnh in
22
các phần tử cụ thể theo kiểu scanf(v[1]), scanf(v[2]), scanf(v[3]),…, ta chỉ cần viết một
lệnh for (int i=0; i
phép lựa chọn trực tiếp.
Các phép tóan khác trên mảng bao gồm các phép toán tạo và hủy bỏ mảng, gán hai
mảng cho nhau và các phép tóan thực hiện nhƣ các phép tóan số học trên từng cặp 2
mảng có cùng kích thƣớc. Chẳng hạn phép cộng hai mảng (cộng các phần tử tƣơng ứng).
Tùy thuộc vào ngôn ngữ mà các phép tóan này có hoặc khơng có.
b. Cài đặt một mảng:
Biểu diễn bộ nhớ tuần tự đƣợc sử dụng để biểu diễn cho một mảng. Mơ hình sau
minh họa cho sự biểu diễn bộ nhớ của mảng A: int A[10]
Địa chỉ cơ sở
Bộ mô tả
Bộ nhớ cho
các phần tử
của mảng
Mảng A
LB
UB
Kiểu phần tử
E
A[LB]
A[LB+1]
…
Kiểu dữ liệu
Cận dƣới của tập chỉ số
Cận trên của tập chỉ số
Kiểu dữ liệu của phần tử
Kích thƣớc của mỗi phần tử
A[UB]
Khối ơ nhớ để lƣu trữ một mảng có hai phần: bộ mơ tả và bộ nhớ dành cho các
phần tử của mảng. Trong bộ mô tả lƣu trữ kiểu dữ liệu của cấu trúc (mảng A), cận dƣới
của tập chỉ số (LB_ Lower Bound), cận trên của tập chỉ số (UP_ Upper Bound). Kiểu dữ
liệu của phần tử và kích thƣớc mỗi phần tử (E). Bộ nhớ dành cho các phần tử của mảng
lƣu trữ liên tiếp các phần tử từ phần tử đầu tiên (A[LB]) cho đến phần tử cuối cùng
(A[UB]). Địa chỉ của ô nhớ đầu tiên trong khối gọi là địa chỉ cơ sở.
Phép toán lựa chọn một phần tử đƣợc thực hiện bằng cách tính vị trí của phần tử cần
lựa chọn theo cơng thức:
Vị trí của phần tử thứ i = + D + (i - LB) * E
Trong đó i là chỉ số của phần tử cần lựa chọn, là địa chỉ cơ sở của khối ô nhớ (địa
chỉ word hoặc byte đầu tiên của khối ơ nhớ dành cho mảng) D là kích thƣớc của bộ mô
tả, LB là cận dƣới của tập chỉ số và E là kích thƣớc của mỗi một đối tƣợng dữ liệu thành
phần (số word hoặc byte cần thiết để lƣu trữ một phần tử). Nếu chỉ số là một giá trị của
kiểu liệt kê chứ không phải số ngun thì hiệu i – LB phải đƣợc tính tốn một cách thích
hợp (chẳng hạn sử dụng hiệu của hai số thứ tự tƣơng ứng của i và LB trong liệt kê).
23
Phép gán một mảng cho một mảng khác có cùng thuộc tính đƣợc thực hiện bằng
cách sao chép nội dung trong khối ô nhớ biểu diễn mảng thứ nhất sang khối ơ nhớ biểu
diễn mảng thứ hai.
Các phép tốn trên toàn bộ mảng đƣợc thực hiện bằng cách sử dụng các vòng lặp xử
lý tuần tự các phần tử của mảng.
2. Mảng nhiều chiều:
Ma trận (mảng hai chiều) đƣợc xem nhƣ là một mảng của các mảng. Mảng 3 chiều
đƣợc xem nhƣ một mảng của các ma trận…
a. Sự đặc tả và cú pháp:
Mảng nhiều chiều tƣơng tự nhƣ mảng nhƣng chỉ có một thuộc tính khác mảng là
mỗi một chiều phải có một tập chỉ số tƣơng ứng. Chẳng hạn sự khai báo của C:
tên kiểu tên mảng[kích thƣớc1] [kích thƣớc2];
Sự khai báo này cho ta thấy mảng tên mảng có hai chiều, chiều thứ nhất đƣợc xác
định bởi tập chỉ số kích thƣớc1 và chiều thứ hai đƣợc xác định bởi tập chỉ số kích
thƣớc2. Có thể xem đây là một ma trận có kích thƣớc1 dịng và kích thƣớc2 cột, nhƣ vậy
sẽ có kích thƣớc1 x kích thƣớc2 phần tử, mỗi phần tử có thể lƣu trữ một số tên kiểu.
Phép lựa chọn một phần tử đƣợc thực hiện bằng cách chỉ ra tên mảng và chỉ số của
mỗi một chiều. Chẳng hạn để lựa chọn một phần tử của ma trận ta viết tên ma trận, theo
sau là cặp chỉ số dòng, cột và đƣợc đặt trong cặp dấu ngoặc vng, ví dụ: msn[2][0]. Nhƣ
vậy phép lựa chọn một phần tử của mảng nhiều chiều là phép lựa chọn trực tiếp.
b. Sự cài đặt:
Sự biểu diễn bộ nhớ đối với mảng nhiều chiều tƣơng tự nhƣ sự biểu diễn bộ nhớ đối
với mảng. Nghĩa là cũng sử dụng sự biểu diễn tuần tự đƣợc chia làm hai phần: bộ mô tả
và bộ nhớ cho các phần tử. Bộ mô tả của mảng giống bộ mô tả của vecto ngoại trừ mỗi
một chiều có một cận dƣới và cận trên của tập chỉ số của chiều đó. Trong bộ nhớ dành
cho các phần tử ta cũng lƣu trữ liên tiếp các phần tử theo một trật tự nào đó.
Với ma trận, về mặt logic thì ma trận là một bảng gồm m dòng và n cột, mỗi một ô
là một phần tử nhƣng bộ nhớ lại chỉ gồm các ơ liên tiếp nhau, vì thế ta phải lƣu trữ ma
trận theo trật tự dòng hoặc theo trật tự cột.
Lƣu trữ theo trật tự dịng có nghĩa là trong bộ nhớ dành cho các phần tử ta lƣu trữ
tuần tự các phần tử trong dòng thứ nhất, tiếp đến là các phần tử trong dòng thứ hai… cho
đến dòng cuối cùng.
Lƣu trữ theo trật tự cột nghĩa là trong bộ nhớ dành cho các phần tử ta lƣu trữ tuần tự
các phần tử trong cột thứ nhất tiếp đến là các phần tử trong cột thứ hai… cho đến cột
cuối cùng.
24