0%

SortingAlgorithm

Sorting

排序算法

排序算法测试的辅助函数

namespace SortTestHelper {
    //生成有n个随机数的数组,每个元素的随机范围为[rangeL,rangeR]
    //这里只是使用int型数组,可以使用template<typename T>
    int *generateRandomArray(int n, int range_l, int range_r) {
        int *array = new int[n];
        //设置随机数种子
        srand(time(NULL));
        for (int i = 0; i < n; i++) {
            array[i] = rand() % (range_r - range_l + 1) + range_l;
        }
        return array;
        //注意这里申请了内存空间,使用完以后记得释放内存
    }

    //生成一个近乎有序的数组,
    //首先生成一个[0...n-1]的完全有序数组,然后随机交换swapTimes对数据
    //swapTimes定义了数组无序的程度
    int *generateNearlyOrderedArray(int n, int swapTimes) {
        int *array = new int[n];
        for (int i = 0; i < n; i++) {
            array[i] = i;
        }
        srand(time(NULL));
        for (int i = 0; i < swapTimes; i++) {
            int pos1 = rand() % n;//交换的位置1
            int pos2 = rand() % n;//交换的位置2
            swap(array[pos1], array[pos2]);
        }
        return array;
        //这里申请了内存空间,使用完以后记得释放
    }

    //拷贝数组a中的元素的新的数组,并返回新的数组
    int *copyArray(int a[], int n) {
        int *array = new int[n];
        for (int i = 0; i < n; i++) {
            array[i] = a[i];
        }
        return array;
        //这里申请了内存空间,使用完后记得释放
    }

    //打印数组内容
    template<typename T>
    void printArray(T array[], int n) {
        for (int i = 0; i < n; i++) {
            cout << array[i] << " ";
        }
        cout << endl;
    }

    // 判断arr数组是否有序
    template<typename T>
    bool isSorted(T arr[], int n) {

        for (int i = 0; i < n - 1; i++)
            if (arr[i] > arr[i + 1])
                return false;

        return true;
    }
     template<typename T>
    void testSort(const string &sortName, void (*sort)(T [], int), T array[], int n) {
        clock_t startTime = clock();
        sort(array, n);
        clock_t endTime = clock();
//        cout<<endTime-startTime<<endl;
//        cout<<CLOCKS_PER_SEC<<endl;
        cout << sortName << " " << double(endTime - startTime) / CLOCKS_PER_SEC << "s" << endl;
        //CLOCKS_PER_SEC 每秒钟的时钟周期数

        assert(isSorted(array, n));
//        cout<<isSorted(array,n)<<endl;
        cout << "############################################################" << endl;
        return;
    }

}

O(n^2)

为什么要学习O(n^2)的排序算法

基础 编码简单,易于实现,是一些简单情景的首选

在一些特殊情况下,简单的排序算法更有效

简单的排序算法思想衍生出复杂的排序算法

作为子过程,改进更加复杂的排序算法

冒泡排序 Bubble Sort

template<typename T>
void BubbleSort(T array, int length) {
    int i, j;
    //总共需要进行多少次冒泡
    for (i = 0; i < length - 1; i++) {
        //每一次冒泡需要比较的次数
        for (j = 0; j < length - 1 - i; j++) {
            if (array[j] > array[j + 1]) {
                //比较以后,大的数往后面放
                swap(array[j], array[j + 1]);
            }
        }
    }
}

选择排序 Selection Sort

template<typename T>
void SelectionSort(T array[], int length) {
    //总共需要进行多少次选择
    for (int i = 0; i < length - 1; ++i) {
        int min = i;//假定每一轮起始的下标对应的值最小,注意这里是下标,往后找到最小值的下标
        for (int j = i + 1; j < length; j++) {
            if (array[j] < array[min]) {
                min = j;
            }
        }
        swap(array[i], array[min]);
    }
}

插入排序 Insertion Sort

template<typename T>
void InsertionSort(T array[], int length) {
    //假设i前面已经排好序
    for (int i = 1; i < length; i++) {
        //从i往前比较,如果大于或者等于就停止比较,如果小于就交换
        for (int j = i; j > 0 && array[j - 1] > array[j]; j--)//注意这种写法j不能等于0 因为j-1会数组越界,产生错误。
        {
            swap(array[j - 1], array[j]);
        }
        //或者可以写成这样
//        for(int j=i;j>=0;j--)
//        {
//            if(array[j-1]>array[j])
//                swap(array[j-1],array[j]);
//            else
//                break;//之前自己忘记写如果条件不满足break循环,导致一些错误,就是数组元素变为很大的值,可能是数组越界了
//        }
    }
}

//第一种插入排序交换次数比较多,优化,只需要比较,需要最后一次交换。
template<typename T>
void InsertionSort_advance(T array[], int length) {
    //假设i前面已经排好序
    for (int i = 1; i < length; i++) {
        //从i往前比较,如果小于就一直比较,直到大于等于就停止比较
        int key = array[i];
        int j;
        for (j = i; j >= 0 && array[j - 1] > key; j--)
            array[j] = array[j - 1];
        array[j] = key;//这里的array[j]与上一行的array[j]不一样
    }
}

希尔排序 Shell Sort

template<typename T>
void ShellSort(T array[], int length) {
    //计算 increment sequence: 1, 4, 13, 40, 121, 364, 1093...
    int step = 1;
    while (step < length / 3)
        step = step * 3 + 1;//跳出循环之后,step为increment sequence 的一项,该项大于或等于且最靠近length/3

    while (step >= 1) {
        //思想就是,按步长分组,每一组按照插入排序思想排序
        for (int i = step; i < length; i++) {
            //插入排序思想
            for (int j = i; j >= 0 && array[j - setp] > array[j]; j--) {
                swap(array[j - step], array[j]);
            }
        }
        step = step / 3;//步长变小
    }
}

void ShellSort_advance(T array[], int length) {
    //计算 increment sequence: 1, 4, 13, 40, 121, 364, 1093...
    int step = 1;
    while (step < length / 3)
        step = step * 3 + 1;//跳出循环之后,step为increment sequence 的一项,该项大于或等于且最靠近length/3
    //int step = length/2;//可以这样设置步长
    while (step >= 1) {
        //思想就是,按步长分组,每一组按照插入排序思想排序
        for (int i = step; i < length; i++) {
            //插入排序思想
            int j;
            int key = array[i];
            for (j = i; j >= 0 && array[j - setp] > key; j--) {
                array[j] = array[j - step];
            }
            array[j] = key;
        }
        step = step / 3;//步长变小
        //step=step/2;
    }
}

归并排序 Merge Sort

//l:left m:middle r:right 使用的是左闭右闭区间[l,r]
template<typename T>
//__表示私有函数
void __merge(T array[], int l, int mid, int r) {
    T aux[r - l + 1];
    //
    for (int i = l; i <= r; i++) {
        aux[i - l] = array[i];
    }
    //初始化,i指向左半部分数组的起始索引位置l,j指向右半部分数组的起始索引位置mid+1
    int i = l, j = mid + 1;
    for (int k = l; k <= r; k++) {
        if (i > mid)//如果左半部分数组已经处理完毕
        {
            array[k] = aux[j - l];
            j++;
        } else if (j > r)//如果右半部分数组已经处理完毕
        {
            array[k] = aux[i - l];
            i++;
        } else if (aux[i - l] < aux[j - l])//左半部分索引指的元素小于右半部分索引指的数组的元素
        {
            array[k] = aux[i - l];
            i++;
        } else//右半部分索引指的元素小于左半部分数组索引指的元素
        {
            array[k] = aux[j - l];
            j++;
        }

    }
}

template<typename T>
//__表示私有函数
void __mergesort(T array[], int l, int r) {
    if (l >= r)
        return;
    int mid = l + (r - l) / 2; //等价于(l+r)/2 为了防止溢出,如果l和r都很大,两个很大int型的数相加可能会溢出
    __mergesort(array, l, mid);
    __mergesort(array, mid + 1, r);
    __merge(array, l, mid, r);
}

template<typename T>
void MergeSort(T array[], int length) {
    __mergesort(array, 0, length - 1);
}

快速排序 Quick Sort

//对array[l,r]进行partition操作
template<typename T>
int __partition(T array[], int l, int r) {
    swap(array[l], array[rand() % (r - l + 1) + l]);//[l,r]的任意一个索引
    T v = array[l];

    //array[l+1,j]<v array[j+1,i)>v 为什么是开区间 因为当前考察的是第i个元素
    int j = l;
    for (int i = l + 1; i <= r; i++) {
        if (array[i] < v) {
            swap(array[j + 1], array[i]);
            j++;
        }
    }
    swap(array[l], array[j]);
    return j;
}

//对array[l,r]部分进行快速排序
template<typename T>
void __quickSort(T array[], int l, int r) {
    if (l >= r)
        return;
    int p = __partition(array, l, r);
    __quickSort(array, l, p - 1);
    __quickSort(array, p + 1, r);
}

template<typename T>
void QuickSort(T array[], int n) {
    srand(time(NULL));
    __quickSort(array, 0, n - 1);
}

//QuickSort2
//对array[l,r]进行partition操作
template<typename T>
int __partition2(T array[], int l, int r) {
    swap(array[l], array[rand() % (r - l + 1) + l]);//[l,r]的任意一个索引
    T v = array[l];

    //array[l+1,j]<=v array(j+1,i]>=v
    int i = l + 1, j = r;
    while (true) {
        while (i <= r && array[i] < v) i++;
        while (j >= l + 1 && array[j] > v) j--;
        if (i > j) break;
        swap(array[i], array[j]);
        i++;
        j--;
    }
    swap(array[l], array[j]);
    return j;
}

//对array[l,r]部分进行快速排序
template<typename T>
void __quickSort2(T array[], int l, int r) {
    if (l >= r)
        return;

    int p = __partition2(array, l, r);
    __quickSort2(array, l, p - 1);
    __quickSort2(array, p + 1, r);
}

template<typename T>
void QuickSort2(T array[], int n) {
    srand(time(NULL));
    __quickSort2(array, 0, n - 1);
}

//递归三路快速排序算法
template<typename T>
void __quickSort3ways(T array[], int l, int r) {
    if (l >= r)
        return;

    swap(array[l], array[rand() % (r - l + 1) + l]);//[l,r]的任意一个索引
    T v = array[l];

    int lt = l; //array[l+1,lt]<v
    int gt = r + 1; //array[gt,r]>v
    int i = l + 1;//array[lt+1,i]==v

    while (i < gt) {
        if (array[i] < v) {
            swap(array[i], array[lt + 1]);
            i++;
            lt++;
        } else if (array[i] > v) {
            swap(array[i], array[gt - 1]);
            gt--;
        } else//array[i]==v
        {
            i++;
        }
    }
    swap(array[l], array[lt]);
    __quickSort3ways(array, l, lt - 1);
    __quickSort3ways(array, gt, r);
}

template<typename T>
void QuickSort3ways(T array[], int n) {
    srand(time(NULL));
    __quickSort3ways(array, 0, n - 1);
}

测试算法的性能

在特定的数据集上运行的时间

void test() {
    int n = 10000;
    //测试1,一般测试
    cout << "Test for random array ,size= " << n << ",random range [0," << n << "]" << endl;

    int *array1 = SortTestHelper::generateRandomArray(n, 0, n);
    int *array2 = SortTestHelper::copyArray(array1, n);
    int *array3 = SortTestHelper::copyArray(array1, n);
    int *array4 = SortTestHelper::copyArray(array1, n);
    int *array5 = SortTestHelper::copyArray(array1, n);

    SortTestHelper::testSort("Bubble Sort", BubbleSort, array1, n);
    SortTestHelper::testSort("Selection Sort", SelectionSort, array2, n);
    SortTestHelper::testSort("Insertion", InsertionSort, array3, n);
    SortTestHelper::testSort("Merge Sort", MergeSort, array4, n);
    SortTestHelper::testSort("Quick Sort", QuickSort, array5, n);

    delete[] array1;
    delete[] array2;
    delete[] array3;
    delete[] array4;
    delete[] array5;

    cout << endl;
}

排序算法的总结

平均时间复杂度 原地排序 额外空间 稳定性
插入排序 O(n^2) O(1)
归并排序 O(nlogn) 不是 O(n+logn)
快速排序 O(nlogn) O(logn) 不是
堆排序 O(nlogn) 不是 O(1) 不是

排序算法的稳定性

Stable

稳定排序:相对于相等的元素,在排序后,原来靠前的元素依然靠前。相等元素的相对位置没有发生改变。

4 3(1) 2 3(3) 1 5 3(3) 6
1 2 3(1) 3(2) 3(3) 4 5 6

在现实生活中的应用

学生成绩表原先按姓名的字典序排序,现在在按分数高低排序后,稳定的排序算法就是可以做到,相同分数的学生按姓名的字典序排序。

稳定性是和具体实现相关的,如果你实现不好,可能会将插入排序和归并排序设计为不稳定的排序

可以通过自定义比较函数,让排序算法不存在稳定性的问题

是否存在神秘的排序算法符合所有的最低标准