Cấu trúc dữ liệu và giải thuật: Giải thuật sắp xếp lựa chọn (Selection sort)

Giải thuật sắp xếp lựa chọn (Selection sort)

1. Ý tưởng thuật toán

Trong dãy gồm n phần tử, tìm phần tử nhỏ nhất (min) và đưa nó lên đầu dãy, rồi “lờ” phần tử này đi. Lúc này, dãy hiện hành là phần còn lại sau khi bỏ qua phần tử bị lờ. Tiếp tục tìm phần tử min trong dãy hiện hành gồm n-1 phần tử, rồi lại đưa nó lên đầu và “lờ” nó đi. Lặp lại thao tác cho đến khi dãy đang xét chỉ còn 1 phần tử, cũng chính là phần tử lớn nhất/ nhỏ nhất (tùy vào sắp xếp tăng hay giảm dần).

Ta sử dụng khái niệm nghịch thế để tìm phần tử nhỏ nhất và sắp xếp chúng.

2. Nghịch thế

Cho dãy số a[0], a[1], a[2], …, a[n]. Gọi i, j là các chỉ số của 2 phần tử a[i], a[j]. Nếu có i<j và a[i]>a[j] thì ta gọi đây là 1 nghịch thế.

Để tìm được phần tử min, nhất thiết ta phải so sánh phần tử min với các phần tử còn lại của dãy, rồi đổi chỗ 2 phần tử. Hành động này nhằm hủy nghịch thế.

3. Cơ chế hoạt động

Cho dãy số: | 2 | 5 | 3 | 1 | 4 |. Sắp xếp dãy số tăng dần.

  • Bước 1: Xét dãy hiện hành: | 2 | 5 | 3 | 1 | 4 |
    min = 1, đổi chỗ với phần tử đầu của dãy hiện hành, ta được dãy mới: | 1 | 5 | 3 | 2 | 4 |

  • Bước 2: Xét dãy hiện hành: | 5 | 3 | 2 | 4 |
    min = 2, đổi chỗ với phần tử đầu của dãy hiện hành, ta được dãy mới: | 2 | 3 | 5 | 4 |

  • Bước 3: Xét dãy hiện hành: | 3 | 5 | 4 |
    min = 3, đổi chỗ với phần tử đầu của dãy hiện hành, ta được dãy mới: | 3 | 5 | 4 |

  • Bước 4: Xét dãy hiện hành: | 5 | 4 |
    min = 4, đổi chỗ với phần tử đầu của dãy hiện hành, ta được dãy mới: | 4 | 5 |

  • Bước 5: Xét dãy hiện hành: | 5 |
    min = 5, cũng chính là phần tử cuối cùng và lớn nhất, kết thúc.

Dãy ban đầu sau khi thực hiện thuật toán: | 1 | 2 | 3 | 4 | 5 |

4. Các bước của thuật toán

Dãy gồm n phần tử

  • Bước 1: i = 0;
  • Bước 2: Tìm phần tử a[min] nhỏ nhất trong dãy hiện hành từ a[i] đến a[n]
  • Bước 3: Đổi chỗ a[min] và a[i]
  • Bước 4:
    +Nếu i < n - 1 thì i = i + 1; Lặp lại Bước 2;
    +Ngược lại: Dừng.

5. Cài đặt (C++)

void selectionSort(int a[], int n) {
    int min; //dùng để lưu vị trí phần tử nhỏ nhất
    for (int i = 0; i < n - 1; i++) {
        min = i;
        for(int j = i + 1; j < n; j++)
            if(a[j] < a[min])
                min = j;
        int t = a[i];
        a[i] = a[min]; 
        a[min] = t;
    }
}

4. Độ phức tạp của thuật toán

Mỗi bước i luôn cần n - i so sánh để tìm phần tử nhỏ nhất ở dãy hiện hành. Biểu thức tính n - i không phụ thuộc vào tình trạng của dãy đã cho, do đó, trong mọi trường hợp ta có độ phức tạp của thuật toán là:
z3482079206089_395b72b3f6f9be59b26d7439036faea9
Mỗi một hoán vị dùng 3 phép gán. Số phép gán lại phụ thuộc vào trạng thái ban đầu của dãy đã cho. Do đó ta chỉ có thể ước lượng trong 2 tình huống:

  • Tốt nhất: số lần so sánh là n(n-1)/2, số phép gán là 0.
  • Xấu nhất: số lần so sánh là n(n-1)/2, số phép gán là 3n(n-1)/2
2 Likes

Cảm ơn cậu đã chia sẻ thuật toán nhé! :smile:

Về bài viết, tớ không có gì để phàn nàn cả. Cơ mà, tớ rất muốn nếu có thời gian, cậu có thể bổ sung thêm một vài phân tích/đánh giá về thuật toán, chẳng hạn như độ phức tạp thời gian, bộ nhớ (trung bình và TH tốt nhất). Tớ nghĩ nó sẽ rất có ích để mọi người hiểu sâu về giải thuật :smile:

4 Likes

Mình nhớ rồi :smile:

2 Likes
83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?