八大排序总结

不同的场景使用不同的排序算法本身没有好坏之分。

(Ps:所有算法均自己测试通过没有bug,如果小伙伴们有疑问请随时留言提问~~~)
当面试官问上面我说的那个问题的时候要首先考虑清楚面试官要干什么?比如:
可以反问面试官:

  • 有没有大量的重复值?-->计数排序
  • 是否大部分数据距离他们 的正确位置很近?是否近乎有序?-->插入排序
  • 是否数据的取值范围非常有限?比如学生成绩排序。-->计数排序
  • 是否需要稳定?-->优先考虑归并
  • 是否使用链表存储?-->链表归并排序或者链表快速排序(关于链表的排序近期会有更新)
  • 数据的大小是否可以装载到内存中?-->外排序
/**
 *  冒泡排序:
 *
 *  时间复杂度:
 *      O(n)^2
 *  空间复杂度:
 *      O(1)
 *  稳定性:
 *      稳定
 *
 *  以升序情况下为例:
 *  1.首先指针指向数组中的第一个元素,让当前元素和他后一个元素做比较,
 *      如果比后一个元素大的话,交换两个元素的位置。
 *  2.指针向后挪动一个单位,继续重复上面的的步骤,直到指针走到数组长度减一的位置,
 *        那个此时数组中的最大值就是数组中的最后一个元素,
 *  3.数组的长度减一,继续重复上面的动作,直到数组的长度为1的时候结束。
 *
 *  缺点:
 *      可能在数组长度不到1的时候,序列就已经有序了,所以,
 *      增加一个标志位,如果在发生交换,更改标志位,
 *      一次排序后,判断标志位是否更改,如果没有更改代表排序已经完成
 */
void bubble_sort(int arr[], int len){
    int flag = 1;
    for (int i = 0;flag && i < len - 1; i++){
        flag = 0;
        for (int j = i; j < len-1-i; j++){
            if (arr[j]>arr[j + 1]){
                flag = 1;
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

/**
 *  插入排序:
 *
 *  时间复杂度:
 *      O(n)^2
 *  空间复杂度:
 *      O(1)
 *  稳定性:
 *      稳定
 *  
 *  是升序情况为例:
 *  将数组分成有序和无序两部分,每次从无序的数组中取出一个元素插入到有序的那部分中
 * 
 */

void insert_sort(int data[], int len){
    for (int i = 0; i < len - 1; i++){
        int temp = data[i + 1];
        int j = i;
        while (j >= 0 && data[j]>temp){
            data[j + 1] = data[j];
            j--;
        }
        data[j + 1] = temp;
    }
}

/**
*   选择排序:
*
*   时间复杂度:
*       O(n)^2
*   空间复杂度:
*       O(1)
*   稳定性:
*       不稳定
*
*   是升序情况为例:
*   它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,
*   存放在序列的起始位置,直到全部待排序的数据元素排完
*/
void select_sort(int data[], int len){
    int min;
    for (int i = 0; i < len-1; i++){
        int min = i;
        for (int j = i + 1; j < len; j++){
            if (data[min]>data[j]){
                min = j;
            }
        }
        if (min != i){
            int temp = data[i];
            data[i] = data[min];
            data[min] = temp;
        }
    }
}
/**
 *  归并排序:
 *
 *  时间复杂度:
 *      O(n log n)
 *  空间复杂度:
 *      O(n)
 *  稳定性:
 *      稳定
 *
 *  该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
 *  将已有序的子序列合并,得到完全有序的序列;
 *  即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表
 */

void array_merge(int data[], int low, int mid, int high){
    int len = high - low;
    int *p = (int*)malloc(sizeof(int)*len);
    int i = low, j = mid, k = 0;
    while (i < mid&&j < high){
        if (data[i] < data[j]){
            p[k++] = data[i++];
        }
        else{
            p[k++] = data[j++];
        }
    }
    while (i < mid){
        p[k++] = data[i++];
    }
    while (j < high){
        p[k++] = data[j++];
    }
    for (int i = 0, j = low; i < k; i++){
        data[j++] = p[i];
    }
    free(p);
}
void merge_sort(int data[], int low, int high){
    if (NULL == data || low < 0 || high <= 0||low>=high){
        return;
    }
    if (high - low == 1){
        return;
    }
    int mid = (high + low) / 2;
    merge_sort(data, low, mid);
    merge_sort(data, mid , high);
    array_merge(data, low, mid, high);
}

/**
 *
 *  快速排序:
 *
 *  时间复杂度:
 *      O(n log n)
 *  空间复杂度:
 *      O(n)
 *  稳定性:
 *      不稳定
 *
 *  首先确定一个基准值,通过比较和交换,使得基准值左边的数字都比基准值小
 *  基准值右边的数都比基准值大,然后以基准值为分割点,分成三部分,分别进行左递归和右递归进行排序。
 *
 *
 *  缺点:
 *  传统基准值的选定时每次参照顺序表的第一个元素作为基准值,
 *  但是如果数组为有序的情况下,会大大降低排序效率,退化成冒泡排序时间复杂度升为O(n^2)
 *  解决方案就是三数取中。
 *
 *
 *  优化1:
 *      当待排序序列的长度分割到一定大小后,使用插入排序
 *  原因:
 *      对于很小和部分有序的数组,快排不如插排好。
 *      当待排序序列的长度分割到一定大小后,继续分割的效率比插入排序要差,此时可以使用插排而不是快排。
 *
 *  优化2:
 *      在一次分割结束后,可以把与Key相等的元素聚在一起,继续下次分割时,不用再对与key相等元素分割
 *      在一次划分后,把与key相等的元素聚在一起,能减少迭代次数,效率会提高不少
 *      具体过程:在处理过程中,会有两个步骤
 *      第一步,在划分过程中,把与key相等元素放入数组的两端
 *      第二步,划分结束后,把与key相等的元素移到枢轴周围
 */

void swap(int *a, int *b){
    int temp = *a;
    *a = *b;
    *b = temp;
}
//三数取中:先找到一个最大值放到data[len-1]的位置,
//之后mid值通过data[low]和data[mid]确定
int midAndSwap(int data[], int low, int high){
    if (NULL == data || low < 0 || high <= 0){
        return 0;
    }
    int mid = (high - low) / 2 + low;
    if (data[low]>data[high - 1]){
        swap(&data[low], &data[high - 1]);
    }
    if (data[mid] > data[high - 1]){
        swap(&data[mid], &data[high - 1]);
    }
    if (data[low] < data[mid]){
        swap(&data[low], &data[mid]);
    }
    return data[low];
}
void insertSort(int data[], int len){
    for (int i = 0; i < len - 1; i++){
        int temp = data[i + 1];
        int j = i;
        while (j >= 0 && data[j]>temp){
            data[j + 1] = data[j];
            j--;
        }
        data[j + 1] = temp;
    }
}

void quickSort(int data[], int low, int high){
    //转换成插入排序数组的长度为(5<len<15)
    if ((high - low) < 2){
        insertSort(data + low, high - low);
        return;
    }

    int left = low;
    int right = high - 1;

    int leftCopy = left;
    int rightCopy = right;

    int key = midAndSwap(data, low, high);
    while (left < right){
        for (; left < high&&data[left] <= key; left++){
            if (key == data[left]){
                swap(&data[left], &data[leftCopy]);
                leftCopy++;
            }
        }
        for (; right >= left&&data[right] >= key; right--){
            if (key == data[right]){
                swap(&data[right], &data[rightCopy]);
                rightCopy--;
            }
        }
        if (left < right){
            int temp = data[left];
            data[left] = data[right];
            data[right] = temp;
        }
    }
    //将枢纽元素移动到中间位置
    left = right;
    while (leftCopy - low){
        swap(&data[--leftCopy], &data[left--]);
    }
    if (right - left){
        right++;
    }
    while (high - 1 - rightCopy){
        swap(&data[++rightCopy], &data[right++]);
    }
    quickSort(data, low, left + 1);
    quickSort(data, right, high);
}


/**
 *  堆排序
 *
 *  时间复杂度:
 *    O(n log n)
 *  空间复杂度:
 *    O(1)
 *  稳定性:
 *    不稳定
 *
 *  堆排序的基本思想是:将待排序序列构造成一个小根堆,此时,整个序列的最小值就是堆顶的根节点。
 *  将其与末尾元素进行交换,此时末尾就为最小值。
 *  然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。
 *  如此反复执行,便能得到一个有序序列了。
 *  
 *  参考博客:https://www.cnblogs.com/chengxiao/p/6129630.html
 */
void heap_sort(int data[], int len){
    if (data == NULL || len <= 0){

    }
    int pa, tag , end = len;
    //end-1解释: 在数组中只剩下一个元素的时候不用比较
    while (end - 1){
        tag = 1;
        while (tag){
            pa = end / 2;
            tag = 0;
            while (pa){
                if (data[pa] < data[2 * pa]){
                    swap(&data[pa], &data[2 * pa]);
                    tag = 1;
                }
                if (2 * pa + 1 <= end&&data[pa] < data[2 * pa + 1]){
                    swap(&data[pa], &data[2 * pa + 1]);
                    tag = 1;
                }
                pa--;
            }
        }
        swap(&data[1], &data[end--]);
    }
}

/**
 *  计数排序:
 *  
 *  时间复杂度:
 *      O(n)
 *  空间复杂度:
 *      O(1)
 *  稳定性:
 *      不稳定
 *  
 *  适合数据特别集中的情况。
 *  这里以升序,且数据范围在0-100之间的数
 *  根据获得的数据表的范围,分割成不同的100个桶,
 *  然后直接统计数据在桶上的频次,然后顺序遍历桶就可以得到已经排好序的数据表。
 *      
 */
void count_sort(int data[], int len){
    if (NULL == data || len <= 0){
        return;
    }
    int table[100] = { 0 };
    for (int i = 0; i < len; i++){
        table[data[i]]++;
    }
    int index = 0;
    for (int i = 0; i < 100; i++){
        while (table[i]){
            data[index++] = i;
            table[i]--;
        }
    }
}
/*
    希尔排序:

    时间复杂度:
        O(n)
    空间复杂度:
        O(1)
    稳定性:
        不稳定

    比较相隔较远距离(称为增量)的数,使得数移动时能跨过多个元素,
    则进行一次比较就可能消除多个元素交换。
    算法先将要排序的一组数按某个增量d分成若干组,每组中记录的下标相差d.对每组中全部元素进行排序,
    然后再用一个较小的增量对它进行,在每组中再进行排序。
    当增量减到1时,整个要排序的数被分成一组,排序完成。
*/

/**
 *  外排序
 *
 *  基本要点:
 *      外部排序顾名思义是对外部存储空间中数据的排序,那为什么不能像选择排序、插入排序、快速排序那样也直接进行排序呢?
 *      原因是外部存储的数据量可能是非常大的,而计算机的内存大小要远远小于外存,计算机一下子读不了那么多数据,
 *      无法一次性对它们进行排序,这就是外部排序产生的原因。
 *  基本思想:
 *      1.  我们这里借助归并排序的思想
 *      假设我的一个文件中有10000个数据,而我的内存每次只能读2000个数据,那我先对文件预处理一下,将原文件切割成5个小文件,
 *      每个文件中有2000个有序数据(在读入内存2000个数据后,对数据排序后再写入到新文件中)。
 *      2.  然后我们开始进行文件合并(这里采用2-路归并)。先分别打开两个文件,分别读取文件的第一行数据,
 *      把数据较小的写入到一个新文件中,然后把数据较小的文件再往下读一行,直到两个文件中的数据全部有序地写到新文件中为止,
 *      这样原先5个2000个数据的文件变为2个4000个数据和1个2000个数据的文件。
 *      3.  重复步骤2,再次合并变为1个8000个数据和1个2000个数据的文件,
 *      再次合并变为1个10000个数据的文件。排序完成,原先有10000个数据的文件变得有序。
 *
 */
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,468评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,620评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,427评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,160评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,197评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,334评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,775评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,444评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,628评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,459评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,508评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,210评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,767评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,850评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,076评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,627评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,196评论 2 341

推荐阅读更多精彩内容