排序算法

冒泡排序(n^2)

第一遍,从第一个元素开始, 相邻两个分别比较,大的放到后面。(最后一个元素是最大的)
第二遍,从第二个元素开始...

void Bubblesort(int *a, int n)
{
    int i, j, k;
    for (i = 1;i < n;i++)
    {
        for (j = 1;j <= n - i;j++)
        {
            if (a[j] > a[j + 1])
                k = a[j], a[j] = a[j + 1], a[j + 1] = k;
        }
    }
}

选择排序(n^2)

第一个和之后的比较,谁大谁放第一个。
第二个和之后的比较,谁大谁放第二个。

void Selectionsort(int *a, int n)
{
    int i, j, k;
    for (i = 1;i <= n;i++)
    {
        for (j = i + 1;j <= n;j++)
        {
            if (a[i] > a[j])
                k = a[i], a[i] = a[j], a[j] = k;
        }
    }
}

快速排序

一趟快速排序的算法是:

1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;

2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];

3)从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]和A[i]互换;

4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]和A[j]互换;

5)重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。

复杂度:O(nlogn)

void quicksort(int *a, int l, int r)
{
    int i = l, j = r, k = a[l];
    if (l < r)
    {
        while (i < j)
        {
            while (i < j&&a[j] >= k)
                j--;
            if (i < j) a[i++] = a[j];
            while (i < j&&a[i] <= k)
                i++;
            if (i < j) a[j--] = a[i];
        }
        a[i] = k;
        quicksort(a, l, i - 1);
        quicksort(a, i + 1, r);
    }
}

插入排序—直接插入排序

相邻的两个元素a(index:i)和b(index:i+1),进行比较,如果a大于b,交换a,b位置,然后b继续去和a前面的元素比较,如果还是b小,继续交换位置。

void insertsort(int *a, int n)
{
    for (int i = 0;i < n;i++)
    {
        int j = i - 1, tmp = a[i];
        while (j >= 0 && tmp < a[j])
            a[j + 1] = a[j], j--;
        a[j + 1] = tmp;
    }
}

插入排序-希尔排序

直接插入排序的进阶版。先在步长为个数一半进行插入排序,然后逐次递减。

希尔排序
void shellsort(int *a, int n)
{
    int i, j, k, id, gap;
    for (gap = n / 2;gap;gap /= 2)
    {
        for (i = gap;i <= n;i++)
        {
            for (j = i - gap;j >= 0 && a[j] > a[j + gap];j -= gap)
                k = a[j], a[j] = a[j + gap], a[j + gap] = k;
        }
    }
}

归并排序 复杂度:O(nlogn)

归并操作(merge),也叫归并算法,指的是将两个顺序序列合并成一个顺序序列的方法。
如 设有数列{6,202,100,301,38,8,1}
初始状态:6,202,100,301,38,8,1
第一次归并后:{6,202},{100,301},{8,38},{1},比较次数:3;
第二次归并后:{6,100,202,301},{1,8,38},比较次数:4;
第三次归并后:{1,6,8,38,100,202,301},比较次数:4;
总的比较次数为:3+4+4=11

速度仅次于快速排序,为稳定排序算法,一般用于对总体无序,但是各子项相对有序的数列,应用见2011年普及复赛第3题“瑞士轮”的标程

//归并排序
func mergeSort(_ arr: inout [Int]) {
    var gap = 1;
    while gap < arr.count {
        mergePass(&arr, gap: gap);
         
        gap *= 2;
    }
}
 
//分解合并序列,gap表示子序列的元素个数
func mergePass(_ arr: inout [Int], gap: Int) {
    var i = 0;
    let count = arr.count;
     //设置初始排序的值如果为1,则相邻的两个排序即[1,2,3,5,6]排[{1,2},{3,4}...]
//之后继续四个排
    while i + 2 * gap - 1 < count {
//0,0,1
        mergeArray(&arr, low: i, mid: i + gap - 1, high: i + 2 * gap - 1);
         
        i += 2 * gap;
    }
     
    //合并剩余的序列
    if i + gap - 1 < count {
        mergeArray(&arr, low: i, mid: i + gap - 1, high: count - 1);
    }
}
 
//合并两个序列


func mergeArray(_ arr: inout [Int], low: Int, mid: Int, high: Int) {
     
    var i = low;
    var j = mid + 1;
    var k = 0;
     
    var array = Array(repeating: 0, count: high - low + 1);
     
    while i <= mid && j <= high {
        if arr[i] < arr[j] {
            array[k] = arr[i];
            i += 1;
            k += 1;
        } else {
            array[k] = arr[j];
            j += 1;
            k += 1;
        }
    }
     
    while i <= mid {
        array[k] = arr[i];
        i += 1;
        k += 1;
    }
     
    while j <= high {
        array[k] = arr[j];
        j += 1;
        k += 1;
    }
     
    //将排序好的序列复制回原数组
    k = 0;
    for i in low...high {
        arr[i] = array[k];
         
        k += 1;
    }
}
 
 
var array = [2, 5, 8, 9, 10, 4, 3, 16, 1, 7, 8];
mergeSort(&array);
print(array);

计数排序

计数排序是一个非基于比较的排序算法,该算法于1954年由 Harold H. Seward 提出。它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。[1-2] 当然这是一种牺牲空间换取时间的做法,而且当O(k)>O(nlog(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(nlog(n)), 如归并排序,堆排序)

它的原理是,计算数组的范围计入一个数组S,然后再统计数组S各个元素的个数。
然后根据S里面各个数量整理出排序后的数组。如:
nums=[2, 1, 3, 1, 5] , 首先扫描一遍获取最小值和最大值, maxValue=5 , minValue=1 ,于是开一个长度为5的计数器数组 counter ,

  1. 分配。统计每个元素出现的频率,得到 counter=[2, 1, 1, 0, 1] ,例如 counter[0] 表示值 0+minValue=1 出现了2次。
  2. 收集。 counter[0]=2 表示 1 出现了两次,那就向原始数组写入两个1, counter[1]=1 表示 2 出现了1次,那就向原始数组写入一个2,依次类推,最终原始数组变为 [1,1,2,3,5] ,排序好了。
void cntsort(int *a, int n, int *s,int *Rank)
{
    int i, j;
    for (i = 1;i <= n;i++)
        s[a[i]]++;
    for (i = 1;i <= 100;i++) 
        s[i] += s[i - 1];
    for (i = n;i >= 1;i--)
        Rank[s[a[i]]--] = a[i];
    for (i = 1;i <= n;i++)
        printf("%d ", Rank[i]);
    printf("\n");
}

桶排序

  1. 将待排序元素划分到不同的痛。先扫描一遍序列求出最大值 maxV 和最小值 minV ,设桶的个数为 k ,则把区间 [minV, maxV] 均匀划分成 k 个区间,每个区间就是一个桶。将序列中的元素分配到各自的桶。
  2. 对每个桶内的元素进行排序。可以选择任意一种排序算法。
  3. 将各个桶中的元素合并成一个大的有序序列。
  4. 假设数据是均匀分布的,则每个桶的元素平均个数为 n/k 。假设选择用快速排序对每个桶内的元素进行排序,那么每次排序的时间复杂度为 O(n/klog(n/k)) 。总的时间复杂度为 O(n)+O(m)O(n/klog(n/k)) = O(n+nlog(n/k)) = O(n+nlogn-nlogk 。当 k 接近于 n 时,桶排序的时间复杂度就可以金斯认为是 O(n) 的。即桶越多,时间效率就越高,而桶越多,空间就越大。

计数排序是一种特殊的桶排序。

基数排序

是一种非比较排序算法,时间复杂度是 O(n) 。它的主要思路是,

  1. 将所有待排序整数(注意,必须是非负整数)统一为位数相同的整数,位数较少的前面补零。一般用10进制,也可以用16进制甚至2进制。所以前提是能够找到最大值,得到最长的位数,设 k 进制下最长为位数为 d 。

  2. 从最低位开始,依次进行一次稳定排序。这样从最低位一直到最高位排序完成以后,整个序列就变成了一个有序序列。
    举个例子,有一个整数序列,0, 123, 45, 386, 106,下面是排序过程:

    第一次排序,个位,000 123 045 386 106,无任何变化
    第二次排序,十位,000 106 123 045 386
    第三次排序,百位,000 045 106 123 386
    最终结果,0, 45, 106, 123, 386, 排序完成。

    为什么同一数位的排序子程序要用稳定排序?因为稳定排序能将上一次排序的成果保留下来。例如十位数的排序过程能保留个位数的排序成果,百位数的排序过程能保留十位数的排序成果。能不能用2进制?能,可以把待排序序列中的每个整数都看成是01组成的二进制数值。那这样的话,岂不是任意一个非负整数序列都可以用基数排序算法?理论上是的,假设待排序序列中最大整数为2 4 . 1,则最大位数 d=64 ,时间复杂度为 O(64n) 。可见任意一个非负整数序列都可以在线性时间内完成排序。
    既然任意一个非负整数序列都可以在线性时间内完成排序,那么基于比较排序的算法有什么意义呢?基于比较的排序算法,时间复杂度是 O(nlogn) ,看起来比 O(64n) 慢,仔细一想,其实不是, O(nlogn) 只有当序列非常长,达到2 个元素的时候,才会与 O(64n) 相等,因此,64这个常数系数太大了,大部分时候, n 远远小于2 ,基于比较的排序算法还是比 O(64n) 快的。
    当使用2进制时, k=2 最小,位数 d 最大,时间复杂度 O(nd) 会变大,空间复杂度 O(n+k) 会变小。当用最大值作为基数时, k=maxV 最大, d=1 最小,此时时间复杂度 O(nd) 变小,但是空间复杂度 O(n+k) 会急剧增大,此时基数排序退化成了计数排序。

堆排序

略(回头再讲)

你可能感兴趣的:(排序算法)