常用的排序算法

排序算法几乎是每个程序开发者都要必备的技能,从大学期间C语言学习的冒泡和选择排序,到后来数据结构课程学习的各种其他排序。这篇文章再次梳理一下这些经典的排序算法。

各种排序算法的时间复杂度


如上图,这里列出了各种排序的时间和空间复杂度。记得在校招期间,一个面试官问我们一群学生,排序算法中那个最快。很多的同学回答了快速排序,然后被直接的pass了,当时我回答了一个堆排序,拿到了后面继续面试的资格。

可以看到快速排序和堆排序这里平均的情况是一样的都是o(nlog2 n),但是在当序列已经接近排好的情况时快速排序的时间复杂度是o(n^2),堆排序没有变。并且堆排序的空间复杂度比快速排序要小。
这里的稳定指的是相同元素,会不会占用内存。
什么时候用什么排序呢?

  • 如果元素比较多,并且比较乱,选择快速排序。
  • 如果元素比较多,并且提前有序,选择堆排序。
  • 如果元素相同的元素比较多,这里选择归并排序。

交换排序

交换排序是每次比较,如果元素和序列不同就要交换。故为交换排序。

冒泡排序

算法思想:比较相邻的元素。如果第一个比第二个大,就交换他们两个。

  private static void sortByBubble(int[] num) {
        //第一层循环从数组的最后往前遍历
        for (int i = num.length - 1; i > 0; --i) {
            //这里循环的上界是 i - 1,在这里体现出 “将每一趟排序选出来的最大的数从sorted中移除”
            for (int j = 0; j < i; j++) {
                //保证在相邻的两个数中比较选出最大的并且进行交换(冒泡过程)
                if (num[j] > num[j + 1]) {
                    int temp = num[j];
                    num[j] = num[j + 1];
                    num[j + 1] = temp;
                }

            }
            for (int k = 0; k < num.length; k++) {
                System.out.print(num[k] + " ");
            }
            System.out.print("\n");
        }
    }

记忆技巧:冒泡是将最大元素冒出来,第一个最大冒出来之后,下一次再遍历的时候比较的元素会少一,这里双层for循环。

  • 第一层循环从数组的最后往前遍历,这里循环的上界是 i - 1,在这里体现出 “将每一趟排序选出来的最大的数从sorted中移除。
  • 第二层循环是从0到本次循环的最大值,也就是i,是一个递增的过程,如果不合适,就交换。
快速排序

算法思想:本质来说,快速排序是冒泡排序的升级版,也是一种交换排序
由于快速排序是一种分治算法,我们可以用分治思想将快排分为三个步骤:

  1. 分:设定一个分割值,并根据它将数据分为两部分
  2. 治:分别在两部分用递归的方式,继续使用快速排序法
  3. 合:对分割的部分排序直到完成

图解:

代码实现

 private static int getMiddle(int[] list, int low, int high) {
        int tmp = list[low];//数组的第一个作为中轴
        while (low < high) {
            while (low < high && list[high] >= tmp) {
                high--;
            }
            list[low] = list[high];   //比中轴小的记录移到低端
            while (low < high && list[low] <= tmp) {
                low++;
            }
            list[high] = list[low];   //比中轴大的记录移到高端
        }
        list[low] = tmp;              //中轴记录到尾
        return low;                   //返回中轴的位置
    }
    private static void quickSort(int[] list, int low, int high){
        if (low < high) {
            int middle = getMiddle(list, low, high);       //将list数组进行一分为二
            quickSort(list, low, middle - 1);        //对低字表进行递归排序
            quickSort(list, middle + 1, high);       //对高字表进行递归排序
        }
    }

记忆技巧:

  • 取得中轴元素,完成交换的三步,两步在大while内,两小while后,结合图来写。
  • 递归拿到进行排序,知道low<high 不成立
  • 测试调用quickSort(num,0,num.length-1);

选择排序

选择排序是在一轮比较之后,选择最值,然后和排序开始位置交换。

直接选择排序

基本思想:在要排序的一组数中,选出最小的一个数与第一个位置的数交换;然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环到倒数第二个数和最后一个数比较为止。

 private static void selectSort(int[] nums) {
        int k, temp; //定义k为最小值的索引,temp是用于交换的临时变量
        for (int i = 0; i < nums.length; i++) {
            k = i; //每次假设第一个最小
            for (int j = i + 1; j < nums.length; j++) {
                if (nums[j] < nums[k]) {
                    k = j;//k是动态记录最小值,这里只记录最小的下标。
                }
            }
            temp = nums[i];
            nums[i] = nums[k];
            nums[k] = temp;
        }
    }

记忆技巧: 双层for循环,第二层只记录最值位置,在第一层内部进行交换。

堆排序

什么是堆?

堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:

对堆中的结点按层进行编号,将这种逻辑结构映射到数组中就是下面这个样子

大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
堆排序基本思想及步骤

  • 堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了

  • 步骤一 构造初始堆。将给定无序序列构造成一个大顶堆(一般升序采用大顶堆,降序采用小顶堆)。

    • 1.假设给定无序序列结构如下,先标号,然后构造一个完全二叉树
    • 2.此时我们从最后一个非叶子结点开始(叶结点自然不用调整,第一个非叶子结点 arr.length/2-1=5/2-1=1,也就是下面的6结点),从左至右,从下至上进行调整。
    • 3.找到第二个非叶节点4,由于[4,9,8]中9元素最大,4和9交换。
    • 这时,交换导致了子根[4,5,6]结构混乱,继续调整,[4,5,6]中6最大,交换4和6。
  • 步骤二 将堆顶元素与末尾元素进行交换,使末尾元素最大。然后继续调整堆,再将堆顶元素与末尾元素交换,得到第二大元素。如此反复进行交换、重建、交换。

    • a.将堆顶元素9和末尾元素4进行交换
    • b.重新调整结构,使其继续满足堆定义
    • c.再将堆顶元素8与末尾元素5进行交换,得到第二大元素8.
    • 后续过程,继续进行调整,交换,如此反复进行,最终使得整个序列有序


      代码实现:

 private static void heapSort(int[] arr) {
        //1.构建大顶堆
        for (int i = arr.length / 2; i >= 0; i--) {
            //从第一个非叶子结点从下至上,从右至左调整结构
            adjustHeap(arr, i, arr.length);
        }
        for (int j = arr.length - 1; j > 0; j--) {
            swap(arr, 0, j);
            adjustHeap(arr, 0, j);
        }

    }

    private static void adjustHeap(int[] arr, int i, int length) {
        int temp = arr[i];//先取出当前元素i
        for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {//从i结点的左子结点开始,也就是2i+1处开始
            if (k + 1 < length && arr[k] < arr[k + 1]) {//如果左子结点小于右子结点,k指向右子结点
                k++;
            }
            if (arr[k] > temp) {//如果子节点大于父节点,将子节点值赋给父节点(不用进行交换)
                arr[i] = arr[k];
                i = k;
            } else {
                break;
            }
        }
        arr[i] = temp;//将temp值放到最终的位置
    }

插入排序

插入排序像我们打扑克,把刚拿到的牌放到合适的位置。

直接插入排序

思想:每步将一个待排序的记录,按其顺序码大小插入到前面已经排序的字序列的合适位置(从后向前找到合适位置后),直到全部插入排序完为止。
代码实现:

   /**
     * 跳格子比较
     * @param nums
     */
    public static void insertSort(int nums[]){
        //插入排序是把原有序列的元素插入到已经排好的序列中。需要双层for循环。
        //第一层第一次for循环从第二个元素开始往第一个元素插入。一直到最后一个num
        //第二层要比较从0开始到i-1的所有元素
        for (int i = 1 ;i< nums.length;i++){
            for (int j = 0;j < i;j++ ){
                if (nums[i]<nums[j]){
                    //如果不满足条件,进行交换
                    int temp = nums[i];
                    nums[i] = nums[j];
                    nums[j] = temp;
                }
            }
        }
    }

记忆技巧:

  • 插入排序是把原有序列的元素插入到已经排好的序列中。需要双层for循环。
  • 第一层第一次for循环从第二个元素开始往第一个元素插入。一直到最后一个num
  • 第二层要比较从0开始到i-1的所有元素
希尔排序
  • 基本思想:先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。

  • 希尔排序(Shell's Sort)是插入排序的一种又称“缩小增量排序”,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。该方法因D.L.Shell于1959年提出而得名。

  • 图解
  • 代码实现

  private static void shellSort(int[] nums) {
        for (int increment = nums.length / 2; increment > 0; increment /= 2) {
            System.out.println("increment:" + increment);
            for (int i = increment; i < nums.length; i++) {
                System.out.println(" i:" + i);
                for (int j = i; j >= increment; j -= increment) {
                    System.out.print(" j:" + j + ",j-increment:" + (j - increment));
                    if (nums[i] < nums[j - increment]) {
                        int temp = nums[i];
                        nums[i] = nums[j - increment];
                        nums[j - increment] = temp;
                    } else {
                        break;
                    }
                }
                System.out.println();
            }
        }
    }

以上就是我对算法排序的理解。

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

推荐阅读更多精彩内容