Java知识java面试技巧

排序算法总结——Java版

2018-06-06  本文已影响570人  ZJXin

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

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

排序算法分类

1. 直接插入排序

算法思想

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

算法实现步骤

算法流程图

直接插入排序

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;
    }
}

算法分析

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

算法思想

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

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

算法实现步骤

算法流程图

希尔排序

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;//把待插入的 值放到正确的位置
    }
}

算法分析

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;
    }
}

算法分析

4. 堆排序

算法思想

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

算法实现步骤

算法流程图

堆排序

算法分析

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;
            }
        }
    }
}

算法分析

算法改进

/**
 * 冒泡排序改进版本一
 * 通过设置标志位来减少遍历次数
 * 
 * @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);
    }
}

算法分析

7.归并排序

算法思想

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

算法实现步骤

算法流程图

归并排序

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];
    }
}

算法分析

8. 基数排序

算法思想

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

算法实现步骤

算法流程图

基数排序

从图片可以看出,最大的数只有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(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) 稳定
上一篇下一篇

猜你喜欢

热点阅读