排序算法总结——Java版

转载请注明出处
作者:@ZJXin
说明:本文所写的排序,默认按从小到大排序。由于本人水平有限,如有不正确的地方欢迎留言指出,谢谢!

排序算法是面试中经常会被问到的问题,甚至会要求手写算法,本文对一些常用的排序算法做了总结。本文涉及的代码全部都运行验证过。(堆排序没有给出代码实现)

排序算法分类

1. 直接插入排序

算法思想

每次将无序区的第一个记录按关键字插入到有序区的合适位置。

算法实现步骤

  • 取出无序区第一个元素,并将该值赋值给一个临时变量
  • 在已经排序的元素序列中从后向前扫描
  • 如果所取元素大于新元素,将该元素向后移动一个位置
  • 重复步骤三,直到找到已排序的元素小于或者等于新元素的位置
  • 将新元素插入到该位置中
  • 重复1~5

算法流程图

直接插入排序

Java代码实现

/**
 * 直接插入排序算法
 * 
 * @param nums 待排序数组
 */
public void insertSort(int []nums){

    //数组长度
    int length = nums.length;
    //要插入的数
    int insertNum;

    for(int i=1;i<length;i++){//遍历数组

        int j = i-1;//已排好序的元素序列的最大下标
        insertNum = nums[i];

        while(j>=0 && nums[j]>insertNum){
            //首先判断j是否>=0,不然将可能产生数组溢出
            //序列从后往前循环
            //将大于insertNum的数往后挪一格
            nums[j+1] = nums[j--];
        }

        //将需要插入的数放在插入的位置
        nums[j+1] = insertNum;
    }
}

算法分析

  • 时间复杂度
    • 最佳情况:O(n)
    • 最坏情况:O(n^2)
    • 平均时间复杂度:O(n^2)
  • 空间复杂度:O(1)
  • 稳定性:稳定
  • 改进:希尔排序(缩小增量排序)

2. 希尔排序(缩小增量排序)

算法思想

希尔排序是对直接插入排序的一种改进。希尔排序将整个待排序序列按增量dk划分为m个子序列,不断缩小增量dk,重复这一过程,直到dk减少到1,对整个序列进行一次直接插入排序,因此,希尔排序也被称为缩小增量排序

希尔排序与直接插入排序的不同之处在于,直接插入排序每次对相邻记录进行比较,记录只移动一个位置,而希尔排序每次对相隔较远(即增量)的记录进行比较,使得记录移动时跨越多个记录,实现宏观上的调整。当增量为1时,此时序列已基本有序,可将前边的各趟的调整看做最后一趟的预处理。

算法实现步骤

  • 选择一个增量序列t1、t2、t3...tk(其中tk=1)
  • 按增量序列个数k,对待排序序列进行k趟排序
  • 每趟排序,根据相应的增量dk,进行插入排序

算法流程图

希尔排序

Java代码实现

/**
 * 希尔排序(缩小增量排序)
 * 不稳定
 * 
 * @param nums 待排序数组
 */
public void sheelSort(int []nums){
    int dk = nums.length;
    do{
        //缩小增量
        //此处缩小增量可以自己设置,一般缩小当前的一半
        dk = dk/2;
        sheelInsert(nums, dk);//进行一次希尔排序
    }while(dk>0);//当增量为1时停止
}

/**
 * 希尔排序一趟排序
 * 如果把增量设置为1,则是简单的插入排序
 *
 * @param nums 待排序数组
 * @param dk 增量
 */
public void sheelInsert(int []nums,int dk){
    int length = nums.length;
    int insertNum;//待插入的值

    for(int i=dk;i<length;i++){

        int j=i-dk;
        insertNum = nums[i];

        while(j>=0 && nums[j]>insertNum){
            //同样的,应该先检测j是否比0小,否则可能产生数组溢出
            //向后移动dk位
            nums[j+dk] = nums[j];
            j = j-dk;
        }
        nums[j+dk] = insertNum;//把待插入的 值放到正确的位置
    }
}

算法分析

  • 时间复杂度
    • 最佳情况:O(n)
    • 最坏情况:O(n^2)
    • 平均情况:O(nlogn)
  • 空间复杂度:O(1)
  • 稳定性:不稳定

3. 简单选择排序

算法思想

每趟在未排序的序列中找到最小的元素,存放到未排序序列的起始位置。

算法实现步骤

  • 首先在未排序序列中找到最小元素,存放到排序序列的起始位置
  • 再从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾。
  • 重复上述步骤,直到所有元素均排序完毕。

算法流程图

简单选择排序

Java代码实现

/**
 * 简单选择排序
 * 每一个循环后再交换,简单选择排序
 * 
 * @param nums 待排序数组
 */
public void selsectSort(int []nums){

    int length = nums.length;//数组长度,将这个值提出来,提高速度

    for(int i=0; i<length; i++){//循环次数

        int key = nums[i];
        int position = i;

        for(int j=i+1;j<length;j++){//选出最小的值和位置

            if(key>nums[j]){
                key = nums[j];
                position = j;
            }
        }
        //交换位置
        nums[position] = nums[i];
        nums[i] = key;
    }
}

算法分析

  • 时间复杂度
    • 最坏、最佳、平均:O(n^2)
  • 空间复杂度:O(1)
  • 稳定性:不稳定

4. 堆排序

算法思想

堆排序是对简单排序的优化,利用大顶堆所有非叶子结点均不小于其左右孩子结点这一特性来排序。

算法实现步骤

  • 将待排序列建成一个大顶堆
  • 将顶堆结点与队尾结点交换位置,堆长度减1
  • 调整剩余结点为堆
  • 重复步骤2~3

算法流程图

堆排序

算法分析

  • 时间复杂度
    • 最坏、最好、平均:O(nlogn)
  • 空间复杂度:O(1)
  • 稳定性:不稳定

5. 冒泡排序

算法思想

冒泡排序每趟排序,对相邻两个元素进行比较,如果顺序错误则交换过来,每趟排序后,都将把未排序序列的最大值放在最后边。每趟排序,越小的元素会经由交换,慢慢地"浮"到数列顶端,这也是该算法名字的由来。

算法实现步骤

  • 将序列中所有元素相邻两两比较,将最大的放在最后面。
  • 将剩余序列中所有元素相邻两两比较,将最大的放在最后面。
  • 重复第二步,直到只剩下一个数。

算法流程图

冒泡排序

Java代码实现

/**
 * 冒泡排序
 * 
 * @param nums 待排序数组
 */
public void bubbleSort(int []nums){

    int length = nums.length;
    int temp;

    for(int i=0; i<length; i++){

        for(int j=0; j<length-i-1; j++){
            if(nums[j]>nums[j+1]){
                //交换相邻两个数
                temp = nums[j];
                nums[j] = nums[j+1];
                nums[j+1] = temp;
            }
        }
    }
}

算法分析

  • 时间复杂度:
    最佳、最坏、平均:O(n^2)
  • 空间复杂度:O(1)
  • 稳定性:稳定

算法改进

  • 改进方案一
    设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。由于pos位置之后的记录均已交换到位,故在进行下一趟排序时只要扫描到pos位置即可。
/**
 * 冒泡排序改进版本一
 * 通过设置标志位来减少遍历次数
 * 
 * @param nums
 */
public void betterBubbleSort1(int []nums){

    int length = nums.length;
    int i= length -1;  //初始时,最后位置保持不变  
    while ( i> 0) {   
       int pos= 0; //每趟开始时,无记录交换  
       for (int j = 0; j< i; j++){  
           if (nums[j]> nums[j+1]) {  
               pos= j; //记录交换的位置   
               int tmp = nums[j]; 
               nums[j]=nums[j+1];
               nums[j+1]=tmp;  
           }   
       i= pos; //为下一趟排序作准备  
       }
    }   
}
  • 改进方案二
    若某一趟排序中未进行一次交换,则排序结束 。
 /**
   * 冒泡排序改进版本二
   * 
   * @param nums 待排序数组
   */
  public void betterBubbleSort2(int[] nums ) {

       int len = nums .length;
       boolean flag = true;
       while (flag) {
           flag = false;
           for (int i = 0; i < len - 1; i++) {
               if (nums [i] > nums [i + 1]) {
                   int temp = nums [i + 1];
                   nums [i + 1] = nums [i];
                   nums [i] = temp;
                   flag = true;
               }
           }
           len--;
       }
 }

6. 快速排序

算法思想

分治法。通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,然后在分别对这两部分记录进行排序。

算法实现步骤

  • 从数列中挑出一个元素,称为“基准”
  • 重新排序数列,所有比基准小的元素放在基准前边,所有比基准大的放在基准后边,该基准最后处于数列中间位置。
  • 递归地把小于基准值元素和大于基准值元素的子序列快速排序

算法流程图

快速排序

Java代码实现

/**
 * 快速排序
 * 不稳定
 * 
 * @param nums 待排序数组
 * @param left 开始位置
 * @param right 结束位置
 */
public void quickSort(int []nums,int left,int right){

    if(left<right){
        int base = nums[left];//选定的基准
        int low = left;
        int high = right;

        while(low<high){

            while(low<high && nums[high]>base){
                //先从后往前遍历,直到数值比基准小
                high--;
            }
            //把数值比基准小的放到基准左边
            nums[low] = nums[high];

            while(low<high && nums[low]<base){
                //从前往后遍历,直到数值比基准大
                low++;
            }
            //把数值比基准大的放到基准右边
            nums[high] = nums[low];
        }
        //把基准放到准确位置去
        nums[low] = base;

        //用同样的方法,递归调用基准左边的数组以及右边的数组
        quickSort(nums, left, low-1);
        quickSort(nums, low+1, right);
    }
}

算法分析

  • 时间复杂度
    • 最佳情况:O(nlogn)
    • 最坏情况:O(n^2)
    • 平均复杂度:O(nlogn)
  • 空间复杂度:O(nlogn)
  • 稳定性:不稳定

7.归并排序

算法思想

分治法。归并排序将待排序列一分为二,并对每个子数组递归排序,然后再把这两个排好序的子数组合并为一个有序的数组。

算法实现步骤

  • 把长度为n的输入序列分为两个长度为n/2的子序列
  • 对这两个子序列分别采用归并排序
  • 将两个排序好的子序列合并成一个最终的排序序列

算法流程图

归并排序

Java代码实现

/**
 * 归并排序
 * 稳定的排序算法
 * 
 * @param nums 待排序数组
 * @param low 待排序数组的起点
 * @param high 待排序数组的终点
 */
public static void mergeSort(int []nums,int low,int high){
    //算出分割点,2-路归并即数组的中点
    int mid = (high+low)/2;

    if(low<high){
        //递归分割
        mergeSort(nums, low, mid);
        mergeSort(nums, mid+1, high);

        //调用归并方法,将分割的进行归并
        merge(nums, low, mid, high);
    }   
}
/**
 * 一次2-路归并
 * 可以想象成将两个链表,按照升序的方法组合成一条链表
 * 
 * @param nums 待排序数组
 * @param low 归并的最低位
 * @param mid 归并的中间位置
 * @param high 归并的最高位
 */
public static void merge(int []nums,int low,int mid,int high){

    //新建数组,放置临时数据
    int []temp = new int[high-low+1];
    int i = low,j=mid+1;
    int k=0;

    //把较小的数先移到新数组中去
    while(i<=mid && j<=high){
        //遍历两路数组,直到一路结束为止
        if(nums[i]<nums[j]){
            temp[k++] = nums[i++]; 
        }else{
            temp[k++] = nums[j++];
        }
    }

    //把左边那路剩余的数放入数组
    while(i<=mid){
        temp[k++] = nums[i++];
    }
    //把右边那路剩余的数放入数组
    while(j<=high){
        temp[k++] = nums[j++];
    }

    //把新数组的值覆盖nums数组
    //新数组的长度有可能比nums数组短
    for(int t=0;t<temp.length;t++){
        nums[t+low] = temp[t];
    }
}

算法分析

  • 时间复杂度
    • 最佳、最坏、平均:O(nlogn)
  • 空间复杂度:O(n)
  • 稳定定:稳定
  • 优劣
    • 优点:稳定性,性能不受输入数据的影响
    • 缺点:需要线性的额外空间

8. 基数排序

算法思想

先将所有关键字统一为相同位数,位数较少的数前边补0.然后从最低位开始依次向高位进行排序,直到按最高位排序完成,关键字序列就成为有序序列。基数排序基于分别排序,分别收集,所以是稳定的。适用于很长的数的排序。

算法实现步骤

  • 确定排序趟次,即确定最大的数的位数
  • 从最低位按照分配和收集进行排序,直至最高位。
  • 在每一趟,分配按照相应关键字的曲子将记录加入到r个不同的队列,收集是从小到大以此将r个队列收尾相接成数组。

算法流程图

基数排序

从图片可以看出,最大的数只有3位,进行3趟排序。先从个位进行排序,第二趟对十位数进行排序,最后对百位数进行排序。

Java代码实现

/**
 * 基数排序
 * 
 * @param nums
 */
public static void radixSort(int []nums){

    //确定排序趟次
    int time = sortTime(nums);

    //建立10个队列   
    List<ArrayList> queue = new ArrayList<ArrayList>();
    for (int i = 0; i < 10; i++) {
        ArrayList<Integer> queue1 = new ArrayList<Integer>();
        queue.add(queue1);
    }

    //进行time次分配和收集     
    for (int i = 0; i < time; i++) {
        //分配数组元素;     
        for (int j = 0; j < nums.length; j++) {
            //得到数字的第time+1位数   
            //先取余,再做除法
            int x = nums[j] % (int) Math.pow(10, i + 1) / (int) Math.pow(10, i);
            ArrayList<Integer> queue2 = queue.get(x);
            queue2.add(nums[j]);
            queue.set(x, queue2);
        }
        //元素计数器     
        int count = 0;
        //收集队列元素
        for (int k = 0; k < 10; k++) {
            while (queue.get(k).size() > 0) {
                //如果该队列有分配到元素,对其进行收集
                ArrayList<Integer> queue3 = queue.get(k);
                nums[count] = queue3.get(0);
                queue3.remove(0);
                count++;
            }
        }

        //查看第N趟排序后的结果
        /*
        System.out.print("\n"+"第"+i+"趟排序结果:");
        for(int m=0;m<nums.length;m++){
            System.out.print(nums[m]+" ");
        }
        */
    }
}

/**
 * 计算基数排序的遍历趟次
 * 
 * @param nums 待排序序列
 * @return 返回一个int类型,表示需要遍历的趟次
 */
public static int sortTime(int []nums){
    //首先确定排序的趟数
    //通过待排序列的最大数来确定趟数time
    int max = nums[0];
    for (int i = 1; i < nums.length; i++) {
        if (nums[i] > max) {
            max = nums[i];
        }
    }
    int time = 0;
    //判断位数;     
    while (max > 0) {
        max /= 10;
        time++;
    }
    return time;        
}

算法分析

  • 时间复杂度
    • 最佳、最坏、平均:O(mn)
  • 空间复杂度:O(n)
  • 稳定性:稳定

总结

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

推荐阅读更多精彩内容

  • 简介:本文主要总结了以下几个排序算法:冒泡排序、选择排序、插入排序、希尔排序、归并排序、快速排序、堆排序、计数排序...
    AlexanderJLiu阅读 931评论 2 29
  • 概述:排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    每天刷两次牙阅读 3,727评论 0 15
  • 概述 排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部...
    蚁前阅读 5,164评论 0 52
  • 概述排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的...
    Luc_阅读 2,255评论 0 35
  • 无论在什么场合,高情商的人在,总能说出适宜的话,他拥有理智而感性的处事之道,就像雪夜里的一团火,不仅能照亮道路,还...
    主持人梓惟阅读 118评论 0 0