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

ASM 1 Algorithm and Datastructure FPT GREENWICH BTECH Distinction(SUPER HOT SALE)

31 68 4

Đ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 31
Dung lượng 1,62 MB

Nội dung

Điểm của bài asm còn tùy thuộc vào người chấm. Chỉ cần paraphase bài này là có thể đạt merit hoặc có thể đạt distinction tùy vào thầy dạy. 1 trong nhưng tool paraphase mình recommend là quillbot.ASSIGNMENT 1 FRONT SHEET Qualification BTEC Level 5 HND Diploma in Computing Unit number and title Unit 20 Advanced Programming Submission date Date Received 1st submission Re submission Date Date Rec.

ASSIGNMENT FRONT SHEET Qualification BTEC Level HND Diploma in Computing Unit number and title Unit 19: Data Structures and Algorithms Submission date 26/02/2022 Date Received 1st submission Re-submission Date Date Received 2nd submission Student Name Student ID Class Assessor name Student declaration I certify that the assignment submission is entirely my own work and I fully understand the consequences of plagiarism I understand that making a false declaration is a form of malpractice Student’s signature Grading grid P1 P2 P3 M1 M2 M3 D1 D2  Summative Feedback: Grade:  Resubmission Feedback: Assessor Signature: Internal Verifier’s Comments: IV Signature: Date: Table of Contents B CREATE A DESIGN SPECIFICATION FOR DATA STRUCTURES EXPLAINING THE VALID OPERATIONS THAT CAN BE CARRIED OUT ON THE STRUCTURES Abstract Data Types Stack Abstract Data Type a) Definition: Stack ADT could be simply understood as a linear data structure utilized for storing data (quite similar to Linked Lists) In other words, the stack can also be viewed as an order list Hence, the order where the data is stored as well as retrieved very matters in the stack Basically, the final element added also is the first element which is removed from the stack LIFO(Last in first out) or FILO(first in last out) list are the other names of stack ADT because of the stack’s mechanism b) Stack operations and working mechanism: c) Applications/Examples of Stack C DETERMINE THE OPERATIONS OF A MEMORY STACK AND HOW IT IS USED TO IMPLEMENT FUNCTION CALLS IN A COMPUTER 10 Definition of Stack Memory 10 Operations of Stack Memory 10 How it is used to implement function calls in a computer 10 D Queue ADT (FIFO) 13 1) Definition: 13 2) Queue operations and working mechanism 13 a) Main operations: 13 b) Other operations: 14 3) E Applications/Examples of Queue 15 Compare the performance of two sorting algorithms 16 Introduction 16 a) Definition: 16 b) Some common sorting algorithms: 16 c) Chosen Sorting Algorithms: 16 Bubble Sort: 18 Quick sort: 21 F USING AN IMPERATIVE DEFINITION, SPECIFY THE ABSTRACT DATA TYPE FOR A SOFTWARE STACK 24 Explanation of how to specify an abstract data type using the example of the software stack 24 1.1 Introduction to the formal specification, types of formal specification languages 24 1.2 Describe what are Pre-condition, Post-condition and error-condition 25 Explanation Of The Advantages Of Encapsulation And Information Hiding When Using An ADT 28 a) What is encapsulation 28 b) Why we need encapsulation? 28 c) Advantages of encapsulation and information hiding when using an ADT Encapsulation uses accessors and mutators Encapsulation uses properties 29 d) G Read-only property and Write-only property 30 CONCLUSION 30 References 30 List of figure Figure 1: Stack Figure 2: Push operation illustration Figure 3: stack overflow Figure 4: Pop operation illustration Figure 5: PeekTop Opeartion Illustration Figure 6: isEmpty operation illustration Figure 7: Example of Stack(1) Figure 8: Example of Stack(2) Figure 9: Example of Stack(3) Figure 10: Example of Stack(4) Figure 11: Code Example 11 Figure 12: Function call examples 13 Figure 13: EnQueue Illustration 13 Figure 14: DeQueue Illustration 14 Figure 15: PeekHead illustration 14 Figure 16: QueueSize operation 14 Figure 17: isEmptyQueue illustration 15 Figure 18: Sorting algorithm 16 Figure 19: Bubble sort flow chart 20 Figure 20: Bubble sort Source code 20 Figure 21: Bubble sort example 21 Figure 22: Quick Sort Flow chart 23 Figure 23: Quick Sort Code implementation 23 Figure 24: Quick sort example 24 Figure 25: Encapsulation Illustration 28 A INTRODUCTION In this assignment, there are some tasks I need to complete such as creating a design specification for a data structure that consists the valid operations Following that, I will demonstrate memory stack operations and how they are applied in computers to handle function calls Additionally, I will describe the abstract data type for a software stack using an imperative description To more specific, taking an example to introduce the concept of data structure for a First In First Out (FIFO) queue The performance of two sorting algorithms will next be compared and discussed: bubble sort and quick sort In the end, discussing about the benefits of encapsulating and concealing data with ADT B CREATE A DESIGN SPECIFICATION FOR DATA STRUCTURES EXPLAINING THE VALID OPERATIONS THAT CAN BE CARRIED OUT ON THE STRUCTURES Abstract Data Types Definition: Basically, Abstract Data Type(ADT) is viewed as an abstraction of data structure, where only operations are defined but not show their actual implementation Specifically, It is not specified what algorithms will be utilized to implement the operations or how the data will be arranged, retrieved or stored in memory (Karumanchi, 2017) Because it provides an implementation-independent perspective, it is considered "abstract." Abstraction is the process of presenting only the necessary information while obscuring the subtleties Each abstract data type always consists of the data collection and some operations on the data or nested data Typically, to make an ADT, this is essential to have a set of activity axioms that govern the interaction of operations Here are some common types of axiomatic operations of a basic ADT: creators(constructor), producers(+,-,*,/ decrease size when deleting element), accessors and mutators Some ADTs which are commonly used are List, Stack and Queue and here are a summary of their accessors and mutators operations: Stack Queue List Mutators Push() Pop() EnQueue() DeQueue() Accessors PeekTop() isEmpty() Size() PeekHead() isEmpty() Size() Insert() Remove() RemoveAt() Replace() Get() Size() isEmpty() isFull() These operations provide for them(List, Stack, Queue) the ability to interact with the data of its instances The term in the operation’s name also shows the clear purpose and logical behaviour of each one Stack Abstract Data Type a) Definition: Stack ADT could be simply understood as a linear data structure utilized for storing data (quite similar to Linked Lists) In other words, the stack can also be viewed as an order list Hence, the order where the data is stored as well as retrieved very matters in the stack Basically, the final element added also is the first element which is removed from the stack LIFO(Last in first out) or FILO(first in last out) list are the other names of stack ADT because of the stack’s mechanism Figure 1: Stack b) Stack operations and working mechanism: Some particular operation terms are given to the two changes that can be made to a stack For simplicity, assume the data parameter is the integer data type:  Push(int): When an element is added to the top of a stack, this operation is called “push” Figure 2: Push operation illustration  The “push” operation inserts and stores the element parameter onto the top of the stack  Each stack also has a fixed capacity; hence, the operation “push” could be performed if the stack is not full In case the stack is full and the push operation is still called to insert an element, this causes stack overflow and the mechanism of push operation will throw a “stack overflow error” exception  When pushing an element successfully, the push mechanism returns the value of the inserted element Figure 3: stack overflow  Int Pop():  When an element on the top is deleted from the stack, this operation is called “pop” Figure 4: Pop operation illustration  The “pop” operation removes and returns the current last(top) element(or the element which is most recently added) in the stack This working mechanism implicitly describes the order of the element in/out(the last inserted element is also the first removed element)  If the stack is empty but the “pop” method is still called, this will cause stack underflow When this case happens, the underflow condition of the “pop” method will immediately throw “nullpointerexception” or other “RuntimeException”  “pop” operation returns the integer value of the removed element  Int PeekTop():  The “PeekTop” operation retrieves and returns the top(last) integer element in the stack without deleting this one like the “pop” operation Figure 5: PeekTop Opeartion Illustration  As the “Pop” operation, If the stack is empty but the “PeekTop” method is still called, this will cause stack underflow When this case happens, the underflow condition of the “PeekTop” method will immediately throw “nullpointerexception” or other “RuntimeException”  Boolean isEmpty():  When other operations (“pop”, “peek top”,…) always need to check the number of existing elements in the stack(empty or not) to decide to perform algorithms or throw an error “isEmpty” will be in charge of this task Figure 6: isEmpty operation illustration  “isEmpty” will check the existence of the top(last) element in the stack If this element does not exist, “isEmpty” returns true and reverse c) Applications/Examples of Stack  One of the applications widely known is Undo sequence in a text editor(word, excel, notebook, etc…) Based on the stack mechanism, the last text or sequence users delete or change is also the first one which will be “undo”(or restored) in the text editor Figure 7: Example of Stack(1)  The other significant example of the stack that anyone who knows the internet sees or uses at least one time is “Page-visited history in a Web browser [Back Buttons]” Simply, the last page(website, site) users access is the first one which will be redirected to when users click the “Back button” on the browser Additionally, the way the history of the web browser stores the list of website users’ access also is one example of the stack The last website user’s access is always pushed to the top of the history list sequentially Figure 8: Example of Stack(2)  Different types of parenthesis are used in programming, such as - (,), and, to open and close blocks of code (Jaswal, 2020) This parenthesis then influences how our program runs by being stored in the stack Figure 9: Example of Stack(3)  Stack memory – the base concept of computer science is also one of the examples Different from Heap memory, stack memory stores the local variables of each method when it is executed in the order of the Stack ADT (last in first out) in temporary memory(RAM) The last function data store also is the first one which will be removed Figure 10: Example of Stack(4) C DETERMINE THE OPERATIONS OF A MEMORY STACK AND HOW IT IS USED TO IMPLEMENT FUNCTION CALLS IN A COMPUTER Definition of Stack Memory The execution of a thread using Stack memory Stack memory mainly stores the function calls, primitive local variables and references to other objects in the heap that the method is referring to as well as temporary method-specific variables Stack is one of the significant stack ADT applications, it always refers to storing data in LIFO(last in first out) order (Kamil, 2019) To store local primitive values and references to other objects(such as an array, String,…) in the function calls, a new stack frame is always created for each method call in the stack memory When the function call is complete, the stack frame is popped out and terminated The size of each stack is limited based on OS Operations of Stack Memory Because the data is inserted and deleted in a last-in-first-out manner based on function call, It has two principal operations:  Push: Storing the new data onto the top of stack memory  Pop: Restoring the last data on the top of stack memory  Peek: Access and retrieve the last data on the top of stack memory How it is used to implement function calls in a computer Before talking about the implementation of function calls, there is a concept needing to be defined and this is the activation record An activation record, which contains the space for all of the function's parameters and local variables, temporary objects, the return address, and other things the function needs, is where most implementations keep the data for a function call as a whole (Kamil, 2019) When a function is called, the activation record is created immediately and the stack memory mechanism will push it to the top of the stack in this thread(Pushing) In this case, the activation record now could be called the stack frame(or block; in other words, the stack frame is an implementation of the activation record) The first activation record(stack frame) to be terminated is the most recent one that has been created For more specific, there is a particular example of the function call: Quick sort is the representative sorting algorithm that applies the divide-and-conquer algorithmic technique It also is the sorting algorithm that gets most widely used in many programs Its partitioning mechanism brings about parallelizable logic Compare the chosen sorting algorithms:  Bubble sort: The list(or array,…) is traversed repeatedly until this one is sorted The name of the comparison sort method refers to pushing the "bubble" of smaller or bigger entries to the end of the list (Toscano, 2015) The algorithm repeats comparing each couple of elements and swapping them if they are not in a certain order until it can traverse the complete collection without discovering any components that need switching places Performance:  In case the list is already sorted in a certain order(ascending or descending) with a small number of elements, bubble sort will examine that in the initial loop that no couple of elements need to be swapped and will then terminate immediately Because it just needs to perform n-1 comparisons; therefore, the complexity now is O(n) and this also is the best case  Except for the above case, other cases such as the list is not already sorted or the huge amount of elements in the list(or array) also bring about Bubble sort the bad case than the average case with time complexity is O(n2) Here is the summary of Bubble sort complexity: Worst case complexity: O(n2) Best case complexity: O(n) Average case complexity: O(n2) Worst case space complexity: O(1) auxiliary Advantages of Bubble sort:  This is the basic and simplest algorithm, which is very suitable for newbies with the sorting algorithm  Easy and straightforward to implement with a short structure Disadvantages of Bubble sort:  Worst performance when compared with other sorting algorithms  Really bad handling with a large amount of data in a collection  Quick sort By dividing the input into two parts by choosing pivot, sorting them, and then recombining them, Quick sort is implied that each iteration operates in this manner (Patel, 2019) Performance:  Best Case Complexity: The best case occurs when the pivot element chosen by the partitioning method is always in the middle or close to the middle element The fastest possible sorting time is O (n*log(n))  Average Case Complexity: When the array components are in a disorganized sequence O(n*log(n)) is the average case time complexity of Quicksort  The worst-case scenario for complexity is when the partitioning algorithm consistently chooses the largest or smallest member as the pivot element Quicksort's worst-case temporal complexity is O(n2) Advantages of Quick sort:  It operates quickly and efficiently  Among various sorting algorithms, it has the lowest temporal complexity  Quick sort is a great option when space is restricted because of its O(log(n)) space complexity Disadvantages of Quick sort:  This sorting method is regarded as unstable since it does not preserve the original order of the key-value pairs  When the pivot element is the biggest or the smallest, or when the sizes of all the parts are the same These worst-case circumstances drastically reduce the quicksort's performance  As a recursive process, it is challenging to implement, especially if recursion is not possible Bubble Sort:  Bubble Sort implemented Step by Step:  This algorithm iterates via elements by using for loop  Starting with the first element(index = 0), continue the next for loop inside the first one and also start with index = Each iterating time of the nested loop performs comparing of the current element with the next element of the array sequentially  Based on the sorting of a certain order If the current element is bigger or smaller than the next element of the array, swap them  If the current element is meet the type of sorting order when compared with the next element, move to the next element  After each time the first loop run the biggest or smallest element will be located at the end of the list(or array, ) For more specific, here are pseudocode, flowchart and code implementation of bubble sort: Pseudocode: Public void bubleSort(list / array of items ) //this algorithm is declared as a function n = array.length; for i = to loop-1 do: flag = false //flag is understood as a checker to somehow get the best case of bubble sort as well as enhance its performance for j = to loop-1 do: if array[j] > array[j+1] then // compare the contiguous elements swap( list[j], list[j+1] ) // swap them flag = true // flag is true informing that this loop time has performed the swap end if end for if(flag is false) then break // if no number was swapped that means the array is sorted now, break the loop If just after one loop and flag is still false, it provides the best case of bubble sort end if end for end void Flowchart of Bubble sort: Figure 19: Bubble sort flow chart Code implementation(Java): To implement this algorithm as a function, the argument of this function now is integer data type: Figure 20: Bubble sort Source code  Example: For illustration, I apply the bubble sort algorithm to a simple array which is not sorted yet Here is this array: {6,9,3,4} and this array will be sorted in descending order Specifically, this sorting process will be described with images Explain  In the first time iteration of two loops, starting from the first index(j/0), the program compares the j index and the j+1 index Because it sort in descending order, is greater than the program swaps them to meet the condition  The second time iteration of the nested loop, continues to compare the 6(j index) and 3(j+1) index because is already less than and located after, there is no swap  Next, it continues to compare 3(j) and 4(j+1 index) The is greater than 3, the program swaps them  Now the first nested loop is completed in looping through all the element, the Figure 21: Bubble sort example outside loop increase the i variable and the next time loop will continue  However, the array now is already sorted, the flag will still be false and it meets the condition (flag == false) the loop break and the algorithm also finishes with the sorted array Quick sort: Quick sort implemented step by step:  Picking the pivot The pivot could be the first or last or even the middle element, picking the pivot is determining mostly the time complexity of this algorithm  Set two variables to point left and right(first and last element) of the array except for the picked pivot  If the value of the element is greater than the pivot, move it to the right side of the pivot or keep it in the original location if it’s already located on the right side of the pivot  If the value of the element is less than the pivot, move it to the left side of the pivot or keep it in the original location if it’s already located on the left side of the pivot  Swap left and right  if left ≥ right, the point where they met is the new pivot For more specific, here are pseudocode, flow chart and code implementation of Quick sort: Pseudocode: For best practice, the quick sort algorithm is divided into three parts: //left –> Starting index, right–> Ending index of array quickSort(arr[], left , right) if left < right then pivot = partition(arr, left , right)//pivot is partitioning index, arr[pivot] is now at right place quickSort(arr, left , pi – 1) // Before pivot quickSort(arr, pi + 1, right) // After pivot end if // my way is picking the last element as the pivot, placing the pivot at its accurate position in the sorted array, and placing all smaller elements to the left of the pivot and all greater elements to the right of the pivot partition (arr[], left, right) pivot = arr[right]; //pick the last element of the array to be the pivot j = (left– 1) // Index of the -1 element and implicitly indicates the right position of the pivot for (i = left; i integer; Result/return: Q (a new stack) Pre-condition: Limited_capacity > Post-condition: Q -> declared and empty Size = Capacity = Limited_capacity Error-condition: no b) Push operation – Push(T Value p): Insert Value into stack Q Note T datatype p (inserted element) Argument/entry: Value -> T; Result/return: S -> new size, new element Pre-condition: p’s type == Q type p != null size < Limited_capacity Post-condition: size -> size +1, top -> p Error-condition: size >= Limited_capacity c) Pop operation – T pop(): delete and return top element in Q Note T datatype D output/deleted element Argument/entry: no Result/return: D Pre-condition: isEmpty() -> false, top -> D Post-condition: size -> size -1 top != D Error-condition: isEmpty()-> true top != D || top -> null d) PeekTop operation – T peekTop(): return the top element of Q Note: T datatype O output/return element Argument/entry: no Result/return: O Pre-condition: isEmpty() -> false, top -> O Post-condition: top -> O, size -> size Error-condition: isEmpty()-> true top != D || top -> null e) Size Operation – int Size(): return the size of Q Note s return number Argument/entry: no Result/return: s Pre-condition: s -> size Post-condition: size -> size Error-condition: s != size f) isEmpty() – boolean isEmpty(): return true or false based on size of Q Note b return boolean Argument/entry: no Result/return: b Pre-condition: no Post-condition: true if size =0, false if size > Error-condition: no g) deleteStack() – deleteStack(): delete the stack Q – other operation Argument/entry: no Result/return: no Pre-condition: Q -> defined Post-condition: Q -> not existed or undefined Error-condition: Q -> undefined before deleteStack() called Explanation Of The Advantages Of Encapsulation And Information Hiding When Using An ADT a) What is encapsulation Encapsulation is a key concept in OOP (object-oriented programming) It refers to the interaction of data and the procedures that operate on that data (Braunschweig, n.d.) Encapsulation is used to protect the values or configuration of a structured data object within a class, preventing unauthorized parties from directly accessing it Publicly available methods (so-called getters and setters) are often given in the class to access the values, and other client classes call these methods to get and alter the data within the object Figure 25: Encapsulation Illustration b) Why we need encapsulation?  Simply, It is the best way to limit and protect our data’s access by concealing the implementation specifics Encapsulation also allows for data concealment The user will have no information about the class's internal implementation(e.g, the data structure link list just provides the public operations without showing how it stores, retrieves and deletes data) The user will be unable to see how the class stores values in variables The user will only see that we are sending the values to a setter method and that variables(or instances) are being initialized with that value  Depending on our needs, we may define the class variables as read-only or write-only  If we accomplish the best practice of encapsulation in our software architecture, we may safely promote change and evolution of our APIs without breaking its users, hence limiting the effect of modifications and module dependencies  Encapsulated code is simple to test for unit testing c) Advantages of encapsulation and information hiding when using an ADT Encapsulation uses accessors and mutators Encapsulation uses properties Because the nature of ADT is that it does not specify how the data type or the operations will be implemented, the process of hiding data is very important for ADT When we implement encapsulation, we usually keep all property variables private and create public ways to set and obtain their values, which are known as accessor and mutator methods The advantages of data hiding only influence the implementation of the ADT class In essence, the advantages of encapsulation when utilizing an ADT include public operations and state controls (Burleson, 2014) The ability to modify the data type of an ADT class is unaware of how the class saves its data The major advantage of employing encapsulation is data security Because the data representations are hidden, user code cannot directly access or rely on them, allowing the representation to be updated without impacting user code Although the concepts of encapsulation and information hiding restrict the access and hiding of the data from ADT, there are some accessor and mutator methods as said above For example, the stack ADT is also implemented with encapsulation to protect the data of each Node which means it hides mostly information from the stack ADT However, the accessors of stack ADT such as the PeekTop() method, Size() and isEmpty() also support user code to access the data(the last element in the stack, total Node in the stack and whether the stack is empty) of this ADT Additional, mutators also help user can perform the necessary actions to stack ADT such as push(), and pop() which also are common mutators of the stack In other words, this ADT has entire control over how the other code interacts with its contents In case they had a public setter to the top, they could change its data in a way that crashes the properties of the stack Because the top data is hidden from user code control, there is no way of harming the stack Specifically, using Accessors and Mutators help the third party(user code) to partly interact with the data of ADT but still ensure the encapsulation (just provide the accessors and mutators for the part of data which could be public and hiding as well as restricting the access or the bad actions to the data of ADT class) d) Read-only property and Write-only property About read-only property in ADT, this property is viewed as the type of observers operation It allows read-only access to ADT values without changing ADT State For example, the Size() operation of the list ADT, this operation only shows the total elements of the list but does not provide the ability to change it It ensures the integrity of the list’s content Some data could be input but not observed, and they will be the write-only property These properties will help hide the data even when they are just input However, even when encapsulation and information hiding is applied, the write-only property is still rarely used because it is said that unrealistic in some case G CONCLUSION In conclusion, these necessary task are completed and discussed in this report: Creating a data structure definition that outlines the valid operations that may govern the interaction of the data structure Demonstrating memory stack operations and how they are utilized to handle function calls Additionally, defining an abstract data type for a program stack using the idea of an order Providing illustrated a specific data structure for a First In First Out (FIFO) queue Comparing the performance of two sort algorithms: bubble sort and quick sort And the last task, discovering the advantages of encapsulating and concealing information with ADT also is done After this report, I also get some basic knowledge about data structure and sorting algorithm which will help me a lot in the future References Anon., 2021 Application of Queue in Data Structure [Online] Available at: https://www.computersciencejunction.in/2021/08/15/application-of-queue-in-data-structure/ Braunschweig, D., n.d Encapsulation [Online] Available at: https://press.rebus.community/programmingfundamentals/chapter/encapsulation/#footnote-18997 Burleson, D., 2014 Encapsulation and Abstract Data Types (ADT) [Online] Available at: http://www.dba-oracle.com/t_object_encapsulation_abstract.htm Fuchs, 1992 Software Engineering Journal s.l.:s.n Jaswal, S., 2020 Applications of Stack: Top Application of Stack in Data Structure [Online] Available at: https://www.thecoderpedia.com/blog/application-of-stack/ Kamil, A., 2019 Function Calls and the Call Stack [Online] Available at: https://eecs280staff.github.io/notes/02_ProceduralAbstraction_Testing.html Karumanchi, N., 2017 Data Structures and Algorithm Made Easy s.l.:CareerMonk Nissanke, N., 2012 Formal Specification: application and techniques london: Springer Pandey, K., 2022 Queue Data Structure - Types, Applications, JavaScript Implementation [Online] Available at: https://blog.masaischool.com/queue-data-structure-types-applications-javascript-implementation/ Patel, H., 2019 Quick Sort [Online] Available at: https://medium.com/m/globalidentity?redirectUrl=https%3A%2F%2Ftowardsdatascience.com%2Fan-overview-of-quicksort-algorithmb9144e314a72 Toscano, N M., 2015 Bubble sort [Online] Available at: https://medium.com/@ntoscano/bubble-sort-vs-quick-sort-6ff48280f745 ... 11 Figure 12 : Function call examples 13 Figure 13 : EnQueue Illustration 13 Figure 14 : DeQueue Illustration 14 Figure 15 : PeekHead... 14 Figure 16 : QueueSize operation 14 Figure 17 : isEmptyQueue illustration 15 Figure 18 : Sorting algorithm 16 Figure 19 : Bubble sort... 13 1) Definition: 13 2) Queue operations and working mechanism 13 a) Main operations: 13 b) Other operations: 14 3) E Applications/Examples

Ngày đăng: 30/10/2022, 21:28

TỪ KHÓA LIÊN QUAN

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

TÀI LIỆU LIÊN QUAN