Professional Documents
Culture Documents
TỔNG QUAN
Đ t ng ủa thuật toán
Thuật toán có các đặc trưng như sau :
- Tính đúng đắn: thuật toán cần phải đảm bảo cho một kết quả
đúng au khi thực hiện đối với các bộ dữ liệu đầu vào. Đ y có thể nói
là đặc trưng quan trọng nhất đối với một thuật toán.
- Tính dừng: thuật toán cần phải đảm bảo ẽ dừng sau một ố hữu
hạn bước.
- Tính xác định: các bước của thuật toán phải được phát biểu rõ
ràng, cụ thể, tránh gây nhầm lẫn.
2
- Tính hiệu quả: thuật toán được x m là hiêụ quả nếu có khả n ng
giải quyết hiệu qủa bài toán đặt ra, đáp ứng yêu cầu về mặt thời gian
xử lý hoặc không gian lưu trữ trong bộ nhớ.
- Tính h á : thuật toán được gọi là có tính phố quát ph
biến) nếu có thể giải quyết được một lớp các bài toán tương tự.
1.2.3. u ễn thuật t n
Có nhiều cách khác nhau để biểu diễn thuật toán, gồm: biểu diễn
bằng ngôn ngữ tự nhiên, lưu đồ giải và mã giả.
3
Biểu diễn bằng lưu đồ giải thuật
Trong cách biểu diễn này sử dụng, hệ thống các ký hiệu để mô tả
chức n ng, ý nghĩa của từng bước thực hiện trong thuật toán.
Hệ thống các ký hiệu và chức n ng của các ký hiệu sử dụng để
biểu diễn thuật toán :
4
Nhận xét:
Với cách biểu diễn này, có ưu điểm là t ng tính trực quan, rõ
ràng trong các bước thực hiện thuật toán, trong đó có đi u vào
từng chi tiết xử lý cụ thể trong từng bước của thuật toán. Tuy
nhiên, hạn chế của cách biểu diễn này là cần nhiều thời gian hơn
trong việc xây dựng các bước thực hiện so với biểu diễn bằng ngôn
ngữ tự nhiên.
a = a;
max i
i= i +1;
}
end while;
Nhận xét :
Với cách biểu diễn này, có ưu điểm là đỡ cồng kềnh so với cách
biểu diễn bằng lưu đồ giải thuật, rõ ràng hơn o với cách biểu diễn
bằng ngôn ngữ tự nhiên. Tuy nhiên, hạn chế của cách biểu diễn này là
5
đòi hỏi phải có kiến thức cơ bản về ngôn ngữ lập trình nào đó để sử
dụng biểu diễn các bước thuật toán1.
Thông thường hai cách biểu diễn thuật toán sử dụng ph biến là
ngôn ngữ tự nhiên và lưu đồ giải thuật.
1
Trong những phần sau của cuốn giáo trình này sử dụng ngôn ngữ lập trình C/
++ để biểu diễn các thuật toán
6
khác. Một công việc mà không phải bất cứ người nào cũng làm được,
vì phải hiểu được các khái niệm liên quan.
Ðánh giá về thời gian của thuật toán không phải là xác định thời
gian tuyệt đối chạy thuật toán mất bao nhiêu gi y, bao nhiêu phút,...)
để thực hiện thuật toán mà là xác định mối liên quan giữa dữ liệu đầu
vào input) của thuật toán và chi phí ố thao tác, ố ph p tính cộng,
trừ, nh n, chia, rút c n,...) để thực hiện thuật toán. Sở dĩ người ta
không quan t m đến thời gian tuyệt đối của thuật toán, vì yếu tố này
phụ thuộc vào tốc độ của máy tính, mà các máy tính khác nhau thì có
tốc độ rất khác nhau. Như thế, một cách t ng quát, chi phí thực hiện
thuật toán là một hàm ố phụ thuộc vào dữ liệu đầu vào
T = f(input)
Tuy vậy, khi ph n tích thuật toán, người ta thường chỉ chú ý đến
mối liên quan giữa độ lớn của dữ liệu đầu vào và chi phí. Trong các
thuật toán, độ lớn của dữ liệu đầu vào thường được thể hiện bằng
một con ố nguyên n. hẳng hạn ắp xếp N con ố nguyên, tìm con
ố lớn nhất trong N ố, tính điểm trung bình của N học inh, ... Lúc
này, người ta thể hiện chi phí thực hiện thuật toán bằng một hàm ố
phụ thuộc vào N:
T = f(N)
Việc x y dựng một hàm T t ng quát như trên trong mọi trường
hợp của thuật toán là một việc rất khó kh n, nhiều lúc không thể thực
hiện được. hính vì vậy mà ta chỉ x y dựng hàm T cho một ố trường
hợp đáng chú ý nhất của thuật toán, thường là trường hợp tốt nhất và
xấu nhất.
Ta x t lại ví dụ thuật toán tìm phần tử giá trị lớn nhất được nêu
ở trên:
- Bước 1: Ghi nhớ amax = a1
- Bước 2: hởi gán giá trị biến i = 2
- Bước 3: Nếu i < N) thì thực hiện :
7
+ Bước 3.1: Nếu ai > amax ) thì
Ghi nhớ amax = ai
+ Bước 3.2 : Gán i = i+1 // ăng i lên mộ đơn vị
Ngược lại :
huyển ang bước 5.
- Bước 4. Trở lại bước 3.
- Bước 5. Phần tử lớn nhất dãy a chính là amax. ết thúc.
Nhận x t :
- Nếu mảng chỉ có 1 phần tử thì phần tử đó là ố lớn nhất.
- Giả ử mảng có N phần tử và ta đã xác định được phần tử lớn
nhất là amax . Nếu b ung thêm phần tử thứ an+1 vào dãy mà an+1 > amax
thì an+1 chính là phần tử lớn nhất của mảng có N+1 phần tử. Trường
hợp ngược lại, nghĩa là an+1 < amax thì amax vẫn là phần tử lớn nhất của
mảng có N+1 phần tử.
Trong thuật toán trên, để đơn giản, ta chỉ xem chi phí là số lần so
sánh ở bước 3.1 và số lần ghi nhớ trong bước 3.1, như thế :
- Trường hợp tốt nhất của thuật toán này xảy ra khi con số lớn
nhất nằm đầu dãy (amax = a1)
- Trường hợp xấu nhất xảy ra khi con số lớn nhất nằm ở cuối dãy
(amax = aN) và dãy được sắp xếp theo thứ tự t ng dần.
Dựa theo ơ đồ khối của thuật toán, ta nhận thấy rằng, trong mọi
trường hợp của bài toán, thao tác ở bước 3.1 luôn được thực hiện và số
lần thực hiện là N - 1 (ứng với việc xét từ phần tử a2 đến aN). Ta gọi
đ y là chi phí cố định hoặc bất biến của thuật toán.
Trường hợ ố nhấ :
Do amax = a1 , suy ra với mọi i >2, ai< amax. Do đó, điều kiện ai >
amax ở bước 3.1 luôn không thỏa mãn nên thao tác ghi nhớ không bao
8
giờ được thực hiện. Như vậy, chi phí chung cho trường hợp này chính
là chi phí cố định của bài toán.
T = f(N) = N-1
Trường hợ xấ nhấ :
Ta có với mọi i>1, ai-1< ai do định nghĩa mảng được ắp xếp
t ng dần) nên điều kiện ai>amax ở bước 3.1 luôn thỏa mãn, thao tác ghi
nhớ luôn được thực hiện. Như vậy, ngoài chi phí chung là N-1 phép so
ánh, ta cần phải d ng thêm N-1 thao tác ghi nhớ ở bước 3.1. Như
vậy, t ng chi phí của trường hợp này
T = f(N) = 2(N-1) = 2N-2
Tuy chi phí của thuật toán trong trường hợp tốt nhất và xấu nhất
có thể nói lên nhiều điều, nhưng vẫn chưa đưa ra được một hình dung
tốt nhất về độ phức tạp của thuật toán. Ðể có thể hình dung chính xác
về độ phức tạp của thuật toán, ta x t đến một yếu tố khác là độ t ng
của chi phí khi độ lớn N của dữ liệu đầu vào t ng.
Th o định nghĩa ở trên, ta nhận thấy chi phí thấp nhất và lớn nhất
của thuật toán tìm ố lớn nhất đều bị chặn bởi O N) tồn tại hằng ố
C = 10, k = 1 để 2N - 2 < 10N với mọi N>1).
Một cách t ng quát, nếu hàm chi phí của thuật toán x t trong
một trường hợp nào đó) bị chặn bởi O f N)) thì ta nói rằng thuật toán
có độ phức tạp là O f N)) trong trường hợp đó.
Như vậy, thuật toán tìm ố lớn nhất có độ phức tạp trong trường
hợp tốt nhất và xấu nhất đều là O N). Người ta gọi các thuật toán có
độ phức tạp O N) là các thuật toán có độ phức tạp tuyến tính.
Bảng dưới đ y thể hiện độ phức tạp của thuật toán được sử dụng
rộng rãi. ác độ phức tạp được sắp xếp theo thứ tự t ng dần.
Tên Ký hiệu
Độ phức tạp hằng số O(C)
Độ phức tạp logarith O(log(N))
Độ phức tạp tuyến tính O(N)
Độ phức tạp NlogN O(N*log(N))
9
Độ phức tạp đa thức O(Nk)
Độ phức tạp lũy thừa O(aN)
Độ phức tạp giai thừa O(N!)
10
1.2.5.3. hia để trị
hiến lươc chia để tri là chiến lược quan trọng trong thiết kế các
thuật toán. tưởng của chiến lươc này rất đơn giản, khi cần giải quyết
mô bài toán, ta ẽ tiến hành chia bài toán đó thành các bài toán nhỏ
hơn. Tiến hành thực hiện giải các bài toán nhỏ hơn đó và au đó kết
hợp nghiệm của các bài toán nhỏ hơn đã giải lại thành nghiệm của bài
toán ban đầu.
1.3.1. Ki u dữ liệu
11
1.3.1.2. Các kiểu dữ liệu
Có hai kiểu dữ liệu là kiểu dữ liệu cơ bản và kiểu dữ liệu có
cấu trúc.
Kiểu dữ liệu cơ bản thường là các loại dữ liệu đơn giản và không
có cấu trúc, bao gồm: các giá trị kiểu số như ố nguyên, số thực, các
giá trị logic,… ác ngôn ngữ lập trình thường cung cấp sẵn kiểu dữ
liệu này cho người lập trình và thường được gọi là các kiểu dữ liệu cơ
sở. Tùy theo các ngôn ngữ lập trình khác nhau mà các kiểu dữ liệu cơ
sở có thể khác nhau về cách thức khai báo, sử dụng, miền giá trị, cũng
như các ph p toán trên đó.
Ví dụ :
Kiểu số nguyên, không dấu trong ngôn ngữ được khai báo sử
dụng bằng từ khóa un ign int, có kích thước 2 bytes và có miền giá trị
từ giá trị 0 đến 65535.
Trong thực tế, khi giải quyết các bài toán, bản thân các kiểu dữ
liệu cơ ở không đủ để biểu diễn các dữ liệu đa dạng, do đó nhu
cầu xây dựng các kiểu dữ liệu mới là cần thiết. Các kiểu dữ liệu
mới thường được xây dựng dựa trên các kiểu dữ liệu cơ ở của
ngôn ngữ lập trình. Những kiểu dữ liệu mới này được gọi là các
kiểu dữ liệu cấu trúc.
Ví dụ:
Kiểu dữ liệu nhân viên, bao gồm các thông tin: mã nhân viên, tên
nhân viên, ngày inh, địa chỉ thường trú, ngày tuyển dụng, chức vụ
được tạo ra từ các kiểu dữ liệu cơ ở, như au
Mã nhân viên: kiểu số
Tên nhân viên: kiểu chu i ký tự
Ngày sinh: kiểu ngày tháng n m
Địa chỉ thường trú: kiểu chu i ký tự
Ngày tuyển dụng: kiểu ngày tháng n m
12
Chức vụ: kiểu chu i ký tự
Các kiểu dữ liệu cấu trúc ph biến thường sử dụng:
Kiểu dữ liệu danh sách liên kết
Kiểu dữ liệu bản ghi
Kiểu dữ liệu cây
Kiểu dữ liệu bảng b m
1.3.2. ut ữ liệu
Theo và Donald Knuth trong The Art of Computer Programming,
1997 [2], cấu trúc dữ liệu là cách lưu dữ liệu trong máy tính sao cho
dữ liệu có thể được sử dụng một cách hiệu quả.
Tính hiệu quả được thể hiện qua các đặc điểm: chính xác, tối ưu
về bộ nhớ, khả n ng h trợ các thao tác tìm kiếm, truy xuất, khả n ng
cập nhật, thêm hoặc xóa trên dữ liệu và cuối c ng là tính đơn giản và
dễ hiểu.
Trong thiết kế và xây dựng một chương trình máy tính, việc chọn
cấu trúc dữ liệu là vấn đề quan trọng, đặc biệt với các chương trình
lớn lại càng có tầm quan trọng cao, vì tính hiệu quả phụ thuộc rất
nhiều vào cách thức t chức và lưu trữ dữ liệu.
M i loại cấu trúc dữ liệu phù hợp với một vài loại ứng dụng khác
nhau, ví dụ cách t chức dữ liệu theo cấu trúc cây đặc biệt phù hợp với
các yêu cầu cần phân nhóm dữ liệu. Thông thường, sau khi cấu trúc
dữ liệu được chọn phù hợp, ta sẽ x m x t đến thuật toán cần sử dụng.
Nhưng đôi khi trình tự công việc diễn ra theo thứ tự ngược lại, khi đó
cấu trúc dữ liệu được chọn phụ thuộc vào những bài toán đã có trước
thuật toán và cần cải tiến để chạy tốt nhất với cách t chức dữ liệu
khác. Rõ ràng, trong cả hai trường hợp, việc lựa chọn cấu trúc dữ liệu
là rất quan trọng.
Thông thường, một cấu trúc dữ liệu được chọn c n thận ẽ cho
ph p thực hiện thuật toán hiệu quả hơn. Việc chọn cấu trúc dữ liệu
thường bắt đầu từ chọn một cấu trúc dữ liệu trừu tượng. Một cấu trúc
13
dữ liệu được thiết kế tốt cho ph p thực hiện nhiều ph p toán, ử dụng
càng ít tài nguyên, thời gian xử lý và không gian bộ nhớ càng tốt.
ác cấu trúc dữ liệu được x y dựng bằng cách ử dụng các kiểu
dữ liệu cơ bản và các ph p toán trên đó được cung cấp bởi một ngôn
ngữ lập trình cụ thể.Tri thức đó đã dẫn đến ự phát triển của
nhiều ngôn ngữ lập trình và phương pháp thiết kế được hình thức hóa,
mà trong đó, nh n tố t chức quan trọng là các cấu trúc dữ liệu chứ
không phải các thuật toán. Đa ố ngôn ngữ lập trình có một tính n ng
thuộc dạng hệ thống modul hóa cho ph p các cấu trúc dữ liệu được
tái ử dụng an toàn trong các ứng dụng khác nhau.
14
CHƯƠNG 2
SẮP XẾP VÀ TÌM KIẾM
2.1. MỐI QUAN HỆ GIỮA NHU CẦU SẮP XẾP VÀ TÌM KIẾM
DỮ LIỆU
Ngày nay, với sự bùng n của dữ liệu, các công cụ tìm kiếm trên
mạng ngày càng trở nên quan trọng hơn bao giờ hết. Chính nhờ các
công cụ tìm kiếm này mà người dùng có cơ hội tìm được nhanh chóng
các thông tin và dữ liệu cần thiết thông qua thao tác nhập vào các từ
khóa nội dung để truy xuất đến các hệ thống lưu trữ và quản lý dữ
liệu. Các công cụ tìm kiếm Google, Yahoo,.. là các ví dụ rất cụ thể
cho nhu cầu tìm kiếm thông tin, khi m i ngày có hàng tỷ thao tác tìm
kiếm được thực hiện trên các công cụ này.
Khi xem xét vấn đề ở phạm vi nhỏ hơn, với các ứng dụng cá
nhân hàng ngày, như phần mềm soạn thảo Microsoft Word chẳng
hạn, thì thao tác tìm kiếm cũng được sử dụng khá nhiều trong quá
trình người dùng sử dụng phần mềm. Do đó có thể nói, cho dù ở
phạm vi nhỏ trong một ứng dụng soạn thảo hay phạm vi lớn hơn là
trong các công cụ tìm kiếm trên mạng thì thao tác tìm kiếm luôn
được sử dụng nhiều nhất.
Bên cạnh đó, khi nói tới thao tác tìm kiếm thì ta cũng thường
đề cập đến thao tác sắp xếp, bởi cả hai thao tác tìm kiếm và sắp xếp
đều thực hiện với dữ liệu và khi dữ liệu trở nên bùng n thì việc t
chức, sắp xếp lại, hệ thống lại dữ liệu đóng vai trò rất quan trọng,
tác động đến hiệu n ng của thao tác tìm kiếm. Để đạt được điều
này dữ liệu phải được t chức theo một thứ tự nào đó để hiệu quả
hơn, vì vậy nhu cầu sắp xếp dữ liệu cũng rất quan trọng. Như thế,
nhu cầu tìm kiếm và nhu cầu sắp xếp dữ liệu luôn song hành với nhau
và hai yếu tố này luôn có mối quan hệ tương h lẫn nhau trong các
hệ thống quản lý và lưu trữ dữ liệu.
15
2.2. ĐỊNH NGHĨA BÀI O N SẮP XẾP
Sắp xếp là quá trình xử lý một danh sách các phần tử để đặt
chúng theo một thứ tự thỏa mãn một tiêu chu n nào đó dựa trên dữ
liệu được lưu trong các phần tử.
Bài toán sắp xếp được phát biểu như au:
ho trước một dãy số a1 , a2 ,… , aN gồm N phần tử được lưu trữ
trong cấu trúc dữ liệu mảng. Hãy sắp xếp lại dãy số a1 , a2 ,… , aN, sao
cho hình thành được dãy mới ak1 , ak2 ,… ,akN có thứ tự (ví dụ t ng
dần, nghĩa là aki > aki-1).
Như thế, sau khi sắp xếp xong thứ tự các phần tử trong dãy sẽ
thay đ i vị trí sao cho thỏa mãn yêu cầu trên.
16
Heap sort
Quick sort
Merge sort
17
int min; // chỉ số hần ử nhỏ nhấ rong dãy hiện hành
for (i=0; i<N-1; i++)
{ min = i;
for (j = i+1; j <N; j++)
{ if (a[j ] < a[min])
min = j; // vị rí hần ử hiện ại nhỏ nhấ
}
if (min!=i)
Swap(a[min],a[i]); //hàm hoán vị a[min], a[i]
}
}
Ví dụ:
Giả ử cho dãy ban đầu là ={3, 9, 6, 1, 2}. Hãy ắp xếp dãy ố
đã cho có thứ tự t ng dần.
Các bước giải thuật được thực hiện như sau:
Vòng lặp i=0; dãy : 3, 9, 6, 1, 2, ố phần tử của dãy 5
- Giả ử phần tử nhỏ nhất Min = [0] = 3
- Tìm được phần tử nhỏ nhất thực tế Min = [3]=1
- Hoán vị [0] và [3], được dãy ố mới 1, 9, 6, 3, 2
- Loại phần tử [0] ra khỏi dãy
Vòng lặp i=1; dãy :1, 9, 6, 3, 2, ố phần tử của dãy 4
- Giả ử phần tử nhỏ nhất Min = [1] = 9
- Tìm được phần tử nhỏ nhất thực tế Min = A[4]=2
- Hoán vị [1] và [4], được dãy ố mới 1, 2, 6, 3, 9
- Loại phần tử A[1] ra khỏi dãy
18
Vòng lặp i=2; dãy :1, 2, 6, 3, 9, ố phần tử của dãy 3
- Giả ử phần tử nhỏ nhất Min = [2] = 6
- Tìm được phần tử nhỏ nhất thực tế Min = [3]=3
- Hoán vị [2] và [3], được dãy ố mới 1, 2, 3, 6, 9
- Loại phần tử [2] ra khỏi dãy
Vòng lặp i=3; dãy :1, 2, 3, 6, 9, ố phần tử của dãy 2
- Giả ử phần tử nhỏ nhất Min = [3] = 6
- Tìm được phần tử nhỏ nhất thực tế Min = [3]=6
- hông hoán vị trong trường hợp này do phần tử giả ử nhỏ
nhất và phần tử nhỏ nhất thực tế c ng nằm ở một ví trí trong
dãy.
- Loại phần tử [3] ra khỏi dãy
Vòng lặp i=4; dãy :1, 2, 3, 6, 9, ố phần tử của dãy 1
Dừng giải thuật.
Vậy dãy au khi ắp xếp t ng 1, 2, 3, 6, 9.
19
2.3.1.2. Giải thuật sắp xếp đ i ch trực tiếp
21
- Vòng lặp j = 4 do [2] > [4] hoán vị [2] và [4]. Dãy
mới {1, 2, 3, 9, 6}
Vòng lặp i=3; dãy : 1, 2, 3, 9, 6
- Vòng lặp j = 4 do [3] > [4] hoán vị [3] và [4]. Dãy
mới {1, 2, 3, 6, 9}
Vậy dãy au khi ắp xếp t ng 1, 2, 3, 6, 9.
Đánh giá giải thuật
ó thể thấy rằng, so với giải thuật sắp xếp chọn trực tiếp, giải
thuật ắp xếp bằng cách đ i ch trực tiếp cần ố bước o ánh tương
đương tức là N*(N - 1) 2 lần o ánh. Nhưng ố bước đ i ch hai
phần tử cũng bằng với ố lần o ánh : N*(N - 1)/2.
Trong trường hợp xấu nhất ố bước đ i ch của giải thuật bằng
với ố lần o ánh, trong trường hợp trung bình ố bước đ i ch là N
*(N - 1)/4. Trong trường hợp tốt nhất, ố bước đ i ch bằng 0. Như
vậy, giải thuật ắp xếp đ i ch trực tiếp nói chung là chậm hơn nhiều
o với giải thuật ắp xếp chọn trực triếp, do ố lần đ i ch nhiều hơn.
22
- Bước 4: Gán a[pos] = x; //đoạn a[1]..a[i] đã được sắp
- Bước 5: Gán i = i+1
Nếu i < n thì
Lặp lại Bước 2
Ngược lại:
Dừng giải thuật
24
- So ánh phần tử [4]= 2 và {A[0]=1, A[1]=3, A[2]=6,
A[3]=9} để chèn [4] vào dãy để được dãy có 5 phần tử
{A[0], A[1], A[2], A[3], A[4] } t ng dần
- Do A[4] nhỏ hơn đồng thời [1], [2], A[3] nên dời ch
A[1], A[2], [3] qua một vị trí, [4] thế ch [1] au dời
ch .
- Ta được 5 phần tử ắp t ng trong dãy 1, 3, 6, 2, 9
Vậy dãy au khi ắp xếp t ng 1, 2, 3, 6, 9.
25
Các bước giải thuật
- Bước 1:Khởi gán i= 0;
- Bước 2:Khởi gán j = N - 1; //Duyệt từ cuối dãy ngược về vị trí i
Trong khi (j > i) thực hiện :
{ Nếu (a[j] < a[j-1]) thì
Swap(a[j],a[j-1]); // hoán vị a[j], a[j-1]
j = j - 1;
}
- Bước 3: i = i + 1;
Nếu i > = N – 1 thì
Dừng giải thuật
Ngược lại :
Lặp lại bước 2.
26
Các bước của giải thuật được thực hiện như sau:
27
Việc thực hiện giải thuật này được chia làm 2 giai đoạn:
- Giai đoạn thứ nhất : Tạo heap từ dãy ban đầu. Th o định nghĩa
của heap thì nút cha bao giờ cũng lớn hơn tất cả các nút con. Do đó,
nút gốc của heap bao giờ cũng là phần tử lớn nhất.
- Giai đoạn thứ hai : Sắp xếp dãy dựa trên heap tạo được ở giai
đoạn trước đó. Do nút gốc là nút lớn nhất nên nó sẽ được chuyển về
vị trí cuối cùng của dãy và phần tử cuối cùng sẽ được thay vào gốc
của h ap. hi đó, ta có một cây mới, c y này chưa phải heap (với
số nút bớt đi 1), ta tiếp tục chuyển cây thành heap và lặp lại quá
trình này cho tới khi heap chỉ còn một nút. Đó chính là phần tử bé
nhất của dãy và được đặt lên đầu dãy.
28
Cài đặt giải thuật
Giai đoạn 1 : Hiệu chỉnh dãy số ban đầu thành Heap
Hiệu chỉnh a1, a1+1, ..,ar thành Heap
void shift(int a[], int l, int r)
{
int x,i,j;
i=l;
j=2*i+1;
x=a[i];
while(j<=r)
{ if (j<r)
if (a[j]<a[j+1])
j++;
if (a[j]<=x)
return;
else
{ a[i]=a[j];
a[j]=x;
i=j;
j=2*i+1;
x=a[i];
}
}
}
29
Hiệu chỉnh a ,..a thành Heap
0 n-1
30
Ví dụ
Giả ử cho dãy ố A= { 6, 5, 3, 1, 8, 7, 2, 4 }. Hãy ắp xếp dãy
ố đã cho t ng dần.
ác bước của giải thuật được thực hiện như au
Giai đoạn 1: Hiệu chỉnh dãy ban đầu thành Heap
Thêm
Hoán
Heap vào
vị
Heap
Null 6
6 5
6, 5 3
6, 5, 3 1
6, 5, 3, 1 8
6, 5, 3, 1, 8 5, 8
31
6, 8, 3, 1, 5 6, 8
8, 6, 3, 1, 5 7
8, 6, 3, 1,
3, 7
5, 7
8, 6, 7, 1, 5,
2
3
8, 6, 7, 1, 5,
4
3, 2
8, 6, 7, 1, 5,
1, 4
3, 2,4
8, 6, 7, 4, 5,
3, 2, 1
32
Loại
Hoán
Heap phần ãy đã sắp Ghi chú
vị
tử
8, 6, 7, 4,
8, 1 Hoán vị phần tử 8 và 1
5, 3, 2,1
Hoán vị phần tử 7 và 1,
1, 6, 7, 4, do hai phần tử này
1, 7 8
5, 3, 2 không thỏa mãn yêu cầu
của h ap
Hoán vị phần tử 3 và 1,
7, 6, 1, 4, do hai phần tử này
1, 3 8
5, 3, 2 không thỏa mãn yêu cầu
của h ap
7, 6, 3, 4,
7, 2 8 Hoán vị phần tử 7 và 2
5, 1, 2
33
Loại phần tử 7 khỏi
2, 6, 3, 4,
7 7, 8 heap và thêm vào dãy
5, 1, 7
đã ắp
Hoán vị phần tử 2 và 6,
2, 6, 3, 4, do hai phần tử này
2, 6 7, 8
5, 1 không thỏa mãn yêu cầu
của h ap
Hoán vị phần tử 2 và 5,
6, 2, 3, do hai phần tử này
2, 5 7, 8
4, 5, 1 không thỏa mãn yêu cầu
của h ap
6, 5, 3, 4,
6, 1 7, 8 Hoán vị phần tử 6 và 1
2, 1
1, 5, 3, 4, Hoán vị phần tử 1 và 5,
1, 5 6, 7, 8
2 do hai phần tử này
không thỏa mãn yêu cầu
34
của h ap
Hoán vị phần tử 1 và 4,
5, 1, 3, 4, do hai phần tử này
1, 4 6, 7, 8
2 không thỏa mãn yêu cầu
của h ap
5, 4, 3,
5, 2 6, 7, 8 Hoán vị phần tử 5 và 2
1, 2
Hoán vị phần tử 2 và 4,
do hai phần tử này
2, 4, 3, 1 2, 4 5, 6, 7, 8
không thỏa mãn yêu cầu
của h ap
4, 2, 3, 1 4, 1 5, 6, 7, 8 Hoán vị phần tử 4 và 1
35
đã ắp
Hoán vị phần tử 1 và 3,
do hai phần tử này
1, 2, 3 1, 3 4, 5, 6, 7, 8
không thỏa mãn yêu cầu
của h ap
3, 2, 1 3, 1 4, 5, 6, 7, 8
Hoán vị phần tử 1 và 2,
3, 4, 5, 6, 7, do hai phần tử này
1, 2 1, 2
8 không thỏa mãn yêu cầu
của h ap
3, 4, 5, 6, 7,
2, 1 2, 1
8
2,3, 4, 5, 6,
1, 2 2 Loại phần tử 2 khỏi
7, 8
heap và thêm vào dãy
36
đã ắp
1, 2, 3, 4, 5,
Dãy đã được ắp t ng
6, 7, 8
2.3.3. Giải thuật sắp xếp độ phức tạp giảm dần – Shell sort
37
Dãy con thứ hai: a a a ...
2 h+2 2h+2
Tiến hành sắp xếp các phần tử trong cùng dãy con sẽ làm cho các
phần tử được đưa về vị trí đúng tương đối
Giảm khoảng cách h để tạo thành các dãy con mới. Dừng giải
thuật khi h = 1
Các bước giải thuật
- Bước 1:
Chọn k khoảng cách h[1], h[2], ..., h[k]
Khởi gán i = 1
- Bước 2:
Ph n chia dãy ban đầu thành các dãy con cách nhau h[i]
khoảng cách
Sắp xếp từng dãy con bằng phương pháp chèn trực tiếp
- Bước 3:
Gán i = i+1
Nếu (i > k) thì
Dừng giải thuật
Ngược lại :
Lặp lại bước 2.
38
for (step = 0 ; step <k; step++)
len = h[step];
{ x = a[i];
{ a[j+len] = a[j];
j = j - len;
a[j+len] = x;
}
Ví dụ:
Giả ử cho dãy A= {6, 5, 3, 2, 8, 7, 1, 4}. Hãy ắp xếp dãy ố đã
cho t ng dần.
Các bước của giải thuật được thực hiện như sau, với h = 3, 2, 1
39
h=3
6 5 3 2 8 7 1 4
6 4 3 2 5 7 1 8
6 4 3 2 5 7 1 8
1 4 3 2 5 7 6 8
…
h=2
1 4 3 6 5 7 2 8
1 4 2 6 3 7 5 8
h=1
1 4 2 6 3 7 5 8
Kết quả sắp xếp
1 2 3 4 5 6 7 8
2.3.4. Giải thuật sắp xếp dựa trên phân hoạch – Quick sort
Đoạn 1 : a .. a x
left j
Đoạn 2: a .. a =x
j+1 i-1
Đoạn 3: a .. a x
i right
41
- Bước 3: Sắp xếp đoạn 1: a .. a
left j
42
x = a[(left+right)/2];
i = left;
j = right;
while (i < j)
{ while (a[i] < x)
i++;
while (a[j] > x)
j--;
if (i <= j)
{ swap(a[i],a[j]); // hàm hoán vị a[i] và a[j]
i++ ;
j--;
}
}
if (left<j)
QuickSort(a, left, j);
if (i<right)
QuickSort(a, i, right);
}
Ví dụ
Giả ử cho dãy A= {6, 5, 3, 2, 8, 7, 1, 4}. Hãy ắp xếp dãy ố đã
cho t ng dần.
ác bước của giải thuật được thực hiện như au :
Phân hoạch đoạn left =1, right = 8
x = A[(left +right)/2]= A[4] = 2
43
6 5 3 2 8 7 1 4
1 2 5 3 8 7 6 4
Đoạn 1 gồm 1 phần tử nên đã được sắp, đoạn 2 cũng đã được
sắp, tiếp tục thực hiện phân hoạch đoạn 3 : {5, 3, 8, 7, 6, 4}
1 2 5 3 8 7 6 4
1 2 5 3 8 7 6 4
1 2 5 3 7 6 4 8
Sau phân hoạch, đoạn 3 không có phần tử nào lớn hơn 8, nên
số phần tử đoạn này không có. Đoạn 1 gồm các phần tử : {5, 3, 7,
6, 4}, đoạn 2 gồm {8} và đã được sắp.Tiếp tục phân hoạch đoạn 1.
1 2 5 3 7 6 4 8
1 2 5 3 7 6 4 8
1 2 5 3 6 4 7 8
Sau phân hoạch, đoạn 3 không có phần tử nào lớn hơn 7, nên
số phần tử đoạn này không có. Đoạn 1 gồm các phần tử : {5, 3, 6,
4}, đoạn 2 gồm {7} và đã được sắp. Tiếp tục phân hoạch đoạn 1.
44
Cập nhật lại left= 3, right=6 : x = A[4]=3
1 2 5 3 6 4 7 8
1 2 3 5 6 4 7 8
Sau phân hoạch, đoạn 1 không có phần tử nào nhỏ hơn 3, nên
số phần tử đoạn này không có. Đoạn 3 gồm các phần tử : {5, 6, 4},
đoạn 2 gồm {3} và đã được sắp. Tiếp tục phân hoạch đoạn 3.
1 2 3 5 6 4 7 8
1 2 3 5 6 4 7 8
1 2 3 5 4 6 7 8
Sau phân hoạch, đoạn 3 không có phần tử nào lớn hơn 6, nên
số phần tử đoạn này không có. Đoạn 1 gồm các phần tử : {5, 4},
đoạn 2 gồm {6} và đã được sắp. Tiếp tục phân hoạch đoạn 1.
1 2 3 5 4 6 7 8
1 2 3 5 4 6 7 8
1 2 3 4 5 6 7 8
45
Sau phân hoạch, đoạn 3 không có phần tử nào lớn hơn 5, nên
số phần tử đoạn này không có. Đoạn 1 gồm 1 phần tử {4} và đã
được sắp, đoạn 2 gồm {5} và đã được sắp.
1 2 3 4 5 6 7 8
Cập nhật lại left= 4, right=4 : Dừng giải thuật.
2.3.5. Giải thuật sắp xếp trộn trực tiếp – Merge sort
46
nên nó là dãy có thứ tự. Cứ m i lần tách – trộn, chiều dài của dãy con
sẽ được nh n đôi.
47
Ví dụ
Giả ử cho dãy A = {12, 13, 45, 32, 100, 34, 65, 10}. Hãy ắp
xếp dãy ố đã cho t ng dần.
ác bước của giải thuật được thực hiện như au :
Có 8 phần tử cần được sắp xếp: ý tưởng của giải thuật là thay vì
sắp xếp 8 phần tử, ta chia dãy đó ra làm đôi và sắp xếp các dãy con rồi
ghép 2 dãy con lại. Ta thực hiện như au
Chia đôi dãy ban đầu thành hai dãy con là {12, 13, 45, 32} gọi là
dãy A và {100, 34, 65, 10}gọi là dãy B.
Sắp 2 dãy con lại: {12, 13, 45, 32} gọi là dãy A, {100, 34, 65,
10} gọi là dãy B.
+ Muốn sắp ta cũng làm như trên: chia đôi , được 2 dãy
mới là dãy A11 = {12,13}; dãy A12 = {45,32}. hia đôi B
được 2 dãy mới là dãy B11 = {100,34}; dãy B12 = {65,10}
+ Sắp xếp A11, B11 , A12 , B12
+ Muốn sắp xếp 11 thì ta cũng chia đôi được 2 dãy con là
A21 = {12} A22 = {13}.
+ Sắp 2 dãy con trên được (đơn giản vì chỉ có một phần tử )
A21 = {12}; A22 = {13}. Sắp xong ta trộn lại thành dãy A11
= {12,13}.
+ Tương tự sắp xếp cho B11, A12, B12 ta cũng có dãy B11 =
{34, 100}; dãy B12 = {10, 65}; dãy A12 = {32, 45}.
+ Sắp xếp xong, ta sẽ trộn A11, A12 lại thành dãy A = {12 13
32 45}
+ Trộn B11, B12 thành dãy B = {10, 34, 65,100}. Sắp xong
A, B, ta sẽ trộn lại thành dãy ban đầu: {10,12,13, 32, 34, 45,
65,100}.
Vậy dãy được sắp xếp t ng dần: {10,12,13, 32, 34, 45, 65,100}.
48
Đánh giá giải thuật
Nhận thấy rằng, số lần lặp của bước 2 (phân phối) và bước 3
(trộn) bằng log2N. Chi phí thực hiện bước 2 và bước 3 tỉ lệ thuận với
N. Như vậy, ta có thể ước tính chi phí thực hiện của giải thuật này
thuộc O(Nlog2N).
49
- Bước 2: So sánh a[i] với giá trị x cần tìm, có 2 khả n ng :
+ Nếu (a[i] = x)
Tìm thấy x. Dừng giải thuật
+ Ngược lại :
Chuyển ang bước 3
- Bước 3: i = i+1 // Xét tiếp phần tử kế tiếp trong dãy
Nếu i = N thì // hết dãy
Dừng giải thuật
Ngược lại:
Quay lại bước 2.
50
Số phép so sánh của thuật toán trong trường hợp xấu nhất là 2*N.
Để giảm thiểu số phép so sánh trong vòng lặp cho thuật toán, ta có
thêm phần tử “lính canh” vào cuối dãy, khi đó giải thuật sẽ có được
hiệu quả cao hơn.
int LinearSearch(int a[],int N, int x)
{
int i=0;
a[N]=x; // a[N] là phần tử “lính canh”
while (a[i]!=x)
i++;
if (i==N)
return 0; // Tìm không thấy x
else
return 1; // Tìm thấy
}
51
- Bước 1: Khởi gán left=0;
Khởi gán right=N-1;
- Bước 2:
mid= (left+right) / 2; //chỉ số phần tử giữa dãy hiện hành
So sánh a[mid] với x. Có 3 khả n ng
Nếu (a[mid]= x) thì
Trả lời tìm thấy x trong dãy. Dừng giải thuật
Nếu (a[mid]>x) thì
Cập nhật Right= mid-1;
Nếu (a[mid]<x) thì
Cập nhật Left= mid+1;
- Bước 3: Nếu (Left <=Right) thì // còn phần tử trong dãy
Quay lại bước 2
Ngược lại :
Dừng giải thuật.
12 8 2 14 3 5
So sánh : A[mid] = 2 và X = 3 : không phải X
12 8 2 14 3 5
So sánh [mid] = 3 và X = 3 đúng giá trị X cần tìm.
53
BÀI TẬP CHƯƠNG 2
1. Trình bày ngắn gọn tư tưởng các giải thuật tìm kiếm, các giải
thuật này có thể được vận dụng trong các trường hợp nào, cho ví
dụ minh họa?
2. Hãy trình bày các ưu, nhược điểm của các giải thuật tìm kiếm?
3. Hãy cài đặt các giải thuật tìm kiếm bằng cách sử dụng các kiểu
vòng lặp khác nhau whil , do…whil , for) và có nhận xét về các
trường hợp này.
4. Giả sử cho một dãy số nguyên N phần tử, các phần tử có thứ tự
t ng dần và được yêu cầu áp dụng giải thuật tìm kiếm tuyến tính
để tìm kiếm phần tử trong dãy, hãy thực hiện các yêu cầu sau :
a. ài đặt theo yêu cầu trên và đánh giá về số lần so sánh
trong các trường hợp tìm kiếm phần tử khi cho phần tử cần
tìm kiếm nằm ở đầu dãy, cuối dãy và giữa dãy.
b. Cải tiến giải thuật tìm tuyến tính để phù hợp với giả thiết
đã cho.
c. Đánh giá và o ánh tính hiệu quả về bộ nhớ và số giao tác
khi thực hiện hai giải thuật trong câu a và câu b.
5. Giả sử cho một dãy số nguyên N phần tử, các phần tử có thứ tự
giảm dần và được yêu cầu áp dụng giải thuật tìm kiếm nhị phân
để tìm kiếm phần tử trong dãy, hãy thực hiện các yêu cầu sau :
a. ài đặt theo yêu cầu trên bằng cách dùng k thuật đệ qui và
không dùng k thuật đệ qui.
b. Đánh giá về số lần o ánh trong các trường hợp tìm kiếm
phần tử khi cho phần tử cần tìm kiếm nằm ở đầu dãy, cuối
dãy và giữa dãy trong câu a.
6. Giả sử cho một dãy số nguyên gồm M phần tử (100<M< 30.000),
au đó chọn ngẫu nhiên trong dãy M một số nguyên K, áp dụng
đồng thời 2 giải thuật tìm kiếm nhị phân và tuyến tính để tìm K
trong M. Hãy cho nhận xét về thời gian thực hiện nếu phát sinh
54
ngẫu nhiên 100 lần M và K khác nhau và thực hiện hai giải thuật
nêu trên để tìm K trong M.
7. Giả sử cho một dãy số nguyên N phần tử (100 < N < 10.000).
a. Hãy cho biết thời gian thực hiện giải thuật tìm kiếm tuyến
tính để tìm kiếm một phần tử K trong dãy N khi áp dụng k
thuật phần tử lính canh và khi không dùng k thuật phần
tử “lính canh”.
b. Thời gian thực hiện giải thuật khi áp dụng k thuật lính
canh và khi không dùng k thuật lính canh trong c u a
có tỷ lệ tuyến tính với nhau khi t ng hoặc giảm số phần tử
N không? Vì sao?
8. Giả sử cho một mảng 2 chiều các số nguyên có kích thước M x N
(100 < M < 10.000), (100 < N < 20.000). Hãy cho biết thời gian
thực hiện giải thuật tìm kiếm tuyến tính để tìm kiếm một phần tử
K trong mảng 2 chiều trên, khi áp dụng k thuật phần tử “lính
canh” và khi không dùng k thuật phần tử “lính canh”.
9. Giả sử sử dụng hàm Random trong ++ để phát sinh ngẫu
nhiên dãy 10.000 số nguyên và lưu trong fil X.
a. Hãy áp dụng giải thuật tìm tuyến tính để tìm kiếm số
nguyên trong fil X được phát sinh ngẫu nhiên)
b. Cho nhận xét về thời gian tìm kiếm trong câu a sẽ như thế
nào, nếu thực hiện 5 lần chạy với dãy số nguyên có 100,
1.000, 5.000, 10.000, 50.000 phần tử trong m i lần chạy
10. Giả sử sử dụng hàm Random trong ++ để phát sinh ngẫu
nhiên dãy 10.000 số nguyên và lưu trong fil X.
a. Hãy áp dụng giải thuật tìm nhị ph n để tìm kiếm số nguyên
trong fil X được phát sinh ngẫu nhiên).
b. Nhận xét về thời gian tìm kiếm trong câu a sẽ như thế nào,
nếu thực hiện 5 lần chạy với dãy số nguyên có 100, 1.000,
5.000, 10.000, 50.000 phần tử trong m i lần chạy.
55
11. Hãy viết chương trình minh họa trực quan hóa các giải thuật
tìm kiếm.
12. Hãy trình bày ngắn gọn tư tưởng của các giải thuật sắp xếp?
13. Hãy trình bày ưu điểm và hạn chế của các giải thuật sắp xếp,
đề xuất cách tốt nhất để khắc phục hạn chế của các giải
thuật này.
14. Giả sử sử dụng hàm Random trong ++ để phát sinh ngẫu
nhiên dãy số nguyên có kích thước 100, 1.000, 5.000, 10.000,
50.000 số. Hãy cài đặt các giải thuật sắp xếp để sắp dãy số
nguyên đã cho theo thứ tự t ng dần và nhận xét về thời gian thực
hiện của các giải thuật.
15. Viết chương trình o ánh các giải thuật Selection sort, Heap Sort,
Quick sort, Merge sort về các yếu tố sau: thời gian chạy, số phép
gán và số phép so sánh.
16. Hãy viết chương trình cài đặt giải thuật Quick sort, so sánh thời
gian chạy giải thuật khi lấy x là phần tử chính giữa dãy, đầu dãy,
cuối dãy và không dùng k thuật đệ qui.
17. Hãy viết chương trình cài đặt giải thuật Quick sort, so sánh thời
gian chạy giải thuật khi lấy x là phần tử chính giữa dãy, đầu dãy,
cuối dãy và dùng k thuật đệ qui
18. Giả ử cho dãy ban đầu là A : {12, 8, 2, 14, 3, 5, 10, 15, 36}. Hãy
sắp xếp dãy đã cho th o quy tắc:
a. Các số chẵn (nếu có) có thứ tự t ng dần.
b. Các số lẻ (nếu có) có thứ tự giảm dần.
c. Tính chất chẵn / lẻ tại m i vị trí trong dãy không thay đ i
sau khi sắp xếp (tức là trước khi sắp xếp, tại vị trí i của
dãy A là số chẵn/lẻ thì tại vị trí i của mảng sau khi sắp
xếp cũng là ố chẵn/lẻ).
19. Dựa trên ý tưởng của giải thuật chèn trực tiếp, hãy trình bày ý
tưởng cải tiến giải thuật này ao cho độ phức tạp trong trường
hợp tốt nhất là O(NlogN).
56
20. Trong ba giải thuật sắp xếp chọn trực tiếp, chèn trực tiếp và n i
bọt, giải thuật nào thực hiện sắp xếp nhanh nhất nếu cho một dãy
đã có thứ tự, giải thích và cho ví dụ minh họa.
21. Hãy đề xuất giải thuật tìm phần tử trung vị (phần tử giữa –
median) trong giải thuật sắp xếp Quick sort sao cho hiệu quả nhất
với một dãy số nguyên N phần tử.
22. Giả sử cho một đa thức P bậc N (0<N<100) gồm các số hạng,
biết rằng thông tin số hạng bao gồm:
- Dấu: âm hoặc dương quy định 0 là m, 1 là dương)
- Hệ số: là một số thực
- Bậc: là một số nguyên (giá trị từ 1 đến 99)
Hãy thực hiện các yêu cầu sau:
a. Định nghĩa cấu trúc dữ liệu để biểu diễn đa thức P.
b. Áp dụng một giải thuật sắp xếp để sắp xếp các số hạng
trong đa thức theo thứ tự t ng dần của bậc.
23. Giả sử cho thông tin một sinh viên, bao gồm:
- Mã sinh viên: là một số nguyên dương qui định là số duy
nhất, không trùng)
- Họ: là một chu i
- Tên: là một chu i
- Ngày, tháng, n m inh là một số nguyên dương th o đúng qui
tắc của ngày tháng n m inh trong thực tế
- Giới tính: là một số nguyên qui định: 0 là Nữ, 1: Nam)
- Điểm trung bình học tập: là một số thực qui định giá trị từ 0.0
đến 10.0)
Hãy thực hiện các yêu cầu sau:
57
a. Định nghĩa cấu trúc dữ liệu để biểu diễn thông tin sinh
viên.
b. Viết hàm nhập danh sách N sinh viên, trong đó N nhập từ
bàn phím và lưu vào trong fil Sinhvi n.dat, với yêu cầu
đảm bảo ràng buộc qui định về miền giá trị ở trên.
c. Áp dụng một giải thuật sắp xếp để sắp xếp danh sách sinh
viên theo mã số inh viên t ng dần với thông tin sinh viên
đọc từ file trong câu b.
d. Áp dụng một giải thuật sắp xếp để sắp xếp danh sách sinh
viên th o điểm trung bình giảm dần với thông tin sinh viên
đọc từ file trong câu b.
e. Hiển thị danh sách sinh viên theo kết quả sắp xếp trong
câu c, d.
24. Trong các giải thuật trình bày trong chương, có phải sẽ luôn có
một giải thuật tốt nhất về thời gian xử lý và không phụ thuộc vào
tình trạng dãy số đã cho ban đầu không?. Hãy giải thích và cho ví
dụ minh họa.
25. Hãy viết chương trình minh họa trực quan hóa các giải thuật
sắp xếp.
58
CHƯƠNG 3
DANH SÁCH LIÊN KẾT
59
3.1.2. Các thao tác trên danh sách
3.1.2.4. Thay thế một phần tử của danh sách bởi một phần
tử khác
Thao tác thực hiện thay thế một phần tử của danh sách bởi một
phần tử khác thường được thực hiện khi kết hợp với một hay nhiều
điều kiện nào đó liên quan đến giá trị của phần tử đã lưu trữ trong
60
danh ách. Như thế, cũng như thao tác loại bỏ, thao tác thay thế cũng
thường gắn với thao tác tìm kiếm trước khi tiến hành thay thế.
62
Mối liên hệ giữa các phần tử trong danh ách được thể hiện tường
minh: m i phần tử ngoài các thông tin về bản thân còn chứa một địa
chỉ đến phần tử kế trong danh ách nên được gọi là danh sách liên kết.
Do có liên kết tường minh, với hình thức này các phần tử trong
danh sách không cần lưu trữ kế cận trong bộ nhớ nên khắc phục được
các khuyết điểm của hình thức t chức mảng, nhưng việc truy xuất
đến một phần tử đòi hỏi phải thực hiện truy xuất qua một số phần tử
khác.
Có nhiều kiểu t chức liên kết giữa các phần tử trong danh
sách:
- Danh sách liên kết đơn là danh ách mà m i phần tử trong danh
sách chỉ liên kết với phần tử đứng sau nó trong danh sách.
- Danh sách liên kết kép là danh sách mà m i phần tử trong danh
sách liên kết với phần tử đứng trước và sau nó bằng hai mối liên kết.
- Danh sách liên kết vòng là danh sách mà phần tử đứng cuối
danh sách lại liên kết với phần tử đầu danh sách theo dạng danh sách
liên kết đơn hoặc danh sác kép.
Hình thức liên kết này cho phép các thao tác thêm, hủy trên danh
ách được thực hiện dễ dàng, phản ánh được bản chất linh động của
danh sách.
63
3.2.2. Danh sách liên kết đơn
Danh sách liên kết đơn là danh ách mà m i phần tử trong
danh sách chỉ liên kết với phần tử đứng sau nó trong danh sách.
3.2.2.2. ác thao tác cơ bản trên danh sách liên kết đơn
Các thao tác trên một danh sách liên kết đơn gồm : tạo danh sách
liên kết đơn r ng, tạo một phần tử có thành phần Info bằng x, tìm một
phần tử có Info bằng x, thêm một phần tử có khóa x vào danh sách,
hủy một phần tử trong danh sách, duyệt danh sách và sắp xếp danh
sách liên kết.
Thao tác này sẽ khởi tạo một danh sách đơn r ng ban đầu.
64
Địa chỉ của nút đầu tiên và địa chỉ của nút cuối c ng đều là
NULL
Hàm cài đặ :
void CreateList(List &l)
{
l.pHead=NULL;
l.pTail=NULL;
}
65
Với thao tác này, các vị trí có thể thêm vào danh sách gồm:
Thêm phần tử vào đầu danh sách
Thêm phần tử vào cuối danh sách
Thêm phần tử vào sau một phần tử q cho trước trong
danh sách
Thêm phần tử vào trước một phần tử q cho trước trong
danh sách
Thao tác thêm một phần tử vào đầu danh sách liên kết
66
}
else
{ p->Next = l.pHead;
l.pHead = p;
}
}
Thao tác thêm một phần tử vào cuối danh sách liên kết
Thao tác thêm phần tử p vào sau phần tử q cho rước trong danh sách
68
}
else
AddHead(l,q);// thêm q vào đầ danh sách
}
Thao tác hủy phần tử đứng dầu danh sách liên kết
69
Tail=NULL
Hàm cài đặ :
int RemoveHead(List &l, int &x)
{
Node *p;
if (l.pHead!=NULL)
{ p=l.pHead;
x=p->Info; //lư data của phần tử cần hủy
l.pHead=l.pHead->Next;
delete p;
if (l.pHead==NULL)
l.pTail=NULL;
return 1;
}
return 0;
}
70
- Bước 2: Nếu (p!=NULL) thì // q không phải là phần tử cuối
q->Next=p->Next; // tách p ra khỏi danh sách
Nếu (p== Tail) //phần tử cần hủy là phần tử cuối
Tail=q;
delete p; // hủy phần tử p
Hàm cài đặ :
int RemoveAfterQ(List &l,Node *q, int &x)
{ Node *p;
if (q!=NULL)
{ p=q->Next; // là hần ử cần xoá
if (p!=NULL) // không hài là hần ử c ối
{
if (p==l.pTail) // hần ử xoá là hần ử c ối
l.pTail=q; //cậ nhậ lại Tail
q->Next=p->Next;
x=p->Info;
delete p;
}
return 1;
}
else
return 0;
}
71
Thao tác hủy phần tử có khoá x trong danh sách liên kết
- Bước 1: Tìm phần tử p có khoá bằng x, gán con trỏ q luôn chạy
trước con trỏ p
- Bước 2 Nếu p!=NULL) thì // ìm hấy hần ử có khoá bằng x
Hủy p ra khỏi danh sách bằng cách hủy phần tử
đứng au q
Ngược lại
Thông báo không tìm thấy phần tử có khoá bằng x
Hàm cài đặ :
int RemoveX(List &l, int x)
{
Node *p,*q = NULL;
p=l.Head;
while ((p!=NULL) && (p->Info!=x)) //tìm x
{ q=p;
p=p->Next;
}
if (p==NULL) //không ìm hấy hần ử có khoá bằng x
return 0;
if (q!=NULL) // ìm hấy hần ử có khoá bằng x
DeleteAfterQ(l,q,x);
else // hần ử cần xoá nằm đầ danh sách
RemoveHead(l,x);
return 1;
}
72
e. ìm một phần tử trong danh sách liên kết
Thao tác thực hiện :
Trong thao tác này, thực hiện duyệt tuần tự các phần tử trong
danh sách, so sánh giá trị của phần tử đang x t với giá trị phần tử cần
tìm và trả lời kết quả có tìm thấy hoặc không tìm thấy phần tử cần tìm.
Các bước tìm phần tử có thành phần Info bằng x trong danh sách:
- Bước 1: p=Head; // địa chỉ của phần tử đầu trong danh sách
- Bước 2:
Trong khi (p!=NULL) và (p->Info!=x)
p=p->Next; // xét phần tử kế
- Bước 3:
+ Nếu (p!=NULL) thì
p lưu địa chỉ của nút có thành phần Info = x
+ Ngược lại :
Không có phần tử cần tìm.
Hàm cài đặt :
Hàm cài đặt trả về địa chỉ của phần tử có thành phần Info bằng x,
ngược lại hàm trả về giá trị NULL nếu không tìm thấy.
Node *Search(LIST l, Data x)
{ Node *p;
p = l.pHead;
while ((p!= NULL) && (p->Info != x))
p = p->pNext;
return p;
}
73
h. uyệt danh sách
Thao tác thực hiện :
Thao tác duyệt danh sách liên kết là thao tác thường được thực
hiện khi có nhu cầu cần xử lý với các dữ liệu lưu trong các phần tử
trong danh ách, như :
Thao tác đếm số lượng các phần tử trong danh sách
Thao tác tìm tất cả các phần tử trong danh sách thỏa mãn điều
kiện nào đó
Thao tác duyệt danh ách để hủy toàn bộ hay một phần danh
sách
…
- Bước 1:
p = Head; // p lư địa chỉ của phần tử đầu trong danh sách
- Bước 2:
Trong khi danh ách chưa hết) thực hiện
+ xử lý yêu cầu cụ thể với phần tử p
74
+ p=p->Next; // chuyển qua phần tử kế tiếp trong
danh sách
Hàm cài đặ :
void PrintList(List l)
{ Node *p;
p=l.pHead;
while (p!=NULL)
{ printf %d , p->Info);
p=p->Next;
}
}
75
- Bước 1: Trong khi danh ách chưa hết) thực hiện:
B1.1:
p = Head;
Head = Head->Next; // cập nhật lại con trỏ Head
B1.2: Hủy p
- Bước 2:
Tail = NULL;// bảo toàn tính nhất quán khi danh sách rỗng
Hàm cài đặ :
void RemoveList(List &l)
{ Node *p;
while (l.pHead!=NULL) //còn hần ử rong danh sách
{ p = l.pHead;
l.pHead = p->Next;
delete p;
}
}
76
Cách thứ nhất: Thực hiện thao tác thay đ i thành phần dữ liệu
trong các phần tử để danh sách có thứ tự
Cách thứ hai: Thực hiện thao tác thay đ i thành phần liên kết
các phần tử, sao cho việc thay đ i này tạo lập được thứ tự liên kết
mong muốn để danh sách có thứ tự.
Nhận xét :
Với cách thứ nhất Thay đ i thành phần dữ liệu
o Ưu điểm: ài đặt đơn giản, tương tự như ắp xếp
mảng
o Nhược điểm Đòi hỏi thêm vùng nhớ khi hoán vị nội
dung của hai phần tử, do đó chỉ phù hợp với những
danh sách có kích thước nhỏ. Khi danh sách có kích
77
thước lớn, chi phí cho việc hoán vị sẽ lớn, dẫn đến làm
cho thao tác sắp xếp chậm.
Ví dụ
Viết hàm ắp xếp danh ách d ng thuật toán ắp xếp chọn trực
tiếp l ction ort) đã học trong chương trước để ắp xếp danh ách
t ng dần th o cách thứ nhất.
void SelectionSort(LIST &l)
{
Node *p,*q,*min;
p=l.pHead;
while (p!=l.pTail)
{ min=p;
q=p->next;
while (q!=NULL)
{ if (q->Info<p->Info)
min=q;
q=q->Next;
}
Swap (min->Info,p->Info); // hàm hoán vị hai giá rị
p=p->Next;
}
}
Nhận xét :
Với cách thứ hai Thay đ i thành phần liên kết
o Ưu điểm: Không phụ thuộc vào kích thước bản chất
dữ liệu lưu tại m i nút, thao tác sắp xếp nhanh hơn.
78
o Nhược điểm ài đặt phức tạp
ác thuật toán ắp xếp hiệu quả trên danh ách liên kết bằng các
thay đ i thành phần liên kết có hiệu quả cao, bao gồm
- Thuật toán sắp xếp Quick Sort
- Thuật toán sắp xếp Merge Sort
- Thuật toán sắp xếp Radix Sort
Ví dụ
Thực hiện ắp xếp danh ách d ng thuật toán ắp xếp Quick Sort:
- Bước 1:
+ Chọn X là phần tử đầu danh sách L làm phần tử cầm canh
+ Loại X ra khỏi danh sách L
- Bước 2:
Tách danh sách L ra làm 2 danh sách :
+ Danh sách L (gồm các phần tử nhỏ hơn hoặc bằng X)
1
- Bước 3:
Nếu (L !=NULL) thì
1
- Bước 4:
Nếu (L !=NULL) thì
2
- Bước 5:
79
Nối danh sách L , X, L lại theo thứ tự ta có danh sách L đã
1 2
được sắp xếp
Minh họa các bước thực hiện thuật toán trên :
Giả ử cho dãy ố ban đầu ={4, 6, 5, 1, 8, 2). Hãy ắp xếp
dãy ố đã cho có thứ tự t ng dần.
80
Nối danh sách L1, X, L2 thành danh sách L
81
AddHead(L1,p); // hàm hêm hần ử vào L1
else
AddHead(L2,p); // hàm hêm hần ử vào L2
}
QuickSort(L1); //Gọi hàm đệ y sắ xế L1
QuickSort(L2); //Gọi hàm đệ y sắ xế L2
if (L1.pHead!=NULL) //nối L1, L2 và X vào danh sách
{ l.pHead=L1.pHead;
L1.pTail->Next=X;
}
else
l.pHead=X;
X->Next=L2.pHead;
if (L2.pHead!=NULL) // L2 có rên mộ hần ử
l.pTail=L2.pTail;
else // L2 không có hần ử nào
l.pTail=X;
}
82
3.2.3.1. Định nghĩa cấu trúc dữ liệu của một nút
Cấu trúc dữ liệu một nút
typedef struct tagDnode
{ Data Info;
struct tagDnode *pPre;
struct tagDnode *pNext;
} DNode;
Cấu trúc danh sách kép
typedef struct tagDList
{ DNode *pHead;
DNode *pTail;
}DList;
83
Hủy phần tử sau phần tử q cho trước trong danh sách
Hủy phần tử trước phần tử q cho trước trong danh sách
• Duyệt danh sách
• Tìm một phần tử trong danh sách theo một điều kiện nào đó
• Sắp xếp danh sách theo một điều kiện nào đó
84
+ return p
Hàm cài đặ
DNode *CreateDNode(int X)
{
DNode *p;
p=new DNode;
if (p==NULL)
{ printf("khong con du bo nho");
exit(1);
}
else
{ p->Info=X;
p->pNext=NULL;
p->pPre=NULL;
return p;
}
}
85
Nếu danh ách r ng thì
+ Head = p
+ Tail = Head
Ngược lại
+ p->Next = Head
+ Head->Pre=p
+ Head = p
Hàm cài đặ
void AddFirst(Dlist &l, Dnode *p)
{
if (l.pHead==NULL) // danh sách rỗng
{ l.pHead=p;
l.pTail=l.pHead;
}
else
{ p->pNext=l.pHead;
l.pHead->pPre=p;
l.pHead=p;
}
}
86
Thao tác này ẽ thực hiện thêm một phần tử vào cuối danh sách
đang có và cập nhật lại phần tử mới thêm vào là phần tử cuối của danh
ách mới.
+ Head = p
+ Tail = Head
Ngược lại
+ p->Pre=Tail
+ Tail->Next=p
+ Tail=p
temp=q->pNext
88
Hàm cài đặ
89
temp=q->Pre;
Hàm cài đặ :
void AddBeforeQ(DList &l,DNode *p,DNode *q)
{
DNode *p;
temp=q->pPre;
if (q!=NULL)
{ p->pNext=q;
q->pPre=p;
p->pPre=temp;
if (temp!=NULL)
90
temp->pNext=p;
if (q==l.pHead)
l.pHead = p;
}
else // nế là NULL, ức danh sách rỗng, iến hành gọi hàm
hêm vào đầ danh sách
AddFirst(l,p);
}
g. Hủy phần tử đầu danh sách
Thao tác thực hiện
Thao tác này thực hiện xóa một phần tử đứng đầu danh ách. Sau
khi thực hiện thao tác hủy, ta ẽ cập nhật lại phần tử đứng đầu danh
sách là phần tử kế tiếp của phần tử đã xóa khỏi danh ách. Nguyên tắc
khi hủy là phải thực hiện cô lập phần tử cần hủy trước khi hủy.
91
Nếu (Head!= NULL) thì
+ p=Head
+ Head=Head->Next
+ Head->Pre=NULL
+ delete p
Hàm cài đặ
void DeleteFirst(DList &l)
{
DNode *p;
if(l.pHead!=NULL)
{ p=l.pHead;
l.pHead=l.pHead->pNext;
l.pHead->pPre=NULL;
delete p;
if (l.pHead==NULL)
l.pTail=NULL;
}
}
92
Nếu H ad!=NULL) thì // danh sách có hơn mộ hần ử
+ p= Tail
+ Tail=Tail->Pre
+ Tail->Next=NULL
+ delete p
Hàm cài đặ
void DeleteEnd(Dlist &l )
{
Dnode *p;
if (l.pHead!=NULL)
{ p=l.pTail ;
l.pTail=l.pTail->Pre ;
l.pTail->pNext=NULL;
93
delete p;
if (l.pTail==NULL)
l.pHead=NULL;
}
}
i. Hủy một phần tử sau phần tử q cho trước
Thao tác thực hiện
Thao tác hủy thực hiện hủy một phần tử đứng sau phần tử q cho
trước trong danh ách đang có, khi đó các mối liên kết giữa phần tử q
và phần tử đứng sau q ẽ thay đ i.
94
+ Nếu p!=NULL) thì
q->Next=p->Next
Nếu p là nút cuối thì
Tail=q
Ngược lại
p->Next->Pre=q;
delete p
Hàm cài đặ
void DeleteLastQ(DList &l,DNode *q)
{
DNode *p; //lư hần ử đứng sau hần ử q
if (q!=NULL)
{ p=q->pNext;
if(p!=NULL)
{ q->pNext=p->pNext;
if (p==l.pTail)
l.pTail=q;
else
p->pNext->pPre=q;
delete p;
}
}
else
DeleteFirst(l);
}
95
j. Huỷ một phần tử đứng trước phần tử q
Thao tác thực hiện
Thao tác hủy thực hiện hủy một phần tử đứng trước phần tử q cho
trước trong danh ách đang có, khi đó các mối liên kết giữa phần tử q
và phần tử đứng trước q ẽ thay đ i.
97
k. Hủy một phần tử có khoá bằng x
Thao ác hực hiện
Thao tác hủy một phần tử có khoá bằng x cho trước trong danh
sách được thực hiện qua các bước như au
Bước 1 Duyệt danh ách và tìm phần tử có thành phần nfo
bằng x
Bước 2
Nếu có tìm thấy phần tử có khóa bằng x thì
Nếu phần tử cần hủy nằm ở đầu danh ách thì
Gọi hàm hủy phần tử ở đầu danh ách
Ngược lại nếu phần tử cần hủy nằm ở cuối danh ách thì
Gọi hàm hủy phần tử ở cuối danh ách
Ngược lại :
Gọi hàm hủy phần tử au q hoặc trước q
Ngược lại
Thông báo không có phần tử bằng x trong danh ách
98
Hàm cài đặ
int DeleteX(DList &l,int x)
{ DNode *p;
DNode *q;
q=NULL;
p=l.pHead;
while (p!=NULL)
{ if (p->Info==x) break;
q=p; //q là nút có hành hần Info = x
p=p->pNext;
}
if (q==NULL)
return 0; //không ìm hấy nút có hành hần Info = x
if (q!=NULL)
if (q==l.pTail)
DeleteLastQ(l,q);
else if (q==l.pHead)
DeleteFirst(l,q);
else DeleteAfterQ(l,q);
return 1;
}
99
BÀI TẬP CHƯƠNG 3
1. Trình bày định nghĩa các loại danh sách liên kết, ưu và nhược
điểm của từng loại danh sách liên kết và cho ví dụ ứng dụng trong
thực tế.
2. Vận dụng các thuật toán sắp xếp đã học trong môn học, hãy cài
đặt các hàm sắp xếp trên danh sách liên kết đơn và kép theo hai
cách:
a. Sử dụng chỉ một con trỏ đầu danh sách.
b. Sử dụng đồng thời hai con trỏ đầu và cuối danh sách.
3. Trong các thuật toán sắp xếp đã học trong môn học, thuật toán
nào dễ, khó vận dụng trên danh sách liên kết đơn, liên kết kép?
Giải thích vì sao?
4. Hãy cài đặt thuật toán Heap sort trên danh sách liên kết kép.
5. Hãy cài đặt thuật toán Merge sort trên danh sách liên kết kép.
6. Hãy cài đặt chương trình tạo một máy tính cho phép thực hiện
các phép tính +, -, *, / và div chia dư) trên các số có tối đa 30
chữ số và các chức n ng nhớ (M+, M-, MC, MR) giống như một
máy tính cầm tay trong thực tế.
7. Hãy cài đặt chương trình cho ph p nhập vào một biểu thức bao
gồm: các số, các toán tử +, -, *, /, div chia dư) và các hàm
toán học sin, cos, tan, ln, ex, trong biểu thức có các dấu mở,
đóng ngoặc "(", ")" và chương trình ẽ tính toán giá trị của biểu
thức này.
8. Một ma trận chỉ chứa rất ít phần tử với giá trị có nghĩa ví dụ:
phần tử ≠ 0) được gọi là ma trận thưa.
Ví dụ như ma trận sau:
100
a. Hãy dùng cấu trúc danh sách liên kết để biểu diễn một ma
trận thưa ao cho tiết kiệm bộ nhớ nhất.
b. Viết hàm cho phép nhập xuất ma trận theo cấu trúc dữ liệu
sử dụng ở câu a.
c. Viết hàm cho phép cộng và nhân hai ma trận đã nhập trong
câu b và hiển thị kết quả lên màn hình.
9. Cho cấu trúc dữ liệu trên như au để mô tả đa thức một biến:
struct PolyNode
{ int coeff; // chứa hệ số
int pow; // chứa số mũ
struct PolyNode *link;
}
typedef struct PolyNode Polynom;
Hãy viết hàm Polynom * PolyMult (Polynom *Poly1, Poly2)
nhận đầu vào là hai đa thức Poly1 và Poly2, tính và trả lại tích
của hai đa thức đã cho.
10. Cho danh sách liên kết được mô tả bởi cấu trúc dữ liệu trên như
sau:
struct Node
{ int info;
struct Node *next;
};
Hãy viết hàm void Shuffle (Node *list) nhận đầu vào là danh sách
liên kết với phân tử đầu tiên được trỏ bởi list, thực hiện các công
việc au đ y
a. Sắp xếp lại các phần tử của danh ách đã cho, sao cho các
nút chẵn đứng trước các nút lẻ và trong trường hợp ngược
lại, thứ tự tương đối ban đầu của các nút là không thay đ i.
101
Một nút được gọi là nút chẵn hay lẻ nếu nó đứng ở vị trí chẵn
hay lẻ trong danh sách (vị trí của các nút trong danh sách
được đánh ố từ phần tử đầu tiên đến phần tử cuối cùng bắt
đầu từ 0).
b. Đưa ra màn hình danh ách thu được.
Ví dụ, nếu danh ách đã cho là 11, 13, 7, 9, 3,10) thì kết quả
hiển thị lên màn hình là (13, 9, 10, 11, 7, 3).
11. Giả sử cho một danh sách liên kết đơn hoặc kép) có thành phần
dữ liệu là các số nguyên dương, người ta muốn tách danh ách đã
cho thành hai danh sách riêng biệt, trong đó một danh ách lưu ố
chẵn, một danh ách lưu ố lẻ. Hãy trình bày giải thuật và thực
hiện cài đặt để tách danh sách đã cho ao cho hiệu quả nhất về
thời gian xử lý và bộ nhớ sử dụng, đặc biệt xét cả trong trường
hợp danh ách đã cho bao gồm tất cả là số chẵn hoặc số lẻ.
12. Hãy trình bày giải thuật và thực hiện cài đặt trộn hai danh sách
liên kết đơn hoặc kép) đã có thứ tự t ng hoặc giảm dần) thành
một danh sách có thứ tự sao cho tối ưu bộ nhớ nhất có thể.
13. Hãy viết chương trình mô phỏng cho bài toán Tháp Hà Nội
bằng cách sử dụng danh sách liên kết.
14. Hãy viết chương trình cho phép thực hiện yêu cầu sau :
a. Nhập vào từ bàn phím một dãy số nguyên và lưu trong một
danh sách liên kết có thứ tự không giảm, bằng cách: với
m i phần tử được nhập vào thì phải tìm vị trí thích hợp để
chèn vào ao cho đảm bảo danh sách có thứ tự không giảm.
b. Nếu thay cấu trúc danh sách liên kết bằng mảng thì thời
gian thực hiện trên mảng sẽ như thế nào so với danh sách
liên kết ?
15. Hãy viết chương trình cho phép thực hiện yêu cầu sau :
102
a. Giả sử cho một danh sách liên kết đơn hoặc k p) lư các số
nguyên, hãy viết chương trình xóa các phần tử trùng nhau
trên danh sách (với các số nguyên trùng nhau, giữ lại một
số nguyên duy nhất).
b. Nếu thay cấu trúc danh sách liên kết bằng mảng thì thời
gian thực hiện trên mảng sẽ như thế nào so với danh sách
liên kết ?
16. Giả sử cho một danh sách liên kết đơn hoặc kép) lưu các ố
nguyên, hãy viết chương trình cho ph p nhập vào danh sách các
số nguyên, sao cho m i số nguyên chỉ xuất hiện một lần trên
danh ách và đảm bảo danh sách luôn trong trạng thái là danh
sách có thứ tự không giảm.
17. Giả sử cho cấu trúc dữ liệu lưu trữ thông tin nhân sự như au
struct NS
{ int maso; // lư hông in mã số nhân sự
char * hoten ; // lư hông in họ và tên nhân sự
int thamnien; // lư hông in số năm hâm niên
float hesoluong ; // lư hông in hệ số lương
float luongcoban ; // lư hông in lương cơ bản
struct Node *next;
};
Hãy thực hiện các yêu cầu sau:
a. Tạo ra danh sách gồm 50 nhân sự bằng cách m i lần thêm
vào một nhân sự sẽ thêm vào từ cuối danh sách.
b. Sắp xếp danh sách theo thâm niên công tác giảm dần.
c. Tính lương trung bình của các nhân sự trong câu a, biết
rằng lương = hệ số lương * lương cơ bản.
103
d. Hiển thị lên màn hình 5 nhân sự cho lương cao nhất, nhưng
có thâm niên công tác ngắn nhất và 5 nhân sự có lương
thấp nhất, nhưng có th m niên công tác l u nhất.
18. Giả sử cho một danh sách hàng hóa bao gồm nhiều mặt hàng,
trong đó m i mặt hàng có các thông tin:
- Tên mặt hàng
- Giá mặt hàng
- Số lượng còn trong kho
Hãy thực hiện các yêu cầu sau:
a. Khai báo danh dách liên kết đơn lưu danh ách các mặt
hàng th o thông tin như mô tả trên.
b. Viết hàm sắp xếp danh sách mặt hàng ở câu a theo giá mặt
hàng t ng dần, nếu cùng giá thì sắp xếp theo tên mặt hàng
và hiển thị lên màn hình.
c. Viết hàm nhập vào 2 số nguyên x, y (x<y), hiển thị lên màn
hình danh sách các mặt hàng có số lượng trong kho lớn
hơn x và nhỏ hơn y.
19. Giả sử cho một danh sách liên kết đơn hoặc kép) lưu các ố
nguyên. Hãy thực hiện các yêu cầu sau:
a. Viết hàm tạo ra một danh sách 50.000 số nguyên ngẫu
nhiên.
b. Viết hàm tính giá trị trung bình của các số nguyên trong
câu a.
c. Viết hàm kiểm tra xem danh sách các số nguyên trong câu
a có thứ tự không? t ng dần hoặc giảm dần)
20. Giả sử cho hai danh sách liên kết đơn hoặc kép) lưu các ố
nguyên, hãy hiển thị lên màn hình:
a. Phần giao của hai danh sách liên kết.
b. Phần hội của hai danh sách liên kết.
104
21. Giả sử cho một danh sách liên kết lưu dữ liệu là các số nguyên,
hãy viết chương trình để đảo ngược danh sách liên kết trong hai
trường hợp :
a. Đảo ngược thành phần dữ liệu.
b. Thay đ i liên kết giữa các phần tử trong danh sách
22. Giả sử người ta muốn quản lý việc bán vé tham dự bu i hòa nhạc
của một dàn nhạc n i tiếng với yêu cầu là không có ai được phép
mua vé nhiều hơn một lần nhằm tạo cơ hội tham dự cho nhiều
người và m i người chỉ được mua tối đa 2 v . Để làm việc này,
ban t chức thực hiện việc phát phiếu đ ng ký cho những người
muốn mua vé.
Thông tin trên phiếu đ ng ký bao gồm: họ tên người mua vé, số
chứng minh nh n d n, địa chỉ, số lượng v đ ng ký mua. Nếu
người nào đó thực hiện việc đ ng ký hai lần thì phiếu đ ng ký lần
hai sẽ bị loại bỏ.
Hãy viết chương trình cho ph p
a. Thực hiện duyệt bán vé theo yêu cầu trên khi ban t chức
phát ra 10.000 v đ ng ký, biết rằng số ghế ngồi là 15.000
ghế, chương trình ẽ chỉ còn giải quyết duyệt bán vé khi
còn ghế trống.
b. Hiển thị lên màn hình danh ách người mua vé và số vé
tương ứng được mua.
23. Giả sử tại một trường học X, người ta có một tập tin dữ liệu, lưu
thông tin tất cả các học inh trong trường. Tập tin bao gồm nhiều
bản ghi r cord), trong đó m i bản ghi lưu thông tin họ và tên
học sinh, mã lớp (ví dụ 7a, 7b, 8c, 9a), điểm trung bình.
Hãy thực hiện các yêu cầu sau :
a. Tạo ra cho m i lớp một danh sách liên kết lưu thông tin tất
cả học sinh của lớp ấy.
105
b.Sắp xếp danh sách các lớp trong câu a theo họ và tên
học sinh
c. Hiển thị lên màn hình danh sách các lớp trong câu b.
24. Giả sử người ta phải xây dựng một chương trình oạn thảo v n
bản, hãy chọn cấu trúc dữ liệu thích hợp để lưu trữ v n bản trong
quá trình soạn thảo. Biết rằng:
- Số dòng v n bản không hạn chế.
- M i dòng v n bản có chiều dài tối đa 80 ký tự.
- Các thao tác yêu cầu gồm:
Di chuyển trong v n bản (lên, xuống, qua trái, qua phải)
Thêm, xóa, sửa ký tự trong một dòng
Thêm, xóa một dòng trong v n bản
Đánh dấu, sao chép khối.
25. Giả sử theo quy tắc t chức quản lý nhân viên của một công ty,
thông tin về một nhân viên bao gồm lý lịch và bảng chấm công:
+ Lý lịch nhân viên:
- Mã nhân viên: chu i 8 ký tự
- Tên nhân viên: chu i 20 ký tự
- Tình trạng gia đình 1 ký tự (M = Married, S = Single)
- Số con: số nguyên ≤ 20
- Trình độ v n hóa chu i 2 ký tự (C1 = cấp 1; C2 = cấp 2;
C3 = cấp 3; ĐH = đại học; CH = cao học)
- Lương c n bản: số ≤ 1000000
+ Chấm công nhân viên:
- Số ngày nghỉ có phép trong tháng: số ≤ 28
- Số ngày nghỉ không phép trong tháng: số ≤ 28
106
- Số ngày làm thêm trong tháng: số ≤ 28
- Kết quả công việc: chu i 2 ký tự (T = Tốt; TB = Đạt; K
= Kém)
- Lương thực lĩnh trong tháng: số ≤ 2000000
107
CHƯƠNG 4
NGĂN XẾP HÀNG ĐỢI
Hình 4.1. Ng n xếp và các thao tác thêm vào và lấy ra trên ng n xếp
108
4.1.2 Các thao tác trên ngăn xếp
ác thao tác trên ng n xếp bao gồm :
Thao tác Push(object): thêm một phần tử vào ng n xếp
Thao tác Pop(): lấy một phần tử ra khỏi ng n xếp và trả lại giá
trị phần tử
Thao tác Top(): trả lại vị trí phần tử nạp sau cùng vào ng n xếp
Thao tác Size(): trả lại số lượng phần tử được lưu trong ng n
xếp
Thao tác isEmpty(): kiểm tra xem có phải ng n xếp r ng không.
4.1.3 Ứng dụng với ngăn xếp
Dựa theo cách thức t chức, cơ chế hoạt động của ng n xếp, ta có
thể xây dựng nhiều ứng dụng đa dạng, như
Xây dựng ứng dụng theo dõi lịch sử duyệt trang trong trình
duyệt web.
Xây dựng chức n ng Undo trong phần mềm soạn thảo v n bản
hoặc các phần mềm khác cần chức n ng phục hồi lại một thao
tác đã thực hiện trước đó
Xây dựng ứng dụng đ i cơ ố.
Xây dựng ứng dụng trong cài đặt chương trình dịch.
Xây dựng chương trình kiểm tra tính hợp lệ của các dấu ngoặc
trong biểu thức.
Sử dụng trong khử đệ quy khi lập trình.
…
109
ài đặt ng n xếp dùng danh sách liên kết
112
Bước 2 : Viết các hàm thực hiện các hao ác rên ngăn xếp,cũng
bao gồm các hao ác như hực hiện trên cấu trúc mảng
Thao tác khởi tạo ng n xếp
Thao tác kiểm tra ng n xếp có r ng không
Thao tác thêm phần tử vào ng n xếp
Thao tác kiểm tra ng n xếp có đầy hay không
Thao tác lấy phần tử ra khỏi ng n xếp
- Hàm khởi tạo ngăn xếp
Stack *StackConstruct ()
{
Stack *s;
s = (Stack *)malloc (sizeof (Stack));
if (s == NULL)
return NULL;
s->top = NULL;
return s;
}
113
}
114
s->top = node;
return 0;
}
Cấu trúc dữ liệu hàng đợi có nhiều ứng dụng giúp h trợ giải
quyết các bài toán trong tin học như: khử đệ quy, lưu vết các quá
trình tìm kiếm theo chiều rộng, quay lui, vét cạn, t chức quản lý và
phân phối tiến trình trong các hệ điều hành, t chức bộ đệm bàn
phím….
4.2.3. à đ t hàng đ i
ó 2 cách cài đặt hàng đợi: cài đặt hàng đợi dùng mảngvà dùng
danh sách liên kết.
116
- Khai báo kiểu dữ liệu lưu trữ trong hàng đợi
typedef <Kiểu dữ liệu> El_type;
- Định nghĩa cấu trúc dữ liệu hàng đợi Queue
typedef struct Queue
{
El_type el[MAX];
int front;
int rear;
} Queue;
Bước 2 : Viết các hàm thực hiện các hao ác rên hàng đợi
- Hàm khởi tạo hàng đợi ban đầu
Eltype *initQ(Queue *q)
{
q = (Queue *)malloc(sizeof(Queue));
if (q != NULL)
{ q->front = -1;
q->rear = -1;
}
return q;
}
117
}
118
q->front = q->front+1;
else
printf("Hàng đợi trống.\n");
}
Nhận xét :
Qua m i lần xóa, phần sử dụng được của mảng sẽ giảm đi do giá
trị biến front t ng lên). Trong trường hợp đó, có thể khắc phục bằng
cách sử dụng mảng vòng.
119
int full_queue(queue q)
{
return ((q.rear-q.front+1) % maxlength==0);
}
- Hàm đưa hêm một phần tử vào hàng đợi
void enqueue(elementtype x,queue *q)
{
if (!full_queue(*q))
{ if (empty_queue(*q))
q->front=0;
q->rear=(q->rear+1) % maxlength;
q->data[q->rear]=x;
}
el printf "Hàng đợi đầy!");
}
- Hàm xóa một phần tử khỏi hàng đợi
void dequeue(queue *q)
{
if (!empty_queue(*q))
{ if (q->front==q->rear)
makenull_queue(q);
else
q->front=(q->front+1) % maxlength;
}
el printf "Hàng đợi r ng");
120
}
Nhận xét :
Mặc d phương pháp ử dụng mảng vòng có thể tận dùng toàn bộ
các mảng đã được cấp pháp ban đầu nhưng khi mảng đầy thì không
thể thêm phần tử vào hàng được nữa. Trong trường hợp này, có thể
khắc phục bằng cách sử dụng danh sách liên kết để mở rộng.
121
}
122
Nhận xét :
Khi sử dụng hàng đợi bằng cách cài đặt thông qua danh sách liên
kết giúp khắc phục được tình trạng đầy của việc sử dụng mảng để cài
đặt hàng đợi, tuy nhiên thao tác trên danh sách phức tạp hơn o với
dùng mảng.
123
4. Hãy trình bày cách tính giá trị của biểu thức hậu tố au đ y nhờ
sử dụng ng n xếp:
a. 1 2 + 3 1 + * 1 1 + 1 + /
b. 3 4 + 3 5 + * 7 + 8 *
5. Viết chương trình nhập vào một số nguyên không âm bất kỳ, sau
đó hiển thị lên màn hình số đảo ngược thứ tự của số nguyên vừa
nhập vào (ví dụ: nếu nhập số 12567, hiển thị lên màn hình số
76521) bằng cách:
a. Sử dụng ng n xếp
b. Sử dụng hàng đợi
6. Viết chương trình chuyển đ i một số nguyên N trong hệ thập
phân (hệ 10) sang biểu diễn ở các hệ sau, sử dụng ng n xếp:
a. Hệ nhị phân (hệ 2)
b. Hệ thập lục phân (hệ 16)
c. Hệ bát phân (hệ 8)
7. Hãy viết chương trình mô phỏng cho bài toán Tháp Hà Nội
bằng cách sử dụng ng n xếp.
8. Có thể vận dụng ng n xếp hoặc hàng đợi để khử đệ quy trong giải
thuật sắp xếp Quick sort không? Nếu có giải thích vì sao ?
9. Có thể vận dụng ng n xếp hoặc hàng đợi để khử đệ quy trong giải
thuật sắp xếp Merge sort không? Nếu có giải thích vì sao ?
10. Viết chương trình tìm tất cả các cặp dấu ngoặc tương ứng trong
một chương trình viết bằng ngôn ngữ C/C++.
124
CHƯƠNG 5
CÂY
5 Định nghĩa ây
y được định nghĩa là tập hợp hữu hạn các nút, được t chức
theo cấu trúc phân cấp, trong đó có một nút đặc biệt được gọi là nút
gốc (root) và tập hợp các nút còn lại nối với nút gốc thông qua các
nhánh trên cây gọi là các nút con và các nút lá. Nút lá là nút tận cùng
của cây.
Hình bên dưới là cấu trúc t chức của c y, trong đó R là nút gốc,
R1, R2,..Rk là các nút con của nút gốc R, các nút T1, T2, T3 là các nút
lá. y trong hình bên dưới là cây bao gồm k nhánh.
125
Hình 5.2. Cây gia phả
Hình 5.4. Cây cấu trúc t chức các chương mục trong một cuốn sách
126
5.1.2. Các khái niệm trên cây
- Nút gốc – root: Nút gốc là nút trên cùng của cây theo thứ tự
phân cấp trong c y. Điều đó có nghĩa là nếu một nút được gọi là nút
gốc thì nút này không có nút nào ở trên nó trong phân cấp của cây.
- Nút lá (leaf): Một nút không có nút con được gọi là nút lá
- Nút con (child): Một nút gọi là nút con khi mà trên nó có một
nút khác theo thứ tự phân cấp
- Nút cha (parent): Một nút được gọi là nút cha nếu tồn tại một
nút là nút con của nó theo thứ tự phân cấp
- T tiên (ancestors) và Hậu duệ (descendants): Nếu có đường đi
từ nút a đến nút b, thì nút a được gọi là nút t tiên (ancestor) của b,
còn b được gọi là nút hậu duệ (descendant) của nút a.
- Nút anh em (sibling): Các nút có cùng nút cha được gọi là nút
anh em.
- Chiều cao (height): Chiều cao của nút trên cây bằng độ dài
đường đi dài nhất từ nút đó đến lá cộng 1
- Đường đi (path): Nếu n1, n2, …, nk là dãy nút trên cây sao cho ni
là cha của ni + 1 với 1 ≤ i ≤ k, thì dãy này được gọi là đường đi từ nút n1
đến nk.
- Độ dài (length) của đường đi bằng số lượng nút trên đường đi
trừ bớt đi 1. Như vậy, đường đi độ dài 0 là đường đi từ một nút đến
chính nó.
- Độ sâu/ mức d pth l v l) độ sâu của nút là độ dài của đường
đi duy nhất từ gốc cộng thêm 1
- Cây con (subtree) của một cây là một nút cùng với tất cả các
hậu duệ của nó.
127
Hình 5.5. Ví dụ một cây con b và các nút con của cây b: e, f, i, j
128
5.1.3. Cây có thứ tự
Trên c y, các nút được sắp xếp ở các vị trí khác nhau và từ đó
chúng ta có các cây khác nhau. Ví dụ dưới đ y minh họa hai cây
khác nhau.
a a
b c c b
Hình 5.8. Ví dụ hai cây bố trí các nút ở vị trí khác nhau
Cây với các nút được sắp xếp có thứ tự được gọi là cây có thứ tự,
ta sẽ xét chủ yếu là cây có thứ tự. Vì vậy, tiếp th o đ y thuật ngữ cây
là chỉ để chỉ cây có thứ tự. Khi muốn khẳng định không quan t m đến
thứ tự, ta sẽ phải nói rõ là cây không có thứ tự.
Thứ tự duyệt cây theo thứ tự trước, giữa và sau sẽ được kết quả
như au
131
- Thứ tự trước : a, b, c, e, h, i, k, l, d, g
- Thứ tự sau : b, h, i, e, l, k, c, g, d, a
- Thứ tự giữa : b, a, h, e, i, c, l, k, g, d
132
Mảng d ng để lưu c y trong máy tính được t chức như au
0 1 1 2 2 3 6 6 6 3
Danh sách liên kết lưu c y trong máy tính được t chức như au
133
5.3. CÂY NHỊ PHÂN
Hình 5.9. Cây nhị ph n có đủ hai con trái và phải tại m i nút
Hình 5.10. Cây nhị ph n không đủ hai con trái và phải tại m i nút
Định nghĩa 2: Cây nhị ph n đầy đủ là cây nhị phân thỏa mãn:
m i nút lá đều có c ng độ u và các nút trong có đúng hai con.
134
Hình 5.11. Cây nhị ph n đầy đủ
Định nghĩa 3: Cây nhị phân hoàn chỉnh là cây nhị ph n độ sâu n,
có thể coi là cây nhị ph n đầy đủ nếu không tính đến các nút ở độ sâu
n và tất cả các nút ở độ sâu n lệch sang trái nhất có thể.
Bổ đề 1:
(i) Số đỉnh lớn nhất ở mức trên i của cây nhị phân là 2i – 1, i ≥ 1.
(ii) Một cây nhị phân với chiều cao k không quá 2k – 1 nút, k ≥ 1.
(iii) Một cây nhị phân có n nút có chiều cao tối thiểu là: k = log2(n+1)
Chứng minh:
(i) Bằng quy nạp theo i.
135
ơ sở: Gốc là nút duy nhất trên mức i = 1. Như vậy, số đỉnh lớn
nhất trên mức i = 1 là 20 = 2i – 1.
Chuy n quy nạp: Giả sử với mọi j, 1 ≤ j < i, ố đỉnh lớn nhất trên
mức j là 2j – 1. Do số đỉnh trên mức i – 1 là 2i – 2, mặt khác th o định
nghĩa, m i đỉnh trên cây nhị phân có không quá hai nút con, ta suy ra
số lượng nút lớn nhất trên mức i không vượt quá hai lần số lượng nút
trên mức i – 1, nghĩa là không vượt quá 2 * 2i – 2 = 2i – 1.
(ii) Số lượng nút lớn nhất của cây nhị phân chiều cao k không
vượt quá t ng số nút lớn nhất trên các mức i = 1, 2, …, k. Theo b đề
1, số này không vượt quá:
(iii) Cây nhị phân n nút có chiều cao thấp nhất k khi số lượng nút
ở các mức i = 1, 2, …, k đều là lớn nhất có thể được. Từ đó ta có
,
suy ra 2k = n + 1, hay k = log2(n + 1).
137
Hàm cài đặt
void PreOrder (TNode *TREE)
{
if (TREE != NULL)
{ printf % \n , TREE->Info);
PreOrder (TREE->pLeft);
PreOrder (TREE->pRight);
}
}
138
Thứ tự sau – LRN
Thao tác thực hiện
- Bước 1 :Gọi hàm đệ quy Po Ord r để duyệt cây con trái theo
thứ tự sau
- Bước 2 : Gọi hàm đệ quy Po Ord r để duyệt cây con phải theo
thứ tự sau
- Bước 3 : Xuất giá trị của nút đang x t
Hàm cài đặt
void PosOrder (TreeNode *TREE)
{
if (TREE != NULL)
{ PosOrder (TREE->pLeft);
PosOrder (TREE->pRight);
printf % \n , TREE->Info);
}
}
Ví dụ:
Giả sử cho cây nhị ph n như hình dưới đ y :
139
Thứ tự duyệt cây theo các thứ tự trước và giữa như trình bày ở
trên có kết quả như au
- Duyệt theo thứ tự trước : 9, 2, 6, 1, 10, 8, 5, 3, 7, 12, 4.
- Duyệt theo thứ tự giữa : 6, 2, 10, 1, 9, 3, 5, 8, 12, 7, 4.
140
tương ứng với một biến; đường nối giữa nó với nút con của nó thể
hiện một giá trị cụ thể cho biến đó. M i nút lá đại diện cho giá trị dự
đoán của biến mục tiêu, các giá trị cho trước của các biến được biểu
diễn bởi đường đi từ nút gốc tới nút lá đó.
Ví dụ
David là quản lý của một c u lạc bộ đánh golf n i tiếng. nh ta
đang có rắc rối về chuyện các thành viên đến hay không đến. ó ngày
ai cũng muốn chơi golf nhưng ố nh n viên c u lạc bộ lại không đủ để
phục vụ. ó hôm, không hiểu vì lý do gì mà chẳng ai đến chơi và c u
lạc bộ lại thừa nh n viên.
Mục tiêu của David là tối ưu hóa ố nh n viên phục vụ m i ngày
bằng cách dựa th o thông tin dự báo thời tiết để đoán x m khi nào
người ta ẽ đến chơi golf. Để thực hiện điều đó, anh cần hiểu được tại
ao khách hàng quyết định chơi và tìm hiểu x m có cách giải thích
nào cho việc đó hay không.Vậy là trong hai tuần, anh ta thu thập
thông tin về thời tiết để giải thích cho c u hỏi về ố người đến chơi
goft. Dựa trên thông tin thu thập được, David vẽ được c y ph n loại
chơi hoặc không chơi golf như au
Hình 5.14. Cây phân loại chơi hoặc không chơi golf
141
Từ cây phân loại này, David rút ra được các thông tin mang tính
quyết định như au Nếu trời nhiề mây, người a l ôn l ôn chơi
golf. Có một số người ham mê đến mức chơi golf cả khi trời mưa.
Tiếp th o, David lại chia nhóm trời nắng thành hai nhóm nhỏ hơn
và thấy rằng : Khách hàng không m ốn chơi golf nế độ ẩm lên á
70%.
uối c ng, David chia nhóm trời mưa thành hai nhómvà thấy
rằng: Khách hàng sẽ không chơi golf nế rời nhiề gió.
Và đ y là lời giải ngắn gọn cho bài toán mô tả bởi c y ph n loại.
David cho phần lớn nh n viên nghỉ vào những ngày trời nắng và m,
hoặc những ngày mưa gió. Vì hầu như ẽ chẳng có ai chơi golf trong
những ngày đó. Vào những hôm khác, khi nhiều người ẽ đến chơi
golf, anh ta có thể thuê thêm nh n viên thời vụ để phụ giúp công việc.
Như thế c y quyết định giúp ta biến một biểu diễn dữ liệu phức
tạp thành một cấu trúc đơn giản hơn rất nhiều.
Hình 5.15. Cây nhị phân tìm kiếm lưu trữ giá trị các số nguyên
142
Nhận xét:
Nhờ ràng buộc về giá trị khóa trên cây nhị phân tìm kiếm, việc
tìm kiếm trở nên có định hướng. Hơn nữa, do cấu trúc cây việc tìm
kiếm trở nên nhanh hơn đáng kể. Chi phí tìm kiếm trung bình là
log2N.
5.4.2. Các thao tác trên cây nhị phân tìm kiếm
143
5.4.2.3. Thêm một phần tử X vào cây
Thao tác thực hiện
Việc thêm một phần tử X vào cây phải bảo đảm điều kiện ràng
buộc của cây nhị phân tìm kiếm. Khi chấm dứt quá trình tìm kiếm
cũng chính là lúc tìm được ch cần thêm vào.
Hàm cài đặt
int insertNode(TREE &T, Data X)
{
if ( T != NULL )
{ if (T->Key == X)
return 0; //đã có hần tử X
else if (T->Key > X)
return insertNode(T->pLeft, X);
else
return insertNode(T->pRight, X);
}
T = new TNode;
if (T == NULL)
return -1; //không thể cấp phát bộ nhớ
T->Key = X;
T->pLeft = NULL;
T->pRight = NULL;
return 1; } //thêm vào thành công
144
Hình 5.16. Minh họa thao tác thêm phần tử X=48 vào cây
Hình 5.17. Minh họa thao tác tìm kiếm trên cây, tìm phần tử X=82
146
5.4.2.5. Hủy một phần tử có khóa X trên cây
Thao tác thực hiện
Việc hủy một phần tử X ra khỏi cây phải bảo đảm điều kiện ràng
buộc của cây nhị phân tìm kiếm
ó 3 trường hợp khi hủy nút X có thể xảy ra:
- Trường hợp 1 : X là nút lá.
- Trường hợp 2 :X chỉ có một nút con (trái hoặc phải)
- Trường hợp 3 :X có đủ cả hai nút con
Trường hợp 1:
Chỉ đơn giản hủy X, vì X không móc nối đến phần tử nào khác.
Hình 5.18. Minh họa thao tác xóa phần tử 75 trên cây
Trường hợp 2:
Trước khi hủy X, ta móc nối cha của X với con duy nhất của nó.
147
Hình 5.19. Minh họa thao tác xóa phần tử 49 trên cây
Trường hợp 3:
Khi không thể hủy trực tiếp, do X có đủ hai nút con, ta sẽ hủy
gián tiếp. Thay vì hủy nút X, ta sẽ tìm một phần tử thế mạng gọi là nút
Y. Phần tử này có tối đa một con. Thông tin lưu tại nút Y sẽ được
chuyển lên lưu tại nút X. Sau đó, nút bị hủy thật sự sẽ là nút X giống
như 2 trường hợp đầu.
Vấn đề là phải chọn nút Y ao cho khi lưu nút Y vào vị trí của nút
X, cây vẫn là cây nhị phân tìm kiếm.
Có 2 phần tử thỏa mãn yêu cầu:
- Phần tử nhỏ nhất (trái nhất) trên cây con phải.
- Phần tử lớn nhất (phải nhất) trên cây con trái.
Việc chọn lựa phần tử nào là phần tử thế mạng hoàn toàn phụ
thuộc vào ý thích của người lập trình. Ở đ y, giả sử ta sẽ chọn phần tử
phải nhất trên cây con trái làm phân tử thế mạng.
Ví dụ:
Giả sử ta muốn xóa phần tử X = 82 ra khỏi cây
148
Hình 5.20. Minh họa thao tác xóa phần tử 82 trên cây
Sau khi hủy phần tử X=82 ra khỏi cây tình trạng của cây sẽ như
trong hình (phần tử 75 là phần tử thế mạng)
Hàm cài đặt
Hàm deleteNode trả về giá trị 1, 0 khi hủy thành công hoặc không
có X trong cây
int deleteNode(TREE &T, Data X)
{
if (T == NULL)
return 0;
else if (T->Key > X)
return deleteNode (T->pLeft, X);
else if(T->Key < X)
return deleteNode (T->pRight, X);
else //T->Key == X
{ LPNODE p = T;
if (T->pLeft == NULL)
149
T = T->pRight;
else if (T->pRight == NULL)
T = T->pLeft;
else //T có 2 nút con
{ LPNODE q = T->pRight;
searchStandFor(p, q); // Hàm tìm phần tử
thế mạng
}
delete p;
}
}
Trong đó, hàm archStandFor được viết như au
void searchStandFor(TREE &p, TREE &q)
{
if (q->pLeft)
searchStandFor(p, q->pLeft);
else
{ p->Key = q->Key;
p = q;
q = q->pRight;
}
}
150
Việc hủy toàn bộ cây có thể được thực hiện thông qua thao tác
duyệt cây theo thứ tự sau: hủy cây con trái, cây con phải, rồi mới hủy
nút gốc.
Hàm cài đặt
void removeTree(TREE &T)
{
if ( T != NULL )
{ removeTree(T->pLeft);
removeTree(T->pRight);
delete T;
}
}
Tất cả các thao tác thêm, xóa trên cây nhị phân tìm kiếm đều có
độ phức tạp trung bình O(h), với h là chiều cao của cây.
Trong trong trường hợp tốt nhất, cây nhị phân tìm kiếm có n nút
sẽ có độ cao h = log2(n). Chi phí tìm kiếm khi đó ẽ tương đương tìm
kiếm nhị phân trên mảng có thứ tự.
Tuy nhiên, trong trường hợp xấu nhất, cây có thể bị suy biến
thành một danh sách liên kết (khi mà m i nút đều chỉ có một nút con
trừ nút lá). Lúc đó, các thao tác trên sẽ có độ phức tạp O(n). Vì vậy
cần cải tiến cấu trúc của cây nhị phân tìm kiếm để đạt được chi phí
cho các thao tác là log2(n).
152
+ rường hợp 2: cây T lệch về bên phải, có các khả n ng au
Ta có thể thấy rằng các trường hợp lệch về bên phải hoàn toàn
đối xứng với các trường hợp lệch về bên trái. Vì vậy ta chỉ cần khảo
át trường hợp lệch về bên trái và làm tương tự cho trường hợp lệch
bên phải.
+ rường hợp 1.1: cây T1 lệch về bên trái. Ta thực hiện phép
quay đơn L ft-Left
+ rường hợp 1.2: cây T1 không lệch. Ta thực hiện phép quay
đơn L ft-Left
153
+ rường hợp 1.3: cây T1 lệch về bên phải. Ta thực hiện phép
quay kép Left-Right
Do T1 lệch về bên phải ta không thể áp dụng ph p quay đơn đã
áp dụng trong 2 trường hợp trên vì khi đó c y T ẽ chuyển từ trạng
thái mất cân bằng do lệch trái thành mất cân bằng do lệch phải, cần áp
dụng cách khác. Hình dưới đ y minh họa phép quay kép áp dụng cho
trường hợp này:
Nhận xét :
Trước khi cân bằng cây T có chiều cao h+2 trong cả 3 trường hợp
1.1, 1.2 và 1.3. Sau khi cân bằng, trong 2 trường hợp 1.1 và 1.3 cây có
chiều cao h+1, ở trường hợp 1.2 cây vẫn có chiều cao h + 2. Đ y cũng
là trường hợp duy nhất sau khi cân bằng nút T cũ có chỉ số cân bằng 0.
Với những x m x t trên, x t tương tự cho trường hợp cây T lệch
về bên phải, ta có thể xây dựng 2 hàm quay đơn và 2 hàm quay k p
như sau:
154
Hàm q ay đơn Lef -Left
void rotateLL ( AVLTree &T)
{
AVLNode * T1 = T-> pLeft ;
T->pLeft = T1->pRight ;
T1-> pRight = T;
switch( T1-> balFactor )
{ case LH: { T-> balFactor = EH;
T1-> balFactor = EH;
break ;
}
case EH: { T-> balFactor = LH;
T1-> balFactor = RH;
break ;
}
}
T = T1;
155
}
Hàm q ay đơn Righ -Right
void rotateRR(AVLTree &T)
{
AVLNode * T1 = T-> pRight ;
T->pRight = T1-> pLeft ;
T1-> pLeft = T;
switch ( T1-> balFactor )
{ case RH: { T-> balFactor = EH;
T1-> balFactor = EH;
break ;
}
case EH: { T-> balFactor = RH;
T1-> balFactor = LH;
break ;
}
}
T = T1;
}
156
Hàm quay kép Left-Right
T2-> pRight = T;
break ;
break ;
T = T2;
158
AVLNode * T2 = T1-> pLeft ;
T2->pLeft = T;
break ;
break ;
break ;
159
T2-> balFactor = EH;
T = T2;
Ta xây dựng 2 hàm cân bằng lại khi cây bị lệch trái hoặc lệch phải,
như au
return 2;
return 1;
return 2;
return 0;
}
160
Hàm cân bằng khi cây bị lệch về bên phải
return 2;
return 1;
return 2;
return 0;
161
Nếu có, ta phải cân bằng lại ở nút này. Việc cân bằng lại chỉ cần thực
hiện một lần tại nơi mất cân bằng.
Hàm cài đặt
Hàm insertNode trả về giá trị -1, 0, 1 khi không đủ bộ nhớ, gặp
nốt cũ hay thành công. Nếu sau khi thêm, chiều cao cây bị t ng lên,
giá trị 2 sẽ được trả về:
{ int res;
if (T != NULL )
{ if(T->key == X)
if (res < 2)
return res;
switch (T->balFactor)
return 1;
return 2;
162
}
return 1;
if (res < 2)
return res;
switch (T->balFactor)
return 1;
return 2;
return 1;
163
}
T = new TNode;
if (T == NULL)
T->key = X;
T->balFactor = EH;
164
Hàm cài đặt
Hàm deleteNode trả về giá trị 1, 0 khi hủy thành công hoặc không
có X trong cây. Nếu sau khi huỷ, chiều cao cây bị giảm đi, giá trị 2 sẽ
được trả về :
int deleteNode(AVLTree &T, DataType X)
{ int res ;
if (T==NULL)
return 0;
if (T->key > X)
{ res = deleteNode (T-> pLeft , X);
if ( res < 2)
return res ;
switch ( T-> balFactor )
{ case LH: { T-> balFactor = EH;
return 2;
}
case EH: { T-> balFactor = RH;
return 1;
}
case balanceRight (T);
}
}
else if (T->key < X)
{ res = deleteNode (T-> pRight , X);
if ( res < 2) return res ;
165
switch (T-> balFactor )
{ case RH: { T-> balFactor = EH;
return 2;
}
case EH: { T-> balFactor = LH;
return 1;
}
case LH: return balanceLeft (T);
}
}
else // if (T->key == X)
{
AVLNode * p = T;
if (T-> pLeft == NULL)
{
T = T->pRight ; res = 2;
}
else if (T-> pRight == NULL)
{
T = T-> pLeft ; res = 2;
}
else // if (T-> pLeft != NULL && T-> pRight != NULL)
{
res = searchStandFor ( p, T -> pRight );
if( res < 2)
166
return res;
switch( T-> balFactor )
{ case RH: { T-> balFactor = EH;
return 2;
}
case EH: { T-> balFactor = LH;
return 1;
}
case LH: return balanceLeft (T);
}
}
delete p;
return res ;
}
}
167
{ case LH: { q->balFactor= EH;
return 2;
}
case EH:{ q->balFactor= RH;
return 1;
}
case RH: return balanceRight(T);
}
}
else
{ p->key = q->key;
p = q;
q = q->pRight;
return 2;
}
}
168
BÀI TẬP CHƯƠNG 5
1. Giả sử cho c y như trong hình au
A
B C
D E F
G H I J
a. Trình bày thứ tự duyệt các nút của cây theo thứ tự trước.
b. Trình bày thứ tự duyệt các nút của cây theo thứ tự giữa.
c. Trình bày thứ tự duyệt các nút của cây theo thứ tự sau.
2. Hãy vẽ cây nhị ph n có độ cao là 3, khi kết quả duyệt theo thứ tự
sau cho ta dãy khóa là: (10, 30, 50, 20, 40, 70, 60)
3. Cho các cây nhị ph n như trong hình au, hãy trình bày cách biểu
diễn bằng cấu trúc mảng, cấu trúc danh sách liên kết cho các cây.
a) b) A
D
I J B C
E F G H D G H I
A B C E F J K
4. Giả sử cho cây nhị ph n trong hình cho dưới đ y. Hãy trình
bày thứ tự các đỉnh xuất hiện khi duyệt cây theo thứ tự trước,
giữa và sau.
169
a) D b) D
B F B F
C E A C E A
H I H I
G G
5. Cho dãy số sau: 20, 5, 1, 17, 30, 24, 7, 8, 25, 32, 50, 18, 99, 68,
29, 50.
Hãy thực hiện các yêu cầu sau:
a. Xây dựng cây nhị phân tìm kiếm từ dãy số đã cho th o thứ
tự thêm các số vào cây theo thứ tự từ trái sang của dãy số.
b. Xóa khỏi cây các nút chứa các khóa 24, 20, 17, 5, 29, 68.
M i lần xóa một nút vẽ lại cây và cân bằng lại cây khi cây bị
mất cân bằng.
6. Giả sử cho thông tin một sinh viên bao gồm các thông tin:
- Mã sinh viên : dạng số nguyên dương, mã inh viên là giá trị
duy nhất để phân biệt
- Họ sinh viên : dạng chu i
- Tên sinh viên : dạng chu i
- Điểm trung bình học tập : dạng số thực, có miền giá trị từ 0.0
đến 10.0
Hãy thực hiện các yêu cầu sau:
a. Khai báo cấu trúc cây nhị phân tìm kiếm để lưu thông tin
sinh viên theo mô tả ở trên.
b. Viết hàm thêm các sinh viên vào cây.
170
c. Viết hàm xóa các inh viên có điểm trung bình < 5.0 ra
khỏi cây.
d. Viết hàm hiển thị danh sách sinh viên khi duyệt cây theo thứ
tự trước.
7. Giả sử cho danh sách các từ khóa trong ngôn ngữ như au
struct, typedef, int, void, return, for, while, goto, else, continue,
break, main.
Hãy thực hiện các yêu cầu sau:
a. Khai báo cấu trúc cây nhị phân tìm kiếm để lưu thông tin các
từ khóa theo mô tả ở trên.
b. Viết hàm thêm các từ khóa trên vào cây
c. Viết hàm hiển thị danh sách các từ khóa lưu trong c y khi
duyệt cây theo thứ tự sau.
8. Giả sử cho các biểu thức số học như au
(A – B) – C
A – (B – C)
A / (B – (C – (D – (E – F))))
((A * (B + C)) / (D – (E + F))) * (G / (H / (I *J)))
Hãy thực hiện các yêu cầu sau:
a. Khai báo cấu trúc dữ liệu c y để lưu các biểu thức trên và
trong trường hợp t ng quát.
b. Giả sử các ký tự , B, , D, E, F, G, H, , J được thay thế là
các số, hãy viết hàm tính giá trị của các biểu thức đã cho và
trong trường hợp t ng quát.
9. Hãy vẽ cây nhị phân bao gồm các ký tự khi kết quả duyệt cây
theo thứ tự giữa có kết quả như au G, F, H, , D, L, , W, R,
Q, P, Z.
171
10. Hãy vẽ cây nhị phân bao gồm các ký tự khi kết quả duyệt cây
theo thứ tự trước có kết quả như au , D, F, G, H, , L, P, Q, R,
W, Z.
11. Hãy vẽ cây nhị phân bao gồm các ký tự khi kết quả duyệt cây
theo thứ tự sau có kết quả như au F, G, H, D, , L, P, Q, R Z,
W, K.
12. Cho một cây nhị phân T, hãy thực hiện các yêu cầu sau:
a. Viết hàm hiển thị danh sách các nút lá trên cây, nếu cây r ng
thì in ra giá trị NULL
b. Viết hàm tính chiều cao của cây, nếu cây r ng thì in ra giá trị
NULL
c. Viết hàm in ra các nút ở mức K trên cây, biết rằng K nằm
trong phạm vi chiều cao của cây, nếu cây r ng thì in ra giá trị
NULL
13. Xét cấu trúc dữ liệu au đ y trong ngôn ngữ C định nghĩa cây nhị
phân:
struct Tnode
{ int key; // dữ liệu của nút
struct Tnode *left;
struct Tnode *right;
};
typedef struct Tnode treeNode;
Hãy thực hiện các yêu cầu sau:
a. Viết hàm int countLeaft (treeNode *Root, int k) trả lại số
lượng lá của cây trỏ bởi con trỏ Root.
b. Viết hàm int OddSum (treeNode *Root) trả lại t ng các số lẻ
được lưu tại các nút của cây nhị phân với gốc được trỏ bởi
con trỏ Root.
172
c. Viết hàm int Sum (treeNode *Root) trả lại giá trị là t ng của
các giá trị số lưu tại các nút của cây nhị phân với gốc được
trỏ bởi con trỏ Root.
d. Viết hàm int EvenLeaf (treeNode *Root) trả lại số lượng nút
lá với trường dữ liệu key của nút là số chẵn của cây với gốc
được cho bởi con trỏ Root.
e. Viết hàm int countNodes (treeNode *Root, int k) trả lại số
nút có trường key lớn hơn k trong c y được trỏ bởi con trỏ
Root.
14. Xét cấu trúc dữ liệu biểu diễn cây nhị phân tìm kiếm:
struct TreeNode
{ int info; // dữ liệu của nút
struct TreeNode *left;
struct TreeNode *right;
};
Hãy thực hiện các yêu cầu sau:
a. Viết hàm int EvenMax (TreeNode *Root) nhận đầu vào Root
là con trỏ đến gốc của cây nhị phân tìm kiếm, trả lại giá trị
info chẵn lớn nhất trong các nút của cây nhị ph n đã cho.
b. Gọi T(n) là thời gian tính của hàm trong c u 1a), hãy đưa ra
đánh giá cho T(n), trong đó n là số lượng nút của cây nhị
ph n đầu vào.
15. Xét cấu trúc dữ liệu mô tả cây nhị phân sau:
struct TreeNode
{ int info; // dữ liệu của nút
TreeNode *left;
TreeNode *right;
};
173
Hãy viết hàm bool IsBST (Tree *Root) nhận đầu vào là con trỏ
Root đến gốc của cây, trả lại giá trị true khi và chỉ khi cây là cây
nhị phân cân bằng.
16. Xét cấu trúc dữ liệu mô tả cây nhị phân sau :
struct TreeNode
{ int info; // dữ liệu của nút
struct TreeNode *left;
struct TreeNode *right;
};
Hãy viết hàm bool IsLeft (TreeNode *Root, int key) nhận đầu
vào là con trỏ Root đến gốc của cây, trả lại giá trị true khi và chỉ
khi mọi nút của c y đều có trường info nhỏ hơn k y.
174
CHƯƠNG 6
BẢNG BĂM
175
trong thực tế thì hai hoặc nhiều khóa khác nhau thông qua hàm H sẽ
có thể cho ra cùng một địa chỉ trong bảng.
- Bước tiếp theo là quá trình giải quyết xung đột (collision) cho
trường hợp những khóa khác nhau có cùng một địa chỉ trong bảng.
Một trong những phương pháp giải quyết đụng độ ph biến là dùng
danh sách liên kết, do sẽ tạo ra sự linh hoạt trong việc kết hợp các
khóa có cùng địa chỉ khi kết hợp danh sách liên kết.
177
Hình 6.1. Mô tả quá trình thực hiện phương pháp nối kết trực tiếp
khi thêm các phần tử vào bảng
178
d. Xây dựng hàm các thao tác trên bảng băm
- Hàm khởi ạo bảng băm
void initbuckets( )
{
int b;
for (b=0; b<M; b++)
bucket[b] = NULL;
}
179
- Hàm thêm hần ử có khóa k vào bảng băm
Giả ử các phần tử trên các buck t là có thứ tự, để thêm một phần
tử khóa k vào bảng b m trước tiên chúng ta xác định buck t ph hợp,
sau đó d ng hàm plac của danh ách liên kết để đặt phần tử vào vi trí
ph hợp trên buck t.
void insert(int k)
{
int b;
b= hashfunc(k)
place(b,k);
}
- Hàm ìm kiếm
Tìm kiếm một phần tử trong bảng b m, nếu không tìm thấy hàm
trả về giá trị NULL,nếu tìm thấy hàm trả về địa chỉ phần tử tìm thấy.
NODEPTR search(int k)
{
NODEPTR p;
int b;
b = hashfunc (k);
p = bucket[b];
while (k > p->key && p !=NULL)
p=p->next;
if (p == NULL | | k !=p->key) // không ìm hấy hần ử
return NULL;
181
else // ìm hấy hần ử
return p;
}
Nhận x t:
Bảng b m d ng phương pháp nối kết trực tiếp ẽ "b m N phần
tử vào bảng gồm M phần tử.
Để tốc độ thực hiện các ph p toán trên bảng hiệu quả, cần chọn
hàm b m ao cho b m đều N phần tử của bảng b m cho M phần tử,
lúc này trung bình m i buck t ẽ có N / M phần tử.
Nếu chọn M càng lớn thì tốc độ thực hiện các ph p toán trên bảng
b m càng nhanh. Tuy nhiên ẽ dùng nhiều bộ nhớ. Do vậy, cần điều
chỉnh M để dung hòa giữa tốc độ truy xuất và dung lượng bộ nhớ cần
ử dụng.
Nếu chọn M = N thì n ng xuất tương đương với truy xuất trên
mảng có bậc O 1)), tuy nhiên tốn nhiều bộ nhớ.
Nếu chọn M = N / k (với k =2,3,4,…) thì ít tốn bộ nhớ hơn k lần,
nhưng tốc độ lại chậm đi k lần.
182
Khi thêm khóa C vào bảng b m, vị trí H ) = 1 đang chứa
khóa A nên xảy ra đụng độ, do đó phải tìm kiếm vị trí trống đầu
tiên từ phía dưới lên. Vị trí trống đầu tiên là N, ta thêm khóa C vào
vị trí N.
Khi thêm vào khóa D, vì vị trí H D) = 1 đã chứa khóa , ta đi
theo con trỏ Next của vị trí này để đến vị trí M, nhưng vị trí M đã
chứa khóa C và con trỏ Next của vị trí này bằng 0 nên ta thêm khóa
D vào vị trí M – 1 (vị trí trống đầu tiên từ phía dưới bảng trở lên).
Hình 6.2. Mô tả quá trình thực hiện phương pháp nối kết hợp nhất
khi thêm các phần tử vào bảng
183
int Next; // con rỏ chỉ nú kế iế khi có x ng độ
};
b. Khai báo bảng băm
struct node hashtable[M];
int avail; // biến chỉ nú rống ở c ối bảng, cậ nhậ khi có
x ng độ
c. Xây dựng hàm băm
Giả ử chúng ta chọn hàm b m dạng modulo f k y)=k y % 10.
int hashfunc(int key)
{
return (key % 10);
}
d. Xây dựng hàm các thao tác trên bảng băm
- Hàm khởi ạo bảng băm
Hàm này khởi tạo bảng b m bằng cách gán tất cả các phần tử trên
bảng có trường k y là Null, con trỏ N xt có giá trị là –1 cho tất cả các
phần tử trên bảng b m.
Gán biến toàn cục avail=M-1 là phần tử cuối danh ách chu n bị
cấp phát nếu xảy ra xung đột.
void initialize()
{
for (int i = 0; i<M; i++)
{ hashtable[i].key = NULLKEY;
hashtable[i].key = -1;
}
avail = M-1;
184
}
-Hàm kiểm ra bảng băm rỗng ?
int empty ()
{
int i;
for (i = 0; i< M; i++)
if (hashtable[i].key !=NULLKEY)
return FALSE;
return TRUE;
}
185
- Hàm lấy hần ử rống rong bảng băm
Hàm này chọn phần tử còn trống phía cuối bảng b m để cấp phát
khi xảy ra xung đột.
int getempty()
{
while (hashtable[avail].key !=NULLKEY)
avail - -;
return avail;
}
187
quyết đụng độ là khi có đụng độ xảy ra thì ta sẽ tìm đến vị trí kế tiếp
nào đó ở trong bảng cho đến khi nào tìm thấy phần tử mong muốn
hoặc vị trí kế tiếp là vị trí trống.
Trong phương pháp địa chỉ mở (open addressing), tất cả các
phần tử đều được cất giữ vào bảng. Do đó m i ô của bảng hoặc là
chứa khóa hoặc là NULL. tưởng của phương pháp này như au
Để thực hiện thêm phần tử, nếu ô địa chỉ sau khi thực hiện
hàm b m bị chiếm giữ, ta sẽ tiến hành dò thử các ô còn lại của
bảng cho đến khi tìm được ô r ng để nạp phần tử vào. Thay vì
tìm tuần tự theo thứ tự 0, 1, …, N – 1 (thời gian Θ N)), dãy
các vị trí sẽ phụ thuộc vào giá trị khóa của phần tử được b
ung. Để xác định các ô dò thử, ta sẽ mở rộng định nghĩa hàm
b m.
Khi tìm kiếm trên bảng b m, ta sẽ thực hiện giống như cách dò
thử như khi thực hiện chèn phần tử vào bảng.
+ Nếu con trỏ trả về là NULL thì phần tử cần tìm không có
trong bảng.
+ Ngược lại là đã có phần tử trong bảng.
Mở rộng định nghĩa hàm b m như au
h: U × {0, 1, …, N – 1} → {0, 1, …, N – 1}
Trong phương pháp địa chỉ mở ta đòi hỏi, với m i khóa k, dãy
dò thử <h k, 0), h k, 1), …, h k, N – 1)> phải là một hoán vị
của < 0, 1, …, N – 1>, do đó m i vị trí trong bảng sẽ được xét
như là một ô để chứa khóa mới khi ta tiến hành b sung vào
bảng.
Cài đặ bảng băm dùng hương há địa chỉ mở
- Hàm b sung khóa k vào bảng băm được thực hiện qua các bước :
- Bước 1 : Khởi tạo i = 0
- Bước 2 : Thực hiện lặp cho đến khi i = N
188
Gán j = h(k, i)
Nếu (T[j] = NULL)
{
Gán T[j] = k
Trả về j
}
Ngược lại :
Gán i = i + 1
- Hàm tìm kiếm khóa k trong bảng băm được thực hiện qua các bước:
- Bước 1 : Khởi gán i = 0
- Bước 2 : Thực hiện lặp cho đến khi T[j] = NULL hoặc i = N
Gán j = h(k, i)
Nếu (T[j] = j)
Trả về j
Gán i = i + 1
Trả về NULL
189
6.3.2.1 Dò tuyến tính
Ý ưởng hương há
Phương pháp địa chỉ mở đơn giản nhất có thể thực hiện là dò
thử tuyến tính. hi đó, một khi có đụng độ xảy ra ta tìm đến vị trí
kế tiếp (chỉ số t ng lên 1).
Giả sử cho hàm b m h’ U → {0, 1, …, N – 1}, phương pháp dò
tuyến tính sử dụng hàm b m mở rộng như sau:
h(k,i) = h’ k) + i) mod N , với i = 0, 1, …, N – 1.
Như thế giả sử cho khóa k, ô đầu tiên trong bảng b m được dò
thử tuyến tính là T[h’ k)]. Sau đó sẽ dò thử ô T[h’ k) + 1], T[h’ k) +
2],… cho đến ô T[N – 1].
Cài đặ bảng băm dùng hương há dò yến tính
a. Khai báo cấu trúc bảng băm
#define NULLKEY –1
#define M 100 // giả sử dùng bảng băm gồm 100 hần ử
struct node
{
int key;
};
b. Khai báo bảng băm
struct node hashtable[M]; // khai báo bảng băm có M hần ử
int NODEPTR; // biến oàn cục chỉ số nú hiện có rên bảng băm
c. Xây dựng hàm băm
Giả ử chúng ta chọn hàm b m: f(key)=key %10.
int hashfunc(int key)
{
return(key% 10);
190
}
d. Xây dựng hàm các thao tác trên bảng băm
- Hàm khởi ạo bảng băm
Gán tất cả các phần tử trên bảng có trường k y là NULL. Gán
biến toàn cục N=0.
void initialize( )
{
int i;
for (i=0;i<M;i++)
hashtable[i].key=NULLKEY;
N=0;
}
- Hàm kiểm ra bảng băm rỗng ?
int empty( )
{
return(N==0 ? TRUE : FALSE);
}
- Hàm ìm kiếm
int search(int k)
191
{
int i;
i=hashfunc(k);
while (hashtable[i].key!=k
&& hashtable[i].key !=NULKEY)
{ i=i+1;
if (i>=M)
i=i-M;
}
if (hashtable[i].key==k)
return i;
else
return M;
}
194
- Hàm kiểm ra bảng băm có rống không ?
int empty( )
{
return(N==0 ? TRUE : FALSE);
}
- Hàm ìm kiếm
int search(int k)
{
int i, d;
i = hashfuns(k);
d = 1;
while (hashtable[i].key!=k
&&hashtable[i].key !=NULLKEY)
{ i = (i+d) % M; //băm lại
d = d+2;
}
hashtable[i].key =k;
N = N+1;
195
return i;
}
6.3.2.3. Hàm b m k p
Ý ưởng hương há
B m k p (double hashing) là phương pháp tốt nhất có thể sử dụng
trong phương pháp địa chỉ mở ,vì dãy dò thử tạo được bởi phương
pháp này có tính chất giống như một hoán vị ngẫu nhiên.
Trong phương pháp hàm b m k p, ta ử dụng hàm b m mở rộng:
h(k, i) = (h1(k) + ih2(k)) mod N,
trong đó h1, h2 là các hàm b m b trợ.
Cài đặ bảng băm dùng hương há băm ké
a.Khai báo cấu trúc bảng băm
#define NULLKEY –1
#define M 100 // giả sử dùng bảng băm gồm 100 hần ử
struct node
{
int key;
};
b.Khai báo bảng băm
struct node hashtable[M]; //khai báo bảng băm có M hần ử
int NODEPTR; // biến oàn cục chỉ số nú hiện có rên bảng băm
c.Xây dựng hàm băm
Giả ử ta chọn hai 2 hàm b m :
f1(key)=key % M
f2(key) =M - 2 - key % (M-2).
196
int hashfunc(int key)
{
return(key%M);
}
int hashfunc2(int key)
{
return(M-2 -key%(M-2));
}
d. Xây dựng hàm các thao tác trên bảng băm
- Hàm khởi ạo bảng băm
Gán tất cả các phần tử trên bảng có trường k y là NULL.
Gán biến toàn cục N = 0.
void initialize( )
{
int i;
for (i=0;i<M;i++)
hashtable[i].key=NULLKEY;
N=0;
}
197
- Hàm kiểm ra bảng băm có đầy không ?
int full( )
{
return (N==M-1 ? TRUE : FALSE);
}
- Hàm ìm kiếm
int search(int k)
{
int i, j ;
i = hashfunc (k);
j = hashfunc2 (k);
while (hashtable[i].key!=k
&& hashtable[i].key ! = NULLKEY)
i = (i+j) % M ; //băm lại
if (hashtable [i]).key == k) // ìm hấy k
return i ;
else // không ìm hấy k
return M ;
}
198
{ printf "Bảng b m đầy") ;
return M ;
}
if (search (k) < M)
{ printf ("Đã có khóa này trong bảng b m") ;
return M;
}
i = hashfunc(k); // hàm băm 1
j = hashfunc2 (k); // hàm băm 2
while (hashtable [i].key ! = NULLEY)
i = (i + j) % M; // băm lại
hashtable [i].key = k ;
N = N+1;
return i ;
}
Nhận x t :
Nên chọn ố địa chỉ M là ố nguyên tố. Bảng b m đầy khi N =
M-1, ta nên dành ít nhất một phần tử trống trên bảng b m.
Bảng b m được cài đặt th o cấu trúc này linh hoạt hơn bảng b m
d ng phương pháp dò tuyến tính và phương pháp dò bậc hai, do d ng
hai hàm b m khác nhau nên việc rải phần tử mang tính ngẫu nhiên
hơn, nếu bảng b m chưa đầy tốc độ truy xuất có bậc O 1).
Trường hợp xấu nhất là bảng b m gần đầy, tốc độ truy xuất chậm
do thực hiện nhiều lần o ánh.
199
6.3.3. Ph ơng ph p nhân
Ý ưởng hương há
Phương pháp nh n (multiplication method) để xây dựng hàm
b m được tiến hành th o hai bước.
Đầu tiên ta nhân k với một hằng số A, 0 < A < 1 và lấy phần
thập phân của kA.
Sau đó nh n giá trị này với m rồi lấy phần nguyên của kết
quả.
Cài đặt
Chọn hằng số A, 0 < A < 1
Hàm h(k) = M(kA - |kA|)
Nên chọn giá trị M = 2p.
Nên chọn giá trị A không quá gần với 0 hoặc 1.
200
Vấn đề thực hiện thao tác trong bảng băm
Việc thực hiện các thao tác trên bảng b m, ví như thao tác loại
bỏ hoặc thao tác thêm một phần tử trên bảng b m đôi khi lại trở nên
phức tạp với một số phương pháp đã trình bày ở trên, do bản chất của
vấn đề nằm ở ch là thao tác có dễ dàng hoặc thuận lợi hay không là
nằm ở cách thức ta t chức cấu trúc lưu trữ bảng b m khi tiến hành sử
dụng.
Do đó, việc xem xét một cách thức t chức dữ liệu nào đó nhằm
mang đến sự dễ dàng, thuận lợi hơn là một yếu tố cũng cần xem xét.
Cấu trúc cây có thể mang lại hiệu quả hơn và có thể phù hợp hơn
trong bối cảnh khối lượng dữ liệu cần lưu trữ trong bảng b m là lớn và
ta chưa biết trước được t ng khối lượng dữ liệu cần lưu trữ này.
201
khóa trong tập được lưu trữ vào bảng A, sử dụng k thuật dò
toàn phương để xử lý xung đột.
5. Cho bảng kích thước 11 ô và tập khóa K = {7, 20, 16, 24, 12,
40, 15}, ta cần nạp các giá trị khóa K vào bảng A sử dụng hàm
b m H k) = k % 11. Hãy vẽ bảng A sau khi tất cả các giá trị khóa
trong tập được lưu trữ vào bảng A, sử dụng k thuật b m k p
để xử lý xung đột, với hàm b m k p thứ 2 tự định nghĩa.
6. Viết chương trình minh hoạ bảng b m d ng phương pháp nối kết
trong các trường hợp sau :
a. Dữ liệu lưu trữ là số nguyên (khoá tìm kiếm là số
nguyên).
b. Dữ liệu lưu trữ là thông tin học sinh, bao gồm: họ tên học
sinh, lớp, tên trường (khoá tìm kiếm là tên lớp).
7. Viết chương trình minh hoạ bảng b m d ng phương pháp dò
tuyến tính trong các trường hợp sau:
a. Dữ liệu lưu trữ là số nguyên (khoá tìm kiếm là số
nguyên).
b. Dữ liệu lưu trữ là thông tin học sinh, bao gồm: họ tên học
sinh, lớp, tên trường (khoá tìm kiếm là tên lớp).
8. Viết chương trình minh hoạ bảng b m d ng phương pháp dò bậc
hai trong các trường hợp sau:
a. Dữ liệu lưu trữ là số nguyên (khoá tìm kiếm là số nguyên).
b. Dữ liệu lưu trữ là thông tin học sinh, bao gồm: họ tên học
sinh, lớp, tên trường (khoá tìm kiếm là tên lớp).
9. Viết chương trình tạo ra một Từ điển với các thao tác sử dụng
bảng b m với các chức n ng như au, biết rằng dữ liệu từ điển
lưu trên tập tin :
a. Thêm một từ mới: thêm vào từ và nghĩa của từ
b. Tra từ: nhập vào từ và cho biết nghĩa của từ
202
c. Cập nhật từ : thay đ i từ hoặc nghĩa của từ
d. Xoá từ : xóa từ và và nghĩa của từ
10. Giả sử cho một tập tin lưu thông tin tên người dùng (username)
và mật kh u (password) của các người d ng, danh ách người
dùng gồm 1000 người.
Hãy thực hiện các yêu cầu sau:
a. Tạo file chứa 1000 người dùng với thông tin tên người
dùng và mật kh u cho từng người.
b. Viết hàm cho phép nhập vào tên người dùng và mật kh u,
dùng bảng b m để kiểm tra tính hợp lệ của tên người
dùng và mật kh u.
203
BÀI TẬP TỔNG HỢP
1. Hãy trình bày và o ánh ưu điểm và hạn chế của các cấu trúc dữ
liệu đã học trong môn học.
2. Việc lưu trữ số nguyên có giá trị tuyệt đối rất lớn, là một yêu cầu
thường gặp khi giải quyết các bài toán trong máy tính.
Hãy thực hiện các yêu cầu sau:
a. Đề xuất cấu trúc dữ liệu thích hợp để lưu trữ các số
nguyên có giá trị tuyệt đối rất lớn trong bộ nhớ trong của
máy tính.
b. Với cấu trúc dữ liệu bạn đề xuất, hãy trình bày thuật toán
và cài đặt các hàm để thực hiện các thao tác cộng, trừ,
nhân, chia nguyên, chia lấy số dư và o ánh về giá trị các
số nguyên này.
3. Giả sử cho các tập tin lưu các v n bản, m i tập tin bao gồm nhiều
dòng, m i dòng trong file có chiều dài không quá 127 ký tự.
Hãy thực hiện các yêu cầu sau:
a. Đề xuất cấu trúc dữ liệu thích hợp để lưu trữ nội dung
v n bản trong bộ nhớ trong của máy tính.
b. Trình bày thuật toán và viết hàm cài đặt tính tần suất xuất
hiện của các từ trong các tập tin v n bản.
c. Trình bày thuật toán và viết hàm cho biết trong m i v n
bản có bao nhiều từ, từ nào xuất hiện nhiều nhất trong
v n bản, trong tập v n bản.
d. Viết hàm thực hiện hiển thị nội dung tập tin lên màn hình,
kết hợp phím Pag Up Pag Down để cuộn lên / cuộn
xuống màn hình trong trường hợp nội dung trong tập tin
v n bản nhiều hơn phạm vi một màn hình
4. Ma trận thưa là ma trận có kích thước lớn (số hàng và số cột lớn),
nhưng trong ma trận lại có rất ít các ô có lưu giá trị, đa phần các ô
là trống hoặc lưu giá trị là 0 (số không).
204
Hãy thực hiện các yêu cầu sau:
a. Hãy đề xuất cấu trúc dữ liệu thích hợp để lưu trữ ma trận
thưa trong bộ nhớ trong của máy tính sao cho hiệu quả
nhất về không gian lưu trữ cần thiết.
b. Hãy trình bày thuật toán và viết hàm thực hiện các thao
tác cộng, trừ, nhân hai ma trận thưa ao cho hiệu quả nhất
về thời gian tính toán.
5. Ngày nay, người ta có thể sử dụng máy tính để xây dựng chương
trình lưu thông tin gia phả của một dòng họ nhằm tiện lợi trong
quản lý, tra cứu thông tin, đồng thời giúp việc thực hiện các thao
tác nhanh chóng.
Trên cơ ở đó, hãy x y dựng một chương trình quản lý gia phả,
biết rằng gia phả lưu thông tin về từng dòng họ mà m i cặp vợ
chồng trong đó có không quá 4 người con, với các yêu cầu sau :
a. Đề xuất cấu trúc dữ liệu thích hợp để lưu trữ gia phả của
một dòng họ nào đó trong bộ nhớ trong của máy tính.
b. Trình bày thuật toán và viết hàm cài đặt thao tác thêm
một người vào gia phả.
c. Trình bày thuật toán và viết hàm cài đặt thao tác tìm kiếm
một người X nào đó có trong gia phả hay không.
d. Trình bày thuật toán và viết hàm cài đặt thao tác xóa một
người Y nào đó trong gia phả.
e. Trình bày thuật toán và viết hàm cài đặt kiểm tra hai
người A và B có quan hệ như thế nào, ví dụ như quan hệ
: bố - con, anh – em, mẹ - con, ông – cháu, anh em
họ,…và A và B ai là người ở thứ bậc cao hơn trong gia
phả.
f. Viết hàm thực hiện hiển thị toàn bộ gia phả lên màn hình,
kết hợp phím Pag Up Pag Down để cuộn lên / cuộn
xuống màn hình trong trường hợp nội dung cần hiển thị
nhiều hơn phạm vi một màn hình.
205
6. Hãy đề xuất cấu trúc dữ liệu và xây dựng chương trình quản lý
một Từ điển Anh – Việt với đầy đủ các chức n ng của một
từ điển.
7. Hãy đề xuất cấu trúc dữ liệu và xây dựng chương trình oạn thảo
v n bản đơn giản với các chức n ng cho ph p oạn thảo, lưu trữ,
x m và in v n bản.
8. Giả sử cho một chu i ký tự có chiều dài 1 triệu ký tự. Hãy thực
hiện các yêu cầu sau:
a. Đề xuất cấu trúc dữ liệu để lưu trữ chu i ký tự này
trong bộ nhớ trong của máy tính sao cho sử dụng ít bộ
nhớ nhất có thể.
b. Trình bày thuật toán và viết hàm cài đặt kiểm tra xem ký
tự nào trong chu i ký tự nào xuất hiện nhiều nhất, ít nhất
với thời gian thực hiện nhanh nhất có thể.
9. Giả sử cho một từ điển bao gồm 10.000 từ và 100 tập tin v n bản,
biết rằng m i tập tin v n bản có tối đa 1.000 từ. Người ta có nhu
cầu kiểm tra xem các từ trong v n bản có được viết đúng chính tả
không bằng cách so sánh lần lượt từng từ trong v n bản với từ
điển. Nếu từ trong v n bản có trong từ điển thì từ đó đã được viết
đúng chính tả, ngược lại là không đúng chính tả.
Hãy thực hiện các yêu cầu sau:
a. Đề xuất giải pháp để thực hiện yêu cầu trên với thời gian
thực hiện nhanh nhất có thể.
b. Nếu số lượng tập tin v n bản t ng lên thành 50.000 thì
thời gian thực hiện trong giải pháp của bạn ở câu a có
t ng tuyến tính theo không? Giải pháp trong câu a có còn
khả thi không?
10. M i loại lịch hiện nay có ngày và tháng nhuận khác nhau.
- Với dương lịch, chu kỳ Trái đất quay quanh Mặt trời là 365 +
1 4 ngày. Th o quy ước hiện nay thì m i n m chỉ có 365 ngày,
206
nên n m dương lịch ẽ chênh với thời gian thực là 1 4 ngày.
Điều này có nghĩa au 4 n m thì dương lịch ẽ dư một ngày và
ẽ có một n m nhuận một ngày. N m nhuận này th o quy ước
rơi vào tháng hai tức là tháng có 29 ngày).
- Với m lịch có 354 ngày và nếu o ánh với dương lịch thì m
lịch ngắn hơn 11 ngày. Như vậy, cứ ba n m, m lịch lại ngắn
hơn dương lịch 33 ngày, tức là ba n m m lịch ẽ nhuận một
tháng chứ không nhuận một ngày như dương lịch.
- Muốn tính n m m lịch nào đó có tháng nhuận hay không chỉ
cần làm ph p toán đơn giản là lấy n m dương lịch chia cho 19
nếu chia hết hoặc có các ố dư 3, 6, 9, 11, 14, 17 thì chắc chắn
n m đó là n m nhuận.
- Giả ử cho a và b là 2 n m dương lịch, a ≤ b, c u hỏi đặt ra là
làm ao biết trong các n m từ a đến b kể cả a và b) có bao
nhiêu n m nhuận th o m lịch?
Hãy trình bày thuật toán và cài đặt các hàm liên quan để giải
quyết vấn đề nêu trên.
207
TÀI LIỆU THAM KHẢO
208