算法学习之不那么简单的排序(1)

也不知道与简单排序对应的应该叫什么, 就叫不那么简单的排序好了.

本篇博客主要学习了希尔排序、归并排序and快速排序。

注: 这一篇和上一篇简单排序都算是学习白话算法系列的学习笔记吧

希尔排序

希尔排序是基于插入排序而来, 插入排序的最好时间复杂度是O(n), 当数组基本有序时, 效率是很高的. 而希尔排序, 设定一个增量, 按增量将数组分组.

例如数组{1,2,3,4}, 增量是2, 那么数组就可以分为{1,3}, {2,4}两组, 增量是1那就是1,2,3,4四组.

分组之后在组内进行插入排序, 再缩小增量重新分组再次排序, 直到增量是1(等同于正常的插入排序), 再插入排序一次, 排序完成.

void shellSort(int arr[], int n){
    
    for (int gap = n/2; gap>0; gap/=2) {
        for (int i = gap; i<n; i++) {
            if (arr[i] < arr[i - gap]) {
                int temp = arr[i];
                int j;
                // 思路与插入排序相同, 用临时变量保存要插入的数, 向数组前面查找插入的位置, 一边查找, 一边将前面较大的数字后移
                // 临时变量不小于前面的某数时, 说明找到了正确的位置, 只要放在那个数后面就可以了
                for (j = i-gap; j>=0 && temp<arr[j]; j-=gap) {
                    arr[j+gap] = arr[j];
                }
                arr[j+gap] = temp;
            }
        }
    }
}

归并排序

归并二字就是递归&合并

归并排序的关键在于合并有序数组, 合并两个有序数组的方式是先比较两数组的第一个元素, 更小的取出放入新数组, 再依次向后比较, 直到某个数组的元素取光, 把另一个数组的元素依次放入新数组既可.

//先来演示合并数组
void mergeArray(int a[], int m, int b[], int n){
    int c[m+n];
    
    int i, j, k;
    //必须初始化, 否则会有残值
    i = j = k = 0;
    
    // 此处不能用for循环, 除非只写第二个表达式, 否则ijk哪个做自增都不合适
    // 其中k看似合适, 但for循环最后会执行一次第三个表达式, k会+1
    while (i < m && j < n) {
        if (a[i] < b[j]) {
            c[k++] = a[i++];
        }else{
            c[k++] = b[j++];
        }
    }
    
    while (i < m) {
        c[k++] = a[i++];
    }
    
    while (j < n) {
        c[k++] = b[j++];
    }
    
    printfArray(c, m+n);
}

下面开始撸正式的归并排序

// 合并有序序列
void mergearray(int arr[], int first, int last, int mid, int temp[]){
    int tempIndex = 0;
    
    int firstSequenceIndex = first;
    int secondSequeceIndex = mid + 1;
    
    // 因为这里用的是数组角标, 而不是长度, 所以用<= 而不是<
    while (firstSequenceIndex <= mid && secondSequeceIndex <= last) {
        // 取较小值放入临时数组
        if (arr[firstSequenceIndex] < arr[secondSequeceIndex] ) {
            temp[tempIndex++] = arr[firstSequenceIndex++];
        }else{
            temp[tempIndex++] = arr[secondSequeceIndex++];
        }
    }
    // 如果前一个序列还有值, 依次放入临时数组
    while (firstSequenceIndex <= mid) {
        temp[tempIndex++] = arr[firstSequenceIndex++];
    }
    // 如果后一个序列还有值, 依次放入临时数组
    while (secondSequeceIndex <= last) {
        temp[tempIndex++] = arr[secondSequeceIndex++];
    }
    // 将排好序的部分赋值给原数组
    for (int i = 0; i < tempIndex; i++) {
        arr[first++] = temp[i];
    }
    
}

// 搞清归并排序, 主要搞清以下两点
// 1. 递归到只有一个数时, 递归函数开始出栈, 一个数肯定是有序序列
// 2. 合并两个有序序列, 可以形成新的有序序列
void mergeSort(int arr[], int first, int last, int temp[]){
    if(first < last){
        // 将数组分成两部分
        int mid = (first + last)/2;
        // 前一半排序
        mergeSort(arr, first, mid, temp);
        // 后一半排序
        mergeSort(arr, mid+1, last, temp);
        // 合并有序序列
        mergearray(arr, first, last, mid, temp);
    }
}

快速排序

快速排序是时间复杂度O(logN*N)的排序算法中比较出名的, 面试算法常常会问, 而手写出来是很有难度的事情. 这里非常感谢白话经典算法系列的作者, 讲解通俗易懂.

快速排序的基本思想一句话概括就是<font color='red'>挖坑填数+分治法</font>, 下面详细描述:

  1. 先取左边第一个数作为基准数
  2. 与基准数比较, 比基准数大的换到右边, 小的换到左边
  3. 左右两边分成两个部分, 再进行一次前两步的操作. 重复对左右两边拆分, 进行前两步操作, 直到只剩一个数.

这样说还是太抽象, 举个栗子吧

数组a = {3, 1, 4, 2, 0}

  1. 取a[0]作为基准数, 使用新变量baseNumber存储
  2. 从右向左比较, 比基准数小的放在基准数的位置上, 数组变成{<font color='red'>0</font>, 1, 4, 2, <font color='red'>0</font>}, 此时出现一个坑a[4]
  3. 从左往右比较, 比基准数大的填入上一个坑a[4], 数组变成{0, 1, <font color='red'>4</font>, 2, <font color='red'>4</font>}, 此时的新坑是a[2]
  4. 再从右向左比较, 比基准数小的填入上一个坑a[2], 数组变成{0, 1, <font color='red'>2</font>, <font color='red'>2</font>, 4}, 此时的坑是a[3]
  5. 再从左向右比较时, 发现左右相遇了, 将baseNumber赋值给a[3], 数组变成{0, 1, 2, 3, 4}

因为数组元素较少, 这样就排序完成了, 但足够大家了解挖坑填数的思路了.

有一点需要说明, 为什么左右相遇了就可以把baseNumber赋值给那个元素? 因为左右两边相遇时, 所有数字都已经比较了一遍, 已经做到"比基准数大的都在右边, 比基准数小的都在左边".

根据上面的分析, 可以很容易写出挖坑填数的代码:

void changeArray(int arr[], int left, int right){
    int i = left;
    int j = right;
    
    // 使用变量存储最左边的数做基准数
    // 基准数也可不使用最左边的, 中间和最后一个当然都可以
    int baseNumber = arr[left];
    
    // 当i=j时意味着数列中所有数都与基准数比较过了, 故结束比较
    while (i < j) {
        
        // 从右往左比较, 找到比基准数小的数的下标
        while (arr[j] > baseNumber && i < j) {
            j--;
        }
        arr[i] = arr[j];
        
        // 从左往右比较, 找到比基准数大的数的下标
        while (arr[i] < baseNumber && i < j) {
            i++;
        }
        arr[j] = arr[i];
    }
    // 将基准数赋值给a[i](也可以是a[j], 此时i=j)
    arr[i] = baseNumber;
    
}

最后baseNumber赋值,arr[i] = baseNumber,可能会有人对这句疑惑, 为何可以直接赋值, 不会少一个数吗?

答案是不会, 从上面的代码看出, 即便while (arr[i] < baseNumber && i < j)这个循环没有走, arr[i]的值也会赋值给arr[j], 这样arr[i]的值必定有两个, 当然可以直接赋值.

接下来彻底完成递归调用:

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

推荐阅读更多精彩内容