数据结构:排序算法

数据结构:排序算法

前言

本文总结了经常使用的九种内部排序算法,因做者水平有限,多有谬误,欢迎批评指正。c++

注:文中数组皆从1开始,没有用0。默认升序排列。算法

另:转载请注明出处。ubuntu

分类

按时间复杂度

  • 普通算法 \(O(n^2)\)数组

    • 直接插入排序
    • 二分插入排序
    • 冒泡排序
    • 直接选择排序
  • 先进算法 \(O(n\log n)\)数据结构

    • 快速排序
    • 堆排序
    • 选择排序
  • 特例优化

    • 希尔排序,\(O(n^{1.3 \sim 2})\)
    • 基数排序,\(O(dn)\),其中\(d\)为元素的最大位数

按排序种类

  • 插入排序
    • 直接插入排序
    • 二分插入排序
    • 希尔排序
  • 交换排序
    • 冒泡排序
    • 快速排序
  • 选择排序
    • 直接选择排序
    • 堆排序
  • 归并排序
  • 基数排序

按算法稳定性

  • 稳定算法
    • 直接插入排序
    • 二分插入排序
    • 冒泡排序
    • 归并排序
    • 基数排序
  • 不稳定算法
    • 希尔排序
    • 快速排序
    • 直接选择排序
    • 堆排序

直接插入排序

算法思想

设待排序序列为\(T\),排序序列为\(S\)ui

\(T\)中依次选取,加入\(S\)中。spa

设选取元素为\(T_i\in T\),将\(x\)\(S\)中的元素\(S_j\)依次比较。debug

若是\(T_i\)大于\(S_j\),则\(j=j+1\);不然将\(S_j\)以及日后的元素后移一位,\(T_i\)做为新的\(S_j\)code

代码

//优化写法
void StraightInsertionSort()
{
    for (int i = 2; i <= n; i++)
    {
        for (int j = i - 1; j >= 1 && a[j] > a[j + 1]; j--)
        {
            swap(a[j], a[j + 1]);
        }
    }
}

二分插入排序

算法思想

在直接插入排序中,\(S_j\)是依次选取的,而\(S\)原本已是有序序列,那么就能够用二分查找的方式,肯定\(T_i\)应该插入到的位置。

代码

void BinaryInsertionSort()
{
    for (int i = 2; i <= n; i++)
    {
        int l = 1, r = i, m;
        while (l <= r)
        {
            m = (l + r) / 2;
            if (a[i] > a[m])
            {
                l = m + 1;
            }
            else
            {
                r = m - 1;
            }
        }
        for (int j = i - 1; j > r; j--)
        {
            swap(a[j], a[j + 1]);
        }
    }
}

希尔排序

算法思想

希尔排序做为直接插入排序的改进,其思想是,将序列\(T\)进行分组,或者说,按必定的增量选取元素,而不是依次选取。将分组的元素进行排序,不断细分分组再排序,最终获得总体有序序列。

因为分组方法/增量的选取不一样,希尔排序的算法时间复杂度也会相应改变。一般选取的为希尔增量,即开始选取序列长度的一半,每次排序后都折半。

可见希尔增量为1时的特殊状况,就是直接插入排序。

代码

//加增量后,分组直接插入排序。
void ShellSort()
{
    printf("Mark: Here we use the Shell Increment.\n");
    for (int d = n / 2; d >= 1; d /= 2)
    {
        /*
         * for (int i = 1; i <= d; i++)
         * {
         *  for (int j = i + d; j <= n; j += d)
         *  {
         *      for (int k = j - d; k >= 1 && a[k] > a[k + d]; k -= d)
         *      {
         *          swap(a[k], a[k + d]);
         *      }
         *  }
         * }
         */
        //写法优化,少一个循环。
        for (int i = d + 1; i <= n; i++)
        {
            for (int j = i - d; j >= d && a[j] > a[j + d]; j -= d)
            {
                swap(a[j], a[j + d]);
            }
        }
    }
}

冒泡排序

算法思想

将无序序列\(T\)两两进行交换,进行屡次(最多\(n-1\)次)便可获得有序序列\(S\)

加个标志符判断已经有序,就不用再排了。

代码

//简简单单冒泡排序的优化版本。
void BubbleSort()
{
    for (int i = 1; i <= n - 1; i++)
    {
        bool flg = 0;
        for (int j = 1; j <= n - 1; j++)
        {
            if (a[j] > a[j + 1])
            {
                swap(a[j], a[j + 1]);
                flg = 1;
            }
            else
            {
                continue;
            }
        }
        if (!flg)
        {
            break;
        }
        else
        {
            continue;
        }
    }
}

快速排序

算法思想

快速排序是对冒泡排序的一种改进,一般选取序列的第一个数做为一个标准值,大于它的放到右边,小于它的放到左边。这时候标准值在序列中的位置就是有序排列后的位置,再分别将左右子列进行一样操做便可。

代码运用递推式写法。

不具备算法稳定性的缘由是,标准值的选取是不必定的,这里只是方便起见选取了第一个。

代码

void QuickSort(int l, int r)
{
    if (l < r)
    {
        int tmp = a[l];
        int il = l, ir = r;
        while (l < r)
        {
            while (l < r && a[r] >= tmp)
            {
                r--;
            }
            if (a[r] < tmp)
            {
                swap(a[l], a[r]);
            }
            while (l < r && a[l] <= tmp)
            {
                l++;
            }
            if (a[l] > tmp)
            {
                swap(a[l], a[r]);
            }
        }
        QuickSort(il, l - 1);
        QuickSort(l + 1, ir);
    }
    else
    {
        return;
    }
}

直接选择排序

算法思想

遍历\(T\),每次选取\(T_{i+1}\)\(T_n\)之间最小的,将其与\(T_i\)交换。

代码

//很简单。
void SelectionSort()
{
    for (int i = 1; i <= n - 1; i++)
    {
        int p = i;
        for (int j = i + 1; j <= n; j++)
        {
            if (a[j] < a[p])
            {
                p = j;
            }
        }
        swap(a[p], a[i]);
    }
}

堆排序

算法思想

先将无序序列构成一颗二叉树,选取非叶子节点,将其调整成一个大根堆,此时将根节点与最后一个元素互换,再进行调整堆的结构,如此反复。

整个排序是利用了大根堆的性质,即根节点必定是整个堆中最大的元素。

代码

void my_Heap(int A[], int len)
{
    for (int i = len % 2 == 0?len / 2:len / 2 + 1; i >= 1; i--)
    {
        ajust_My_Heap(A, i, len);
    }
    for (int i = len; i > 1; i--)
    {
        swap(a[1], a[i]);
        ajust_My_Heap(A, 1, i - 1);
        //debug用

        /*for(int j= 1;j<=len;j++)
         * {
         *  j==len?printf("%d\n",a[j]):printf("%d ",a[j]);
         * }
         */
    }
}

void ajust_My_Heap(int A[], int i, int len)
{
    int tmp = A[i];

    for (int k = i * 2; k <= len; k = k * 2)
    {
        if (k + 1 <= len && A[k] < A[k + 1])
        {
            k++;
        }
        if (A[k] > tmp)
        {
            A[i] = A[k];
            i    = k;
        }
        else
        {
            break;
        }
    }
    A[i] = tmp;
}

void HeapSort()
{
    //STL写法
    //make_heap(&a[1], &a[n+1]);
    //sort_heap(&a[1], &a[n+1]);
    //或用手写堆来进行操做
    my_Heap(a, n);
}

归并排序

算法思想

两路归并排序是运用了分治思想,是将无序序列先进行分割,而后局部调整,而后合并。

合并的时候,若有\(S^1\)\(S^2\)为两个有序序列,要将其合并成有序序列\(S\)\(S_1=\min\{S^1_1,S^2_1\}\),若是\(S_1\)选取了\(S^1_1\),那么\(S_2=\min\{S^1_2,S^2_1\}\)。如此进行下去,便可获得\(S\)。运用了\(S^1\)\(S^2\)的有序性质。

代码

void Two_MergeSort(int l, int r)
{
    if (l == r)
    {
        return;
    }
    int m = (l + r) / 2;

    Two_MergeSort(l, m);
    Two_MergeSort(m + 1, r);
    if ((r - l) == 1 && a[l] > a[r])
    {
        swap(a[l], a[r]);
    }
    else
    {
        int i = l, j = m + 1, p = l;
        int b[n];
        memset(b, 0, sizeof(b));
        //核心代码要好好理解写法,即使知道了具体的算法思想
        while (i <= m && j <= r)
        {
            b[p++] = a[i] <= a[j] ? a[i++] : a[j++];
        }
        while (i <= m)
        {
            b[p++] = a[i++];
        }
        while (j <= r)
        {
            b[p++] = a[j++];
        }
        for (int k = l; k <= r; k++)
        {
            a[k] = b[k];
        }
    }
}

基数排序

算法思想

对于不少个位数相同的数进行排序时,效率很高。

LSD基数排序是按照最低位优先法,将序列从低位开始,安排到十个队列当中,十个队列分别记录他们的这一位数字。利用队列的FIFO性质,进行收集。再将收集的序列,按更高一位,重复上述方法,一直进行到最高位,收集出来即是有序序列。

注意,必定是队列。若是从最高位开始,应用队列的性质是得不到逆序的。即MSD基数排序,应该换为用栈存储。

代码

void RadixSort()
{
    //找出最大数位
    int maxData = a[1];

    for (int i = 2; i <= n; i++)
    {
        if (a[i] > maxData)
        {
            maxData = a[i];
        }
    }
    int l   = 1;
    int tmp = 10;

    while (maxData >= tmp)
    {
        tmp *= 10;
        l++;
    }
    int cnt[10];
    int b[n];
    int r = 1;
    int k;

    for (int i = 1; i <= l; i++, r *= 10)
    {
        memset(cnt, 0, sizeof(cnt));
        for (int j = 1; j <= n; j++)
        {
            k = (a[j] / r) % 10;
            cnt[k]++;
        }
        //前缀和来肯定b[j]的位置,很是巧妙
        for (int j = 1; j < 10; j++)
        {
            cnt[j] = cnt[j - 1] + cnt[j];
        }
        for (int j = n; j >= 1; j--)
        {
            k         = (a[j] / r) % 10;
            b[cnt[k]] = a[j];
            cnt[k]--;
        }
        for (int j = 1; j <= n; j++)
        {
            a[j] = b[j];
        }
    }
    return;
}

结语

完整代码请戳此处

点个关注不迷路,后续会有更多内容,不限于数据结构,更有PDE、Complex Analysis等重磅内容。

谢谢朋友们!

相关文章
相关标签/搜索