MỘT số vấn đề về KIỂU dữ LIỆU TRỪU TƯỢNG

76 411 0
MỘT số vấn đề về KIỂU dữ LIỆU TRỪU TƯỢNG

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

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

Ngày đăng: 14/10/2015, 14:03

Từ khóa liên quan

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan