Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 76 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
76
Dung lượng
740,5 KB
Nội dung
SỞ GIÁO DỤC VÀ ĐÀO TẠO TỈNH BẮC GIANG
TRƯỜNG THPT CHUYÊN BẮC GIANG
NGUYỄN THỊ HỢP
MỘT SỐ VẤN ĐỀ VỀ
KIỂU DỮ LIỆU TRỪU TƯỢNG
Tổ: Toán-Tin
Năm học: 2011-2012
Mã số: …………………..
Bắc Giang, tháng 4 năm 2012
Phần thứ nhất
MỞ ĐẦU
I. LÝ DO CHỌN ĐỀ TÀI
Những năm gần đây đề thi chọn HSG QG môn Tin học được phân chia
theo ba cấp độ khó dần. Mỗi đề thi gồm có ba bài, trong đó Bài 2 và Bài 3 đòi
hỏi học sinh không những giỏi về thuật toán mà còn phải biết vận dụng linh hoạt
các kiến thức về cấu trúc dữ liệu. Mặt khác các tài liệu viết về cấu trúc dữ liệu
còn rất trừu tượng, tổng quát và khó có thể áp dụng để giảng dạy cho học sinh
chuyên Tin cũng như đội tuyển HSG.
Do đó, trong hai năm học 2010-2011 và 2011-2012, tôi chọn chuyên đề
nghiên cứu là: “Một số vấn đề về kiểu dữ liệu trừu tượng” để bước đầu có thể
đọc và hiểu một phần nho nhỏ về kiểu dữ liệu trừu tượng.
Đây là một trong những nội dung rất khó nhưng tôi thấy rất cần thiết cho
công việc giảng dạy của tôi cũng như của nhóm Tin Trường THPT Chuyên Bắc
Giang.
II. ĐỐI TƯỢNG NGHIÊN CỨU
Đối tượng nghiên cứu chủ yếu là các kiểu dữ liệu trừu tượng và các bài
toán nâng cao trong lập trình.
III. LỊCH SỬ VẤN ĐỀ
Tài liệu bồi dưỡng HSG Tin rất ít không như một số bộ môn khác. Do đó,
tất cả các chuyên đề chuyên sâu của nhóm Tin được hoàn thành trong những
năm vừa qua đều là những tài liệu quí giá không những cho học sinh và giáo
viên trong trường Chuyên mà còn cần thiết cho giáo viên các trường THPT
khác.
Để giải một bài toán tin thành công ngoài việc bạn phải tìm ra một thuật
giải tốt, bạn cần thiết phải trả lời được câu hỏi: “Thuật giải đó tác động lên dữ
liệu gì? Nó được tổ chức như thế nào?”. Nếu dữ liệu không phù hợp thì thuật
toán dù tốt cũng khó đáp ứng được yêu cầu đặt ra của bài toán. Việc lựa chọn
cấu trúc dữ liệu phù hợp với thuật giải tốt chính là nghệ thuật lập trình.
Việc chọn vấn đề này không phải là ngẫu nhiên hay vì dễ viết mà vì tôi
thấy mình còn hiểu quá ít về nó – đó có khi là thử thách. Ngay trong quá trình
dạy đội tuyển HSG, tôi và học sinh cũng còn nhiều lúng túng về cấu trúc dữ liệu
trừu tượng vì thế kết quả đạt được chưa cao.
Từ những khó khăn gặp phải tôi đã tìm tài liệu, đọc tài liệu, thực hành cài
đặt và tôi chỉ mạnh dạn viết nên những điều mình đã hiểu và làm được.
Rất mong được sự góp ý của các đồng nghiệp để tài liệu được cải tiến tốt
hơn trong các lần cải biên sau!
IV. MỤC ĐÍCH NGHIÊN CỨU, ĐÓNG GÓP MỚI CỦA CHUYÊN ĐỀ
Xuất phát từ yêu cầu giảng dạy nâng cao cho đội tuyển HSG Tin dự thi
chọn HSG QG và giảng dạy cho các lớp chuyên Tin đòi hỏi cần thiết có thêm
một số chuyên đề chuyên sâu mới, một trong những chuyên đề cần thiết đó là:
“Một số vấn đề về kiểu dữ liệu trừu tượng”.
Chuyên đề giúp học sinh và giáo viên hiểu được:
1. Các khái niệm về kiểu dữ liệu, kiểu dữ liệu trừu tượng, cấu trúc dữ liệu.
2. Tập các giá trị và tập các phép toán có thể thực hiện trên một số kiểu dữ liệu
trừu tượng.
3. Các bài tập ứng dụng từ cơ bản đến nâng cao.
V. PHƯƠNG PHÁP NGHIÊN CỨU
Để hoàn thành nhiệm vụ của chuyên đề, tôi đã sử dụng các phương pháp
nghiên cứu như: Đối sánh, Phân tích, Thực nghiệm, Tổng hợp.
VI. CẤU TRÚC CỦA CHUYÊN ĐỀ
1. Ngoài phần mở đầu chuyên đề được chia thành bốn chương như sau:
- Chương 1: Cơ sở lý thuyết
- Chương 2: Danh sách tuyến tính
- Chương 3: Cây nhị phân
- Chương 4: Bài tập áp dụng
2. Với mỗi cấu trúc dữ liệu trừu tượng tôi đều triển khai theo cấu trúc 3
phần như sau:
- Phần 1: Hệ thống các khái niệm liên quan.
- Phần 2: Biểu diễn kiểu dữ liệu và tập các phép toán áp dụng kiểu dữ liệu.
- Phần 3: Cài đặt các phép toán bằng một ngôn ngữ lập trình cụ thể
VII. KẾ HOẠCH VIẾT CHUYÊN ĐỀ
Năm học 2010-2011: Hoàn thành Chương 1 và Chương 2
Năm học 2011-2012: Hoàn thành Chương 3 và Chương 4
Phần thứ hai
NỘI DUNG CHUYÊN ĐỀ
Chương I
CƠ SỞ LÝ THUYẾT
I.1. Các khái niệm
I.1.1. Kiểu dữ liệu (Data types)
Kiểu dữ liệu (data type) được đặc trưng bởi:
- Tập các giá trị (a set of values);
- Cách biểu diễn dữ liệu (data representation) được sử dụng chung cho tất
cả các giá trị này;
- Tập các phép toán (set of operations) có thể thực hiện trên tất cả các giá
trị;
Các kiểu dữ liệu dựng sẵn (Built-in data types)
Trong các ngôn ngữ lập trình thường có một số kiểu dữ liệu nguyên thuỷ đã
được xây dựng sẵn. Ví dụ:
- Kiểu số nguyên (Integer numeric types). Chẳng hạn, byte, shortint,
integer, word, longint, int64.
- Kiểu số thực dấu phẩy động (floating point numeric types). Chẳng hạn,
real, single, double, extended, comp.
- Kiểu lôgíc (logical type): Boolean.
- Kiểu kí tự (Character type): Char.
- Kiểu mảng (Array type). Chẳng hạn mảng các phần tử cùng kiểu.
Phép toán đối với một số kiểu dữ liệu nguyên thuỷ
- Đối với kiểu số nguyên: +, -, *, DIV, MOD
- Đối với kiểu số thực: +, -, *, /
- Đối với kiểu kí tự: so sánh
- Đối với kiểu lôgíc: so sánh, AND, OR, NOT, XOR
Nhận thấy rằng: Các ngôn ngữ lập trình khác nhau có thể sử dụng mô tả
kiểu dữ liệu khác nhau. Chẳng hạn, PASCAL và C có những mô tả các kiểu dữ
liệu số khác nhau.
I.1.2. Kiểu dữ liệu trừu tượng (Abstract Data Types)
Kiểu dữ liệu trừu tượng (Abstract Data Type – ADT) bao gồm:
- Tập các giá trị (set of values) và
- Tập các phép toán (set of operations) có thể thực hiện với tất cả các giá trị
này.
Cách biểu diễn dữ liệu trong định nghĩa của kiểu dữ liệu (Data Type) đã bị
bỏ qua trong định nghĩa ADT. Cách biểu diễn dữ liệu (data representation) được
sử dụng chung cho tất cả các giá trị này. Việc làm này có ý nghĩa làm trừu tượng
hoá khái niệm kiểu dữ liệu. ADT không còn phụ thuộc vào cài đặt, không phụ
thuộc ngôn ngữ lập trình.
Ví dụ:
ADT
Đối tượng (Object)
Phép toán (Operations)
Danh sách (List)
các nút
chèn, xoá, tìm, …
Đồ thị (Graphs)
đỉnh, cạnh
duyệt, đường đi, …
Integer
+,-,*, v.v…
-∞…,-1,0,1,…+∞
Real
+,-,*, v.v…
-∞,…,+∞
Ngăn xếp (Stack)
các phần tử
pop, push, isEmpty,…
Hàng đợi (Queue)
các phần tử
pop, push, isEmpty,…
Cây nhị phân
các nút
traversal, find,…
Điều dễ hiểu là các kiểu dữ liệu nguyên thuỷ mà các ngôn ngữ lập trình cài
đặt sẵn cũng được coi là thuộc vào kiểu dữ liệu trừu tượng. Trên thực tế chúng
là cài đặt của kiểu dữ liệu trừu tượng trên ngôn ngữ lập trình cụ thể.
Định nghĩa:(Xem [3] Trang 47) Ta gọi việc cài đặt (implementation) một
ADT là việc diễn tả bởi các câu lệnh của một ngôn ngữ lập trình để mô tả các
biến trong ADT và các thủ tục trong ngôn ngữ lập trình để thực hiện các phép
toán của ADT, hoặc trong các ngôn ngữ hướng đối tượng, là các lớp (class) bao
gồm cả dữ liệu (data) và các phương thức xử lý (methods).
I.1.3. Cấu trúc dữ liệu
Có thể nói những thuật ngữ: kiểu dữ liệu, kiểu dữ liệu trừu tượng và cấu
trúc dữ liệu (Data Types, Abstract Data Types, Data Structures) nghe rất giống
nhau, nhưng thực ra chúng có ý nghĩa khác nhau.
Trong ngôn ngữ lập trình, kiểu dữ liệu của biến là tập các giá trị mà biến
này có thể nhận. Ví dụ, biến kiểu boolean chỉ có thể nhận giá trị true hoặc false
(đúng hoặc sai). Các kiểu dữ liệu cơ bản có thể thay đổi từ ngôn ngữ lập trình
(NNLT) này sang NNLT khác. Ta có thể tạo những kiểu dữ liệu phức hợp từ
những kiểu dữ liệu cơ bản. Cách tạo cũng phụ thuộc vào ngôn ngữ lập trình.
Kiểu dữ liệu trừu tượng là mô hình toán học cùng với những phép toán xác
định trên mô hình này. Nó không phụ thuộc vào ngôn ngữ lập trình.
Để biểu diễn mô hình toán học trong ADT ta sử dụng cấu trúc dữ liệu.
Cấu trúc dữ liệu (Data Structures) là một họ các biến, có thể có kiểu dữ
liệu khác nhau, được liên kết lại theo một cách thức nào đó.
Việc cài đặt ADT đòi hỏi lựa chọn cấu trúc dữ liệu để biểu diễn ADT. Ta
sẽ xét xem việc làm đó được tiến hành như thế nào?
Ô (cell) là đơn vị cơ sở cấu thành cấu trúc dữ liệu. Có thể hình dung ô như
là cái hộp đựng giá trị phát sinh từ một kiểu dữ liệu cơ bản hay phức hợp.
Cấu trúc dữ liệu được tạo nhờ đặt tên cho một nhóm các ô và đặt giá trị cho
một số ô để mô tả sự liên kết giữa các ô. Ta xét một số cách tạo nhóm.
Một trong những cách tạo nhóm đơn giản nhất trong các ngôn ngữ lập trình
đó là mảng (array). Mảng là một dãy các ô có cùng kiểu xác định nào đó.
Ví dụ: Khai báo trong Pascal sau đây:
Var name: array[1..10] of integer;
Khai báo biến name gồm 10 phần tử kiểu integer.
Có thể truy xuất đến phần tử của mảng nhờ chỉ ra tên mảng cùng với chỉ số
của nó.
Một phương pháp chung nữa hay dùng để nhóm các ô là cấu trúc bản ghi
(record structure).
Bản ghi (record) là ô được tạo bởi một họ các ô (gọi là các trường) có thể
có kiểu rất khác nhau. Các bản ghi lại thường được nhóm lại thành mảng; kiểu
được xác định bởi việc nhóm các trường của bản ghi trở thành kiểu của phần tử
của mảng.
Phương pháp thứ ba để nhóm các ô là file. File, cũng giống như mảng một
chiều, là một dãy các giá trị cùng kiểu nào đó.
Khi lựa chọn cấu trúc dữ liệu cài đặt ADT một vấn đề cần được quan tâm
là thời gian thực hiện các phép toán đối với ADT sẽ như thế nào. Bởi vì, các
cách cài đặt khác nhau có thể dẫn đến thời gian thực hiện phép toán khác nhau.
Con trỏ (Pointer)
Một trong những ưu thế của phương pháp nhóm các ô trong các ngôn ngữ
lập trình là ta có thể biểu diễn mối quan hệ giữa các ô nhờ sử dụng con trỏ.
Định nghĩa. Con trỏ (pointer) là ô mà giá trị của nó chỉ ra một ô khác.
Khi vẽ các cấu trúc dữ liệu, để thể hiện ô A là con trỏ trỏ đến ô B, ta sẽ sử
dụng mũi tên hướng từ A đến B.
B
A
Hình 1
Phân loại các cấu trúc dữ liệu:
Trong nhiều tài liệu về CTDL thường sử dụng phân loại cấu trúc dữ liệu
sau đây:
• Cấu trúc dữ liệu cơ sở (Base data structures). Ví dụ, trong Pascal: integer,
char, real, boolean,…; trong C: int, char, float, double,…
• Cấu trúc dữ liệu tuyến tính (Linear data structure). Ví dụ, Mảng (Array),
Danh sách liên kết (Linked list), Ngăn xếp (Stack), Hàng đợi (Queue),…
• Cấu trúc dữ liệu phi tuyến (Non linear data structures). Ví dụ, Cây
(Trees), Đồ thị (Graphs), bảng băm (hash table),…
I.2. Sơ lược giới thiệu về con trỏ và biến động
I.2.1. Biến tĩnh (Static variable)
Biến tĩnh là biến được khai báo ngay trong mục var của chương trình.
Chúng được xác định rõ ràng khi khai báo và sau đó được dùng thông qua tên
của nó.
Thời gian tồn tại của các biến tĩnh cũng là thời gian tồn tại của khối chương
trình có chứa khai báo các biến này.
Ví dụ:
- Các biến tĩnh được khai báo trong chương trình chính sẽ tồn tại trong suốt
thời gian chương trình đó chạy.
- Các biến tĩnh được khai báo trong chương trình con sẽ tồn tại mỗi khi
chương trình con đó được gọi.
I.2.2. Biến động (Dynamic variable)
I.2.2.1. Khái niệm
Biến động là biến không được khai báo trước trong chương trình. Khi nào
cần dùng ta mới yêu cầu máy cấp phát bộ nhớ cho biến động đó. Khi nào không
cần dùng có thể xoá biến động đi để giải phonga bộ nhớ.
Vùng bộ nhớ lưu trữ các biến động là HEAP (kích thước rất lớn)
Biến động không có tên, nó được truy nhập thông qua biến con trỏ (pointer
variable).
Ví dụ: Để truy nhập vào biến động do con trỏ p trỏ tới ta viết là p^
P^
Nguyễn Linh Chi
9.5
P
Hình 2
I.2.2.2. Cấp phát vùng nhớ cho biến động
Để cấp phát vùng nhớ cho các biến động do con trỏ p trỏ tới, ta dùng thủ
tục NEW như sau: NEW(p);
Khi đó máy sẽ tạo ra một vùng nhớ có kiểu và kích thước do p qui định,
hướng con trỏ p trỏ tới byte đầu tiên của vùng biến động trên.
Ta chỉ được dùng biến động p^ khi đã có lệnh New(p);
I.2.2.3. Giải phóng hay thu hồi ô nhớ của biến động
Khi một biến động không được dùng tới nữa ở trong chương trình, ta có thể
thu hồi lại ô nhớ nó chiếm để dùng vào việc khác bằng thủ tục DISPOSE.
Để giải phóng ô nhớ của biến động p^, ta dùng lệnh: DISPOSE(p);
I.2.3. Khai báo kiểu con trỏ (Xem [4], Trang 122)
Kiểu con trỏ là một kiểu dữ liệu đặc biệt dùng để biểu diễn địa chỉ của các
đối tượng (biến, mảng, bản ghi, …), có bao nhiêu đối tượng thì có bấy nhiêu
kiểu con trỏ. Kiểu con trỏ nguyên dùng để biểu thị địa chỉ của biến nguyên, kiểu
con trỏ bản ghi dùng để biểu diễn địa chỉ của bản ghi…
Cách khai báo một kiểu con trỏ:
TYPE
Typepointer = ^Kiểu_đối_tượng;
Ví dụ:
Type
IntPtr = ^integer;
hsPtr = ^hs;
hs
= record
Ho_ten: string[27];
Dtb
: real;
End;
I.2.4. Khai báo biến con trỏ (Pointer)
Biến con trỏ được khai báo thông qua các kiểu con trỏ đã được định nghĩa
trong phần TYPE hoặc có thể khai báo trực tiếp.
Cách khai báo:
VAR
Namepointer1: Typepointer;
Namepointer2: ^Kiểu_đối_tượng;
I.2.5. Các thao tác đối với biến con trỏ
I.2.5.1. Phép gán (:=)
Ta có thể gán hai con trỏ cùng kiểu cho nhau.
Ví dụ:
Var
p,q: ^integer;
Ta có thể thực hiện phép gán: p:= q; (ý nghĩa: con trỏ q trỏ đến vùng nhớ
nào thì con trỏ p trỏ đến vùng nhớ đó);
I.2.5.2. So sánh hai biến con trỏ cùng kiểu
Chỉ có hai phép so sánh là bằng (=) và khác () với hai biến con trỏ cùng
kiểu.
Chú ý: Các giá trị của biến con trỏ không thể đọc vào từ bàn phím hay in
trực tiếp trên màn hình, máy in được, tức là không thể dùng với các thủ tục
Read/Write.
I.2.5.3. Hằng con trỏ NIL
NIL là một giá trị hằng đặc biệt dành cho các biến con trỏ để báo con trỏ
không trỏ vào đâu cả. Ta có thể gán NIL cho bất kỳ biến con trỏ nào. Chẳng hạn
khi gán p:=NIL thì p không trỏ đến dữ liệu nào cả.
I.2.6. Con trỏ kiểu mảng và mảng các con trỏ
Dùng để cấp phát động các mảng.
Ví dụ minh hoạ cách nhập, in biến a là biến con trỏ kiểu mảng, biến b là
mảng các con trỏ. Đối với mảng các bản ghi cùng làm tương tự.
Const maxn = 100;
Type mang = array[1..maxn] of integer;
Var
a: ^mang; {con trỏ kiểu mảng}
b: array[1..maxn] of ^integer; {mảng các con trỏ}
i,n: integer;
Begin
write(‘Vào n =’); readln(n);
new(a);
for i:=1 to n do
begin
write(‘a[‘,i, ‘]=’);
readln(a^[i]);
end;
for i:=1 to n do
begin
new(b[i]);
write(‘b[‘,i, ‘]=’);
readln(b[i]^);
end;
for i:=1 to n do write(a^[i], ‘ ’); writeln;
for i:=1 to n do write(b[i]^, ‘ ’);
End.
Chương II
DANH SÁCH TUYẾN TÍNH
II.1. Khái niệm và các phép toán cơ bản
II.1.1. Khái niệm
Danh sách tuyến tính (Linear List) là dãy gồm 0 hoặc nhiều hơn các phần
tử cùng kiểu cho trước: (a1, a2, …, an), n > 0.
• ai là phần tử của danh sách.
• a1 là phần tử đầu tiên và an là phần tử cuối cùng.
• n là độ dài của danh sách.
Khi n = 0, ta có danh sách rỗng (empty list). Các phần tử được sắp thứ tự
tuyến tính theo vị trí của chúng trong danh sách. Ta nói a i đi trước ai+1, ai+1 đi sau
ai và ai ở vị trí i.
Ví dụ:
• Danh sách các sinh viên được sắp thứ tự theo tên.
• Danh sách điểm thi sắp xếp theo thứ tự giảm dần.
Đưa vào ký hiệu:
L
: danh sách các đối tượng có kiểu element_type
x
: một đối tượng kiểu này
p
: kiểu vị trí
END(L)
: hàm trả lại vị trí đi sau vị trí cuối cùng trong danh sách L
II.1.2. Các phép toán cơ bản
Dưới đây ta kể ra một số phép toán đối với danh sách tuyến tính
1. Insert(x,p,L)
• Chèn x vào vị trí p trong danh sách L
• Nếu p = END(L), chèn x vào cuối danh sách
Nếu L không có vị trí p, kết quả là không xác định
2. Locate(x,L)
• Trả lại vị trí của x trong L
• Trả lại END(L) nếu x không xuất hiện
3. Retrieve(p,L)
•Trả lại phần tử ở vị trí p trong L
•Không xác định nếu p không tồn tại hoặc p=END(L)
4. Delete(p,L)
• Xoá phần tử ở vị trí p trong L. Nếu L là a 1, a2, …, an, thì L sẽ là a1, a2, …,
ap-1, ap+1,…, an.
• Kết quả là không xác định nếu L không có vị trí p hoặc p=END(L)
5. Next(p,L)
• Trả lại vị trí đi ngay sau vị trí p
• Nếu p là vị trí cuối cùng trong L thì Next(p,L)=END(L)
• Kết quả không xác định nếu p là END(L) hoặc p không tồn tại.
6. Prev(p,L)
• Trả lại vị trí trước vị trí p
• Kết quả không xác định nếu p là 1 hoặc p không tồn tại.
7. Makenull(L)
• Hàm này biến L trở thành danh sách rỗng và trả lại vị trí END(L).
8. First(L)
• Trả lại vị trí đầu tiên trong L. Nếu L là rỗng hàm này trả lại END(L).
9. Printlist(L)
• In ra danh sách các phần tử của L theo thứ tự xuất hiện.
II.2. Các cách cài đặt danh sách tuyến tính
Có các cách cài đặt danh sách tuyến tính cơ bản sau đây:
• Dùng mảng (Array-based): Cất giữ các phần tử của danh sách vào các ô
liên tiếp của mảng.
• Danh sách liên kết (Linked list / Pointer-based): Các phần tử của danh
sách có thể cất giữ ở các chỗ tuỳ ý trong bộ nhớ. Mỗi phần tử có con trỏ (hoặc
móc nối-link) đến phần tử tiếp theo.
• Địa chỉ không trực tiếp (Indirect addressing): Các phần tử của danh sách
có thể cất giữ ở các chỗ tuỳ ý trong bộ nhớ. Tạo bảng trong đó phần tử thứ i của
bảng cho biết nơi lưu trữ phần tử thứ i của danh sách.
II.2.1. Biểu diễn dưới dạng mảng
(Array-based Representation of Linear List).
Ta cất giữ các phần tử của danh sách tuyến tính vào các ô liên tiếp của
mảng (array).
Danh sách sẽ là cấu trúc gồm hai thành phần:
• Thành phần 1: là mảng các phần tử
• Thành phần 2: last – cho biết vị trí của phần tử cuối cùng trong danh sách.
Vị trí có kiểu nguyên (integer chẳng hạn) và chạy trong khoảng từ 0 đến
maxlength-1. Hàm END(L) trả lại giá trị last-1.
Danh sách có thể được mô tả bằng hình vẽ sau:
Phần tử đầu
tiên
list
Phần tử thứ hai
Phần tử cuối
cùng
last
rỗng
maxlength
Hình 3
Cấu trúc dữ liệu mô tả danh sách dưới dạng mảng:
Const maxlength = 1000; {giá trị thích hợp}
Type
Elementtype = enteger; {kiểu của phần tử là nguyên}
LIST = Record
elements: array[1..maxlength] of Elementtype;
last: integer;
end;
position = integer; {vị trí có kiểu nguyên}
var L: LIST;
Cài đặt các phép toán cơ bản bằng ngôn ngữ lập trình Pascal:
Hàm END(L)
function END(var L: LIST): position;
begin
exit(L.last+1)
end;
Insert(x,p,L): chèn x vào vị trí p trong danh sách L
procedure INSERT(x: elementtype; p: position; var L: LIST);
var q: position;
begin
if L.last >= maxlength then write(‘Error: List is full’)
else
if (p>L.last+1) or (pL.last) or (p Q.rear);
end;
Function IsFull(var Q: TQueue): Boolean;
begin
IsFull:= (Q.rear = max);
end;
Function Get(var Q: TQueue): TElement;
begin
if IsEmpty(Q) then write('Queue rong!')
else Get:= Q.items[Q.front];
end;
Procedure Push(x: TElement; var Q: TQueue);
begin
if IsFull(Q) then write('Queue day!')
else
with Q do
begin
rear:= rear + 1;
items[rear]:= x;
end;
end;
Function Pop(var Q: TQueue): TElement;
begin
if IsEmpty(Q) then write('Queue rong!')
else
with Q do
begin
Pop:= items[front];
front:= front + 1;
end;
end;
procedure Test;
const r = 100;
var i,x,k,n: integer;
a: array[1..max] of TElement;
begin
Init(Queue);
randomize;
n:= 10;
For i:=1 to n do
begin
x:= random(r);
Push(x,Queue);
end;
writeln('Cac phan tu thuoc hang doi');
with Queue do
for i:=front to rear do write(items[i], ' ');
readln;
k:= 0;
While not IsEmpty(Queue) do
begin
x:= Pop(Queue);
if x mod 2 = 0 then
begin
k:= k+1;
a[k]:= x;
end;
end;
writeln;
writeln;
for i:= 1 to k do write(a[i],' '); readln;
end;
BEGIN
Test;
END.
II.4.3. Tổ chức hàng đợi bằng danh sách vòng (Xem [1], Trang 21)
Xét việc biểu diễn ngăn xếp và hàng đợi bằng mảng, giả sử mảng có tối đa
10 phần tử, ta thấy rằng nếu thực hiện 6 lần thao tác Push, rồi 4 lần thao tác Pop,
rồi tiếp tục 8 lần thao tác Push nữa thì không có vấn đề gì xảy ra với Stack
nhưng sẽ gặp thông báo lỗi tràn mảng đối với Queue.
Lý do dẫn đến lỗi trên là do chỉ số cuối hàng đợi rear luôn tăng lên và
không bao giờ giảm đi cả. Đó chính là nhược điểm mà ta nói tới khi cài đặt: Chỉ
có các phần tử từ vị trí front tới vị trí rear là thuộc hàng đợi, các phần tử từ vị trí
1 tới front-1 là vô nghĩa.
Để khắc phục điều này, ta có thể biểu diễn hàng đợi bằng một danh sách
vòng (dùng mảng hoặc danh sách nối vòng đơn): coi như các phần tử của hàng
đợi được xếp quanh vòng tròn theo một chiều nào đó (chẳng hạn chiều kim đồng
hồ). Các phần tử nằm trên phần cung tròn từ vị trí front tới vị trí rear là các phần
tử của hàng đợi. Có thêm một biến n lưu số phần tử trong hàng đợi. Việc đẩy
thêm một phần tử vào hàng đợi tương đương với việc ta dịch chỉ số rear theo
chiều vòng một vị trí rồi đặt giá trị mới vào đó. Việc lấy ra một phần tử trong
hàng đợi tương đương với việc lấy ra phần tử tại vị trí front rồi dịch chỉ số front
theo chiều vòng.
Hình vẽ sau mô tả cách dùng danh sách vòng để mô tả hàng đợi:
Hình 9
Để tiện cho việc dịch chỉ số theo vòng, khi cài đặt danh sách vòng bằng
mảng, người ta thường dùng cách đánh chỉ số từ 0 để tiện sử dụng phép lấy dư
(modulus – mod).
Const max = 1000; {Dung lượng cực đại}
Type
Telement = integer; {Kiểu giá trị một phần tử}
TQueue = record
Items: array[0..max-1] of TElement;
Front: integer;
Rear: integer;
N
: integer;
End;
Var Queue: TQueue;
Các thao tác cơ bản trên hàng đợi được cài đặt bằng ngôn ngữ lập trình
Free Pascal như sau:
program Caidathangdoibangdsvong;
uses crt;
const
max = 1000;
type
TElement = integer;
TQueue = record
items: array[0..max-1] of Telement;
front: integer;
rear: integer;
n
: integer;
end;
var
Queue: TQueue;
Procedure init(var Q: TQueue);
begin
Q.front:= 1;
Q.rear:= max-1;
Q.n:=0;
end;
Function IsEmpty(var Q: TQueue): Boolean;
begin
IsEmpty:= (Q.n = 0);
end;
Function IsFull(var Q: TQueue): Boolean;
begin
IsFull:= (Q.n = max);
end;
Function Get(var Q: TQueue): TElement;
begin
if IsEmpty(Q) then write('Queue rong!')
else Get:= Q.items[Q.front];
end;
Procedure Push(x: TElement; var Q: TQueue);
begin
if IsFull(Q) then write('Queue day!')
else
with Q do
begin
rear:= (rear + 1) mod max;
items[rear]:= x;
inc(n);
end;
end;
Function Pop(var Q: TQueue): TElement;
begin
if IsEmpty(Q) then write('Queue rong!')
else
with Q do
begin
Pop:= items[front];
front:= (front + 1) mod max;
end;
end;
procedure Test;
const r = 100;
var i,x,nn,k: integer;
a: array[1..max] of TElement;
begin
Init(Queue);
randomize;
nn:= 10;
For i:=1 to nn do
begin
x:= random(r);
Push(x,Queue);
end;
writeln('Cac phan tu thuoc hang doi');
with Queue do
for i:=front to rear do write(items[i], ' ');
readln;
k:= 0;
While not IsEmpty(Queue) do
begin
x:= Pop(Queue);
if x mod 2 = 0 then
begin
k:= k+1;
a[k]:= x;
end;
end;
writeln;
writeln;
for i:= 1 to k do write(a[i],' '); writeln;
end;
BEGIN
clrscr;
Test;
END.
II.4.4. Tổ chức hàng đợi bằng danh sách nối đơn kiểu FIFO
Tương tự như cài đặt ngăn xếp bằng biến động và con trỏ trong một danh
sách nối đơn, ta cũng không viết hàm IsFull để kiểm tra hàng đợi đầy.
type
TElement = integer;
PElem = ^Elem;
Elem = record
val: TElement;
next: PElem;
end;
var
front, rear: PElem; {Con trỏ tới phần tử đầu và cuối hàng
đợi}
procedure Init;
begin
front:= Nil;
end;
Function IsEmpty: boolean;
begin
IsEmpty:= (front=Nil);
end;
Function Get: TElement;
begin
If IsEmpty then write('Stack rong')
else Get:= (front^.val);
end;
Procedure Push(x: Telement);
var p: PElem;
begin
New(p);
p^.val:= x;
p^.next:= nil;
if front = nil then front:= p
else rear^.next:= p;
rear:= p;
end;
Function Pop: TElement;
var p: PElem;
begin
If IsEmpty(top) then write('Stack rong')
else
begin
Pop:= front^.val;
p: front^.next;
dispose(front);
front := p;
end;
end;
Chương III
CÂY NHỊ PHÂN
III.1. Định nghĩa, tính chất và phân loại
III.1.1. Định nghĩa (Xem [3], trang 82)
Cây nhị phân là cây mà mỗi nút có nhiều nhất là hai con.
Vì mỗi nút chỉ có không quá hai con, nên ta sẽ gọi chúng là con trái và con
phải (left and right child). Như vậy mỗi nút của cây nhị phân hoặc là không có
con, hoặc chỉ có con trái, hoặc chỉ có con phải, hoặc có cả con trái và con phải.
Left child
Right child
Hình 10
Vì ta phân biệt con trái và con phải nên khái niệm cây nhị phân không
trùng với cây có thứ tự (Xem 3 trang 109). Vì thế, chúng ta sẽ không so sánh cây
nhị phân với cây tổng quát.
III.1.2. Tính chất của cây nhị phân
Bổ đề 1:
(i) Số đỉnh lớn nhất ở trên mức 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 có 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à [log2(n+1)].
Chứng minh:
(i) Bằng qui nạp theo i:
Cơ 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 qui nạp: Giả sử với mọi nút j, 1 < j < i-1, số đỉnh lớn nhất trên mức
j là 2j-1. Do số đỉnh trên mức i-1 là 2 i-2, mặt khác theo định nghĩa mỗi đỉnh trên
cây nhị phân có không quá 2 con, ta suy ra số lượng nút lớn nhất trên mức i là
không vượt quá 2 lần số lượng nút trên mức i-1, nghĩa là không vượt quá 2*2 i1
=2i nút.
(ii) Số lượng nút lớn nhất của cây nhị phân chiều cao k là không vượt quá
tổng số lượng nút lớn nhất trên các mức i = 1, 2, …, k, theo (i) của bổ đề 1, số
này không vượt quá 1+2+4+ …+2k-1+2k=2k – 1.
(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ó:
n = 2k – 1, suy ra 2k = n+1, hay k = [log2(n+1)].
III.1.3. Phân loại
Cây nhị phân đầy đủ (full binary tree): là cây nhị phân thỏa
mãn:
. mỗi nút lá đều có cùng độ sâu và
. các nút trong có đúng 2 con
Ví dụ: Cây nhị phân đầy đủ được cho trong hình vẽ sau:
Hình 11
Bổ đề 2: Cây nhị phân đầy đủ với độ sâu n có 2n – 1 nút.
Chứng minh: Suy trực tiếp từ bổ đề 1.
Cây nhị phân hoàn chỉnh (Complete Binary Tree): là cây nhị phân độ
sâu n thỏa mãn:
. 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à lệch sang trái nhất có thể được.
Bổ đề 3: Cây nhị phân hoàn chỉnh độ sâu n có số lượng nút nằm trong
khoảng từ 2n-1 đến 2n – 1.
Chứng minh: Suy trực tiếp từ định nghĩa và bổ đề 1.
Ví dụ: Cây nhị phân hoàn chỉnh được cho trong hình vẽ sau:
Hình 12
Cây nhị phân cân đối (balanced binary tree): là cây nhị phân mà chiều
cao của cây con trái và chiều cao của cây con phải chênh lệch nhau không quá 1
đơn vị.
Ví dụ: Cây nhị phân cân đối được cho trong hình vẽ sau:
Hình 13
Nhận xét:
. Nếu cây nhị phân là đầy đủ thì nó là hoàn chỉnh.
. Nếu cây nhị phân là hoàn chỉnh thì nó là cân đối.
III.2. Biểu diễn cây nhị phân
Ta xét hai phương pháp:
. Biểu diễn sử dụng mảng
. Biểu diễn sử dụng con trỏ
III.2.1. Biểu diễn cây dùng mảng
Giả sử T là cây với các nút đặt tên là 1, 2, …, n. Cách đơn giản để biểu diễn
T là hỗ trợ thao tác parent(i, T) (trả lại nút cha của nút i trong cây T) bởi danh
sách tuyến tính A, trong đó mỗi phần tử A[i] chứa con trỏ đến cha của nút i.
Riêng gốc của T có thể phân biệt bởi con trỏ rỗng.
Khi dùng mảng, ta đặt a[i] = j nếu j là nút cha của nút i, và A[i] = 0 nếu nút
i là gốc.
Type
Tree = Array[1..maxn] of
longint;
Var
A : Tree;
Ví dụ: Cây trong hình 14 được biểu diễn bởi mảng A
1
3
2
4
7
6
5
8
9
Hình 14
A:
cs:
0
1
1
2
1
3
2
4
2
5
3
6
4
7
4
8
6
9
Nhận xét: Trong trường hợp cây nhị phân hoàn chỉnh, sử dụng cách biểu
diễn này ta có thể cài đặt nhiều phép toán với cây hiệu quả.
Xét cây nhị phân hoàn chỉnh T có n nút, trong đó mỗi nút chứa một giá trị.
Gán nhãn cho các nút của cây hoàn chỉnh T từ trên xuống dưới và từ trái qua
phải bằng các số 1, 2, …, n. Đặt tương ứng cây T với mảng A, trong đó phần tử
thứ i của A là giá trị cất giữ trong nút thứ i của cây T, i=1, 2, …,n.
Ví dụ: Cây nhị phân hoàn chỉnh sau được biểu diễn bằng mảng A.
1
H
2
3
D
K
4
5
B
9
A
A:
cs:
D
2
L
10
E
R
C
H
1
J
F
8
7
6
K
3
B
4
Hình 15
F
J
5
6
L
7
A
8
C
9
E
10
Một số thao tác trên cây được biểu diễn bởi mảng A như sau:
Để tìm
Sử dụng
Hạn chế
Con trái của A[i]
A[2*i]
2*i n
III.2.2. Biểu diễn cây nhị phân dùng con trỏ
Mỗi nút của cây, ngoài trường Val để lưu giá trị của nút đó còn có con trỏ
đến con trái và con trỏ đến con phải:
Pointer to left child
Pointer to right child
Val
Hình 16
Kiểu dữ liệu được mô tả như sau:
Type
PNode = ^Node;
Node = record
Val: integer;
Left,Right: PNode;
end;
Var root : Pnode;
III.3. Phép duyệt cây nhị phân (Xem [6], trang 74)
Phép xử lý các nút trên cây mà ta gọi chung là phép thăm (Visit) các nút
một cách hệ thống sao cho mỗi nút chỉ được thăm một lần gọi là phép duyệt cây.
Giả sử rằng nếu như một nút không có nút con trái (hoặc nút con phải) thì
liên kết Left (Right) của nút đó được liên kết thẳng tới một nút đặc biệt mà ta
gọi là NIL (hay NULL), nếu cây rỗng thì nút gốc của cây đó cũng được gán
bằng NIL. Khi đó có ba cách duyệt cây hay được sử dụng:
III.3.1. Duyệt theo thứ tự trước (preorder traversal)
Trong phép duyệt theo thứ tự trước thì giá trị trong mỗi nút bất kì sẽ được
liệt kê trước giá trị lưu trong hai nút con của nó, có thể mô tả bằng thủ tục đệ qui
sau:
Procedure NLR(N : Pnode);
{Duyệt nhánh cây nhận N là gốc của nhánh đó}
Begin
NLR(N^.Left);
NLR(N^.Right);
End;
Quá trình duyệt theo thứ tự trước bắt đầu bằng lời gọi NLR(root), với root
là nút gốc của cây.
Hình 17
Nếu ta duyệt cây trong hình 17 theo thứ tự trước thì các giá trị sẽ lần lượt
được liệt kê theo thứ tự: H D B A C F E K J L.
III.3.2. Duyệt theo thứ tự giữa (inorder traversal)
Trong phép duyệt theo thứ tự giữa thì giá trị trong mỗi nút bất kì sẽ được
liệt kê sau giá trị lưu ở nút con trái và được liệt kê trước giá trị lưu ở nút con
phải của nút đó, có thể mô tả bằng thủ tục đệ qui sau:
Procedure LNR(N : Pnode);
{Duyệt nhánh cây nhận N là gốc của nhánh đó}
Begin
LNR(N^.Left);
LNR(N^.Right);
End;
Quá trình duyệt theo thứ tự giữa bắt đầu bằng lời gọi LNR(root), với root là
nút gốc của cây.
Nếu ta duyệt cây trong hình 17 theo thứ tự giữa thì các giá trị sẽ lần lượt
được liệt kê theo thứ tự: A B C D E F H J K L.
III.3.3. Duyệt theo thứ tự sau (postorder traversal)
Trong phép duyệt theo thứ tự sau thì giá trị trong mỗi nút bất kì sẽ được liệt
kê sau giá trị lưu ở hai nút con của nút đó, có thể mô tả bằng thủ tục đệ qui sau:
Procedure LRN(N : Pnode);
{Duyệt nhánh cây nhận N là gốc của nhánh đó}
Begin
LRN(N^.Left);
LRN(N^.Right);
End;
Quá trình duyệt theo thứ tự sau bắt đầu bằng lời gọi LRN(root), với root là
nút gốc của cây.
Nếu ta duyệt cây trong hình 17 theo thứ tự sau thì các giá trị sẽ lần lượt
được liệt kê theo thứ tự: A C B E F D J L K H.
Dưới đây là chương trình cài đặt một số thao tác cơ bản trên cây nhị phân:
Program Thaotactrencay;
uses crt;
const nl = #13#10; bl = #32;
type
PNode = ^Node;
Node = record
Val: integer;
L,R: PNode;
end;
(*---------------------------------------
Đếm số phần tử chia hết cho k
(*--------------------------------------function Count(t: PNode; k: integer): integer;
var d: integer;
begin
if t = nil then d:= 0 else
begin
if t^.Val mod k = 0 then d:= 1 else d:= 0;
d:= d + Count(t^.L,k) + Count(t^.R,k);
end;
Count:= d;
end;
(*--------------------------------------Tạo một nút mới có giá trị v và 2 con trỏ là lp và rp
--------------------------------------- *)
function NewElem(v: integer; lp,rp: PNode): PNode;
var e: PNode;
begin
new(e); e^.Val:= v; e^.L:= lp; e^.R:= rp;
NewElem:= e;
end;
(*---------------------------------------Tìm vị trí xuất hiện của trị v trên cây T
1: v có xuất hiện tại node u,
0: v ko xuất hiện
---------------------------------------*)
function Find(t: PNode; v: integer; var u: PNode): integer;
begin
u:= nil;
while t nil do
begin
u:= t;
if v =a[i] nếu right-left>=a[i] thì cập nhật a[i] với Max. Chú ý: Nếu
a[i]=a[i]).
+ a[left[i-1]]a[i].
Như vậy ta dễ dàng thấy nếu right[i]-left[i]+1>=a[i] thì ta sẽ cắt được tấm
biển có cạnh là a[i] và chứa tấm ván i.
Bây giờ ta tính lần lượt left[i] và right[i] bằng Stack.
Ta có nhận xét: Nếu a[i-1]=a[i]
thì left[i] luôn nhỏ hơn hoặc bằng left[i-1] vì trong đoạn left[i-1]..i-1 các phần tử
luôn lớn hơn hoặc bằng a[i-1] mà a[i-1]>=a[i] nên
các phần tử trong đoạn
left[i-1]..i luôn lớn hơn hoặc bằng a[i].
Tương tự ta có nhận xét trên đối với right.
Ta dùng Stack lưu lại left[i], left[left[i]], và i để thu hẹp phạm vi tìm kiếm
left[i], thay vì phải duyệt từ i-1 đến khi nào gặp phần tử nhỏ hơn, thì với những
phần tử có chiều cao lớn hơn hoặc bằng a[i] thì nếu a[i+1]0) and (a[Stack[Top]]>=a[i]) do
begin
Pop(x);
{Neu a[x]>=a[i] thi a[left[x]..x]>=a[i]}
y:=left[x];
end;
left[i]:=y;
Push(i);
end;
end;
{Tim right[i]}
Init;
for i:=N downto 1 do
begin
if Top=0 then
begin
Push(i);
right[i]:=i;
end
else
begin
y:=i;
While (Top>0) and (a[Stack[Top]]>=a[i]) do
begin
Pop(x);
{Neu a[x]>=a[i] thi a[x..right[x]]>=a[i]}
y:=right[x];
end;
right[i]:=y;
Push(i);
end;
end;
kq:=0;
for i:=1 to n do
if (right[i]-left[i]+1>=a[i]) and (a[i]>kq) then kq:=a[i];
end;
Procedure xuat;
begin
assign(fo,tfo);
rewrite(fo);
write(fo,kq);
close(fo);
end;
Begin
Nhap;
Solution;
Xuat;
End.
Bài 5: Giải mã văn tự MAYA (IOI 2006)
Các bạn có thể Test bài này trên VNOI.INFO với mã bài là PBCWRI
Giải mã văn tự MayA là một nhiệm vụ phức tạp hơn so với các nghiên cứu
trước đây. Trên thực tế, sau gần 200 năm người ta chưa làm sáng tỏ gì nhiều lắm
trong lĩnh vực này. Chỉ trong phạm vi ba thập niên cuối này mới có những tiến
bộ đáng kể trong nghiên cứu.
Văn tự MayA đặt cơ sở dựa vào các hình vẽ nhỏ, được biết dưới dạng các
nét vạch biểu diễn âm tiết. Từ trong tiếng May A thường được viết dưới dạng ô
vuông chứa một một số các nét vạch. Đôi khi một từ bị bổ dọc thành nhiều ô
hoặc một ô lại chứa nhiều nét vạch hơn số nét cần thiết cho một từ.
Một trong số các vấn đề liên quan tới giải mã văn tự May A nảy sinh khi
xác định trình tự đọc âm tiết. Khi điền các vạch vào ô vuông, đôi khi người May
A lại quy trình trình tự đọc dựa trên các tiêu chuẩn thẩm mỹ riêng chứ không
theo một quy luật chung. Điều này dẫn đến việc, ngay cả khi đã biết rõ âm tiết
của nhiều nét vạch, các nhà khảo cổ học cũng không dám khẳng định chắc chắn
cách phát âm cả từ.
Các nhà khảo cổ đang khảo sát một từ W cụ thể. Họ biết những nét gạch
tạo thành từ đó, nhưng không biết hết các cách vẽ chúng. Biết bạn đến tham dự
IOI-06, họ đề nghị bạn giúp đỡ. Các nhà khảo cổ sẽ chọ bạn biết g nét gạch tạo
thành từ W và dãy S các nét vạch (theo trình tự xuất hiện) của câu đang khảo
sát. Hãy xác định các khả năng xuất hiện từ W trong câu được khảo sát.
Yêu cầu: Cho các nét vạch tạo thành từ W và dãy S các nét vạch trong bản
văn tự chạm trổ. Hãy lập trình xác định số khả năng xuất hiện từ W trong S. Vì
mọi trình tự xuất hiện các nét vạch trong W đều là chấp nhận được, các nhà
khảo cổ yêu cầu bạn tìm số lượng dãy các nét vạch liên tiếp trong S, mỗi dãy
tương ứng với hoán vị g nét vạch trong W.
Giới hạn:
1 ≤ g ≤ 3000
số lượng vạch trong W
g ≤ |S| ≤ 3 000 000 Số nét vạch trong dãy S
Dữ liệu vào: Đọc từ file văn bản writing.inp
WRITING.INP
Ý nghĩa
4 11
Dòng 1: Chứa 2 số nguyên g và |S| viết rời nhau.
cAda
Dòng 2: Chứa g nét vạch liên tiếp nhau tạo thành W.
AbrAcadAbRa
Mỗi nét vạch tươn ứng với một ký tự. Các ký tự hợp lệ
là ‘a’-‘z’ và ‘A’-‘Z’; ký tự hoa và thường là khác
nhau.
Dòng 3: Chứa |S| ký tự liên tiếp biểu diễn S.
Valid characters are ‘a’-‘z’ and ‘A’-‘Z’; Các ký tự hợp
lệ là ‘a’-‘z’ và ‘A’-‘Z’; ký tự hoa và thường là khác
nhau..
Kết quả: Ghi kết quả ra file văn writing.out
WRITING.OUT
Ý nghĩa
2
Dòng 1: Phải chứa số khả năng xuất hiện W trong S.
Chấm điểm: Có một bộ phận Tests được đánh giá tổng cộng 50 điểm, mỗi
test thoả mãn ràng buộc g ≤ 10.
Chú ý:Các kí tự xét trong hang đá xuất hiện W phải liên tiếp nhau.
Hướng dẫn:
Bài này thực chất không khó, có khá nhiều cách giải và cách đơn giản nhất
là xét tất cả các từ có độ dài g trong S rồi sắp xếp từ đó và so sánh với từ W
(cũng đã sắp xếp), vì các từ trong khoảng ‘a’..’z’ và ‘A’..’Z’ ta có thể dùng thuật
toán đếm phân phối tuy nhiên thuật toán trên chỉ giải quyết được 75% bài toán.
Sau đây tôi xin trình bày cách dùng Queue để giải quyết triệt để bài toán
trên: Queue lưu chỉ số các kí tự trong hang đá.
Gọi dd[i] là số kí tự có mã ASCII là i trong từ W. d[i] là số kí tự có mã
ASCII là i trong Queue.
Giả sử xét đến kí tự thứ i trong từ S, tất nhiên đã xét hết các kí tự từ 1 đến
i-1 ta có: Gọi k=ord[S[i]] (Mã ASCII kí tự S[i])
Nếu d[k]+1>dd[k] (số kí tự S[i] trong Queue cộng thêm S[i] nhiều hơn
trong từ W) thì ta phải lấy ra trong Queue (tất nhiên là lấy ở front) đến khi nào
d[k]+1=dd[k] thì thôi.Và nếu dd[k]>0(Có kí tự S[i] trong W) thì đẩy i vào
Queue.
Nếu số kí tự trong Queue đúng bằng g (Queue[rear]-Queue[front]+1=g
tăng kq lên 1 (1))thì có nghĩa trong hang đá các kí tự thứ Queue[front] đến
Queue[rear] tạo thành từ W.
Chứng minh:
Nếu S[i] không xuất hiên trong W thì cũng không được đưa vào Queue và
khi đó vì S[i] không xuất hiện trong W nên dd[k]=0 và d[k]=0(k=ord[S[i]]) nên
d[k]+1 luôn lớn hơn dd[k] ta lấy đến khi nào Queue rỗng thì thôi.
Nếu S[i] xuất hiện trong W mà d[k]+1dd[k] có nghĩa là trong Queue có
số kí tự S[i] lớn hơn dd[k](nói thế thôi chứ trong Queue chỉ chứa tối đa dd[k] kí
tự S[i] thôi nha) thì ta lấy bớt phần tử ra khỏi Queue để thỏa mãn d[k]=dd[k], ta
thấy các phần tử được lấy ra ở front thỏa mãn các kí tự trong Queue là các kí tự
liên tiếp trong S.
Vậy ta thấy trong Queue luôn chứa các kí tự có trong W và số lượng các kí
tự này luôn dd[k]-1) and (dem>0) do Pop(x);
if dd[k]>0 then Push(i);
if Queue[rear]-Queue[front]+1=m then inc(kq);
end;
end;
Procedure xuat;
begin
assign(fo,tfo);
rewrite(fo);
write(fo,kq);
close(fo);
end;
Begin
Init;
Nhap;
Solution;
Xuat;
End.
Độ phức tạp: O(n)
Bài 6: Chiến trường Ô qua (Mã bài: KAGAIN VNOI.INFO)
Lại nói về Lục Vân Tiên, sau khi vượt qua vòng loại để trở thành Tráng Sỹ,
anh đã gặp được Đôrêmon và được chú mèo máy cho đi quá giang về thế kỷ 19.
Trở lại quê hương sau nhiều năm xa cách, với tấm bằng Tráng Sỹ hạng 1 do
Liên Đoàn Type Thuật cấp, anh đã được Đức Vua cử làm đại tướng thống lãnh 3
quân chống lại giặc Ô Qua xâm lăng. Đoàn quân của anh sẽ gồm N đại đội, đại
đội i có A[i] ( > 0 ) người. Quân sỹ trong 1 đại đội sẽ đứng thành 1 cột từ người
1 -> người A[i], như vậy binh sỹ sẽ đứng thành N cột. Vì Vân Tiên quyết 1 trận
sẽ đánh bại quân Ô Qua nên đã cử ra 1 quân đoàn hùng mạnh nhất. Trong sử cũ
chép rằng, quân đoàn của Vân Tiên cử ra lúc đó là một nhóm các đại đội có chỉ
số liên tiếp nhau ( tức là đại đội i, i + 1, … j ). Vì sử sách thì mối mọt hết cả nên
chỉ biết được mỗi thế. Ngoài ra theo giang hồ đồn đại thì sức mạnh của 1 quân
đoàn = số người của đại đội ít người nhất * số đại đội được chọn. Nhiệm vụ của
bạn là dựa trên các thông số của các nhà khảo cổ có được, hãy cho biết quân
đoàn mà Vân Tiên đã chọn ra là từ đại đội nào đến đại đội nào. Chú ý nếu có
nhiều phương án thì ghi ra phương án mà chỉ số của đại đội đầu tiên được chọn
là nhỏ nhất.
Input
Dòng 1: Số T là số bộ test.
T nhóm dòng tiếp theo, mỗi nhóm dòng mô tả 1 bộ test. Nhóm dòng thứ i:
Dòng 1: N ( [...]... sách nối đơn là một kiểu dữ liệu trừu tượng Để cài đặt kiểu dữ liệu trừu tượng này, chúng ta có thể dùng mảng các nút (trường next chứa chỉ số của nút kế tiếp) hoặc biến cấp phát động (trường next chứa con trỏ tới nút kế tiếp) Cấu trúc dữ liệu mô tả danh sách dưới dạng danh sách móc nối: Cách 1: Dùng mảng các nút Const max = 1000; {Số phần tử cực đại} Elementtype = integer; {Kiểu dữ liệu của phần tử}... (Backtracking) • Khử đệ qui • … Các ứng dụng khác (Indirect applications): • Cấu trúc dữ liệu hỗ trợ cho các thuật toán • Thành phần của các cấu trúc dữ liệu khác II.4 Hàng đợi (Queue) II.4.1 Kiểu dữ liệu trừu tượng hàng đợi Hàng đợi (Queue) là một kiểu danh sách mà việc bổ sung một phần tử được thực hiện ở cuối danh sách và việc loại bỏ một phần tử được thực hiện ở đầu danh sách Khi cài đặt hàng đợi, có hai vị... nối kép (Doubly linked list) Khi nào dùng danh sách móc nối: • Khi không biết kích thước của dữ liệu – hãy dùng con trỏ và bộ nhớ động (Unknown data size – use pointer & dynamic storage) • Khi không biết kiểu dữ liệu – hãy dùng con trỏ void (Unknown data type – use void pointers) • Khi không biết số lượng dữ liệu – hãy dùng danh sách móc nối (Unknown number of data – linked structure) II.2.2.2 Danh... thưởng của Công ty X Các khách hàng được xếp thành một vòng tròn đánh số 1,2,…,n Giám đốc Công ty lựa chọn ngẫu nhiên một số m (m < n) Bắt đầu từ một người được chọn ngẫu nhiên trong số khách hàng, Giám đốc đếm theo chiều kim đồng hồ và dừng lại mỗi khi đếm đến m Khách hàng ở vị trí này sẽ dời khỏi cuộc chơi Quá trình được lặp lại cho đến khi chỉ còn một người Người cuối cùng còn lại là người trúng thưởng!... nhất Dưới đây là một số nhận xét về hai cách cài đặt: • Cài đặt mảng phải khai báo kích thước tối đa Nếu ta không lường trước được giá trị này thì nên dùng cài đặt con trỏ • Có một số thao tác có thể thực hiện nhanh trong cách này nhưng lại chậm trong cách cài đặt kia: Insert và Delete đòi hỏi thời gian hằng số trong cài đặt con trỏ nhưng trong cài đặt mảng đòi hỏi thời gian O(n) với n là số phần tử của... gian hằng số trong cài đặt mảng, nhưng thời gian đó là O(n) trong cài đặt con trỏ • Cách cài đặt mảng đòi hỏi dành không gian nhớ định trước không phụ thuộc vào số phần tử thực tế của danh sách Trong khi đó cài đặt con trỏ chỉ đòi hỏi bộ nhớ cho các phần tử đang có trong danh sách Tuy nhiên cách cài đặt con trỏ lại đòi hỏi thêm bộ nhớ cho con trỏ II.3 Ngăn xếp (Stack) II.3.1 Kiểu dữ liệu trừu tượng ngăn... (không có nút kế tiếp), trường liên kết này được gán một giá trị đặc biệt, chẳng hạn con trỏ nil Như vậy việc quản lý một danh sách móc nối dù có ít hay có rất nhiều phần tử chỉ thông qua một con trỏ header duy nhất Hình ảnh danh sách móc nối với việc quản lý từ một đầu giống như một cậu bé đang cầm đầu dây của một chiếc diều đang bay trên bầu trời hay một cô gái cầm dải lụa rất dài múa dẻo trên sân khấu... đợi được xếp quanh vòng tròn theo một chiều nào đó (chẳng hạn chiều kim đồng hồ) Các phần tử nằm trên phần cung tròn từ vị trí front tới vị trí rear là các phần tử của hàng đợi Có thêm một biến n lưu số phần tử trong hàng đợi Việc đẩy thêm một phần tử vào hàng đợi tương đương với việc ta dịch chỉ số rear theo chiều vòng một vị trí rồi đặt giá trị mới vào đó Việc lấy ra một phần tử trong hàng đợi tương... động Type Elementtype = integer; {Kiểu dữ liệu của phần tử} PElem = ^Elem; Elem = Record val : Elementtype; next: ^PElem; end; var header: PElem; Danh sách nối đơn gồm các nút được nối với nhau theo một chiều Mỗi nút là một bản ghi (record) gồm hai trường: • Trường val chứa giá trị lưu trong nút đó • Trường next chứa liên kết (con trỏ) tới nút kế tiếp, tức là chứa một thông tin đủ để biết nút kế tiếp... gian nhớ dùng cho các biến động không còn đủ để thêm một phần tử mới Tuy nhiên, việc kiểm tra điều này phụ thuộc vào máy tính, chương trình dịch và ngôn ngữ lập trình Mặt khác, không gian bộ nhớ dùng cho các biến động thường rất lớn nên ta sẽ không viết mã cho hàm IsFull: kiểm tra ngăn xếp tràn Các khai báo dữ liệu: Type Elementtype = integer; {Kiểu dữ liệu của phần tử} PElem = ^Elem; Elem = Record val ... là: Một số vấn đề kiểu liệu trừu tượng Chuyên đề giúp học sinh giáo viên hiểu được: Các khái niệm kiểu liệu, kiểu liệu trừu tượng, cấu trúc liệu Tập giá trị tập phép toán thực số kiểu liệu trừu. .. ngữ lập trình khác sử dụng mô tả kiểu liệu khác Chẳng hạn, PASCAL C có mô tả kiểu liệu số khác I.1.2 Kiểu liệu trừu tượng (Abstract Data Types) Kiểu liệu trừu tượng (Abstract Data Type – ADT)... học 2010-2011 2011-2012, chọn chuyên đề nghiên cứu là: Một số vấn đề kiểu liệu trừu tượng để bước đầu đọc hiểu phần nho nhỏ kiểu liệu trừu tượng Đây nội dung khó thấy cần thiết cho công việc giảng