1. Trang chủ
  2. » Công Nghệ Thông Tin

giáo trình cấu trúc dữ liệu

104 258 1

Đ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

Thông tin cơ bản

Định dạng
Số trang 104
Dung lượng 814,5 KB

Nội dung

GIÁO TRÌNH CẤU TRÚC DỮ LIỆU Giáo trình Cấu trúc dữ liệu LỜI NÓI ĐẦU Cấu trúc dữ liệu và Giải thuật là môn học đóng vai trò quan trọng trong quá trình đào tạo kỹ sư, cử nhân các ngành Khoa học máy tính và Công nghệ thông tin. Cuốn giáo trình này được nghiên cứu và hình thành dựa trên cơ sở mục tiêu đào tạo và đề cương chi tiết của môn học Cấu trúc dữ liệu và Giải thuật do Hội đồng nghiên cứu khoa học, tổ Tin học Khoa Kỹ thuật trường Cao đẳng Kinh tế - Kỹ thuật Hải Dương xây dựng. Cuốn giáo trình này trình bày các vấn đề có bản nhất, thiết yếu nhất về hai khái niệm DỮ LIỆU và GIẢI THUẬT, đó cũng là nền tảng quan trọng cho những ai muốn nghiên cứu trong lĩnh vực tin học. Nội dung cuốn sách gồm 5 chương * Chương 1: MỘT SỐ KHÁI NIỆM: trình bày một số khái niệm về thuật toán: Ký hiệu ô lớn và các phương pháp phân tích, đánh giá truật toán; cấu trúc dữ liệu: Các kiểu dữ liệu trừu tượng cùng các phép toán trên các kiểu dữ liệu đó. Ở đây trình bày hệ kiểu cấu trúc Dữ liệu của ngôn ngữ lập trình Pascal. * Chương 2: GIẢI THUẬT ĐỆ QUY: trình bày về đệ quy, một số thuật toán ứng dụng đệ quy. * Chương 3: CÁC THUẬT TOÁN SẮP XẾP VÀ TÌM KIẾM: trình bày nội dung, giải thuật của một số thuật toán sắp xếp, tìm kiếm cơ bản, hay gặp. So sánh, đánh giá, nhận xét ưu nhược điểm của mỗi loại thuật toán. * Chương 4: DANH SÁCH LIÊN KẾT: trình bày mô hình dữ liệu kiểu Danh sách, các cấu trúc dữ liệu cài đặt danh sách, các phép toán trên danh sách. Trong đó hai cấu trúc đặc biệt STACK và QUEUE được nghiên cứu kỹ . * Chương 5: CÂY: trình bày cấu trúc dữ liệu Cây: Biểu diễn cây cùng với các phép toán cơ bản trên cây, trong đó Cây nhị phân được đặc biệt chú ý. Đọc xong cuốn sách này người đọc được cung cấp đầy đủ các khái niệm, các thuật toán từ cơ bản đến nâng cao, phù hợp cho Giảng viên cũng như HS - SV ngành Công nghệ Tin học nghiên cứu học tập. Toàn bộ các chương, mục đều có ví dụ, hình vẽ minh hoạ cụ thể. Cuối mỗi chương Khoa KỸ THUẬT_ Trường Cao đẳng Kinh tế - Kỹ thuật Hải Dương 2 Giáo trình Cấu trúc dữ liệu đều có phần tóm tắt lại những ý chính, những ví dụ ứng dụng … và bài tập (Lý thuyết và thực hành) để bạn đọc có thể đúc rút được nội dung, củng cố được kiến thức của mỗi chương. Do tính đặc thù của ngôn ngữ lập trình Pascal nên toàn bộ các ví dụ, bài tập trong giáo trình đều được viết bằng ngôn ngữ lập trình này, vì vậy người đọc chỉ cần biết sử dụng ngôn ngữ Pascal ngoài ra không đòi hỏi các kiến thức chuyên môn khác. Chúng tôi đã có nhiều cố gắng trong việc trình bày tinh giản một khối lượng kiến thức đồ sộ, cố gắng đặt vấn đề và giải quyết vấn đề, trình bày các khái niệm thật Logic, tự nhiên, sử dụng ngôn từ trong sáng, các ví dụ sát với thực tế dễ hiểu, dễ áp dụng, các bài tập có tính khả thi cao nhưng cũng không quá khó…để giúp bạn đọc dễ dàng hơn trong nghiên cứu, học tập. Tuy vậy cuốn sách chắc chắn không tránh khỏi những thiếu sót, những vấn đề cần bổ xung, những vấn đề cần lược bỏ, Chúng tôi chân thành mong nhận được ý kiến đóng góp, phê bình của độc giả để cuốn sách này có thể hoàn chỉnh hơn nữa. Thư góp ý xin gửi về địa chỉ: phamhuy_ktkt@yahoo.com Tôi xin chân thành cảm ơn Thầy chuyên gia Trịnh Nhật Tiến (Trưởng khoa Công nghệ thông tin trường ĐH Công nghệ Hà Nội) cùng các thầy giáo trong tổ bộ môn Tin học, khoa Kỹ thuật trường Cao đẳng Kinh tế - Kỹ thuật Hải Dương đã đọc bản thảo và góp nhiều ý kiến về nội dung để tôi có thể hoàn thành cuốn tài liệu này. Hải Dương, tháng 8 năm 2005 Tác giả: PHẠM TRỌNG HUY Khoa KỸ THUẬT_ Trường Cao đẳng Kinh tế - Kỹ thuật Hải Dương 3 Giáo trình Cấu trúc dữ liệu Chương 1: MỘT SỐ KHÁI NIỆM 1.1 Khái niệm thuật toán (Giải thuật) 1.1.1 Khái niệm. Thuật toán là một dãy hữu hạn các quy tắc ( chỉ thị, mệnh lệnh) mô tả chính xác một quá trình tính toán. Theo đó với mỗi bộ dữ liệu vào sẽ cho một kết quả ( Yêu cầu của bài toán ). Các đặc trưng của Thuật toán đơn định: * Tính đơn định: Thực hiện đúng các bước của thuật toán với một dữ liệu vào thì chỉ cho duy nhất một kết quả nghĩa là ở mỗi bước của thuật toán, các thao tác phải hết sức rõ ràng, không gây nên sự nhập nhằng, lộn xộn, đa nghĩa * Tính dừng: Thuật toán phải dừng và cho ra kết quả sau một số hữu hạn các bước. * Tính đúng: Cho ra kết quả phù hợp yêu cầu bài toán với những dữ liệu vào đúng đắn. * Tính phổ dụng: Thuật toán phải giải quyết được một lớp rộng các bài toán. * Tính khả thi: Thuật toán phải được máy tính thực hiện trong khoảng thời gian và điều kiện ( bộ nhớ ) cho phép. Để mô tả một thuật toán có thể sử dụng nhiều phương pháp khác nhau, đối với các bài toán đơn giản phải mô tả Thuật toán một cách tường minh đầy đủ, đối với các bài toán lớn mà trong đó có những thuật toán chuẩn, quen thuộc ta có thể mô tả tổng thể, những chỗ đã biết có thể chú thích hoặc có thể bỏ qua. Khi đó ta chỉ tập trung giải quyết các phần trọng điểm tránh việc làm cho mô tả Thuật toán rắc rối phức tạp. 1.1.2. Các bước phân tích bài toán. Bước đầu tiên trong việc phân tích một thuật toán là xác định đặc trưng dữ liệu sẽ được dùng làm dữ liệu nhập của thuật toán và quyết định phân tích nào là thích hợp. Về mặt lý tưởng, chúng ta muốn rằng với một phân bố tùy ý được cho của dữ liệu nhập, sẽ có sự phân bố tương ứng về thời gian hoạt động của thuật toán. Chúng ta không thể đạt tới điều lý tưởng này cho bất kỳ một thuật toán nào, vì vậy chúng ta chỉ quan tâm đến Khoa KỸ THUẬT_ Trường Cao đẳng Kinh tế - Kỹ thuật Hải Dương 4 Giáo trình Cấu trúc dữ liệu cách cố gắng chứng minh thời gian chạy luôn luôn nhỏ hơn một “chặn trên” bất chấp dữ liệu nhập như thế nào và cố gắng tính được thời gian chạy trung bình cho dữ liệu nhập “ngẫu nhiên”. Bước thứ hai trong phân tích một thuật toán là nhận ra các thao tác trừu tượng của thuật toán để tách biệt sự phân tích với sự cài đặt. Ví dụ, chúng ta tách biệt sự nghiên cứu có bao nhiêu phép so sánh trong một thuật toán sắp xếp khỏi sự xác định cần bao nhiêu micro giây trên một máy tính cụ thể; yếu tố thứ nhất được xác định bởi tính chất của thuật toán, yếu tố thứ hai lại được xác định bởi tính chất của máy tính. Sự tách biệt này cho phép chúng ta so sánh các thuật toán một cách độc lập với sự cài đặt cụ thể hay độc lập với một máy tính cụ thể. Bước thứ ba trong quá trình phân tích thuật toán là sự phân tích về mặt toán học, với mục đích tìm ra các giá trị trung bình và trường hợp xấu nhất cho mỗi đại lượng cơ bản. Chúng ta sẽ không gặp khó khăn khi tìm một chặn trên cho thời gian chạy chương trình, vấn đề là phải tìm ra một chặn trên tốt nhất, tức là thời gian chạy chương trình khi gặp dữ liệu nhập của trường hợp xấu nhất. Trường hợp trung bình thông thường đòi hỏi một phân tích toán học tinh vi hơn trường hợp xấu nhất. Mỗi khi đã hoàn thành một quá trình phân tích thuật toán dựa vào các đại lượng cơ bản, nếu thời gian kết hợp với mỗi đại lượng được xác định rõ thì ta sẽ có các biểu thức để tính thời gian chạy. Nói chung, tính năng của một thuật toán thường có thể được phân tích ở một mức độ vô cùng chính xác, chỉ bị giới hạn bởi tính năng không chắc chắn của máy tính hay bởi sự khó khăn trong việc xác định các tính chất toán học của một vài đại lượng toán học trừu tượng. Tuy nhiên, thay vì phân tích một cách chi tiết chúng ta thường ước lượng để tránh sa vào chi tiết. 1.1.3. Phân tích Thuật toán: Hầu hết các bài toán đều có nhiều thuật toán khác nhau để giải quyết chúng. Như vậy, làm thế nào để chọn được Thuật toán tốt nhất? Đây là một lĩnh vực được quan tâm nghiên cứu nhiều trong khoa học máy tính. Chúng ta sẽ khảo sát các kết quả nghiên cứu mô tả các tính năng của các thuật toán cơ bản cũng như so sánh các thuật toán đồng thời cũng sẽ khảo sát hướng dẫn tổng quát về phân tích thuật toán. Khoa KỸ THUẬT_ Trường Cao đẳng Kinh tế - Kỹ thuật Hải Dương 5 Giáo trình Cấu trúc dữ liệu Khi nói đến hiệu quả của một thuật toán, người ta thường quan tâm đến chi phí cần dùng để thực hiện nó. Chi phí này thể hiện qua việc sử dụng tài nguyên như bộ nhớ, thời gian sử dụng CPU, … Ta có thể đánh giá thuật toán bằng phương pháp thực nghiệm thông qua việc cài đặt thuật toán rồi chọn các bộ dữ liệu thử nghiệm. Thống kê các thông số nhận được khi chạy các dữ liệu này ta sẽ có một đánh giá về thuật toán. Tuy nhiên, phương pháp thực nghiệm có một số nhược điểm sau khiến nó khó có khả năng áp dụng trên thực tế:  Do phải cài đặt bằng một ngôn ngữ lập trình cụ thể nên thuật toán sẽ chịu sự hạn chế của ngôn ngữ lập trình này.  Hiệu quả của thuật toán sẽ bị ảnh hưởng bởi trình độ của người cài đặt.  Việc chọn được các bộ dữ liệu thử nghiệm đặc trưng cho tất cả tập các dữ liệu vào của thuật toán là rất khó khăn và tốn nhiều chi phí.  Các số liệu thu nhận được phụ thuộc nhiều vào phần cứng mà thuật toán được thử nghiệm trên đó. Điều này khiến cho việc so sánh các thuật toán khó khăn nếu chúng được thử nghiệm ở những máy tính khác nhau. Vì những lý do trên, người ta đã tìm kiếm những phương pháp đánh giá thuật toán hình thức hơn, ít phụ thuộc môi trường cũng như phần cứng hơn. Một phương pháp như vậy là phương pháp đánh giá thuật toán theo hướng xấp xỉ tiệm cận qua các khái niệm toán học O lớn →O(); o nhỏ → o(); Ω(); ≡(). Thông thường các vấn đề mà chúng ta giải quyết có một “kích thước” tự nhiên (thường là số lượng dữ liệu được xử lý) mà chúng ta sẽ Khoa KỸ THUẬT_ Trường Cao đẳng Kinh tế - Kỹ thuật Hải Dương 6 Trung b ình Dữ liệu vào BA C D E F G 1 ms 2 ms 3 ms 4 ms 5 ms Tốt nhất Xấu nhất Thời gian chạy Giáo trình Cấu trúc dữ liệu gọi là N. Chúng ta muốn mô tả tài nguyên cần được dùng (thông thường nhất là thời gian cần thiết để giải quyết vấn đề) như một hàm số theo N. Chúng ta quan tâm đến trường hợp trung bình, tức là thời gian cần thiết để xử lý dữ liệu nhập thông thường T(n), và cũng quan tâm đến trường hợp xấu nhất, tương ứng với thời gian cần thiết khi dữ liệu rơi vào trường hợp xấu nhất có thể có. Việc xác định chi phí trong trường hợp trung bình thường được quan tâm nhiều nhất vì nó đại diện cho đa số trường hợp sử dụng thuật toán. Tuy nhiên, việc xác định chi phí trung bình này lại gặp nhiều khó khăn. Vì vậy, trong nhiều trường hợp, người ta xác định chi phí trong trường hợp xấu nhất (chặn trên) thay cho việc xác định chi phí trong trường hợp trung bình. Hơn nữa, trong một số bài toán, việc xác định chi phí trong trường hợp xấu nhất là rất quan trọng. Ví dụ, các bài toán trong hàng không, phẫu thuật, … Như đã được chú ý ở trên, hầu hết các thuật toán đều có một tham số chính là N, thông thường đó là số lượng các phần tử dữ liệu được xử lý mà ảnh hưởng rất nhiều tới thời gian chạy. Tham số N có thể là bậc của một đa thức, kích thước của một tập tin được sắp xếp hay tìm kiếm, số nút trong một đồ thị, v.v… Thông thường để đánh giá thuật toán người ta dựa trên hai tiêu chuẩn sau: Tiêu chuẩn 1 Độ đơn giản, dễ hiểu, dễ cài đặt ( viết chương trình ). Tiêu chuẩn 2 Sử dụng tiết kiệm tài nguyên hệ thống và với thời gian ngắn nhất. Tuỳ từng trường hợp mà một trong hai tiêu chuẩn trên được quan tâm, chẳng hạn khi viết một chương trình chỉ để sử dụng một số ít lần, và thời gian để viết chương trình với thuật toán theo tiêu chuẩn 2 lại mất nhiều hơn một thuật toán khác đơn giản ngắn gọn hơn thì tiêu chuẩn 1 được chú trọng, ngược lại nếu một chương trình đợc sử dụng nhiều lần ( chương trình con ) hoặc nhiều người sử dụng thì tiêu chuẩn 2 lại rất quan trọng. Một ví dụ điển hình với bài toán cổ Tháp Hà nội như sau: Có 3 cọc A, B,C lúc đầu ở cọc A có m đĩa được lồng vào theo thứ tự đĩa bé ở trên, yêu cầu là phải chuyển toàn bộ số đĩa từ cọc A sang cọc B và cũng được sắp xếp theo trật tự đĩa bé ở trên. Ở đây cọc C đóng vai trò là cọc trung Khoa KỸ THUẬT_ Trường Cao đẳng Kinh tế - Kỹ thuật Hải Dương 7 A C B Giáo trình Cấu trúc dữ liệu gian trong quá trình chuyển đĩa, các đĩa tại cọc C cũng tuân theo quy tắc đĩa bé ở trên. Hình minh hoạ bài toán Tháp Hà Nội Để chuyển m đĩa từ cọc A sang cọc B ta thực hiện thuật toán, đầu tiên chuyển m -1 đĩa ở trên cùng sang cột C, sau đó chuyển đĩa lớn nhất từ cột A sang cột B, thuật toán được thực hiện đệ quy cho đến đĩa cuối cùng. Như vậy ta thấy nếu m=1 thì cần 1 lần chuyển, nếu m=2 cần 3 lần chuyển, nếu m=3 cần 7 lần chuyển quy nạp ta được với mọi m ta cần 2 m -1 lần chuyển. Vậy nếu m lớn thì thời gian để thực hiện thuật toán là không thể. Giả sử m = 30 thì số lần chuyển là 107374824 và nếu mỗi lần chuyển mất 01 giây thì cũng cần chuyển trong khoảng 1243 ngày liên tục, như vậy thuật toán đó là bất khả thi. Hầu hết tất cả các thuật toán trong giáo trình này có thời gian chạy tiệm cận tới một trong các hàm sau: a. Hằng số: Hầu hết các chỉ thị của các chương trình đều được thực hiện một lần hay nhiều nhất chỉ một vài lần. Nếu tất cả các chỉ thị của cùng một chương trình có tính chất này thì chúng ta sẽ nói rằng thời gian chạy của nó là hằng số. Điều này hiển nhiên là điều mà ta phấn đấu để đạt được trong việc thiết kế thuật toán. b. LogN: Khi thời gian chạy của chương trình là logarit tức là thời gian chạy chương trình tiến chậm khi N lớn dần. Thời gian chạy thuộc loại này xuất hiện trong các chương trình mà giải một bài toán lớn bằng cách chuyển nó thành một bài toán nhỏ hơn, bằng cách cắt bớt kích thước một hằng số nào đó. Với mục đích của chúng ta, thời gian chạy có được xem Khoa KỸ THUẬT_ Trường Cao đẳng Kinh tế - Kỹ thuật Hải Dương 8 Giáo trình Cấu trúc dữ liệu như nhỏ hơn một hằng số “lớn“. Cơ số của logarit làm thay đổi hằng số đó nhưng không nhiều: Khi N là 1000 thì logN là 3 nếu cơ số là 10, là 10 nếu cơ số là 2; khi N là một triệu, logN được nhân gấp đôi. bất cứ khi nào N được nhân đôi, logN tăng lên thêm một hằng số. c. N: Khi thời gian chạy của một chương trình là tuyến tính, nói chung đây là trường hợp mà một số lượng nhỏ các xử lý được làm cho mỗi phần tử dữ liệu nhập. Khi N là một triệu thì thời gian chạy cũng cỡ như vậy. Khi N được nhân gấp đôi thì thời gian chạy cũng được nhân gấp đôi. Đây là tình huống tối ưu cho một thuật toán mà phải xử lý N dữ liệu nhập (hay sản sinh ra N dữ liệu xuất). d. NlogN: Đây là thời gian chạy tăng dần lên cho các thuật toán mà giải một bài toán bằng cách tách nó thành các bài toán con nhỏ hơn, kế đến giải quyết chúng một cách độc lập và sau đó tổ hợp các lời giải. Chúng ta nói rằng thời gian chạy của thuật toán như thế là “NlogN”. Khi N là một triệu, NlogN khoảng 20 triệu. khi N được nhân gấp đôi, thời gian chạy bị nhân lên nhiều hơn gấp nhiều đôi. e. N 2 : Khi thời gian chạy của một thuật toán là bậc hai, trường hợp này chỉ có ý nghĩa thực tế cho các bài toán tương đối nhỏ. Thời gian bình phương thường tăng dần lên trong các thuật toán mà xử lý tất cả các phần tử dữ liệu (có thể là hai vòng lặp lồng nhau). Khi n là một ngàn thì thời gian chạy là 1 triệu. khi N được nhân đôi thì thời gian chạy tăng lên gấp 4 lần. f. N 3 : Tương tự, một thuật toán mà xử lý các bộ ba của các phần tử dữ liệu (có thể là 3 vòng lặp lồng nhau) có thời gian chạy bậc ba và cũng chí ý nghĩa thực tế trong các bài toán nhỏ. Khi N là một trăm thì thời gian chạy là một triệu. Khi N được nhân đôi thì thời gian chạy tăng lên gấp 8 lần. g. 2N: Một số ít thuật toán có thời gian chạy lũy thừa lại thích hợp trong một số trường hợp thực tế. Khi N là hai mươi thì thời gian chạy là 1 triệu. khi N tăng gấp đôi thì thời gian chạy được nâng lên luỹ thừa hai! Thời gian chạy của một chương trình cụ thể đôi khi là một hệ số hằng nhân với các số hạng nói trên (“số hạng dẫn đầu”) cộng thêm một số hạng nhỏ hơn. Giá trị của hệ số hằng và các số hạng phụ thuộc vào kết quả của sự phân tích và các chi tiết cài đặt. Hệ số của số hạng dẫn đầu Khoa KỸ THUẬT_ Trường Cao đẳng Kinh tế - Kỹ thuật Hải Dương 9 Giáo trình Cấu trúc dữ liệu liên quan tới số chỉ thị bên trong vòng lặp: Ở một tầng tùy ý của thiết kế thuật toán thì phải cẩn thận giới hạn số chỉ thị như thế. Với N lớn thì các số hạng dẫn đầu đóng vai trò chủ chốt; với N nhỏ thì các số hạng cùng đóng góp vào sự so sánh các thuật toán sẽ khó khăn hơn. Trong hầu hết các trường hợp, chúng ta sẽ gặp các chương trình có thời gian chạy là “tuyến tính”, “NlogN”, “bậc ba”,… với hiểu ngầm là các phân tích hay nghiên cứu thực tế phải được làm trong trường hợp mà tính hiệu quả là rất quan trọng. 1.1.4. Ví dụ về việc xác định độ phức tạp của thuật toán: Ví dụ với bài toán tính tổng các số nguyên dương từ 1 đến n ta có thể tính theo thuật toán sau: Input n; Tong:=0; For i:= 1 to n do Tong := Tong + i; Output Tong; Với thuật toán này thời gian tính toán tỷ lệ thuận với n, khi n càng lớn thì thời gian càng tốn hay độ phức tạp tính toán là O(n) Nếu tính tổng các số nguyên dương từ 1 đến n theo thuật toán: Input n; Tong := n*(n+1)/2; Output Tong; Thì độ phức tạp tính toán này là O(1) không phụ thuộc vào giá trị n 1.2. Khái niệm cấu trúc dữ liệu (CTDL ) Máy tính điện tử đã được phát minh với chủ đích như là một thiết bị có khả năng làm thuận tiện cho những tính toán phức tạp và tốn nhiều thời gian, nhưng cùng với sự phát triển công nghệ, ngày nay trong đa số các ứng dụng tính toán thì khả năng truy xuất và lưu trữ các khối thông tin lớn đóng vai trò chủ yếu. Thông tin được lưu trữ bao gồm một tập hợp các dữ liệu mà từ đó sau một quá trình xử lý, tổng hợp có thể thu được kết quả mong muốn. Vì vậy việc xây dựng và tổ chức thông tin trên máy tính phải đảm bảo tính xác thực của vấn đề, phù hợp với bản chất của vấn đề. Việc chọn lựa tuỳ thuộc vào hướng giải quyết vấn đề và công cụ để giải quyết vấn đề đó. Có những vấn đề chỉ thích ứng với một cách tổ chức thông tin nhất định, đối với những cách tổ chức thông tin khác thì sẽ kém hiệu quả Khoa KỸ THUẬT_ Trường Cao đẳng Kinh tế - Kỹ thuật Hải Dương 10 . GIÁO TRÌNH CẤU TRÚC DỮ LIỆU Giáo trình Cấu trúc dữ liệu LỜI NÓI ĐẦU Cấu trúc dữ liệu và Giải thuật là môn học đóng vai trò quan trọng trong quá trình đào tạo kỹ sư, cử. Dương 19 Giáo trình Cấu trúc dữ liệu Các bài toán thường gặp trên dữ liệu kiểu tệp: Lập một cơ sở dữ liệu để lưu dữ liệu dạng quản lý danh sách, hồ sơ như tìm kiếm, cập nhật dữ liệu, thêm bớt dữ liệu. Dương 12 Cấu trúc dữ liệu + Giải thuật = Chương trình Giáo trình Cấu trúc dữ liệu dữ liệu ta phải tính đến trường hợp phát triển của các đại lượng chứa trong biến qua đó chọn kiểu dữ liệu thích

Ngày đăng: 03/07/2014, 13:09

TỪ KHÓA LIÊN QUAN

TRÍCH ĐOẠN

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN

w