排序算法

冒泡排序(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<Int>(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) 会急剧增大,此时基数排序退化成了计数排序。

堆排序

略(回头再讲)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,164评论 0 52
  • 总结一下常见的排序算法。 排序分内排序和外排序。内排序:指在排序期间数据对象全部存放在内存的排序。外排序:指在排序...
    jiangliang阅读 1,323评论 0 1
  • Ba la la la ~ 读者朋友们,你们好啊,又到了冷锋时间,话不多说,发车! 1.冒泡排序(Bub...
    王饱饱阅读 1,785评论 0 7
  • 参考:十大经典排序算法 0、排序算法说明 0.1排序的定义 对一序列对象根据某个关键字进行排序。 0.2 术语说明...
    谁在烽烟彼岸阅读 1,005评论 0 12
  • 逻辑层次的广泛应用,先建立亲和,觉察是在价值和目标上聊,近期的打算是什么,如何打算去做,现在做的价值,还是有点模糊...
    691bd9461501阅读 268评论 0 0