2022-08-29

十大经典排序算法
1.经典排序:时间复杂度O(N^2),空间复杂度:O(1),稳定性:稳定

void classic_sort(int* arr,size_t len)
{
    for(int i = 0;i<len-1;i++)
{
    for(int j=i+1;j<len;j++)
{
    if(arr[i]>arr[j])
      swap(arr[i],arr[j]);
}
}
}

2.冒泡排序:最优时间复杂度:O(N),最差时间复杂度:O(N^2),
平均时间复杂度:O(N^2),空间复杂度:O(1),稳定性:稳定

void bubble_sort(int* arr,size_t len)
{
    bool flag = true;
    for(int i=len-1;flag&&i>0;i--)
{
    flag = false;
    for(int j = 0;j<i;j++)
{
    if(arr[j]>arr[j+1])
{
    swap(arr[j],arr[j+1]);
    flag = true;
}
}
}
}

3.选择排序:时间复杂度O(N^2),时间复杂度:O(1),稳定性:不稳定

void selsect_sort(int* arr,sieze_t len)
{
    for(int i =0;i<len-1,i++)
{
    int min = i;
    for(int j=i+1;j<len;j++)
    {
        if(arr[min]>arr[j])
        {
            min=j;
        }
      if(min != i)
      swap(arr[min],arr[j]);
    }
}
}

4.插入排序:
往有序的序列中添加新的数据,使序列继续保持有序,具体步骤是:假定新的数据存放在i=len位置,val与它前面的数据逐一进行比较val<arr[i-1],如果前面的数据大于tmp大,则把前面数据的向后拷贝一下,然后i自减1重复以比较,直到arr[i-1]<=tmp或0==i,则位置i就是tmp应该存放的位置,新插入的数据就排序完成。

// 时间复杂度:O(N^2),空间复杂度:O(1),稳定性:稳定
void insert_sort(int* arr,size_t len)
{
    for(int i=1; i<len; i++)
    {
        int tmp = arr[i], j = i;
        while(j-1>=0 && arr[j-1]>tmp)
        {
            arr[j] = arr[j-1];
            j--;
        }
        arr[j] = tmp;
    }
}

5.、希尔排序:
设计该算法的作者叫希尔,所以叫希尔排序,它在插入排序的基础上引入了增量概念(数据在插入时,每次移动的距离),插入排序默认一次只移动一个位置,当数据量比较大时,移动的速度比较慢,希尔排序先以数量的一半为移动增量,进行插入排序,对数据进行大致排序,然后再减小增量对数据进行微调,进而完成插入排序。

// 时间复杂度:O(NlogN),空间复杂度:O(1),稳定性:不稳定
void shell_sort(int* arr,size_t len)
{
    for(int k=len/2; k>0; k/=2)
    {
        for(int i=k; i<len; i+=k)
        {
            int tmp = arr[i], j = i;
            while(j-k>=0 && arr[j-k]>tmp)
            {
                arr[j] = arr[j-k];
                j-=k;
            }
            arr[j] = tmp;
        }
    }
}

6.快速排序:
先在待排序的序列中找出一个标杆,然后与剩余的数据进行比较,比标杆小的数据放在标杆的左边,比标杆大的数据放在它右边,这样就做到以标杆为准的大致有序,然后再次同样的方法对标杆左边的数据进行排序、标杆右边的数据进行排序,直到整个序列完全有序。
注意:快速排序之所以叫快速排序,综合各种情况它的表示最好,速度最快,如果对待排序的数据不了解,建议优先选择快速排序。

// 时间复杂度:O(NlogN),空间复杂度:O(1),稳定性:不稳定
void _quick_sort(int* arr,int left,int right)
{123
    // 备份左右边界
    int l = left, r = right;
    // 把最左边的数据作为标杆,记录标杆下标
    int pv = arr[left], pi = left;

    while(l<r)
    {
        // 从右向左寻找比标杆小的数据
        while(l<r && arr[r]>=pv) r--;
        // 找到比标杆小的数据
        if(l<r)
        {
            // 把它移动标杆的左边
            arr[pi] = arr[r];
            // 记录标杆的新位置
            pi = r;
        }
        // 从左向右寻找比标杆大的数据
        while(l<r && arr[l]<=pv) l++;
        // 打到比标杆大的数据
        if(l<r)
        {
            // 把它移动到标杆的右边
            arr[pi] = arr[l];
            // 记录标杆的新位置
            pi = l;
        }
    }
    // 把标杆的值存放到最新位置
    arr[pi] = pv;

    // 快速排序标杆左边的数据
    if(pi-left>1)
        _quick_sort(arr,left,pi-1);
    // 快速排序标杆右边的数据
    if(right-pi>1)
        _quick_sort(arr,pi+1,right);
}
void quick_sort(int* arr,size_t len)
{
    _quick_sort(arr,0,len-1);
}

7.堆排序:
所谓堆排序就是把待排序的数据当作一个大根堆,然后逐步把堆顶的最大值弹出存储在序列的末尾,也就是借助大根堆这一数据结构完成的排序。

注意:理论上来说堆排序的速度不比快排序慢,但是对无序的序列排序需要先创建堆,时间复杂度是O(N),然后再逐一出堆完成排序时间复杂度是O(NlogN),所以对无序的序列排序快速排序比堆的速度要快,所以一般在实际应用中不使用堆排序,只活在教课书中。
// 时间复杂度:O(NlogN),空间复杂度:O(1),稳定性:不稳定
void _heap_sort(int* arr,int root,size_t len)
{
    while(root*2+1<len)
    {
        // 假定左子树是左右子树中的最大值
        int max = root*2+1;
        // 判断右子树是否大于左子树,如果大于则更新最大值下标
        if(max+1<len && arr[max+1]>arr[max])
            max++;
        // 如果最大子树依然小于根,则结束
        if(arr[max] < arr[root])
            return;
        // 把最大子树与根交换
        swap(arr[max],arr[root]);
        root = max;
    }
}
void heap_sort(int* arr,size_t len)
{
    // 创建大根堆
    for(int i=len/2-1; i>=0; i--)
        _heap_sort(arr,i,len);

    for(int i=len-1; i>0; i--)
    {
        // 把堆的根与末尾的数据交换,数量减-1
        swap(arr[0],arr[i]);
        // 调整堆
        _heap_sort(arr,0,i);
    }
    printf("%s:",__func__);
}

8.归并排序:
把待排序的数据以k=2为单位进行分组,每组分为左右两部分,然后按从小到大的顺序合并到另一块空间,然后k*=2重复该过程,直到k/2>=len则排序完成。
归并排序需要一块额外的空间,用于存储合并的结果,它的时间复杂度与快速、堆相同,但是在排序过程中没有进行数据交换,而直接数据拷贝,因此节约了大量的数据交换的时间,但也耗费了额外的内存,所以它是一各典型的用空间换取时间的排序算法。
注意:如果用户对排序速度有很高的要求,但不在意内存的耗费,适合使用归并排序。

// 时间复杂度:O(NlogN),空间复杂度:O(N),稳定性:稳定
void merge_sort(int* arr,size_t len)
{
    // src指向的是待合并的数据,dest用于存储合并后的数据
    int *src = arr, *dest = malloc(sizeof(arr[0])*len);
    for(int k=1; k<len; k*=2)
    {
        // 以k为单位进行合并
        for(int i=0,j=i; i<len; i+=k*2)
        {
            // 计算出左边的起始位置和结束位置
            int l = i, le = i+k < len ? i+k : len;
            // 计算出右边的起始位置和结束位置
            int r = le, re = i+k*2 < len ? i+k*2 : len;
            // 左右两边的数据进行合并,谁小谁存储在dest里面,直到有一方结束
            while(l<le && r<re)
            {
                dest[j++] = src[l]<src[r] ? src[l++] : src[r++];
            }
            // 把左边剩余的数据拷贝到dest里面
            while(l<le)
                dest[j++] = src[l++];
            // 把右边剩余的数据拷贝到dest里面
            while(r<re)
                dest[j++] = src[r++];
        }
        // 合并完成后,dest中存储的就是下一次待合并的数据,所以两个指针交换
        swap(src,dest);
    }
    // 执行完swap(src,dest)后,src指向的是排序后的数据,如果src!=arr则说明排序后的数据存储在堆内在中,需要拷贝给arr
    if(src != arr)
    {
        memcpy(arr,src,sizeof(arr[0])*len);
        dest = src;
    }
    // 释放堆内存
    free(dest);
}

9.计数排序:
首先定义一个计算数据出现的次数的数组cnts,并所有成员初始化0,然后使用数据的值作为数组的下标,然后统计每个数据出现的次数。
以i=[0,max]遍历cnts数组,当cnst[i]>0说明i出现过,然后把i往待排序的数组中存储cnts[i]个,然后排序完成。
注意:计算排序的局限比较大,它只能对整型数据进行排序,无法对浮点型、字符串型数据进行排序,待排序的数据重复性越高,差值越小,速度就越快,反之虽然也可以排,但得不尝失。

// 时间复杂度:O(N+K),空间复杂度:O(K),稳定性:稳定
void count_sort(int* arr,size_t len)
{
    int cnts[100] = {}; 
    for(int i=0; i<len; i++)
    {   
        cnts[arr[i]]++;
    }

    int k = 0;
    for(int i=0; i<100; i++)
    {
        for(int j=0; j<cnts[i]; j++)
        {   
            arr[k++] = i;
        }
    }
}

10.基数排序
先根据数据个位的大小对数据进行排序,然后再对排序结果的十位进行排序,然后百位、千位...,直到排序完成。
使用这种方式排序的优点是不需要对待排序的数据进行比较、交换,所以它的排序速度要比普通排序快的多,但局限性很大,只能对整型数据排序,还需要额外的内存空间。
注意:当数据的位数不多,并且差别不大,的整形数据适合使用基数排序。

// 时间复杂:O(n+k) 空间复杂度:O(n+k) 稳定性:稳定
void radix_sort(int* arr,size_t len)
{
    int (*radix)[len+1] = calloc(10,sizeof(arr[0])*(len+1));

    int exp = 1;
    while(exp < 1000000000)
    {
        for(int i=0; i<len; i++)
        {
            int row = arr[i]%(exp*10)/exp;
            radix[row][++radix[row][0]] = arr[i];
        }
        exp *= 10;

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

推荐阅读更多精彩内容