C++:常用排序算法复习

利用 randarr 函数生成65535个 0 ~ RAND_MAX 的随机数进行从大到小排序。设待排序的数组长度为 len (实例函数中的 arrmaxn )

1. 冒泡排序 bubble sort

  • 每次通过逐一比较交换的方法选出一个最大值放在数列的末尾,逐一比较当前序列内的相邻两个元素,发现前面的大于后面就交换两元素,这样每轮都会把当前序列的最大值交换到序列末尾。
void bubblesort(void)
{
    for (int i = 0; i != arrmaxn - 1; ++i)
        for (int j = 0; j != arrmaxn - i - 1; ++j)
            if (arr[j] > arr[j + 1])
                swap(arr[j], arr[j + 1]);
    return;
}
  • 函数中循环变量 i 的含义是当前已经排好放在序列末尾的元素个数,范围应该是 0 ~ len - 2 ,如果已经排好 len - 1 个元素,那么剩下的一个元素没有排序的必要了啦~
  • 循环变量 j 的含义是两两比较元素,前一个元素的下标,每次应当从 0 开始,到当前序列末尾 len - i - 1 结束

2. 选择排序 selection sort

  • 选择排序,最简单的排序,每次找出一个最小值放在最前面。
void selectionsort(void)
{
    for (int i = 0; i != arrmaxn - 1; ++i)
    {
        auto tempindex = i;
        for (int j = i + 1; j != arrmaxn; ++j)
            if (arr[j] < arr[tempindex])
                tempindex = j;
        swap(arr[i], arr[tempindex]);
    }
    return;
}
  • 循环变量 i 的含义是当前排序好放在序列前面的元素个数
  • 循环变量 j 的范围是 0 ~ len - 1,每次需要从未排序的序列里选出一个最小的

3. 插入排序 insertion sort

  • 插入排序较上面两种排序好一些,它已排序序列中不断后移序列找到合适的位置插入未排序元素。
void insertionsort(void)
{
    for (int i = 1; i != arrmaxn; ++i)
    {
        auto tempindex = i - 1, tempvalue = arr[i];
        while (tempindex >= 0 && arr[tempindex] > tempvalue)
        {
            arr[tempindex + 1] = arr[tempindex];
            --tempindex;
        }
        arr[tempindex + 1] = tempvalue;
    }
    return;
}
  • 循环变量 i 含义是未排序元素从第二个元素开始,一直到最后一个元素,依次为它们在前面已排序序列中找到合适位置插入,范围就是 1 ~ len - 1
  • 先拷贝待排序元素值(下标是 i ),在已排序序列(下标是 0 ~ i - 1 )中如果当前位置不是合适位置,就将元素值后移,如果找到合适位置就放入之前拷贝的元素值。

4. 希尔排序 shell sort

  • 插入排序的加强版,通过设置一个递减的增量 gap ,将原有序列分为多个序列进行插入排序。因为每个序列的长度会分的很短,因此每个序列的插入排序查找合适位置的步骤会缩小很多,并且每次排序后递减增量后的序列也会变得易于插入排序。
void shellsort(void)
{
    auto gap = arrmaxn >> 1;
    while (gap)
    {
        for (int i = gap; i != arrmaxn; ++i)
        {
            auto tempindex = i - gap, tempvalue = arr[i];
            while (tempindex >= 0 && arr[tempindex] > tempvalue)
            {
                arr[tempindex + gap] = arr[tempindex];
                tempindex -= gap;
            }
            arr[tempindex + gap] = tempvalue;
        }
        gap >>= 1;
    }
    return;
}
  • 选用常用增量,初始值为待排序序列长度的一半,每次递减一半。

5. 归并排序 merge sort

  • 分治法来排序,不断二分序列,分裂到每个序列长度只有 2 的时候,再不断归并每个序列段
void mergesort(int l = 0, int r = arrmaxn - 1)
{
    if (l < r)
    {
        auto mid = (l + r) >> 1;
        mergesort(l, mid);
        mergesort(mid + 1, r);
        int templ = l, tempr = mid + 1, spindex = 0;
        while (templ <= mid && tempr <= r)
            backuparr[spindex++] = (arr[templ] < arr[tempr]) ? (arr[templ++]) : (arr[tempr++]);
        while (templ <= mid)
            backuparr[spindex++] = arr[templ++];
        while (tempr <= r)
            backuparr[spindex++] = arr[tempr++];
        for (int i = 0; i != r - l + 1; ++i)
            arr[l + i] = backuparr[i];
    }
    return;
}
  • 归并排序和快速排序是需要非常熟练编写的(…也就这两个手打有点难度),因为一些算法会用到它们,比如求第K大数、归并法求逆序对等等。
  • 建议归并排序写成一个函数,感觉没有必要再分出来用于归并序列的函数。

6. 快速排序 quick sort

  • 最常用的排序算法!内部排序中平均时间最短的排序算法。通过在当前序列中选取一个元素作为基准分开当前序列,小于该元素的的元素构成一个序列,大于该元素的元素构成一个序列,以此递归完成排序。
void quicksort(int l = 0, int r = arrmaxn - 1)
{
    if (l >= r)
        return;
    int temp = arr[l], templ = l, tempr = r;
    while (templ != tempr)
    {
        while (templ != tempr && arr[tempr] >= temp)
            --tempr;
        arr[templ] = arr[tempr];
        while (templ != tempr && arr[templ] <= temp)
            ++templ;
        arr[tempr] = arr[templ];
    }
    arr[templ] = temp;
    quicksort(l, templ - 1);
    quicksort(tempr + 1, r);
    return;
}
  • 快速排序有很多种写法,但是原理是不变的。给出的实例函数这种采用当前序列的第一个元素作为基准,那么在不断交换两侧元素的时候,必须先从右往左查找较小值,再从左往右查找较大值。
    如果出现当前序列是第一个(基准元素)最小的,那么如果是先从左查找后从右查找的话,最后锁定的位置,即 templ 和 tempr 相遇的地方会在第二个元素的位置。此时交换就导致一个比基准值大的元素跑到基准值前面去了!
    各类快速排序,需要严谨的分析它们是如何处理边界的。

7. 堆排序 heap sort

  • 利用最大堆,建堆后,每次移动堆顶到序列末尾,然后堆的大小 - 1 ,维护最大堆。
void heapsort(void)
{
    for (int i = arrmaxn >> 1; i; --i)
    {
        int copyi = i;
        while (1)
        {
            int maxindex = copyi;
            if (copyi * 2 <= arrmaxn && arr[copyi * 2 - 1] > arr[maxindex - 1])
                maxindex = copyi * 2;
            if (copyi * 2 + 1 <= arrmaxn && arr[copyi * 2] > arr[maxindex - 1])
                maxindex = copyi * 2 + 1;
            if (maxindex != copyi)
            {
                swap(arr[copyi - 1], arr[maxindex - 1]);
                copyi = maxindex;
            }
            else
                break;
        }
    }
    int lens = arrmaxn;
    while (lens)
    {
        swap(arr[0], arr[lens - 1]);
        --lens;
        int copyi = 1;
        while (1)
        {
            int maxindex = copyi;
            if (copyi * 2 <= lens && arr[copyi * 2 - 1] > arr[maxindex - 1])
                maxindex = copyi * 2;
            if (copyi * 2 + 1 <= lens && arr[copyi * 2] > arr[maxindex - 1])
                maxindex = copyi * 2 + 1;
            if (maxindex != copyi)
            {
                swap(arr[copyi - 1], arr[maxindex - 1]);
                copyi = maxindex;
            }
            else
                break;
        }
    }
    return;
}
  • 由于待排序数组的下标是从 0 开始的,需要注意处理堆下标是从 1 开始的相互匹配的问题~
  • 由于STL库里有二叉堆相关的函数,所以并不需要经常手写!

8. 计数排序 counting sort

  • 一种外部排序,利用“桶”来排序,遍历待排序序列,将遇到的数对应的“桶”加一。之后遍历从小到大遍历每个“桶”,“桶”的值多大就放回序列几个当前数。原理很简单,类似于散列表的思想吧,比较常用。
  • 在待排序元素大小波动不大,且元素值易于表示的时候可以使用,速度会快于所有内部排序。
void countingsort(void)
{
    for (const auto i : arr)
        ++bucket[i];
    int spindex = 0;
    for (int i = 0; i <= RAND_MAX; ++i)
        while (bucket[i])
            arr[spindex++] = i, --bucket[i];
    return;
}

9. STL函数排序

  • sort 函数,排序函数,平均时间快,不稳定排序,类似于优化的快速排序。第三个参数默认是 less 函数,可以自定义比较函数,或者重载运算符" < "
sort(begin(arr), end(arr));
  • stable_sort 函数,稳定排序版排序函数(归并排序)
stable_sort(begin(arr), end(arr));
  • sort_heap 函数,堆排序,必须用在堆上,所以需要配合 make_heap 函数使用。
make_heap(begin(arr), end(arr));
sort_heap(begin(arr), end(arr));
  • is_sorted 函数,验证当前序列是否是已排序的,也很有用~
is_sorted(begin(arr), end(arr));

10. 简易测试

bubblesort is sorted.  19008 ms
selectionsort is sorted.  4760 ms
insertionsort is sorted.  2882 ms
shellsort is sorted.  17 ms
mergesort is sorted.  12 ms
quicksort is sorted.  8 ms
heapsort is sorted.  20 ms
heapsortSTL is sorted.  17 ms
sortSTL is sorted.  12 ms
stablesortSTL is sorted.  13 ms
countingsort is sorted.  1 ms
  • 通过测试结果,发现前三个时间复杂度在 O( n^2 ) 的时间是慢不少哒
  • 外部排序—计数排序速度超群。
     

复习于2019-05-18

 

END

你可能感兴趣的:(C/C++)