Các thuật toán tìm kiếm và sắp xếp năm 2024
Sắp xếp là một khái niệm mà chúng ta dễ dàng gặp trong cuộc sống cũng như trong công việc. Cùng lấy một vài ví dụ: Show
Trong Tin học, việc sắp xếp luôn luôn diễn ra không ngừng mà đôi khi chúng ta không nhận ra. Chẳng hạn, trong mỗi folder của máy tính, các tệp đều được sắp xếp theo thứ tự bảng chữ cái, và người dùng hoàn toàn có thể điều chỉnh việc sắp xếp theo những thứ tự khác nhau như: ngày tạo tệp, kích thước tệp,...Có đến 40%40\% thời gian tính toán của máy tính là dành cho việc sắp xếp. Mục tiêu của tất cả những việc này, không gì khác ngoài giúp cho dữ liệu được tổ chức một cách khoa học, ngăn nắp, dễ dàng tìm kiếm khi cần đến. Không phải ngẫu nhiên mà giải thuật Sắp xếp nhanh (Quick Sort) được bình chọn là một trong những giải thuật tiêu biểu nhất của thế kỷ XX. Trong chuyên đề này, chúng ta sẽ cùng xem xét bài toán sắp xếp các đối tượng trong một tập hợp cố định. Giả sử, ta có một tập hợp gồm nn phần tử a1,a2,...,an;a_1, a_2,..., a_n; mỗi phần tử có một giá trị ai.keya_i.key được gọi là khóa sắp xếp. Khi đó mỗi phần tử có thể được biểu diễn bằng một
2 hoặc
3, hoặc cũng có thể chỉ là các phần tử đơn lẻ thông thường với kiểu dữ liệu nguyên thủy. Nhiệm vụ đặt ra là cần sắp xếp mảng aa gồm nn đối tượng thành tập có giá trị khóa tăng dần: a1.key≤a2.key≤⋯≤an.keya_1.key \le a_2.key \le \cdots \le a_n.key II. Một số giải thuật sắp xếp thường gặp1. Sắp xếp nổi bọt (Bubble Sort)Ý tưởng: Xét các cặp phần tử của dãy, nếu như chúng đang đứng sai thứ tự (phần tử đứng trước lại có khóa lớn hơn phần tử đứng sau) thì đổi chỗ chúng, cho tới khi không còn cặp nào đứng sai thứ tự. Cài đặt mã giả C++:
Đánh giá độ phức tạp: Tại lượt duyệt thứ ii của vòng lặp bên ngoài, ta cần thực hiện n−in - i lần phép so sánh
4. Như vậy tổng số phép so sánh là: (n−1)+(n−2)+⋯+1=n.(n−1)2(n - 1) + (n - 2) + \cdots + 1 = \frac{n.(n - 1)}{2} Vậy giải thuật có độ phức tạp tương đương O(n2)O(n^2). Do đó, trong lập trình thi đấu giải thuật này chỉ phù hợp khi cần sắp xếp những tập hợp có khoảng 20002000 phần tử hoặc ít hơn, nếu nhiều hơn sẽ gây ra quá thời gian chạy (TLE). Ví dụ 1: Sắp xếp tăng dần mảng AA gồm nn số nguyên a1,a2,...,an?a_1, a_2,..., a_n?
Ví dụ 2: Một danh sách học sinh gồm nn học sinh, mỗi học sinh có 33 thông số: Tên, Lớp, Điểm tổng kết. Cần sắp xếp các học sinh này theo thứ tự tăng dần về Điểm tổng kết? Ta có thể biểu diễn danh sách học sinh bằng một mảng AA gồm nn phần tử kiểu
2 lưu ba thông số trên:
Kế đến, áp dụng giải thuật sắp xếp nổi bọt với khóa sắp xếp là trường \text{final_point} của mỗi phần tử:
2. Sắp xếp nhanh (Quick Sort)Giải thuật Sắp xếp nổi bọt chưa đủ tốt với các trường hợp tập sắp xếp có kích thước lớn. Trong trường hợp đó, ta cần một giải thuật tốt hơn, đó là Sắp xếp nhanh. Ý tưởng: Coi rằng ta đang cần sắp xếp một đoạn có chỉ số từ 11 tới nn trên mảng. Để sắp xếp một đoạn al...ra_{l...r} trong dãy, ta làm như sau:
Mã giả C++: Chọn amida_{mid} là phần tử ở giữa đoạn (l,r),(l, r), đồng thời duy trì hai biến i=l,j=ri = l, j = r. Chạy ii sang phải và jj sang trái, nếu phát hiện một cặp vị trí sai thứ tự: i≤ji \le j và ai.key≥amid.key≥aj.keya_i.key \ge a_{mid}.key \ge a_j.key thì hoán đổi vị trí của chúng. Duy trì làm như vậy tới khi i>j,i > j, rồi tiếp tục gọi đệ quy để sắp xếp hai đoạn con trái - phải:
Ở chương trình chính, khi cần sắp xếp một mảng thì chỉ cần gọi hàm
6 là được! Đánh giá độ phức tạp:
Ví dụ 1: Sắp xếp tăng dần mảng AA gồm nn phần tử số nguyên a1,a2,...,ana_1, a_2,..., a_n:
Ví dụ 2: Một danh sách hàng hóa gồm nn món hàng, mỗi món có hai thông số là Giá và Số lượng. Cần sắp xếp lại danh sách hàng hóa theo thứ tự tăng dần về giá, nếu hai món hàng có giá bằng nhau thì xếp chúng giảm dần theo số lượng? Với bài toán này, ta sử dụng một mảng AA gồm các phần tử kiểu
7 để kiểm soát Giá và Số lượng của các món hàng. Đồng thời, cần viết trước một hàm để kiểm tra xem một phần tử aia_i có đang nằm sai vị trí so với phần tử aja_j hay không? Coi rằng aia_i là phần tử đứng trước và aja_j là phần tử đứng sau, thì thứ tự đúng phải là: ai.first Sử dụng hàm trên để kiểm tra một cặp (ai,aj)(a_i, a_j) trong đoạn cần sắp xếp có nằm đúng vị trí so với amida_{mid} hay không, nếu không thì cần đổi chỗ chúng:
3. Sắp xếp bằng đếm phân phối (Counting Sort)3.1. Kĩ thuật đếm phân phốiĐếm phân phối là một kĩ thuật khá hữu ích khi cần đếm số lượng các phần tử trong một mảng. Tạo mảng freq[v]freq[v] là số lượng giá trị vv trong mảng AA gồm nn phần tử, ta xây dựng mảng này như sau:
Kĩ thuật này sử dụng được trong nhiều bài toán, với điều kiện phải tạo ra được một mảng freqfreq có kích thước mm - với mm là giá trị lớn nhất có thể trong mảng AA. Việc mm tối đa có thể bằng bao nhiêu sẽ phụ thuộc nhiều vào trình biên dịch, nhưng đối với C++ và kinh nghiệm của người viết, thì mm chỉ nên đạt tới khoảng 10810^8 với kiểu
8 và khoảng 10710^7 với kiểu
9 (điều này liên quan tới việc tính toán bộ nhớ khi khai báo mảng, tổng bộ nhớ sử dụng trong cả bài không được vượt quá giới hạn bộ nhớ của đề bài cho phép - thông thường là 512MB512MB). 3.2. Áp dụng đếm phân phối trong sắp xếpBây giờ, ứng dụng đếm phân phối, ta sẽ đếm các giá trị khóa của dãy AA bằng mảng freq[ai.key]freq[a_i.key]. Giả sử các khóa sắp xếp khác nhau trong dãy lần lượt là 0,1,2,...,k0, 1, 2,..., k thì sau khi sắp xếp, ta có:
Cài đặt mã giả C++:
Đánh giá độ phức tạp: Giải thuật sắp xếp bằng đếm phân phối sẽ có độ phức tạp là O(max(n,k)),O\big(\text{max}(n, k)\big), do tổng số lần thực hiện cập nhật lại các phần tử trên mảng chỉ là nn lần, nhưng ta phải duyệt qua kk khóa khác nhau. Đây là một giải thuật tốt trong trường hợp các khóa sắp xếp trong mảng không quá lớn. Nếu như khóa quá lớn, không thể khởi tạo được mảng tần số thì ta cần áp dụng tới kĩ thuật Rời rạc hóa để "nén" các khóa lại thành nhỏ hơn hoặc bằng n,n, kĩ thuật này sẽ được đề cập ở một chuyên đề khác. Ví dụ 1: Sắp xếp mảng số nguyên dương AA gồm nn phần tử có giá trị không vượt quá 10610^6 theo thứ tự tăng dần?
0 Ví dụ 2: Cho một dãy kí tự SS chỉ gồm toàn các chữ cái latin in thường, hãy tìm ra các kí tự xuất hiện nhiều lần nhất trong dãy? Nhận xét ở bài này, khóa của các phần tử là các kí tự. Mảng đếm phân phối không thể dùng trực tiếp chỉ số là các kí tự, nhưng ta có thể mã hóa các kí tự ấy thành các con số dựa trên số hiệu ASCIIASCII của từng kí tự. Vậy chỉ cần khai báo một mảng freq[26]freq[26] và mã hóa các kí tự thành số hiệu của chúng sau khi trừ đi số hiệu của kí tự
0:
1 III. Hàm sắp xếp trong thư viện STL C++Trong thư viện STL của C++ cung cấp sẵn một hàm sắp xếp sử dụng giải thuật Quick Sort. Để sử dụng hàm này, các bạn cần khai báo thư viện
1 và không gian tên
2. Dưới đây là hướng dẫn sử dụng hàm chi tiết: 1. Hàm sắp xếp cơ bảnThư viện thuật toán cung cấp một hàm sắp xếp có thể sắp xếp các kiểu dữ liệu bao gồm số, kí tự, chuỗi kí tự và cả các kiểu dữ liệu tự định nghĩa của người dùng. Cú pháp như sau:
2 Trong đó, ll và rr là hai biến trỏ vào địa chỉ của phần tử đầu và phần tử cuối trong đoạn cần sắp xếp. Hàm sẽ sắp xếp toàn bộ các phần tử thuộc đoạn [l,r−1][l, r - 1]. Tuy nhiên hàm
3 sẽ đứng đơn lẻ chứ không đi kèm các câu lệnh khác. Mặc định hàm
3 sẽ sắp xếp các phần tử trong đoạn cần sắp xếp theo thứ tự tăng dần (số hoặc kí tự theo đúng quy tắc riêng của mỗi kiểu dữ liệu). Ví dụ:
3 Kết quả chạy chương trình trên như sau:
4 2. Tùy biến việc sắp xếp theo ý thíchHàm sắp xếp thực tế còn có một tham số thứ ba, dùng để điều chỉnh việc sắp xếp theo ý muốn của người dùng. Cú pháp dạng này của hàm sắp xếp là:
5 Trong đó,
5 là một hàm kiểu
6 do người dùng tự định nghĩa, hoặc là một trong hai từ khóa thể hiện phép so sánh:
7 hoặc
8. 2.1. Sử dụng 2 phép toán
7 và
8 Hai từ khóa
7 và
8 thể hiện cho hai phép toán sắp xếp tăng dần hoặc giảm dần (thực ra chính là thể hiện của các toán tử
3 và
4), khi muốn điều chỉnh cách sắp xếp ta chỉ cần thêm hai phép toán này vào tham số thứ ba của hàm sắp xếp theo cú pháp:
6 Trong đó, {Kiểu_phần_tử} là kiểu dữ liệu của các phần tử trong tập hợp cần sắp xếp. Ví dụ:
7 Kết quả chạy chương trình trên như sau:
8 Lưu ý:
2.2. Sử dụng hàm sắp xếp tự định nghĩaKhi muốn sắp xếp theo những cách riêng, ví dụ như sắp xếp các số chẵn ra phía đầu, số lẻ ra phía cuối, hoặc khi kiểu dữ liệu của tập cần sắp xếp là những kiểu dữ liệu do người dùng tự định nghĩa, ta có thể tự viết ra một hàm
5 dùng làm tham số thứ ba cho hàm
7. Cú pháp như sau:
9 Trong đó, {Tham_số_thứ_nhất} đại diện cho phần tử đứng trước, {Tham_số_thứ_hai} đại diện cho phần tử đứng sau trong dãy. Hàm
3 sẽ tự động sắp xếp lại các phần tử theo thứ tự bạn quy định giống như hai tham số này. Lấy ví dụ, nếu ta muốn sắp xếp một dãy số nguyên giảm dần, bạn cũng có thể viết như sau: Thuật toán sắp xếp chọn tìm kiếm thế nào?Sắp xếp chọn là một thuật toán sắp xếp đơn giản, dựa trên việc so sánh tại chỗ. Chọn phần tử nhỏ nhất trong n phần tử ban đầu, đưa phần tử này về vị trí đúng là đầu tiên của dãy hiện hành. Sau đó không quan tâm đến nó nữa, xem dãy hiện hành chỉ còn n-1 phần tử của dãy ban đầu, bắt đầu từ vị trí thứ 2. Thế nào là thuật toán sắp xếp?Trong khoa học máy tính và trong toán học, thuật toán sắp xếp là một thuật toán sắp xếp các phần tử của một danh sách (hoặc một mảng) theo thứ tự (tăng hoặc giảm). Người ta thường xét trường hợp các phần tử cần sắp xếp là các số. Bài toán sắp xếp đã được nhiều nhà khoa học quan tâm. Thuật toán sắp xếp một dãy các so nguyên theo thứ tự tăng dần là thuật toán gì?Selection Sort Algorithm (sắp xếp chọn lọc): Thuật toán này sẽ liên tục chọn phần tử hay dữ liệu có giá trị nhỏ và sẽ đưa nó lên đầu, và sẽ tiếp tục lặp lại việc chọn phần tử để sắp xếp dãy số theo thứ tự tăng dần. Tại mỗi bước sắp xếp của phương pháp sắp xếp nổi bọt đậy so được duyệt bắt đầu từ đâu?Bắt đầu từ vị trí đầu tiên của danh sách (bên trái), so sánh các cặp số với nhau, nếu không đúng thứ tự nhỏ-lớn thì đảo vị trí. Sau khi chạy tới cuối danh sách, tiếp tục chạy lại từ vị trí đầu danh sách cho đến khi hoàn thành so sánh và đảo vị trí. |