Tìm hiểu về sắp xếp mảng trong JavaScript

Javascript căn bản | by Học Javascript

Trong lập trình, việc sắp xếp dữ liệu đóng vai trò quan trọng giúp tổ chức, tìm kiếm và xử lý thông tin hiệu quả. JavaScript cung cấp nhiều phương pháp để sắp xếp mảng, từ các phương thức tích hợp sẵn như sort()reverse(), đến các thuật toán sắp xếp nâng cao như Bubble Sort, Quick Sort, Merge Sort,... Việc hiểu và sử dụng đúng cách các phương thức sắp xếp sẽ giúp tối ưu hiệu suất chương trình, đặc biệt khi làm việc với tập dữ liệu lớn.

Trong bài viết này, mình sẽ tìm hiểu chi tiết về các phương pháp sắp xếp mảng trong JavaScript, cách sử dụng chúng và những lưu ý quan trọng khi thao tác với mảng.

Sắp xếp mảng trong JavaScript

Sắp xếp mảng (Array Sorting) là quá trình sắp xếp các phần tử trong mảng theo một thứ tự cụ thể, chẳng hạn như:

  • Tăng dần (Ascending Order): Sắp xếp từ nhỏ đến lớn (1 → 2 → 3 hoặc A → B → C).
  • Giảm dần (Descending Order): Sắp xếp từ lớn đến nhỏ (3 → 2 → 1 hoặc Z → Y → X).
  • Sắp xếp tùy chỉnh: Dựa trên tiêu chí cụ thể (ví dụ: sắp xếp theo độ dài chuỗi, theo giá trị thuộc tính của đối tượng).

Tại sao cần sắp xếp mảng?

Việc sắp xếp mảng có vai trò quan trọng trong lập trình, giúp:

  • Truy xuất dữ liệu nhanh hơn: Khi dữ liệu được sắp xếp, việc tìm kiếm và xử lý sẽ trở nên dễ dàng và hiệu quả hơn.
  • Cải thiện hiển thị dữ liệu: Khi hiển thị danh sách dữ liệu như bảng xếp hạng, danh sách sản phẩm, hoặc danh sách điểm số, sắp xếp giúp dữ liệu có cấu trúc và dễ đọc hơn.
  • Hỗ trợ tìm kiếm nhị phân (Binary Search): Tìm kiếm nhị phân hoạt động hiệu quả hơn trên mảng đã được sắp xếp, giúp giảm thời gian tìm kiếm từ O(n) xuống O(log n).
  • Tối ưu hóa thuật toán: Một số thuật toán như tìm kiếm, lọc hoặc gom nhóm dữ liệu hoạt động nhanh hơn khi dữ liệu đã được sắp xếp.

Các phương pháp sắp xếp phổ biến trong JavaScript

JavaScript cung cấp hai cách chính để sắp xếp mảng:

Sử dụng phương thức có sẵn của JavaScript

  • sort(): Phương thức này sắp xếp mảng theo thứ tự mặc định hoặc theo một điều kiện tùy chỉnh do người dùng định nghĩa.
  • reverse(): Đảo ngược thứ tự của mảng, thường được sử dụng sau sort() để chuyển từ sắp xếp tăng dần sang giảm dần.

Viết thuật toán sắp xếp thủ công

Khi cần kiểm soát quá trình sắp xếp hoặc tối ưu hóa hiệu suất, có thể áp dụng các thuật toán sắp xếp phổ biến:

  • Bubble Sort (Sắp xếp nổi bọt): So sánh từng cặp phần tử liên tiếp và hoán đổi vị trí nếu cần.
  • Selection Sort (Sắp xếp chọn): Tìm phần tử nhỏ nhất/lớn nhất và đặt vào vị trí tương ứng.
  • Insertion Sort (Sắp xếp chèn): Chèn từng phần tử vào vị trí thích hợp trong danh sách đã được sắp xếp.
  • Merge Sort (Sắp xếp trộn): Chia nhỏ mảng thành các phần rồi ghép lại theo thứ tự.
  • Quick Sort (Sắp xếp nhanh): Chọn một phần tử làm “pivot”, sau đó chia mảng thành hai phần rồi sắp xếp đệ quy.

Mỗi thuật toán có ưu và nhược điểm riêng, tùy thuộc vào kích thước và đặc điểm của dữ liệu mà ta lựa chọn cách sắp xếp phù hợp.

Phương thức sắp xếp có sẵn trong JavaScript

JavaScript cung cấp hai phương thức chính để sắp xếp mảng: sort()reverse(). Đây là những phương thức giúp lập trình viên dễ dàng sắp xếp dữ liệu mà không cần tự viết thuật toán sắp xếp thủ công.

Phương thức sort() - Sắp xếp mảng theo thứ tự mong muốn

Cách hoạt động của sort()

  • Mặc định, sort() sắp xếp các phần tử trong mảng theo thứ tự chữ cái Unicode (dạng chuỗi).
  • Để sắp xếp theo số hoặc điều kiện tùy chỉnh, ta cần truyền một hàm so sánh (callback function) vào phương thức sort().

Sắp xếp theo thứ tự mặc định (Unicode - dạng chuỗi)

Khi không truyền hàm so sánh, sort() sẽ chuyển các phần tử thành chuỗi và sắp xếp theo thứ tự Unicode. Điều này có thể gây ra kết quả không mong muốn khi sắp xếp số.

Ví dụ:

let arr = [25, 3, 100, 7, 50];
arr.sort();
console.log(arr); // Kết quả: [100, 25, 3, 50, 7] (Sai mong đợi)

Giải thích:

  • Do sort() sắp xếp theo thứ tự chuỗi, nên "100" được xem xét trước "25""1" nhỏ hơn "2".
  • Điều này không đúng với kiểu số, vì vậy cần truyền vào một hàm so sánh.

Sắp xếp số tăng dần và giảm dần

Để sắp xếp số đúng cách, ta truyền vào sort() một hàm so sánh:

Tăng dần (Ascending Order):

let arr = [25, 3, 100, 7, 50];
arr.sort((a, b) => a - b);
console.log(arr); // Kết quả: [3, 7, 25, 50, 100]

Giảm dần (Descending Order):

let arr = [25, 3, 100, 7, 50];
arr.sort((a, b) => b - a);
console.log(arr); // Kết quả: [100, 50, 25, 7, 3]

Giải thích:

  • a - b: Nếu a nhỏ hơn b, giá trị trả về âm → a sẽ đứng trước b.
  • b - a: Nếu b nhỏ hơn a, giá trị trả về âm → b sẽ đứng trước a.

Sắp xếp chuỗi theo thứ tự bảng chữ cái (A → Z, Z → A)

Mặc định, sort() đã sắp xếp chuỗi theo bảng chữ cái. Nhưng để đảm bảo đúng với các ký tự có dấu, ta có thể dùng localeCompare().

Sắp xếp tăng dần (A → Z):

let names = ["Hồng", "An", "Đức", "Bình"];
names.sort((a, b) => a.localeCompare(b));
console.log(names); // Kết quả: ["An", "Bình", "Đức", "Hồng"]

Sắp xếp giảm dần (Z → A):

let names = ["Hồng", "An", "Đức", "Bình"];
names.sort((a, b) => b.localeCompare(a));
console.log(names); // Kết quả: ["Hồng", "Đức", "Bình", "An"]

Sắp xếp mảng đối tượng theo thuộc tính

Khi làm việc với mảng đối tượng, ta có thể sắp xếp theo một thuộc tính cụ thể.

Ví dụ: Sắp xếp danh sách sinh viên theo điểm số:

let students = [
    { name: "Nam", score: 85 },
    { name: "Hùng", score: 92 },
    { name: "Mai", score: 78 }
];

// Sắp xếp tăng dần theo điểm số
students.sort((a, b) => a.score - b.score);
console.log(students);

// Sắp xếp giảm dần theo điểm số
students.sort((a, b) => b.score - a.score);
console.log(students);

Phương thức reverse() - Đảo ngược thứ tự phần tử trong mảng

Cách hoạt động của reverse()

  • reverse() dùng để đảo ngược thứ tự của các phần tử trong mảng.
  • Phương thức này thay đổi trực tiếp mảng gốc chứ không tạo mảng mới.

Sử dụng reverse() đơn giản

let arr = [1, 2, 3, 4, 5];
arr.reverse();
console.log(arr); // Kết quả: [5, 4, 3, 2, 1]

Sắp xếp giảm dần bằng sort() kết hợp reverse()

Thay vì viết sort((a, b) => b - a), ta có thể dùng reverse() sau khi sắp xếp tăng dần:

let arr = [25, 3, 100, 7, 50];
arr.sort((a, b) => a - b).reverse();
console.log(arr); // Kết quả: [100, 50, 25, 7, 3]

Tuy nhiên, cách này kém tối ưu hơn sort((a, b) => b - a).

Lưu ý quan trọng khi sử dụng sort() và reverse()

sort() thay đổi mảng gốc

let arr = [3, 1, 4];
arr.sort();
console.log(arr); // Kết quả: [1, 3, 4]

Nếu cần giữ nguyên mảng gốc, hãy tạo bản sao trước:

let originalArr = [3, 1, 4];
let sortedArr = [...originalArr].sort();
console.log(sortedArr);  // [1, 3, 4]
console.log(originalArr); // [3, 1, 4]

Cần truyền hàm so sánh khi sắp xếp số
Nếu không, sort() có thể cho kết quả sai do sắp xếp theo kiểu chuỗi.

reverse() thay đổi trực tiếp mảng
Nếu muốn giữ nguyên mảng gốc, hãy tạo bản sao trước khi dùng reverse().

Sắp xếp số và chuỗi trong JavaScript

Khi làm việc với JavaScript, việc sắp xếp dữ liệu là một trong những thao tác phổ biến, đặc biệt là khi sắp xếp số hoặc chuỗi trong mảng. JavaScript cung cấp phương thức sort() để thực hiện điều này, nhưng có một số lưu ý quan trọng để đảm bảo sắp xếp đúng cách.

Sắp xếp số trong mảng

Mặc định, sort() sắp xếp theo chuỗi Unicode, không phải số. Do đó, khi sắp xếp số, nếu không truyền vào một hàm so sánh, kết quả có thể sai.

Sắp xếp số tăng dần

Để sắp xếp các số theo thứ tự tăng dần (từ nhỏ đến lớn), ta truyền vào sort() một hàm so sánh với công thức:

array.sort((a, b) => a - b);

Ví dụ:

let numbers = [25, 3, 100, 7, 50];
numbers.sort((a, b) => a - b);
console.log(numbers); // Kết quả: [3, 7, 25, 50, 100]

Giải thích:

  • Nếu a - b trả về số âm (tức a nhỏ hơn b), a được giữ nguyên trước b.
  • Nếu a - b trả về số dương (tức a lớn hơn b), a sẽ được đổi chỗ với b.

Sắp xếp số giảm dần

Để sắp xếp số theo thứ tự giảm dần (từ lớn đến nhỏ), ta chỉ cần đảo ngược thứ tự phép trừ trong hàm so sánh:

array.sort((a, b) => b - a);

Ví dụ:

let numbers = [25, 3, 100, 7, 50];
numbers.sort((a, b) => b - a);
console.log(numbers); // Kết quả: [100, 50, 25, 7, 3]

Giải thích:

  • Nếu b - a trả về số âm (tức b nhỏ hơn a), b sẽ được giữ nguyên trước a.
  • Nếu b - a trả về số dương (tức b lớn hơn a), b sẽ được đổi chỗ với a.

Sắp xếp chuỗi trong mảng trong JavaScript

Mặc định, phương thức sort() trong JavaScript sắp xếp các phần tử theo thứ tự chuỗi Unicode. Điều này hoạt động tốt với các chuỗi đơn giản, nhưng nếu có dấu hoặc ngôn ngữ đặc biệt, cần dùng localeCompare().

Sắp xếp chuỗi theo thứ tự bảng chữ cái (A → Z)

Mặc định, sort() sắp xếp chuỗi theo thứ tự chữ cái (Unicode).

Ví dụ:

let fruits = ["Banana", "Apple", "Orange", "Mango"];
fruits.sort();
console.log(fruits); // Kết quả: ["Apple", "Banana", "Mango", "Orange"]

Sắp xếp chuỗi theo thứ tự ngược lại (Z → A)

Để sắp xếp chuỗi theo thứ tự ngược lại (Z → A), có hai cách:

  • Dùng reverse() sau sort()
  • Dùng hàm so sánh localeCompare()

Cách 1: Dùng reverse()

let fruits = ["Banana", "Apple", "Orange", "Mango"];
fruits.sort().reverse();
console.log(fruits); // Kết quả: ["Orange", "Mango", "Banana", "Apple"]

Cách 2: Dùng hàm so sánh với sort()

let fruits = ["Banana", "Apple", "Orange", "Mango"];
fruits.sort((a, b) => b.localeCompare(a));
console.log(fruits); // Kết quả: ["Orange", "Mango", "Banana", "Apple"]

Sắp xếp chuỗi có dấu theo đúng thứ tự bảng chữ cái

Mặc định, sort() không xử lý tốt các chuỗi có dấu tiếng Việt hoặc các ngôn ngữ đặc biệt. Ta cần dùng localeCompare() để đảm bảo sắp xếp đúng.

Ví dụ:

let names = ["Hồng", "An", "Đức", "Bình"];
names.sort((a, b) => a.localeCompare(b));
console.log(names); // Kết quả: ["An", "Bình", "Đức", "Hồng"]

Giải thích:

  • localeCompare() giúp sắp xếp theo đúng bảng chữ cái của ngôn ngữ hiện tại.
  • Nếu không dùng localeCompare(), JavaScript có thể không xếp đúng thứ tự các ký tự đặc biệt.

Sắp xếp mảng đối tượng (Object Array Sorting)

Trong JavaScript, khi làm việc với danh sách dữ liệu phức tạp như danh sách sản phẩm, danh sách nhân viên, hoặc danh sách người dùng, ta thường cần sắp xếp dựa trên một thuộc tính cụ thể trong object.

Dưới đây là cách sắp xếp mảng object theo số và chuỗi.

Sắp xếp theo một thuộc tính số trong object trong JavaScript

Nếu object chứa thuộc tính dạng số (ví dụ: giá sản phẩm, tuổi nhân viên), ta có thể sử dụng phương thức sort() kết hợp với một hàm so sánh.

Sắp xếp danh sách sản phẩm theo giá (price)

Dữ liệu ban đầu:

let products = [
    { name: "Laptop", price: 1200 },
    { name: "Smartphone", price: 800 },
    { name: "Tablet", price: 500 },
    { name: "Monitor", price: 300 }
];

Sắp xếp theo giá tăng dần

Để sắp xếp danh sách theo giá từ thấp đến cao, ta sử dụng:

products.sort((a, b) => a.price - b.price);
console.log(products);

Kết quả:

[
    { name: "Monitor", price: 300 },
    { name: "Tablet", price: 500 },
    { name: "Smartphone", price: 800 },
    { name: "Laptop", price: 1200 }
]

Sắp xếp theo giá giảm dần

Để sắp xếp theo giá từ cao đến thấp, chỉ cần đảo ngược phép trừ:

products.sort((a, b) => b.price - a.price);
console.log(products);

Kết quả:

[
    { name: "Laptop", price: 1200 },
    { name: "Smartphone", price: 800 },
    { name: "Tablet", price: 500 },
    { name: "Monitor", price: 300 }
]

Giải thích:

  • a.price - b.price: Nếu a.price nhỏ hơn b.price, thì a đứng trước b.
  • b.price - a.price: Nếu b.price nhỏ hơn a.price, thì b đứng trước a.

Sắp xếp theo chuỗi trong object trong JavaScript

Nếu muốn sắp xếp danh sách theo một thuộc tính chuỗi (ví dụ: tên sản phẩm, tên người dùng), ta sử dụng localeCompare().

Sắp xếp danh sách người dùng theo tên (name)

Dữ liệu ban đầu:

let users = [
    { name: "Linh", age: 25 },
    { name: "Anh", age: 30 },
    { name: "Bình", age: 22 },
    { name: "Đức", age: 27 }
];

Sắp xếp theo tên A → Z (Alphabetically)

users.sort((a, b) => a.name.localeCompare(b.name));
console.log(users);

Kết quả:

[
    { name: "Anh", age: 30 },
    { name: "Bình", age: 22 },
    { name: "Đức", age: 27 },
    { name: "Linh", age: 25 }
]

Sắp xếp theo tên Z → A (Ngược bảng chữ cái)

users.sort((a, b) => b.name.localeCompare(a.name));
console.log(users);

Kết quả:

[
    { name: "Linh", age: 25 },
    { name: "Đức", age: 27 },
    { name: "Bình", age: 22 },
    { name: "Anh", age: 30 }
]

Giải thích:

  • a.name.localeCompare(b.name): Sắp xếp theo thứ tự bảng chữ cái.
  • b.name.localeCompare(a.name): Sắp xếp ngược lại.

Sắp xếp theo nhiều tiêu chí

Đôi khi, ta cần sắp xếp theo nhiều thuộc tính, ví dụ:

  • Sắp xếp theo giá tăng dần, nếu giá bằng nhau thì sắp xếp theo tên A → Z.

Ví dụ:

products.sort((a, b) => {
    if (a.price === b.price) {
        return a.name.localeCompare(b.name);
    }
    return a.price - b.price;
});
console.log(products);
Loại sắp xếp Cách sử dụng
Sắp xếp số tăng dần array.sort((a, b) => a.property - b.property);
Sắp xếp số giảm dần array.sort((a, b) => b.property - a.property);
Sắp xếp chuỗi A → Z array.sort((a, b) => a.property.localeCompare(b.property));
Sắp xếp chuỗi Z → A array.sort((a, b) => b.property.localeCompare(a.property));
Sắp xếp theo nhiều tiêu chí Dùng if bên trong sort() để kiểm tra điều kiện

Lưu ý:

  • sort() thay đổi trực tiếp mảng gốc. Nếu muốn giữ nguyên mảng ban đầu, hãy sao chép mảng trước bằng [...array].sort(...).
  • localeCompare() đảm bảo sắp xếp đúng chuẩn bảng chữ cái, nhất là với chuỗi có dấu.

Các thuật toán sắp xếp thủ công trong JavaScript

Ngoài phương thức sort() có sẵn trong JavaScript, ta có thể tự triển khai các thuật toán sắp xếp phổ biến. Dưới đây là năm thuật toán sắp xếp quan trọng, kèm theo mô tả, cách hoạt động và mã nguồn minh họa.

Bubble Sort (Sắp xếp nổi bọt)

  • Lặp qua mảng nhiều lần, so sánh từng cặp phần tử liền kề.

  • Nếu phần tử trước lớn hơn phần tử sau, đổi chỗ chúng.
  • Lặp lại quá trình này cho đến khi không còn sự thay đổi nào nữa.

Dễ hiểu, dễ triển khai.
Rất chậm với mảng lớn vì có thời gian chạy O(n²).

function bubbleSort(arr) {
    let n = arr.length;
    let swapped;
    do {
        swapped = false;
        for (let i = 0; i < n - 1; i++) {
            if (arr[i] > arr[i + 1]) {
                [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]]; // Hoán đổi
                swapped = true;
            }
        }
        n--; // Sau mỗi lần lặp, phần tử lớn nhất sẽ được đặt đúng vị trí
    } while (swapped);
    return arr;
}

console.log(bubbleSort([5, 3, 8, 4, 2]));

Selection Sort (Sắp xếp chọn)

  • Duyệt toàn bộ mảng và tìm phần tử nhỏ nhất.
  • Đổi phần tử nhỏ nhất với phần tử đầu tiên.
  • Tiếp tục với phần còn lại của mảng.

Tốt hơn Bubble Sort nhưng vẫn chậm (O(n²)).
Không cần bộ nhớ phụ vì chỉ đổi chỗ trực tiếp.

function selectionSort(arr) {
    let n = arr.length;
    for (let i = 0; i < n - 1; i++) {
        let minIndex = i;
        for (let j = i + 1; j < n; j++) {
            if (arr[j] < arr[minIndex]) {
                minIndex = j;
            }
        }
        if (minIndex !== i) {
            [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]; // Hoán đổi
        }
    }
    return arr;
}

console.log(selectionSort([5, 3, 8, 4, 2]));

Thuật toán Tốt nhất Trung bình Tệ nhất Ưu điểm Nhược điểm
Bubble Sort O(n) O(n²) O(n²) Dễ triển khai Chậm với mảng lớn
Selection Sort O(n²) O(n²) O(n²) Ít đổi chỗ, dễ hiểu Chậm hơn Insertion Sort
Insertion Sort O(n) O(n²) O(n²) Nhanh với mảng nhỏ Chậm với mảng lớn
Merge Sort O(n log n) O(n log n) O(n log n) Ổn định, hiệu quả với dữ liệu lớn Cần bộ nhớ phụ
Quick Sort O(n log n) O(n log n) O(n²) Nhanh nhất trong thực tế

Pivot chọn sai có thể chậm

Kết bài

Sắp xếp mảng là một kỹ thuật quan trọng trong lập trình, giúp tổ chức dữ liệu theo thứ tự mong muốn, từ đó tối ưu hóa quá trình tìm kiếm và truy xuất thông tin. JavaScript cung cấp phương thức sort() để sắp xếp mảng một cách nhanh chóng, nhưng trong một số trường hợp, việc sử dụng các thuật toán sắp xếp thủ công như Bubble Sort, Selection Sort, Insertion Sort, Merge Sort, Quick Sort sẽ giúp kiểm soát tốt hơn hiệu suất và bộ nhớ.

Mỗi thuật toán sắp xếp có ưu nhược điểm riêng, do đó, việc lựa chọn thuật toán phù hợp phụ thuộc vào kích thước dữ liệu, yêu cầu hiệu suất, và tính ổn định của sắp xếp. Trong thực tế, Quick Sort và Merge Sort là hai thuật toán thường được sử dụng do hiệu suất cao và ổn định trên dữ liệu lớn.

Việc hiểu rõ các phương pháp sắp xếp không chỉ giúp lập trình viên tối ưu mã nguồn mà còn nâng cao kỹ năng giải quyết vấn đề khi làm việc với dữ liệu phức tạp trong các ứng dụng thực tế.

Bài viết liên quan

  • 2