常用排序算法

一、概述

不论是在面试中还是日常开发中,排序算法都是经常会用/考到的。常用的排序算法共8种,可分为5类:插入排序选择排序交换排序归并排序基数排序

常用排序算法

二、插入排序

插入排序中比较常见的是直接插入排序希尔排序

  • 直接插入排序

    • 基本思想:
      在要排序的一组数中,假设前面(n-1) [n>=2] 个数已经是排好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数也是排好顺序的。如此反复循环,直到全部排好顺序。
    • 性能/稳定性
    平均时间复杂度 空间复杂度 稳定性
    O(n*2) O(1) 稳定
    • 示例代码:
    public static void straightInsertionSort(int[] arr) {
         int length = arr.length;  //单独把数组长度拿出来 提高效率
         int insertNum; //要插入的数
         for (int i = 1; i < length; i++) {  //第一个数不用排序,所以下标从1开始
             insertNum = arr[i]; //取出要排序的这个数(后面移动时会丢失这个数)
             int j = i - 1; //序列元素个数(已排好序的元素个数)
             while (j >= 0 && arr[j] > insertNum) { //从后往前循环,将大于insertNum的数向后移动
                 arr[j + 1] = arr[j];  //元素向后移动
                 j--;
             }
             arr[j + 1] = insertNum; //找到位置,插入当前元素。
         }
     }
    
  • 希尔排序
    针对直接插入排序的低效率问题,有人对其进行了改进与升级,这就是现在的希尔排序。希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法

    • 基本思想
      将数的个数设为n,取奇数k=n/2,将下标差值为k的数分为一组,构成有序序列。再取k=k/2 ,将下标差值为k的数分为一组,构成有序序列。如此重复,直到k=1执行简单插入排序。
    • 性能/稳定性
    平均时间复杂度 空间复杂度 稳定性
    O(n*1.3) O(1) 不稳定
    • 示例代码:
    public static void shellSort(int[] arr) {
         int length = arr.length;
         while (length != 0) {
             length = length / 2;
             for (int i = 0; i < length; i++) { //分组
                 for (int j = i + length; j < arr.length; j += length) {//元素从第二个开始
                     int k = j - length;  //k为有序序列最后一位的位数
                     int temp = arr[j];  //要插入的元素
                     while (k >= 0 && arr[k] > temp) {  //从后往前遍历
                         arr[k + length] = arr[k];
                         k -= length;  //向后移动len位
                     }
                     arr[k + length] = temp;
                 }
             }
         }
     }
    

三、选择排序

  • 直接选择排序

    • 基本思想:
      遍历整个序列,将最小的数放在最前面。遍历剩下的序列,将最小的数放在最前面。重复第二步,直到只剩下一个数。
    • 性能/稳定性
    平均时间复杂度 空间复杂度 稳定性
    O(n*2) O(1) 不稳定
    • 示例代码:
    public static void simpleSelectSort(int[] arr) {
         int length = arr.length;
         for (int i = 0; i < length; i++) { //循环次数
             int value = arr[i]; //用于存储最小值
             int position = i; //用于存储最小值下标
             for (int j = i + 1; j < length; j++) { //找到最小值的位置
                 if (arr[j] < value) {
                     value = arr[j];
                     position = j;
                 }
             }
             arr[position] = arr[i]; //交换
             arr[i] = value;
         }
     }
    
  • 堆排序

    • 基本思想
      利用大顶堆设计出的一种算法,是对简单选择排序的优化。
    • 性能/稳定性
    平均时间复杂度 空间复杂度 稳定性
    O(nlog2n) O(1) 不稳定
    • 示例代码:
    public static void heapSort(int[] arr) {
         int length = arr.length;
         //循环建堆
         for (int i = 0; i < length - 1; i++) {
             //建堆
             buildMaxHeap(arr, length - 1 - i);
             //交换顶堆和最后一个元素
             swap(arr, 0, length - 1 - i);
         }
     }
    
    //交换元素
     private static void swap(int[] arr, int i, int j) {
         arr[i] = arr[i] + arr[j];
         arr[j] = arr[i] - arr[j];
         arr[i] -= arr[j];
     }
    
     /**
      * 对arr数组从0到lastIndex建大顶堆
      * <p>
      * 首先获取lastIndex的父节点(k)并从其开始循环
      * 判断k是否有子节点 -> 取子节点较大值下标 -> 交换父子节点 -> 循环
      *
      * @param arr       arr
      * @param lastIndex 最后一个节点
      */
     private static void buildMaxHeap(int[] arr, int lastIndex) {
         //从lastIndex处节点(最后一个节点)的父节点开始
         //认为存储方式是按层次存储的,先左后右
         for (int i = (lastIndex - 1) / 2; i >= 0; i--) {
             //k保存正在判断的节点
             int k = i;
             //如果当前k节点的子节点存在
             while (k * 2 + 1 <= lastIndex) {
                 //k节点的左子节点索引
                 int biggerIndex = 2 * k + 1;
                 //如果biggerIndex小于lastIndex,即biggerIndex+1代表的k节点的右子节点存在
                 if (biggerIndex < lastIndex) {
                     //如果右子节点的值较大
                     if (arr[biggerIndex] < arr[biggerIndex + 1]) {
                         //biggerIndex总是记录较大子节点的索引
                         biggerIndex++;
                     }
                 }
    
                 //如果k节点的值小于其较大的子节点的值
                 if (arr[k] < arr[biggerIndex]) {
                     //交换他们
                     swap(arr, k, biggerIndex);
                     //将biggerIndex赋予k,开始while循环的下一次循环,重新保证k节点的值大于其左右子节点的值
                     k = biggerIndex;
                 } else {
                     break;
                 }
             }
         }
     }
    

四、交换排序

  • 冒泡排序

    • 基本思想
      依次比较相邻的两个数,将小数放在前面,大数放在后面。即在第一趟:首先比较第1个和第2个数,将小数放前,大数放后。然后比较第2个数和第3个数,将小数放前,大数放后,如此继续,直至比较最后两个数,将小数放前,大数放后。重复第一趟步骤,直至全部排序完成。
    • 性能/稳定性
    平均时间复杂度 空间复杂度 稳定性
    O(n*2) O(1) 稳定
    • 示例代码:
    public static void bubbleSort(int[] arr) {
         int length = arr.length;
         for (int i = 0; i < length; i++) {  //设置循环次数
             for (int j = 0; j < length - i - 1; j++) {
                 if (arr[j] > arr[j + 1]) {
                     //交换位置
                     arr[j] += arr[j + 1];
                     arr[j + 1] = arr[j] - arr[j + 1];
                     arr[j] -= arr[j + 1];
                 }
             }
         }
     }
    
  • 快速排序

    • 基本思想
      通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归整个数据变成有序序列。
    • 性能/稳定性
    平均时间复杂度 空间复杂度 稳定性
    O(nlog2n) O(nlog2n) 不稳定
    • 示例代码:
    public static void quickSort(int[] arr, int start, int end) {
         if (start < end) {
             int baseNum = arr[start]; //选基准值
             int midNum; //记录中间值
             int i = start;
             int j = end;
             do {
                 //从前往后找到大于baseNum的第一个数的索引(下标)
                 while (arr[i] < baseNum && i < end) {
                     i++;
                 }
                 //从后往前找到小于baseNum的第一个数的索引(下标)
                 while (arr[j] > baseNum && j > start) {
                     j--;
                 }
                 if (i <= j) {
                     midNum = arr[i];
                     arr[i] = arr[j];
                     arr[j] = midNum;
                     i++;
                     j--;
                 }
             } while (i <= j);
             //到这里时j < i了
             if (start < j) {
                 quickSort(arr, start, j);
             }
             if (i < end) {
                 quickSort(arr, i, end);
             }
         }
     }
    

五、归并排序

  • 基本思想
    建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。

    • 性能/稳定性
    平均时间复杂度 空间复杂度 稳定性
    O(nlog2n) O(1) 稳定

    示例代码:

   public static void mergeSort(int[] arr, int start, int end) {
        int mid = (start + end) / 2;
        if (start < end) {
            mergeSort(arr, start, mid);
            mergeSort(arr, mid + 1, end);
            //左右归并
            merge(arr, start, mid, end);
        }
    }

    //进行归并操作
    private static void merge(int[] arr, int start, int mid, int end) {
        //开辟新空间用于存储
        int[] temp = new int[end - start + 1];
        int i = start;
        int j = mid + 1;
        int k = 0;
        //把较小的数先移到新数组中
        while (i <= mid && j <= end) {
            if (arr[i] < arr[j]) {
                temp[k++] = arr[i++];
            } else {
                temp[k++] = arr[j++];
            }
        }
        //把左边剩余的数移入数组
        while (i <= mid) {
            temp[k++] = arr[i++];
        }
        //把右边剩余的数移入数组
        while (j <= end) {
            temp[k++] = arr[j++];
        }
        //把新数组中的数覆盖到arr数组
        if (temp.length >= 0) System.arraycopy(temp, 0, arr, start, temp.length);
    }

六、基数排序(桶子法)

  • 基本思想
    将整数按位数切割成不同的数字,然后按每个位数分别比较。

  • 性能/稳定性

    平均时间复杂度 空间复杂度 稳定性
    O(d(r+n)) O(rd+n) 稳定
  • 示例代码

   public static void radixSort(int[] arr) {
        int max = findMax(arr, 0, arr.length - 1);

        //需要遍历的次数由数组最大值的位数来决定
        for (int i = 1; max / i > 0; i = i * 10) {
            int[][] buckets = new int[arr.length][10];
            //获取每一位数字(个、十、百、千位...分配到桶子里)
            for (int j = 0; j < arr.length; j++) {
                int num = (arr[j] / i) % 10;
                //放入桶子里
                buckets[j][num] = arr[j];
            }
            //回收桶子里的元素
            int k = 0;

            //有10个桶子
            for (int j = 0; j < 10; j++) {
                //对每个桶子里的元素进行回收
                for (int l = 0; l < arr.length; l++) {
                    //如果桶子里面有元素就回收(数据初始化会为0)
                    if (buckets[l][j] != 0) {
                        arr[k++] = buckets[l][j];
                    }
                }
            }
        }
    }
   
   //找到数组中最大值
    private static int findMax(int[] arrays, int start, int end) {

        //如果该数组只有一个数,那么最大的就是该数组第一个值了
        if (start == end) {
            return arrays[start];
        } else {

            int a = arrays[start];
            int b = findMax(arrays, start + 1, end);//找出整体的最大值

            if (a > b) {
                return a;
            } else {
                return b;
            }
        }
    }

七、总结

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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