Java 常见排序算法(冒泡,插入,选择,快速,归并,希尔,堆排序,基数)

面试中经常会遇到一些排序的问题,在此总结一下java中常见的几种排序算法:冒泡,插入,选择,快速,归并,希尔,堆排序,基数。

参考:https://www.cnblogs.com/chengxiao/p/6194356.html(归并排序)

排序算法在线演示图:http://tools.jb51.net/aideddesign/paixu_ys

冒泡排序

冒泡排序个人认为是最简单最容易理解的一种排序算法,它的基本原理是依次比较数组中相邻的两个元素,从前到后,如果后面的元素大于前面的元素,就交换两个元素的位置,直到比较到最后一个元素,这样比较第一遍过后,整个数组的最大元素就会跑到数组的最后面;然后再进行第二轮的比较,第二轮比较的结果是数组中第二大的元素会跑到数组的倒数第二位置,依次类推,直到排序完整个数组。

代码实现

    /**
     * 冒泡排序
     * @param arr 待排序数组
     *
     */
    private static void bubbleSort(int [] arr){
        //控制循环次数
        for (int i=0;i<arr.length;i++) {
            for (int j = 0; j < arr.length - 1 - i; j++) {
                //比较相邻元素大小,如果前面元素大于后面元素则交换元素位置
                if (arr[j] > arr[j + 1]) {
                    int tmp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = tmp;
                }
            }
        }
    }

插入排序

插入排序的基本思想是从前往后,对每一个元素都依次与它前面的元素对比,如果它小于前面的元素则交换两个元素的位置,继续向前比较,直到它大于或等于前面的元素位置。

代码实现

    /**
     * 插入排序
     * @param arr 待排序数组
     *
     */
    private static void insertSort(int [] arr){
        //控制循环次数,从1开始循环
        for (int i =1;i<arr.length;i++) {
            for (int j = i; j>0; j--) {
                //每个元素和它前面的元素比较,只到它大于或等于前面的元素位置
                if (arr[j]<arr[j-1]){
                    int tmp = arr[j];
                    arr[j] = arr[j-1];
                    arr[j-1] = tmp;
                }else {
                    break;
                }
            }
        }
    }

选择排序

选择排序是遍历数组中所有元素,每次都找出数组中最小的值,放到数组的最前面。

1.第一次遍历数组中所有的元素,找出最小值,放到数组的最前面;

2.第二轮遍历从数组的第二个位置开始,找出剩余元素的最小值,放到数组的第二个位置;

3.依次类推,知道最后一个元素。

代码实现

    /**
     * 选择排序
     * @param arr 待排序数组
     */
    private static void selectSort(int [] arr){
        //遍历数组所有的元素
        for (int i=0 ; i<arr.length ; i++){
            int min = arr[i];
            int min_index = i;
            //找出剩余元素中最小值
            for (int j=i+1;j<arr.length;j++){
                if (arr[j] < min){
                    min = arr[j];
                    min_index = j;
                }
            }
            //剩余中的最小值与arr[i]元素交换
            if (min_index != i){
                int tmp = arr[i];
                arr[i] = min;
                arr[min_index] = tmp;
            }
        }
    }

快速排序

快速排序属于选择排序的一种,基本思想是,选择数组中的一个元素,保证该元素前面所有的元素都小于它,后面所有的元素都大于它,然后再对两边部分也用同样的思路,找到某个元素,也保证前面的元素都小于它,后面所有的元素都大于它。

如何找到这个元素呢?通常都是选择数组中的第一个元素作为第一个标杆元素,很显然这个标杆元素不满足我们前面的要求,因此我需要改变这个元素的位置,让它做到它前面的元素都小于他,后面的元素都大于它。

步骤:

对比的时候需要从数组的左边和右边同时和这个元素对比。

1.首先从左依次往右找,如果小于我们选择的元素(标杆元素),则继续往右找,当找个某个元素大于我们选择元素,这时需要它与右边的我们查到位置的元素做交换;然后再从右边开始往左找,如果右边的元素大于我们选择的元素(标杆元素),则继续往左找,同样当找到某个元素小于我们选择的元素(标杆元素),这时需要它与左边的我们查到位置的元素做交换;然后再从左边往后找,依次进行,知道左边的位置和右边的位置重合位置,这时将我们标杆元素的值赋给这个位置。这样就可以保证这个元素左边的小于它,右边的元素大于它。

2.然后再对这个元素两边部分重复步骤1,依次类推。

简单的理解就是,找个某个元素(通常选择第一个元素)把比它小的元素都放到它的左边,比它大的元素都放到它右边。这样如果数组中每个元素都满足这个条件,那么这个数组整体就是有序的了。

代码实现

    /**
     * 快速排序
     * 使用了递归的思想
     * @param arr 待排序数组
     * @param start 开始索引
     * @param end 结束索引
     *
     */
    private static void quickSort(int [] arr,int start,int end){
        if (start>=end){
            return;
        }
        int num = arr[start];
        int low = start;
        int high = end;
        while (start < end) {
            while (start < end && arr[end] >= num) {
                end--;
            }
            arr[start] = arr[end];
            while (start < end && arr[start] <= num) {
                start++;
            }
            arr[end] = arr[start];
        }
        arr[start] = num;
        quickSort(arr, low, start-1);
        quickSort(arr, start+1, high);
    }

归并排序

归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。

步骤:

1.将一个数组拆分为一个个大小为1的数组,大小为1的数组肯定是有序的,因为只有一个元素;

2.然后再对上面的数组两两进行合并。

代码实现

/**
     * 归并排序入口
     * @param arr 待排序数组
     */
    private static void sort(int [] arr){
        //创建临时存放合并数据,避免频繁创建数组,开辟内存空间
        int [] tmp_arr = new int[arr.length];
        mergeSort(arr,0,arr.length-1,tmp_arr);
    }

    /**
     * 归并排序 递归,将数组拆分长度为1的数组,在一一合并
     * @param arr 待排序数组
     * @param left 数组左下标
     * @param right 数组右下标
     * @param tmp_arr 临时数组
     */
    private static void mergeSort(int arr [] ,int left,int right,int [] tmp_arr){
        if (left < right){
            int mid = (left+right)/2;
            mergeSort(arr,left,mid,tmp_arr);
            mergeSort(arr,mid+1,right,tmp_arr);
            merge(arr,left,mid,right,tmp_arr);
        }
    }

    /**
     * 归并排序,合并两个有序数组
     * @param arr 待排序数组
     * @param left 数组左下标
     * @param mid 数组中间下标
     * @param right 数组右下标
     * @param temp 临时数组
     */
    private static void merge(int [] arr,int left,int mid,int right,int [] temp){
        int i = left;
        int j = mid+1;
        int t = 0;
        while (i<=mid && j<=right){
            if (arr[i] <= arr[j]){
                temp[t++] = arr[i++];
            }else {
                temp[t++] = arr[j++];
            }
        }
        //左边剩余元素补充到temp数组中
        while (i<=mid){
            temp[t++] = arr[i++];
        }
        //右边剩余元素补充到temp数组中
        while (j<=right){
            temp[t++] = arr[j++];
        }
        t=0;
        //将临时中的元素复制到原数组中
        while (left <= right){
            arr[left++] = temp[t++];
        }
    }

希尔排序

希尔排序属于插入排序的一种优化,设想一下,如果一个数组中的大部分元素是有序,如果在使用插入排序的话会减少很多频繁的对比交换过程,这样就可以大大的提高排序的效率。

步骤:

1.首先将数组按照arr.length/2作为步长分为n个部分,再对这n部分内的元素使用插入排序;

2.然后再将数组分为arr.length/2/2的步长分为m个部分,同样对这个m个部分内的元素使用插入排序;

3.依次内推直到步长为1,再使用插入排序,这时实际上就是正常的插入排序。

代码实现

    //希尔排序
    private static void shellSort(int [] arr){
        //控制步长
        for (int d=arr.length/2; d>0 ;d=d/2){
            //控制循环次数
            for (int i=d;i<arr.length;i++){
                //循环对比
                for (int j=i;j>=d;j=j-d){
                    if (arr[j]<arr[j-d]){
                        int tmp = arr[j];
                        arr[j] = arr[j-d];
                        arr[j-d] = tmp;
                    }else {
                        break;
                    }
                }

            }

        }
    }

堆排序

我们知道任何一个数组都可以看成是一个完全二叉树(堆),堆又可以分为大顶堆和小顶堆,而堆排序就是利用了大顶堆的特性,大顶堆是指完全二叉树中所有节点都大于它的子节点,这样堆中最大的元素就在根节点的位置。

完全二叉树:

第n个元素的左子节点是:2*n+1

第n个元素的右子节点是:2*n+2

第n个元素的父节点是:(n-1)/2

步骤:

1.将一个数组调整为大顶堆;

2.第一个(根节点)与最后一个元素互换;

3.再把剩余的元素也调整为大顶堆;

4.然后重复步骤1,步骤2,直到最后一个元素。

代码实现

    /**
     * 堆排序
     * @param arr 待排序数组
     */
    private static void headSort(int [] arr){
        //将数组调整为大顶堆
        for (int i = (arr.length - 1) / 2; i >= 0; i--) {
            headT1(arr, arr.length, i);
        }

        for (int k=arr.length-1;k>=0;k--) {
            //交换第一个元素和第个元素的位置
            int temp = arr[k];
            arr[k] = arr[0];
            arr[0] = temp;
            //继续调整为大顶堆
            headT1(arr,k,0);
        }
    }

    /**
     *
     * @param arr 待排数组
     * @param size 数组的长度
     * @param index 待比较的数组下标
     *
     * 对于任意一个数组都可以看成是一个完成二叉树(从上往下,从左到右是连续的二叉树)即为堆,堆分为大顶堆和小顶堆,
     *       大顶堆:所有节点都大于它的子节点
     *       小顶堆:所有节点都小于它的子节点
     *       数组看成完全二叉树(堆),将数组转成一个大顶堆,然后拿出它的根节点即为数组的最大值放到数组的最后面,再把剩余的元素也转成大顶堆,.....。
     *  直到最后一个元素,则可以完成整个数组的排序
     *  如何把一个堆转成大顶堆呢?
     *      从它的最后一个非叶子节点,把它转成一个大顶堆,然后依次往上,把整个二叉树都比较一遍
     */
    private static void headT1(int [] arr,int size,int index){
        int leftIndex = 2*index+1;
        int rightIndex = 2*index+2;
        //存储最大值的下标
        int max = index;
        if (leftIndex<size && arr[leftIndex] > arr[max] ){
            max = leftIndex;
        }
        if (rightIndex<size && arr[rightIndex] > arr[max] ){
            max = rightIndex;
        }
        if (max != index){
            int temp = arr[max];
            arr[max] = arr[index];
            arr[index] = temp;
            headT1(arr,size,max);
        }
    }

基数排序

步骤:

1.创建一个行位10,列为待排序数组长度的二维数组,用与存放待排序数组中的元素;

2.创建一个长度为10的数组,来记录二位数组中存放了多少个元素;

3.然后从前往后取出数组中每个元素的个位数,十位数,百位数.......,然后根据各位数的大小,如果为0就放到二位数组的第一列,如果为1就放到二位数组的第二列....为9就到第10列;

4.比较为个位数,将二维数组的元素按照从前往后从左往右的顺序放回到原数组,然后在去除十位数;

5.依次类推,直到数组中最大数的最长位数,这时已完成排序。

代码实现

    /**
     *  基数排序
     * @param arr  待排序数组
     */
    private static void cardinalitySort(int [] arr){
        int max = arr[0];
        //找到数组中最大的数
        for (int i=1;i<arr.length;i++){
            if (arr[i]>max){
                max=arr[i];
            }
        }
        int maxLen = (max+"").length();
        int [][] temp = new int[10][arr.length];
        int [] indexArr = new int[10];

        for (int i=0,n=1;i<maxLen;i++,n*=10){
            for (int j=0;j<arr.length;j++){
                //获取arr[j]数的第i位数,来决定他的存放位置
                int rem = arr[j]/n%10;
                indexArr[rem]++;
                temp[rem][indexArr[rem]-1] = arr[j];
            }

            int index = 0;

            //把二位数数组temp中的元素放回原数组
            for (int k=0;k<10;k++){
                for (int m=0;m<indexArr[k];m++){
                    arr[index] = temp[k][m];
                    index++;
                }
            }
            //把临时数组清空
            for (int l=0;l<indexArr.length;l++){
                indexArr[l] = 0;
            }
        }
    }

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值