Quicksort vs heapsort


Câu trả lời:


60

Bài báo này có một số phân tích.

Ngoài ra, từ Wikipedia:

Đối thủ cạnh tranh trực tiếp nhất của quicksort là heapsort. Heapsort thường chậm hơn một chút so với quicksort, nhưng thời gian chạy trong trường hợp xấu nhất luôn là Θ (nlogn). Quicksort thường nhanh hơn, mặc dù vẫn có khả năng xảy ra trường hợp xấu nhất ngoại trừ trong biến thể intsort, biến thể này chuyển sang heapsort khi phát hiện trường hợp xấu. Nếu biết trước rằng heapsort là cần thiết, thì việc sử dụng trực tiếp nó sẽ nhanh hơn là chờ đợi introsort chuyển sang nó.


12
Điều quan trọng cần lưu ý là trong các triển khai điển hình, không phải nhanh hay sắp xếp theo thứ tự đều là các loại ổn định.
MjrKusanagi

@DVK, Theo liên kết cs.auckland.ac.nz/~jmor159/PLDS210/qsort3.html của bạn , sắp xếp đống cần 2.842 phép so sánh cho n = 100, nhưng cần 53,113 phép so sánh cho n = 500. Và điều đó ngụ ý rằng tỷ lệ giữa n = 500 và n = 100 là 18 lần và nó KHÔNG phù hợp với thuật toán sắp xếp đống với độ phức tạp O (N logN). Tôi đoán rất có thể việc triển khai sắp xếp đống của họ có một số loại lỗi bên trong.
DU Jiaen

@DUJiaen - hãy nhớ rằng O () là về hành vi tiệm cận nói chung N và có một số nhân càng tốt
DVK

Điều này KHÔNG liên quan đến hệ số nhân. Nếu một thuật toán có độ phức tạp là O (N log N), nó phải tuân theo xu hướng Thời gian (N) = C1 * N * log (N). Và nếu bạn lấy Thời gian (500) / Thời gian (100), rõ ràng C1 sẽ bị biến mất và kết quả sẽ được đóng thành (500 log500) / (100 log100) = 6.7 Nhưng từ liên kết của bạn, nó là 18, tức là quá nhiều so với quy mô.
DU Jiaen

2
Liên kết đã chết
PlsWork

123

Heapsort được đảm bảo O (N log N), điều tốt hơn nhiều so với trường hợp xấu nhất trong Quicksort. Heapsort không cần thêm bộ nhớ cho một mảng khác để đưa dữ liệu có thứ tự khi Mergesort cần. Vậy tại sao các ứng dụng thương mại lại gắn bó với Quicksort? Quicksort có gì đặc biệt so với các triển khai khác?

Tôi đã tự mình kiểm tra các thuật toán và tôi thấy rằng Quicksort thực sự có một điều gì đó đặc biệt. Nó chạy nhanh, nhanh hơn nhiều so với thuật toán Heap và Merge.

Bí mật của Quicksort là: Nó gần như không thực hiện các hoán đổi phần tử không cần thiết. Hoán đổi tốn nhiều thời gian.

Với Heapsort, ngay cả khi tất cả dữ liệu của bạn đã được sắp xếp thứ tự, bạn sẽ hoán đổi 100% các phần tử để sắp xếp thứ tự mảng.

Với Mergesort, nó thậm chí còn tồi tệ hơn. Bạn sẽ ghi 100% các phần tử trong một mảng khác và viết nó trở lại mảng ban đầu, ngay cả khi dữ liệu đã được sắp xếp.

Với Quicksort bạn không hoán đổi những gì đã được đặt hàng. Nếu dữ liệu của bạn được sắp xếp hoàn toàn, bạn hầu như không trao đổi gì! Mặc dù có rất nhiều rắc rối về trường hợp xấu nhất, nhưng một chút cải thiện về lựa chọn trục, bất kỳ ngoại trừ việc lấy phần tử đầu tiên hoặc cuối cùng của mảng, có thể tránh được. Nếu bạn nhận được một pivot từ phần tử trung gian giữa phần tử đầu tiên, cuối cùng và phần tử giữa, nó là thiếu sót để tránh trường hợp xấu nhất.

Những gì vượt trội trong Quicksort không phải là trường hợp xấu nhất, mà là trường hợp tốt nhất! Trong trường hợp tốt nhất, bạn thực hiện cùng một số phép so sánh, ok, nhưng bạn hầu như không hoán đổi gì. Trong trường hợp trung bình, bạn hoán đổi một phần của các phần tử, nhưng không phải tất cả các phần tử, như trong Heapsort và Mergesort. Đó là những gì mang lại cho Quicksort thời gian tốt nhất. Trao đổi ít hơn, tốc độ cao hơn.

Việc triển khai bên dưới trong C # trên máy tính của tôi, đang chạy ở chế độ phát hành, đánh bại Array. Sắp xếp 3 giây với trục xoay giữa và 2 giây với trục xoay được cải thiện (vâng, có một khoản chi phí để có được một trục xoay tốt).

static void Main(string[] args)
{
    int[] arrToSort = new int[100000000];
    var r = new Random();
    for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);

    Console.WriteLine("Press q to quick sort, s to Array.Sort");
    while (true)
    {
        var k = Console.ReadKey(true);
        if (k.KeyChar == 'q')
        {
            // quick sort
            Console.WriteLine("Beg quick sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
            QuickSort(arrToSort, 0, arrToSort.Length - 1);
            Console.WriteLine("End quick sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
            for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
        }
        else if (k.KeyChar == 's')
        {
            Console.WriteLine("Beg Array.Sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
            Array.Sort(arrToSort);
            Console.WriteLine("End Array.Sort at " + DateTime.Now.ToString("HH:mm:ss.ffffff"));
            for (int i = 0; i < arrToSort.Length; i++) arrToSort[i] = r.Next(1, arrToSort.Length);
        }
    }
}

static public void QuickSort(int[] arr, int left, int right)
{
    int begin = left
        , end = right
        , pivot
        // get middle element pivot
        //= arr[(left + right) / 2]
        ;

    //improved pivot
    int middle = (left + right) / 2;
    int
        LM = arr[left].CompareTo(arr[middle])
        , MR = arr[middle].CompareTo(arr[right])
        , LR = arr[left].CompareTo(arr[right])
        ;
    if (-1 * LM == LR)
        pivot = arr[left];
    else
        if (MR == -1 * LR)
            pivot = arr[right];
        else
            pivot = arr[middle];
    do
    {
        while (arr[left] < pivot) left++;
        while (arr[right] > pivot) right--;

        if(left <= right)
        {
            int temp = arr[right];
            arr[right] = arr[left];
            arr[left] = temp;

            left++;
            right--;
        }
    } while (left <= right);

    if (left < end) QuickSort(arr, left, end);
    if (begin < right) QuickSort(arr, begin, right);
}

10
+1 cho các cân nhắc về không. các hoạt động hoán đổi, đọc / ghi cần thiết cho các thuật toán sắp xếp khác nhau
ycy

2
Đối với bất kỳ chiến lược lựa chọn tổng hợp thời gian không đổi, xác định nào, bạn có thể tìm thấy một mảng tạo ra trường hợp xấu nhất O (n ^ 2). Nó không đủ để loại bỏ chỉ mức tối thiểu. Bạn phải chọn một cách đáng tin cậy các trục nằm trong một dải phân đoạn nhất định.
Antimony,

1
Tôi tò mò không biết đây có phải là mã chính xác mà bạn đã chạy cho các mô phỏng của mình giữa sắp xếp nhanh được mã hóa thủ công của bạn và Array.sort tích hợp trong C # không? Tôi đã thử nghiệm mã này và trong tất cả các thử nghiệm của tôi, tốt nhất thì kiểu sắp xếp nhanh được mã hóa thủ công cũng giống như Array.sort. Một điều tôi kiểm soát trong quá trình thử nghiệm điều này là tạo hai bản sao giống hệt nhau của mảng ngẫu nhiên. Rốt cuộc, một ngẫu nhiên nhất định có thể có khả năng thuận lợi hơn (nghiêng về trường hợp tốt nhất) so với một ngẫu nhiên khác. Vì vậy, tôi chạy các bộ giống hệt nhau qua từng bộ. Array.sort bị ràng buộc hoặc đánh bại mọi lúc (phát hành bản dựng btw).
Chris

1
Sắp xếp hợp nhất không nhất thiết phải sao chép 100% các phần tử, trừ khi đó là một cách triển khai rất đơn giản từ sách giáo khoa. Thật đơn giản để thực hiện nó, do đó bạn chỉ cần sao chép 50% trong số chúng (phía bên trái của hai mảng đã hợp nhất). Việc trì hoãn việc sao chép cho đến khi bạn thực sự phải "hoán đổi" hai phần tử cũng là điều tầm thường, vì vậy với dữ liệu đã được sắp xếp, bạn sẽ không có bất kỳ chi phí bộ nhớ nào. Vì vậy, thậm chí 50% thực sự là trường hợp xấu nhất, và bạn có thể có bất cứ điều gì từ đó đến 0%.
ddekany

1
@MarquinhoPeli Tôi muốn nói rằng bạn chỉ cần thêm 50% bộ nhớ khả dụng so với kích thước của danh sách được sắp xếp, không phải 100%, có vẻ là một quan niệm sai lầm phổ biến. Vì vậy, tôi đã nói về việc sử dụng bộ nhớ cao nhất. Tôi không thể đưa ra liên kết, nhưng thật dễ dàng để xem nếu bạn cố gắng hợp nhất hai nửa đã được sắp xếp của một mảng tại chỗ (chỉ nửa bên trái có vấn đề khi bạn ghi đè các phần tử mà bạn chưa sử dụng). Bạn phải sao chép bao nhiêu bộ nhớ trong toàn bộ quá trình sắp xếp là một câu hỏi khác, nhưng rõ ràng trường hợp xấu nhất không thể dưới 100% đối với bất kỳ thuật toán sắp xếp nào.
ddekany

15

Đối với hầu hết các tình huống, nhanh hơn nhanh hơn một chút là không thích hợp ... đơn giản là bạn không bao giờ muốn nó thỉnh thoảng trở nên chậm chạp. Mặc dù bạn có thể điều chỉnh QuickSort để tránh các trường hợp chậm, nhưng bạn sẽ làm mất đi vẻ sang trọng của QuickSort cơ bản. Vì vậy, đối với hầu hết mọi thứ, tôi thực sự thích HeapSort hơn ... bạn có thể triển khai nó với vẻ đẹp đơn giản đầy đủ của nó và không bao giờ bị chậm.

Đối với các tình huống bạn NÊN muốn tốc độ tối đa trong hầu hết các trường hợp, QuickSort có thể được ưu tiên hơn HeapSort, nhưng cả hai đều không phải là câu trả lời đúng. Đối với các tình huống quan trọng về tốc độ, cần kiểm tra chặt chẽ các chi tiết của tình huống. Ví dụ: trong một số mã quan trọng về tốc độ của tôi, rất phổ biến là dữ liệu đã được sắp xếp hoặc sắp xếp gần (nó đang lập chỉ mục nhiều trường liên quan thường di chuyển lên và xuống cùng nhau HOẶC di chuyển lên và xuống đối diện nhau, vì vậy một khi bạn sắp xếp theo một, những cái khác sẽ được sắp xếp hoặc sắp xếp ngược lại hoặc đóng ... một trong hai cách này có thể giết QuickSort). Đối với trường hợp đó, tôi không triển khai ... thay vào đó, tôi đã triển khai SmoothSort của Dijkstra ... một biến thể HeapSort là O (N) khi đã được sắp xếp hoặc sắp xếp gần ... nó không quá thanh lịch, không quá dễ hiểu, nhưng nhanh ... đọchttp://www.cs.utexas.edu/users/EWD/ewd07xx/EWD796a.PDF nếu bạn muốn viết mã khó hơn một chút.


6

Các phép lai tại chỗ Quicksort-Heapsort cũng thực sự thú vị, vì hầu hết chúng chỉ cần so sánh n * log n trong trường hợp xấu nhất (chúng tối ưu đối với số hạng đầu tiên của tiệm cận, vì vậy chúng tránh được các tình huống xấu nhất của Quicksort), O (log n) dư không gian và chúng bảo tồn ít nhất "một nửa" hành vi tốt của Quicksort đối với tập dữ liệu đã được sắp xếp thứ tự. Một thuật toán cực kỳ thú vị được Dikert và Weiss trình bày trong http://arxiv.org/pdf/1209.4214v1.pdf :

  • Chọn trục p làm giá trị trung bình của một mẫu ngẫu nhiên gồm các phần tử sqrt (n) (điều này có thể được thực hiện trong tối đa 24 phép so sánh sqrt (n) thông qua thuật toán của Tarjan & co, hoặc so sánh 5 sqrt (n) thông qua nhện phức tạp hơn nhiều - thuật toán đạt yêu cầu của Schonhage);
  • Phân chia mảng của bạn thành hai phần như trong bước đầu tiên của Quicksort;
  • Chất đống phần nhỏ nhất và sử dụng các bit phụ O (log n) để mã hóa một đống trong đó mọi phần tử con bên trái đều có giá trị lớn hơn giá trị anh chị em của nó;
  • Trích xuất đệ quy gốc của đống, sàng lọc phần gốc để lại của lacune cho đến khi nó chạm đến một lá của đống, sau đó lấp đầy lacune bằng một phần tử thích hợp được lấy từ phần khác của mảng;
  • Lặp lại phần không có thứ tự còn lại của mảng (nếu p được chọn làm trung vị chính xác, thì không có đệ quy nào cả).

2

Comp. giữa quick sortmerge sortvì cả hai đều là loại sắp xếp tại chỗ nên có sự khác biệt giữa thời gian chạy trường hợp wrost của thời gian chạy trường hợp wrost để sắp xếp nhanh làO(n^2) và đối với sắp xếp theo đống thì nó vẫn còn O(n*log(n))và đối với lượng dữ liệu trung bình, sắp xếp nhanh sẽ hữu ích hơn. Vì nó là thuật toán ngẫu nhiên nên xác suất nhận được đúng ans. trong thời gian ngắn hơn sẽ phụ thuộc vào vị trí của phần tử trục mà bạn chọn.

Vì vậy, một

Tốt cuộc gọi: kích thước của L và G đều nhỏ hơn 3s / 4

Cuộc gọi tồi tệ: một trong L và G có kích thước lớn hơn 3s / 4

đối với số lượng nhỏ, chúng ta có thể sắp xếp chèn và đối với lượng dữ liệu rất lớn, chúng ta có thể sắp xếp theo đống.


Mặc dù sắp xếp hợp nhất có thể được thực hiện với sắp xếp tại chỗ, nhưng việc triển khai rất phức tạp. AFAIK, hầu hết các triển khai sắp xếp hợp nhất không đúng chỗ, nhưng chúng ổn định.
MjrKusanagi

2

Heapsort có lợi ích là có trường hợp chạy xấu nhất là O (n * log (n)), vì vậy trong các trường hợp mà quicksort có khả năng hoạt động kém (chủ yếu là các tập dữ liệu được sắp xếp nói chung) thì heapsort được ưu tiên hơn nhiều.


4
Quicksort chỉ hoạt động kém trên tập dữ liệu đã được sắp xếp gần hết nếu phương pháp chọn trục xoay kém được chọn. Cụ thể, phương pháp chọn trục quay không tốt sẽ là luôn chọn phần tử đầu tiên hoặc cuối cùng làm trục quay. Nếu một trục xoay ngẫu nhiên được chọn mỗi lần và sử dụng một phương pháp tốt để xử lý các phần tử lặp lại, thì khả năng xảy ra trường hợp xấu nhất là rất nhỏ.
Justin Peel

1
@Justin - Điều đó rất đúng, tôi đã nói về một cách triển khai ngây thơ.
zellio

1
@Justin: Đúng, nhưng khả năng xảy ra giảm tốc lớn luôn có, tuy nhiên rất nhẹ. Đối với một số ứng dụng, tôi có thể muốn đảm bảo hành vi O (n log n), ngay cả khi nó chậm hơn.
David Thornley

2

Chà nếu bạn đi đến cấp độ kiến ​​trúc ... chúng tôi sử dụng cấu trúc dữ liệu hàng đợi trong bộ nhớ đệm. Vì vậy, những gì có sẵn trong hàng đợi sẽ được sắp xếp. sắp xếp (bằng cách sử dụng mảng) nó có thể xảy ra như vậy là cha mẹ có thể không có mặt trong mảng con có sẵn trong bộ nhớ cache và sau đó nó phải đưa nó vào bộ nhớ đệm ... điều này rất tốn thời gian. Đó là quicksort là tốt nhất !! 😀


1

Đống xây dựng một đống và sau đó liên tục trích xuất mục tối đa. Trường hợp xấu nhất của nó là O (n log n).

Nhưng nếu bạn thấy trường hợp xấu nhất của sắp xếp nhanh , đó là O (n2), bạn sẽ nhận ra rằng sắp xếp nhanh sẽ là một lựa chọn không tốt cho dữ liệu lớn.

Vì vậy, điều này làm cho việc sắp xếp là một điều thú vị; Tôi tin rằng lý do có rất nhiều thuật toán sắp xếp tồn tại ngày nay là vì tất cả chúng đều 'tốt nhất' ở những vị trí tốt nhất của chúng. Ví dụ: sắp xếp bong bóng có thể thực hiện sắp xếp nhanh nếu dữ liệu được sắp xếp. Hoặc nếu chúng ta biết điều gì đó về các mục được sắp xếp thì có lẽ chúng ta có thể làm tốt hơn.

Điều này có thể không trả lời trực tiếp câu hỏi của bạn, tôi nghĩ rằng tôi sẽ thêm hai xu của mình.


1
Không bao giờ sử dụng sắp xếp bong bóng. Nếu bạn nghĩ rằng dữ liệu của mình sẽ được sắp xếp một cách hợp lý, thì bạn có thể sử dụng sắp xếp chèn hoặc thậm chí kiểm tra dữ liệu để xem chúng có được sắp xếp hay không. Không sử dụng bubbleort.
vy32

nếu bạn có một tập dữ liệu NGẪU NHIÊN rất lớn, cách tốt nhất của bạn là nhanh chóng. Nếu được đặt hàng một phần thì không, nhưng nếu bạn bắt đầu làm việc với các bộ dữ liệu khổng lồ, bạn nên biết ít nhất điều này về chúng.
Kobor42 ngày

1

Heap Sort là một cược an toàn khi xử lý các đầu vào rất lớn. Phân tích tiệm cận cho thấy thứ tự phát triển của Heapsort trong trường hợp xấu nhất là Big-O(n logn), tốt hơn so với Quicksort trong Big-O(n^2)trường hợp xấu nhất. Tuy nhiên, Heapsort trên thực tế hơi chậm hơn so với một kiểu sắp xếp nhanh được triển khai tốt. Heapsort cũng không phải là một thuật toán sắp xếp ổn định.

Trong thực tế, lý do heapsort chậm hơn quicksort là do vị trí tham chiếu tốt hơn (" https://en.wikipedia.org/wiki/Locality_of_reference ") trong quicksort, nơi các phần tử dữ liệu nằm trong các vị trí lưu trữ tương đối gần. Các hệ thống thể hiện vị trí tham chiếu mạnh mẽ là ứng cử viên tuyệt vời để tối ưu hóa hiệu suất. Sắp xếp đống, tuy nhiên, giải quyết các bước nhảy vọt lớn hơn. Điều này làm cho nhanh chóng thuận lợi hơn cho các đầu vào nhỏ hơn.


2
Sắp xếp nhanh cũng không ổn định.
Antimony

1

Đối với tôi, có một sự khác biệt rất cơ bản giữa heapsort và quicksort: thứ hai sử dụng một đệ quy. Trong thuật toán đệ quy, đống tăng theo số lần đệ quy. Điều này không quan trọng nếu n nhỏ, nhưng ngay bây giờ tôi đang sắp xếp hai ma trận với n = 10 ^ 9 !!. Chương trình chiếm gần 10 GB ram và bất kỳ bộ nhớ bổ sung nào sẽ khiến máy tính của tôi bắt đầu hoán đổi sang bộ nhớ đĩa ảo. Đĩa của tôi là đĩa RAM, nhưng việc hoán đổi sang nó tạo ra sự khác biệt lớn về tốc độ . Vì vậy, trong một statpack được mã hóa bằng C ++ bao gồm ma trận kích thước có thể điều chỉnh, với kích thước chưa được lập trình viên biết trước và kiểu sắp xếp thống kê phi tham số, tôi thích heapsort để tránh chậm trễ khi sử dụng với ma trận dữ liệu rất lớn.


1
Trung bình bạn chỉ cần bộ nhớ O (logn). Chi phí đệ quy là không đáng kể, giả sử bạn không gặp may với các trục, trong trường hợp đó, bạn có những vấn đề lớn hơn cần lo lắng.
Antimony

-1

Để trả lời câu hỏi ban đầu và giải quyết một số ý kiến ​​khác tại đây:

Tôi chỉ so sánh việc triển khai lựa chọn, nhanh chóng, hợp nhất và sắp xếp đống để xem chúng sẽ xếp chồng lên nhau như thế nào. Câu trả lời là tất cả chúng đều có mặt trái của chúng.

TL; DR: Nhanh là cách sắp xếp có mục đích chung tốt nhất (khá nhanh, ổn định và chủ yếu là tại chỗ) Cá nhân tôi thích sắp xếp theo đống mặc dù trừ khi tôi cần một loại ổn định.

Lựa chọn - N ^ 2 - Nó thực sự chỉ tốt cho ít hơn 20 phần tử hoặc hơn, sau đó nó hoạt động tốt hơn. Trừ khi dữ liệu của bạn đã được sắp xếp, hoặc gần như vậy. N ^ 2 thực sự chậm rất nhanh.

Theo kinh nghiệm của tôi, nhanh chóng không phải lúc nào cũng nhanh như vậy. Tuy nhiên, phần thưởng cho việc sử dụng sắp xếp nhanh như là một phân loại chung là nó nhanh và ổn định. Nó cũng là một thuật toán tại chỗ, nhưng vì nó thường được triển khai một cách đệ quy, nó sẽ chiếm thêm không gian ngăn xếp. Nó cũng nằm ở đâu đó giữa O (n log n) và O (n ^ 2). Thời gian trên một số loại dường như xác nhận điều này, đặc biệt là khi các giá trị nằm trong một phạm vi hẹp. Nó nhanh hơn cách sắp xếp lựa chọn trên 10.000.000 mục, nhưng chậm hơn so với hợp nhất hoặc đống.

Sắp xếp hợp nhất được đảm bảo O (n log n) vì sắp xếp của nó không phụ thuộc vào dữ liệu. Nó chỉ làm những gì nó làm, bất kể bạn đã cho nó những giá trị nào. Nó cũng ổn định, nhưng các loại rất lớn có thể thổi bay ngăn xếp của bạn nếu bạn không cẩn thận trong việc triển khai. Có một số triển khai sắp xếp hợp nhất tại chỗ phức tạp, nhưng nhìn chung, bạn cần một mảng khác ở mỗi cấp để hợp nhất các giá trị của mình vào. Nếu những mảng đó nằm trên ngăn xếp, bạn có thể gặp sự cố.

Sắp xếp đống là tối đa O (n log n), nhưng trong nhiều trường hợp thì nhanh hơn, tùy thuộc vào mức độ bạn phải di chuyển các giá trị của mình lên đống sâu log n. Heap có thể dễ dàng được triển khai tại chỗ trong mảng ban đầu, vì vậy nó không cần thêm bộ nhớ và nó lặp đi lặp lại, do đó không phải lo lắng về việc tràn ngăn xếp trong khi đệ quy. Các lớn nhược điểm để đống loại là nó không phải là một ổn định sắp xếp, điều đó có nghĩa nó ra ngay nếu bạn cần điều đó.


Sắp xếp nhanh không phải là một loại ổn định. Ngoài ra, những câu hỏi có tính chất này khuyến khích phản hồi dựa trên quan điểm và có thể dẫn đến việc chỉnh sửa các cuộc chiến tranh và lập luận. Các câu hỏi kêu gọi phản hồi dựa trên ý kiến ​​rõ ràng không được khuyến khích theo hướng dẫn của SO. Người trả lời nên tránh bị cám dỗ trả lời ngay cả khi họ có kinh nghiệm và trí tuệ đáng kể. Gắn cờ cho họ để đóng hoặc đợi ai đó có đủ danh tiếng để gắn cờ và đóng họ. Nhận xét này không phản ánh kiến ​​thức của bạn hoặc tính hợp lệ của câu trả lời của bạn.
MikeC
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.