排序(sorting)

什么是 排序(sorting) 算法?给定一个 n个元素的线性表 (a_0, a_1, …, a_{n-1}),排序算法返回这个序列重新排列的结果 (A_0, A_1, …, A_{n-1}),满足A0≤A​1≤…≤A​n−1 。这里的小于等于号可以替换为其他比较符号。

比如,对于一个线性表 (5, 2, 3, 7, 6),排序算法会返回 (2, 3, 5, 6, 7)。当然,也可以是 (7, 6, 5, 3, 2)。

计算机科学发展至今,已经出现了许多种不同的排序算法。在本章的课程中,我们会着重介绍 插入排序(insertion sort)、冒泡排序(bubble sort)、归并排序(merge sort)、选择排序(selection sort) 和 快速排序(quick sort) 这 5种排序算法。

对于排序算法,有很多种分类方法。

根据算法的时间复杂度,可以将排序算法分为复杂度为 O(nlogn)、O(n) 或 O(n^2)等时间复杂度的排序算法。比如 O(n)的 基数排序(radix sort)、O(nlogn) 的归并排序、O(n^2) 的冒泡排序。

根据排序过程中元素是否完全保存在内存中,可以将算法分为 内部排序(internal sort) 和 外部排序(external sort)。本章介绍的这 55 种排序算法都是内部排序算法。

对于一个排序算法,如果排序前的线性表中满足 i < j且 a_i = a_j 的任意两个元素 a_i和 a_j,在排序后 a_i 仍在 a_j之前,则称这个排序算法为 稳定排序(stable sort),否则称这个排序算法为 不稳定排序(unstable sort)。

排序算法在数据结构和算法中有非常重要的地位。有很多算法都要基于排序算法的结果来进行,比如折半查找算法等。对于现有的排序算法,我们已经可以证明其中几种(堆排序、归并排序)的运行时间上界 O(nlogn) 和比较排序算法(不考虑不用通过比较获得排序结果的希尔排序等)的最坏情况下界是相等的,也就是说,这几种算法已经是渐进最优的比较排序算法。

我们会按照排序的稳定性,首先介绍三种稳定排序:插入排序、冒泡排序和归并排序;之后介绍两种不稳定排序:选择排序和快速排序。

我们回顾一下排序算法的稳定性的概念:如果线性表中的两个元素 a_i和 a_j满足 i < j 且 a_i = a_j,那么这两个元素在排序在经过稳定排序以后 a_i一定在 a_j的前面。

在稳定排序算法中,有三个最知名的排序算法:插入排序、冒泡排序和归并排序。这三个排序算法的名字都很能体现出它们各自的特点。

首先介绍的是插入排序算法。

插入排序是一种非常直观的排序算法,它的基本思想是将线性表分为已排序的前半部分和待排序的后半部分,从待排序部分选出第一个元素,插入到已排序部分的对应位置中,直到全部记录都插入到已排序部分中。

插入排序每次插入的时间复杂度为 O(n),一共执行 n-1次,因此总体时间复杂度为 O(n^2)。查找插入位置的过程可以使用折半查找算法将查找的时间复杂度优化到 O(logn),但因为还需要 O(n)的时间复杂度来在顺序表上执行移动操作,所以总体时间复杂度依然是 O(n^2)。

接下来介绍冒泡排序算法。

和插入排序算法不同,冒泡排序算法是一种基于交换的排序算法。基于交换的排序,是指根据线性表中两个元素关键字的比较结果来对换这两个元素在序列中的位置。

冒泡排序算法的基本思想为:假如待排序线性表的长度为 n,从前往后两两比较相邻元素的关键字,若 a_{i-1}>a_i
​​ ,则交换它们,直到线性表比较完成。每趟交换以后最后一个元素一定是最大的,不再参与下一趟交换。也就是对于第 i 趟交换,只需要比较到 a_{n-i}即可。直到一趟比较内没有进行交换,算法结束。时间复杂度和插入排序一样,也为 O(n^2)。

最后一个要介绍的排序算法是归并排序算法。

什么是“归并”?归并的意思是将两个有序的线性表组合成一个新的有序线性表。

对于归并排序,若当前要排序的区间为 a_0…a_{n-1}
​​ ,则首先让两个区间内的元素有序,再将这两个区间合并成一个更大的有序区间,直到整个线性表都被排序完成。

回顾一下刚才的动画,归并排序一共需要进行 O(logn) 层归并操作,每层归并操作的总时间复杂度为 O(n),因此总体的时间复杂度为 O(nlogn)。和其他排序有所不同,为了实现归并操作,每次合并都需要开辟额外的空间来临时保存合并后的排序结果,总共需要开辟 n 个元素的空间,所以归并排序的空间复杂度为 O(n)。

插入排序

#include <iostream>
#include <cstring>
using namespace std;
class Vector {
private:
    int size, length;
    int * data;
public:
    Vector(int input_size) {
        size = input_size;
        length = 0;
        data = new int[size];
    }
    ~Vector() {
        delete[] data;
    }
    bool insert(int loc, int value) {
        if (loc < 0 || loc > length) {
            return false;
        }
        if (length >= size) {
            return false;
        }
        for (int i = length; i > loc; --i) {
            data[i] = data[i - 1];
        }
        data[loc] = value;
        length++;
        return true;
    }
    void print() {
        for (int i = 0; i < length; ++i) {
            if (i > 0) {
                cout << " ";
            }
            cout << data[i];
        }
        cout << endl;
    }
    void sort() {
        for(int i=0;i<length;i++){
            for(int j=i-1;j>=0;j--){
                if(data[j]>data[j+1]){
                    swap(data[j],data[j+1]);
                }
                else{
                    break;   
                }
            }
        }
    }
};
int main() {
    int n;
    cin >> n;
    Vector arr(n);
    for (int i = 0; i < n; ++i) {
        int x;
        cin >> x;
        arr.insert(i, x);
    }
    arr.sort();
    arr.print();
    return 0;
}

冒泡排序

#include <iostream>
#include <cstring>
using namespace std;
class Vector {
private:
    int size, length;
    int * data;
public:
    Vector(int input_size) {
        size = input_size;
        length = 0;
        data = new int[size];
    }
    ~Vector() {
        delete[] data;
    }
    bool insert(int loc, int value) {
        if (loc < 0 || loc > length) {
            return false;
        }
        if (length >= size) {
            return false;
        }
        for (int i = length; i > loc; --i) {
            data[i] = data[i - 1];
        }
        data[loc] = value;
        length++;
        return true;
    }
    void print() {
        for (int i = 0; i < length; ++i) {
            if (i > 0) {
                cout << " ";
            }
            cout << data[i];
        }
        cout << endl;
    }
    void sort() {
        for(int i=0;i<length-1;i++){
            bool swapped=false;
            for(int j=0;j<length-i-1;j++){
                if(data[j] > data[j+1]){
                    swap(data[j],data[j+1]);
                    swapped=true;
                }
            }
            //如果这一趟没有一次交换,就直接退出
            if(swapped==false){
                break;   
            }
        }
    }
};
int main() {
    int n;
    cin >> n;
    Vector arr(n);
    for (int i = 0; i < n; ++i) {
        int x;
        cin >> x;
        arr.insert(i, x);
    }
    arr.sort();
    arr.print();
    return 0;
}

归并排序

#include <iostream>
#include <cstring>
using namespace std;
class Vector {
private:
    int size, length;
    int * data, * temp;
    void merge_sort(int l,int r){
        if(l==r){
            return;   
        }

        int mid=(l+r)/2;
        merge_sort(l,mid);
        merge_sort(mid+1,r);

        int x=l,y=mid+1,loc=l;
        while(x<=mid||y<=r){

            //左半部分还有元素待插入
            //且右半部分已经没有元素待插入||左半部分待插入不比右半部分大
            if(x<=mid&&(y>r||data[x]<=data[y])) {
               temp[loc]=data[x];
               x++;
            }
            else{
                temp[loc]=data[y];
                y++;
            }
            loc++;
        }
        for(int i=l;i<=r;i++){
            data[i]=temp[i];   
        }
    }
public:
    Vector(int input_size) {
        size = input_size;
        length = 0;
        data = new int[size];
        temp = new int[size];
    }
    ~Vector() {
        delete[] data;
        delete[] temp;
    }
    bool insert(int loc, int value) {
        if (loc < 0 || loc > length) {
            return false;
        }
        if (length >= size) {
            return false;
        }
        for (int i = length; i > loc; --i) {
            data[i] = data[i - 1];
        }
        data[loc] = value;
        length++;
        return true;
    }
    void print() {
        for (int i = 0; i < length; ++i) {
            if (i > 0) {
                cout << " ";
            }
            cout << data[i];
        }
        cout << endl;
    }
    void sort() {
        merge_sort(0,length-1);
    }
};
int main() {
    int n;
    cin >> n;
    Vector arr(n);
    for (int i = 0; i < n; ++i) {
        int x;
        cin >> x;
        arr.insert(i, x);
    }
    arr.sort();
    arr.print();
    return 0;
}

和稳定的排序算法不同,如果线性表中的两个元素 a_i 和 a_j满足 i < j且 a_i = a_j,那么这两个元素在排序在经过不稳定的排序算法以后 a_i并不一定在 a_j的前面。

常见的不稳定排序算法有 选择排序(selection sort)、快速排序(quick sort)、堆排序(heap sort)、希尔排序(shell sort) 等。在本章的课程中,我们只介绍其中的选择排序和快速排序。

选择排序的思想是,每趟从线性表的待排序区域选取关键字最小的元素,将其放到已排序区域的最后。因为每趟可以让待排序区域的元素数量减少一个,所以总共需要 n-1 趟操作就可以将整个线性表排序完成。很显然,选择排序的时间复杂度也是 O(n^2)。

在每次查找关键字最小的元素时,可以使用堆对效率进行优化。使用堆来优化的选择排序就是堆排序。由于一共要查找 n次最小值,每次查找的时间为 O(logn),所以堆排序的时间复杂度为 O(nlogn)。

快速排序是目前应用最广泛的排序算法之一。它的基本思想是,每次从待排序区间选取一个元素(我们在后面的课程中都是选取第一个)作为基准记录,所有比基准记录小的元素都在基准记录的左边,而所有比基准记录大的元素都在基准记录的右边。之后分别对基准记录的左边和右边两个区间进行快速排序,直至将整个线性表排序完成。

快速排序的时间复杂度不是稳定的,可以证明快速排序的平均时间复杂度为 O(nlogn),最坏情况为 O(n^2),可以通过随机选择基准记录来尽可能避免最坏情况的出现。

【Selection Sorting】

#include <iostream>
#include <cstring>
using namespace std;
class Vector {
private:
    int size, length;
    int * data;
public:
    Vector(int input_size) {
        size = input_size;
        length = 0;
        data = new int[size];
    }
    ~Vector() {
        delete[] data;
    }
    bool insert(int loc, int value) {
        if (loc < 0 || loc > length) {
            return false;
        }
        if (length >= size) {
            return false;
        }
        for (int i = length; i > loc; --i) {
            data[i] = data[i - 1];
        }
        data[loc] = value;
        length++;
        return true;
    }
    void print() {
        for (int i = 0; i < length; ++i) {
            if (i > 0) {
                cout << " ";
            }
            cout << data[i];
        }
        cout << endl;
    }
    void sort() {
        for(int i=0;i<length-1;i++){
            for (int j=i+1;j<length;j++){
                if(data[i]>data[j]){
                    swap(data[i],data[j]);   
                }
            }
        }
    }
};
int main() {
    int n;
    cin >> n;
    Vector arr(n);
    for (int i = 0; i < n; ++i) {
        int x;
        cin >> x;
        arr.insert(i, x);
    }
    arr.sort();
    arr.print();
    return 0;
}

【Quick Sorting】

#include <iostream>
#include <cstring>
using namespace std;
class Vector {
private:
    int size, length;
    int * data;
    void quick_sort(int l,int r){
        int pivot=data[l],i=l,j=r;
        do{
            while(i<=j&&data[i]<pivot){
                i++;   
            }
            while(i<=j&&data[j]>pivot){
                j--;
            }
            if(i<=j){
                swap(data[i],data[j]);
                i++;
                j--;
            }
        }while(i<=j);

        //分成左右两半后,不断递分别递归两半
        if(l<j){
            quick_sort(l,j);   
        }
        if(i<r){
            quick_sort(i,r);
        }
    }
public:
    Vector(int input_size) {
        size = input_size;
        length = 0;
        data = new int[size];
    }
    ~Vector() {
        delete[] data;
    }
    bool insert(int loc, int value) {
        if (loc < 0 || loc > length) {
            return false;
        }
        if (length >= size) {
            return false;
        }
        for (int i = length; i > loc; --i) {
            data[i] = data[i - 1];
        }
        data[loc] = value;
        length++;
        return true;
    }
    void print() {
        for (int i = 0; i < length; ++i) {
            if (i > 0) {
                cout << " ";
            }
            cout << data[i];
        }
        cout << endl;
    }
    void sort() {
        quick_sort(0,length-1);
    }
};
int main() {
    int n;
    cin >> n;
    Vector arr(n);
    for (int i = 0; i < n; ++i) {
        int x;
        cin >> x;
        arr.insert(i, x);
    }
    arr.sort();
    arr.print();
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值