Tài liệu cấu trúc dữ liệu cơ bản

91 290 0
Tài liệu cấu trúc dữ liệu cơ bản

Đ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

CHƯƠNG 1 : CÁC KHÁI NIỆM CƠ BẢN I. TỪ BÀI TOÁN ĐẾN CHƯƠNG TRÌNH 1. Giải thuật 2. Ngôn ngữ già và tinh chế từng bước 3. Tóm tắt ba giai đoạn để giải một bài toán II. CÁC KIỂU DỮ LIỆU TRỪU TƯỢNG 1. Đ ịnh nghĩa 2. Ví dụ III. CÁC KIỂU DỮ LIỆU - CÁC CẤU TRÚC DỮ LIỆU - CÁC KIỂU DỮ LIỆU TRỪU TƯỢNG IV. THỜI GIAN CHẠY MỘT CHƯƠNG TRÌNH 1. Đ o một thời gian chạy của một chương trình 2. Đ ộ phức tạp của giải thuật 3. Cách tính thời gian chạy của chương trình I. TỪ BÀI TOÁN ÐẾN CHƯƠNG TRÌNH 1. Giải thuật TOP Giải thuật la ìmột chuỗi các chỉ thị, mỗi chỉ thị có ý nghĩa rõ ràng để có thể giải quyết bài toán trong một khoảng thời gian hữu hạn. Nói cách khác, giải thuật là một cách để giải bài toán nào đó, nhưng nó phải áp dụng được cho mọi bài toán cùng loại. Nếu cách giải chỉ đúng cho một vài trường hợp đặc biệt thì nó không phải là giải thuật. Có nhiều cách để thể hiện giải thuật như dùng lời, dùng lưu đồ, có một cách diễn đạt giải thuật được dùng rất phổ biến đó là dùng ngôn ngữ giả. Ngôn ngữ giả là sự kết hợp của ngôn ngữ tự nhiên và các cấu trúc của một ngôn ngữ lập trình nào đó. Ví dụ: Cho một ngã 5 như hình vẽ Giải: Chúng ta có thể mô hình hóa bài toán nói trên theo mô hình toán học đã biết đó là đồ thị (Graph). Ðồ thị là tập hợp các điểm gọi là các đỉnh của đồ thị và một tập hợp các cạnh nối một số các cặp đỉnh với nhau. 2 đỉnh có ít nhất 1 cạnh nối được gọi là 2 đỉnh kề nhau. Tại ngả 5 này có 13 lối đi (AB, AC, AD, BA, BC, BD, DA, DB, DC, EA, EB, EC, ED). Ta sẽ biểu diễn mỗi lối đi là một đỉnh của đồ thị và 2 lối đi không thể cùng đi đồng thời ta nối chúng bằng 1 cạnh. Tóm lại, ta có: • Ðỉnh là các tuyến đường đi cho phép. • Cạnh nối 2 đỉnh dùng để chỉ tuyến đường không thể lưu thông đồng thời. Ta có đồ thị hình 2 như sau : Bài toán của chúng ta rõ ràng là bài toán tô màu cho đồ thị hình 2. Bài toán tô màu cho đồ thị được phát biểu như sau: "Tô màu cho các đỉnh của đồ thị sao cho số màu được dùng là ít nhất và 2 đỉnh kề nhau (có cung nối) không được tô cùng 1 màu. Tuy nhiên, bài toán tô màu cho đồ thị không có giải thuật tốt. Nói cách khác, giải thuật của bài toán tô màu là: "Thử tất cả các khả năng". Thực tế cách giải này khó có thể áp dụng đưọc vì vậy ta cần suy nghĩ cách khác để giải quyết vấn đề. Nếu bài toán nhỏ ta có thể vét cạn các khả năng để tìm ra lời giải tối ưu. Một cách giải quyết dùng cho bài toán chúng ta là: "Cố gắng tô màu cho đồ thị bằng ít màu nhất" một cách nhanh chóng. Một cách giải quyết như vậy gọi là một Heuricstic. Một Heuricstic hợp lý cho bài toán tô màu đồ thị được gọi là giải thuật Greedy. Chọn 1 đỉnh chưa tô màu và tô màu cho nó. Với các đỉnh còn lại mà không có cạnh chung với đỉnh đang xét thì tô các đỉnh đó cùng 1 màu với đỉnh đang xét. Duyệt danh sách các đỉnh chưa tô màu, lấy 1 đỉnh trong số chúng và tô bằng màu mới rồi quay lại bước 1. Ðây là giải thuật hiển nhiên và luôn đạt kết quả tốt (mặc dù có thể không là lời giải tối ưu). 1 2 3 4 5 Têm Xanh Xanh Âoí Âoí Xanh Xanh Xanh Âoí Âoí Ví dụ : Xét đồ thị dưới đây và cách tô màu cho nó Dùng Greedy: Tối ưu: + Xanh : 1, 2 + Xanh: 1, 3, 4 + Ðỏ : 3, 4 + Ðỏ : 2, 5 + Tím : 5 Áp dụng giải thuật Greedy cho bài toán của đồ thị hình 2, ta được kết quả: + Xanh : AB, AC, AD, BA, DC, ED + Ðỏ : BC, BD, EA + Tím : DA, DB + Vàng : EB, EC Nhận xét: Một đồ thị có k đỉnh mà mỗi cặp đỉnh bất kỳ đều được nối với nhau thì phải dùng k màu để tô. Một đồ thị trong đó có k đỉnh mà mỗi cặp đỉnh bất kỳ trong k đỉnh này đều được nối với nhau thì không thể dùng ít hơn k màu để tô cho đồ thị. Ðồ thị hình 2 có 4 đỉnh AC, DA, EB, BD mà mỗi cặp đỉnh bất kỳ trong số chúng đều được nối với nhau. Vậy đồ thị hình 2 không thể tô với ít hơn 4 màu. Ðều này khẳng định lời giải trên là lời giải tối ưu. 2. Ngôn ngữ giả và tinh chế từng bước TOP Một khi ta đã có mô hình thích hợp cho bài toán ta cần hình thức hóa một giải thuật trong thuật ngữ của mô hình đó. Mở đầu ta viết những mệnh đề tổng quát rồi tinh chế dần thành những mệnh đề cụ thể hơn và cứ tiếp tục như thế. Ðến cuối cùng ta được những chỉ thị thích hợp trong một ngôn ngữ lập trình. Ví dụ: Xét giải thuật Greedy áp dụng cho bài toán tô màu Giả sử đồ thị đang xét là G. Giải thuật Greedy xác định một tập hợp NewClr các đỉnh của G được tô cùng một màu. Giải thuật Greedy được lập cho đến khi tất cả các đỉnh của G đều được tô màu. Procedure Greedy ( Var G : Graph ; Var NewClr : Set ); Begin {1} NewClr : = 0 ; {2} For mỗi đỉnh của V chưa tô màu Do {3} If V không được nối với đỉnh nào trong NewClr Then Begin {4} Ðánh dấu V đã được tô màu ; {5} Thêm V vào tập NewClr ; End ; End ; Chú ý : Trong giải thuật bằng ngôn ngữ giả chúng ta đã dùng một số từ khóa của Pascal (các từ được in đậm). Graph và Set là các kiểu dữ liệu trừu tượng, chúng sẽ được xác định bằng phép định nghĩa kiểu của Pascal. Câu lệnh If {3} có thể được tinh chế một bước nữa như sau: Procedure Greedy ( Var G : Graph ; Var NewClr : Set ); Begin {1} NewClr : = 0 ; {2} For mỗi đỉnh của V chưa tô màu Do Begin {3.1} Found : = False ; {3.2} For mỗi đỉnh của W trong NewClr Do {3.3} If có cạnh nối giữa V với W Then {3.4} Found : = True ; {3.5} If Found = False Then Begin {4} Ðánh dấu V đã được tô màu ; {5} Thêm V vào tập NewClr ; End; End; End ; Ta có thể coi Graph và Set như các tập hợp. Có nhiều cách để biểu diễn 1 tập hợp trong ngôn ngữ lập trình. Ðể đơn giản ta xem tập hợp như một danh sách (List) các số nguyên là chỉ số của các đỉnh và kết thúc bằng 1 giá trị đặc biệt Null. Giải thuật Greedy được tinh chế 1 bước nữa như sau: Procedure Greedy ( Var G:Graph ; Var NewClr: Set ); Var Found : Boolean ; V, W : Integer ; Begin NewClr : = 0 ; V : = Ðỉnh đầu tiên trong G ; While V < > Null Do Begin Found : = False ; W : = Ðỉnh đầu tiên trong NewClr ; While W < > Null Do Begin If có cạnh nối giữa V với W Then Found : = True ; W : = Ðỉnh kế tiếp trong NewClr ; End; If Found = False Then Begin Ðánh dấu V đã được tô màu ; Thêm V vào tập NewClr ; End; V : = Ðỉnh kế trong G ; End; End ; { Greedy} 3. Tóm tắt ba giai đoạn để giải 1 bài toán TOP Giai đoạn 1: Xây dựng mô hình toán thích hợp cho bài toán và tìm một giải thuật giải quyết bài toán trên mô hình đó. Giai đoạn 2: Giải thuật được trình bày bằng ngôn ngữ giả dựa trên các kiểu dữ liệu trừu tượng. Giai đoạn 3: Chọn một cách cài đặt một kiểu dữ liệu trừu tượng và thay ngôn ngữ giả bằng các mã lệnh của 1 ngôn ngữ lập trình . Kết quả là ta được 1 chương trình hoàn chỉnh có thể giải quyết được vấn đề đặt ra. II. CÁC KIỂU DỮ LIỆU TRỪU TƯỢNG (Abstract Data Type) 1. Ðịnh nghĩa TOP Một kiểu dữ liệu trừu tượng (ADT) là một mô hình toán học cùng với một tập hợp các phép toán tác động trên mô hình đó. Thông thường ta sẽ thiết kế giải thuật theo thuật ngữ của kiểu dữ liệu trừu tượng, nhưng để cài đặt được giải thuật trong một ngôn ngữ lập trình, ta phải tìm cách biểu diễn kiểu dữ liệu trừu tượng qua các kiểu dữ liệu của ngôn ngữ. 2. Ví dụ TOP Kiểu Set có thể được biểu diễn qua danh sách (List) và được quản lý bằng mảng (Array) hay con trỏ (Pointer). Kiểu Graph là một danh sách các số nguyên và các phép toán tác động trên nó là: MakeNull (G): Tạo đồ thị rỗng. W= First (G): Hàm trả về phần tử đầu danh sách của G ( = Null nếu G rỗng). W= Next (G): Hàm trả về phần tử kế tiếp trong G ( = Null nếu là phần tử cuối). Insert (V ,G): Xen phần tử V vào đồ thị G. III. CÁC KIỂU DỮ LIỆU (DATA TYPE) - CÁC CẤU TRÚC DỮ LIỆU (DATA STRUCTURE) - CÁC KIỂU DỮ LIỆU TRỪU TƯỢNG (ABSTRACT DATA TYPE - ADT) TOP Trong một ngôn ngữ lập trình, kiểu dữ liệu của một biến là tập hợp các giá trị mà biến đó có thể nhận được. Mỗi một ngôn ngữ lập trình đều có một số kiểu dữ liệu cơ sở khác nhau. Một kiểu dữ liệu được thành lập bằng sự tập hợp các kiểu dữ liệu khác nhau gọi là kiểu dữ liệu hợp thành hay kiểu dữ liệu có cấu trúc hay cấu trúc dữ liệu. Ví dụ: A: Array [1 N] of Record Field1: < Type1>; Field2: < Type2>; FieldN: < TypeN>; End; IV. THỜI GIAN CHẠY CỦA MỘT CHƯƠNG TRÌNH TOP Trong khi giải bài toán ta có thể có một vài giải thuật, vấn đề là chọn giải thuật nào, tiêu chuẩn như thế nào, thường có 2 yêu cầu chính: Dễ hiểu, dễ viết chương trình. Giải thuật dùng các tài nguyên hiệu quả như dùng ít bộ nhớ, chạy càng nhanh càng tốt, 1. Ðo thời gian chạy của một chương trình TOP Thời gian chạy của một chương trình phụ thuộc vào các yếu tố: Dữ liệu đầu vào. Tốc độ của máy được dùng. Tính chất của trình biên dich được dùng. Ðộ phức tạp tính toán của giải thuật. Trong thực tế thời gian chạy của một chương trình có thể xem như là một hàm của dữ liệu vào. Cụ thể, đó là kích thước của dữ liệu vào. Vì vậy, ta gọi T(n) là thời gian chạy chương trình với dữ liệu vào có độ dài là n. Ðối với nhiều chương trình thì thời gian chạy chương trình còn phụ thuộc vào đặc tính của dữ liệu vào. Nghĩa là dữ liệu vào có cùng độ dài nhưng thời gian chạy chương trình là khác nhau. Vì vậy, ta có thể xem T(n) là thời gian chạy chương trình trong trường hợp xấu nhất trên dữ liệu vào có độ dài là n. Hay nói cách khác T(n) là thời gian chạy chương trình với mọi dữ liệu vào có độ dài là n. Ðo thời gian chạy chương trình như thế nào ? 2. Ðộ phức tạp của giải thuật TOP 3. Cách tính thời gian chạy chương trình TOP Cách tính thời gian chạy của một chương trình bất kỳ là một vấn đề không đơn giản. Nhưng đối với nhiều chương trình ta có thể tính thời gian chạy của nó theo 2 quy tắc sau: ( áp dụng được cho tất cả các chương trình không đệ quy). Chú ý : Thời gian chạy của các lện gán, Read, Write là O(1). Thời gian chạy của một chuỗi tuần tự các lệnh được xác định theo quy tắc cộng. Như vậy thời gian này là thời gian thi hành một lệnh nào đó lâu nhất trong chuỗi lệnh (câu lệnh đó được gọi là Câu lệnh tích cực). Thời gian chạy của cấu trúc If là thời gian lớn nhất để thực hiện các lệnh sau Then hoặc sau Else cộng với thời gian kiểm tra điều kiện. Thường thời gian kiểm tra điều kiện là O(1). Thời gian thực hiện vòng lặp là tổng (trên tất cả các lần lặp) thời gian thực hiện từng vòng lặp. Nói cách khác thời gian này được tính theo quy tắc nhân tức là tích của số lần lặp với thời gian thực hiện của thân vòng lặp. Thời gian gọi thủ tục hay hàm là thời gian thực hiện đoạn chương trình tương ứng với hàm hay thủ tục đó. Ví dụ 1: Câu lệnh: for i: = 1 to n do for j: = 1 to n do x:= x + 1; Ví dụ 2: Procedure BubbleSort ( Var a: array [ 1 n ] of Integer); Var i, j, Temp : Integer; Begin for i: = 1 to n do for j: = 1 to n do If a[i] > a[j] then Begin Temp:= a[i]; a[i]: = a[j]; (1) a[j]:= Temp; End; End; Trong đó 5 dạng hàm đầu tiên gọi là hàm đa thức, còn 3 dạng hàm cuối gọi là hàm mũ. Một giải thuật mà thời gian thực hiện có dạng là một hàm đa thức thì có thể chấp nhận được. BÀI TẬP CUỐI CHƯƠNG I Bài 1 : Viết các thủ tục thực hiện các phép toán cộng và nhân 2 ma trận, rồi tính thời gian chạy của các thủ tục này. Bài 2 : Hãy viết chương trình tính gần đúngĠ theo công thức : Sau đó hãy tính thời gian chạy của chương trình. Bài 3 : Viết chương trình nhập vào một ma trận vuông cấp n. Sau đó in ra các phần tử thuộc các đường chéo song song với đường chéo chính. Phác họa giải thuật của chương trình và tính độ phức tạp của giải thuật. Bài 4 : Hãy tìm một giải thuật để tínhĠ sao cho độ phức tạp của giải thuật sẽ là O(n) và viết chương trình thể hiện giải thuật đã đề ra bằng ngôn ngữ Pascal. Bài 5 : Hãy viết chương trình đổi một số nguyên dương ở hệ thập phân (cơ số 10) sang hệ nhị phân (cơ số 2) và đánh giá thời gian thực hiện của giải thuật. CHƯƠNG 2 : CÁC KIỂU DỮ LIỆU TRỪU TƯỢNG CƠ BẢN (BASIC ABSTRACT DATA TYPE) I. KHÁI NIỆM VỀ CON TRỎ 1. Đ ịnh nghĩa 2. Khai báo 3. Một số hàm thường sử dụng ở biến con trỏ II. DANH SÁCH 1. Khái niệm 2. Các phép toán trên danh sách 3. Cài đặt danh sách III. CẤU TRÚC CHỒNG/NGĂN XẾP (STACK) 1. Đ ịnh nghĩa 2. Các phép toán trên Stack 3. Cài đặt Stack bằng mảng 4. Cài đặt stack bằng con trỏ IV. CẤU TRÚC HÀNG ĐỢI 1. Đ ịnh nghĩa 2. Các phép toán cơ bản trên hàng 3. Cài đặt hàng V. DANH SÁCH LIÊN KẾT KÉP 1. Nhận xét 2. Khai báo 3. Thủ tục khởi tạo danh sách liên kết kép rổng 4. Hàm kiểm tra danh sách liên kết kép rổng 5. Thủ tục xen một phần tử vào danh sách liên kết kép 6. Thủ tục xóa một phần tử khỏi danh sách liên kết kép I. KHÁI NIỆM VỀ CON TRỎ (Pointer) 1. Ðịnh nghĩa TOP Con trỏ là một kiểu dữ liệu (dành 4 bytes trong bộ nhớ) mà các giá trị của nó là địa chỉ của các phần tử của kiểu dữ liệu mà nó trỏ đến. Các giá trị của biến thuộc kiểu T được phát sinh bất cứ lúc nào khi biến đó được cấp phát bằng lệnh New. 2. Khai báo TOP Gián tiếp : Type Tên kiểu = ^ Kiểu dữ liệu; Var Tên biến : Tên kiểu; Khai báo trực tiếp : Var Tên biến : ^ Kiểu dữ liệu; Ví dụ 1: Type Ref = ^ Nhansu; Nhansu = Record Data : DataType; Next : Ref; End; Var p: ^Nhansu; Ví dụ 2: Var N : ^ Integer; {N là con trỏ chỉ đến số nguyên} Ta xét một khai báo có dạng : Type P = ^T; • P là biến kiểu con trỏ. • T là kiểu dữ liệu mà con trỏ trỏ tới. • P^ : Cho kết quả là giá trị kiểu T mà nó đang trỏ tới. • @ Chỉ địa chỉ của giá trị mà nó đang trỏ tới. Vậy muốn truy xuất nội dung tại vị trí mà p đang trỏ tới ta dùng P^ Ngoài ra, còn có một con trỏ tổng quát, nó không chỉ đến một kiểu dữ liệu xác định nào cả. Var p : Pointer; Kiểu dữ liệu con trỏ chiếm 4 bytes trong bộ nhớ, 4 bytes này sẽ chứa địa chỉ của kiểu dữ liệu mà nó trỏ tới còn bản thân biến mà nó trỏ tới thì chưa có trong bộ nhớ. Ví dụ : Var p : ^ Integer; i : Integer; Begin i : = 5; p : = @ i; {Hàm lấy địa chỉ của i và gán cho p} Writeln ('Nội dung mà p đang trỏ tới :', p^); Writeln ('Giá trị của biến i : ', i); End. 3. Một số hàm thường sử dụng ở biến con trỏ : TOP a. Cấp phát vùng nhớ : New (p) [...]... danh sách liên kết bằng con nháy (trên cơ sở mảng ) : Danh sách không phải là một cấu trúc dữ liệu tiền định trong đa số các ngôn ngữ lập trình Vì vậy, người lập trình phải cài đặt bằng các cấu trúc tiền định khác có sẳn trong ngôn ngữ lập trình Một số ngôn ngữ lập trình không có định nghĩa biến kiểu con trỏ Trong trường hợp này ta sẽ giả con trỏ (dựa trên cơ sở cấu trúc tiền định mảng, được định nghĩa... { Chương trình chính } IV CẤU TRÚC HÀNG ÐỢI 1 Ðịnh nghĩa : TOP Hàng là một danh sách đặc biệt mà phép thêm vào chỉ thực hiện ở một đầu của danh sách gọi là cuối hàng (Rear) Phép loại bỏ lại được thực hiện ở một đầu kia của danh sách gọi là đầu hàng (Front) Như vậy hàng là một cấu trúc dữ liệu có tính chất "Vào trước - ra trước" FIFO : First In First Out 2 Các phép toán cơ bản tên hàng : TOP a Thủ tục... sau : Gọi là danh sách liên kết vòng Hãy cài đặt một danh sách nối kết vòng như vậy CHƯƠNG 3 : I CẤU TRÚC CÂY ĐỊNH NGHĨA VÀ CÁC KHÁI NIỆM CƠ BẢN 1 Định nghĩa 2 Các khái niệm cơ bản 3 Các phép duyệt cây quan trọng 4 Cây có nhản và cây biểu thức 5 II Quy tắc biểu diễn một biểu thức toán học trên cây KIỂU DỮ LIỆU TRỪU TƯỢNG CÂY 1 2 III Các phép toán trên cây Cài đặt cây CÂY NHỊ PHÂN 1 2 Tính chất 3 IV Định... dàng hơn Ta xem mỗi phần tử của danh sách là một ô Ở mỗi ô ta dùng 2 con trỏ ở 2 đầu : Con trỏ Next chỉ đến nút tiếp theo Con trỏ Prevous chỉ đến nút liền trước Ngoài ra còn có một trường dữ liệu dùng để lưu trữ dữ liệu của ô đang xét 2 Khai báo: TOP Type ElementType = ; { Kiểu phần tử trong danh sách } DList= ^ CellType; CellType = Record Elements: ElementType; Next,Previous:DList; End; Var List =... ('Loi') Else If Move (Node[p].Next, Available) then Writeln ('Da xoa') Else Writeln ('Loi') End; III CẤU TRÚC CHỒNG / NGĂN XẾP (STACK) 1 Ðịnh nghĩa : TOP Stack là một danh sách đặc biệt mà phép thêm vào hoặc loại bỏ một phần tử chỉ thực hiện tại một đầu gọi là đỉnh (Top) của Stack Như vậy, Stack là một cấu trúc có tính chất vào trước, ra sau (FILO - First In Last Out) 2 Các phép toán trên Stack TOP a Thủ... TempPtr^.Elements :=x; TempPtr^.Next := Stack; TOP • Stack := TempPtr; End; Hàm cho phần tử đầu Stack : Function Top (Stack : StackType) : ElementType; Begin Top := Stack^.Elements; End; Ví dụ: Dùng cấu trúc Stack để viết chương trình đổi một số nguyên ở dạng thập phân thành dạng nhị phân Program Nhi_phan; Type ElementType = integer; { Kiểu phần tử } PointerType = ^ StackNode; StackNode = Record Elements... IV Định nghĩa Cài đặt cây nhị phân GIẢI THUẬT MÃ HÓA HUFFMAN 1 2 V Đặt vấn đề Giải thuật mã hóa HuffMan CÂY TÌM KIẾM NHỊ PHÂN 1 Định nghĩa 2 Cài đặt cây tìm kiếm nhị phân I ÐỊNH NGHĨA VÀ CÁC KHÁI NIỆM CƠ BẢN 1 Ðịnh nghĩa : TOP Cây (Trees) là một tập hợp hữu hạn các phần tử gọi là nút cây (Node), trong đó có một nút đặc biệt gọi là nút gốc (Root) Trên tập hợp các nút này có một quan hệ phân cấp gọi là . số kiểu dữ liệu cơ sở khác nhau. Một kiểu dữ liệu được thành lập bằng sự tập hợp các kiểu dữ liệu khác nhau gọi là kiểu dữ liệu hợp thành hay kiểu dữ liệu có cấu trúc hay cấu trúc dữ liệu. Ví. III. CÁC KIỂU DỮ LIỆU (DATA TYPE) - CÁC CẤU TRÚC DỮ LIỆU (DATA STRUCTURE) - CÁC KIỂU DỮ LIỆU TRỪU TƯỢNG (ABSTRACT DATA TYPE - ADT) TOP Trong một ngôn ngữ lập trình, kiểu dữ liệu của một biến. II. CÁC KIỂU DỮ LIỆU TRỪU TƯỢNG 1. Đ ịnh nghĩa 2. Ví dụ III. CÁC KIỂU DỮ LIỆU - CÁC CẤU TRÚC DỮ LIỆU - CÁC KIỂU DỮ LIỆU TRỪU TƯỢNG IV. THỜI GIAN CHẠY MỘT CHƯƠNG TRÌNH 1. Đ o một thời gian

Ngày đăng: 09/07/2015, 13:45

Từ khóa liên quan

Mục lục

  • Xanh

  • Xanh

  • Xanh

  • Âoí

  • Âoí

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

Tài liệu liên quan