返回顶部

面试题: java中常见的排序算法的实现及比较

1.冒泡排序

1.1 冒泡排序普通版

  每次冒泡过程都是从数列的第一个元素开始,然后依次和剩余的元素进行比较,若小于相邻元素,则交换两者位置,同时将较大元素作为下一个比较的基准元素,继续将该元素与其相邻的元素进行比较,直到数列的最后一个元素 . 示意图如下:

img

/**
 * 冒泡排序:
 *  依次比较相邻的元素,若发现逆顺序,则交换。小的向前换,大的向后换,
 *  本次循环完毕之后再次从头开始扫描,直到某次扫描中没有元素交换,
 *  说明每个元素都不比它后面的元素大,至此排序完成。
 */
import java.util.Arrays;
public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = new int[]{9, 2, 1, 0, 5, 3, 6, 4, 8, 7};
        System.out.println("排序前:" + Arrays.toString(arr));
        sort(arr);
        System.out.println("排序后:" + Arrays.toString(arr));
    }
    public static void sort(int[] arr) {
        for (int i = 1; i < arr.length; i++) {  //第一层for循环,用来控制冒泡的次数
            for (int j = 0; j < arr.length - 1; j++) { //第二层for循环,用来控制冒泡一层层到最后
                //如果前一个数比后一个数大,两者调换 ,意味着泡泡向上走了一层
                if (arr[j] > arr[j + 1]) {
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
    }
}

运行结果:

排序前:[9, 2, 1, 0, 5, 3, 6, 4, 8, 7]
排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1.2 冒泡排序升级版

  在这个版本中,改动了两点 . 第一点是加入了一个布尔值,判断第二层循环中的调换有没有执行,如果没有进行两两调换,说明后面都已经排好序了,已经不需要再循环了,直接跳出循环,排序结束 ; 第二点是第二层循环不再循环到arr.length - 1,因为外面的i循环递增一次,说明数组最后就多了一个排好序的大泡泡.第二层循环也就不需要到最末尾一位了,可以提前结束循环

/**
     * 升级版冒泡排序:
     * 加入一个布尔变量,如果内循环没有交换值,说明已经排序完成,提前终止
     */
import java.util.Arrays;
public class BubbleSort {
    public static void main(String[] args) {
        int[] arr = new int[]{9, 2, 1, 0, 5, 3, 6, 4, 8, 7};
        System.out.println("排序前:" + Arrays.toString(arr));
        plusSort(arr);
        System.out.println("排序后:" + Arrays.toString(arr));
    }
    
    public static void plusSort(int[] arr){
        if(arr != null && arr.length > 1){
            for(int i = 0; i < arr.length - 1; i++){
                // 初始化一个布尔值
                boolean flag = true;
                for(int j = 0; j < arr.length - i - 1 ; j++){
                    if(arr[j] > arr[j+1]){
                        // 调换
                        int temp;
                        temp = arr[j];
                        arr[j] = arr[j+1];
                        arr[j+1] = temp;
                        // 改变flag
                        flag = false;
                    }
                }
                if(flag){
                    break;
                }
            }
        }
    }
}

运行结果:

排序前:[9, 2, 1, 0, 5, 3, 6, 4, 8, 7]
排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

2.选择排序

  选择排序也是一种简单直观的排序算法,实现原理比较直观易懂:首先在未排序数列中找到最小元素,然后将其与数列的首部元素进行交换,然后,在剩余未排序元素中继续找出最小元素,将其与已排序数列的末尾位置元素交换。以此类推,直至所有元素圴排序完毕

/**
 * 选择排序:
 *  每一次从待排序的数据元素中选出最小(或最大)的一个元素,
 *  存放在序列的起始位置,直到全部待排序的数据元素排完。
 */
import java.util.Arrays;
public class SelectSort {
    public static void main(String[] args) {
        int[] arr = new int[] {3,4,5,7,1,2,0,9,3,6,8};
        System.out.println("排序前:"+Arrays.toString(arr));
        selectSort(arr);
        System.out.println("排序后:"+Arrays.toString(arr));
    }
    public static void selectSort(int[] arr) {
        for(int i=0;i<arr.length;i++) {
            int minIndex=i;
            for(int j=i+1;j<arr.length;j++) {
                if(arr[minIndex]>arr[j]) {
                    minIndex=j;
                }
            }
            if(i!=minIndex) {
                int temp=arr[i];
                arr[i]=arr[minIndex];
                arr[minIndex]=temp;
            }
        }
    }
}

运行结果:

排序前:[3, 4, 5, 7, 1, 2, 0, 9, 3, 6, 8]
排序后:[0, 1, 2, 3, 3, 4, 5, 6, 7, 8, 9]

3.插入排序

  一次插入排序的操作过程:将待插元素,依次与已排序好的子数列元素从后到前进行比较,如果当前元素值比待插元素值大,则将移位到与其相邻的后一个位置,否则直接将待插元素插入当前元素相邻的后一位置,因为说明已经找到插入点的最终位置

/**
 * 插入排序:
 *  从第一个元素开始,该元素可以认为已经被排序
 *  取出下一个元素,在已经排序的元素序列中从后向前扫描
 *  如果该元素(已排序)大于新元素,将该元素移到下一位置
 *  重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
 *  将新元素插入到该位置后
 *  重复上面步骤
 */
import java.util.Arrays;
public class InsertSort {
    public static void main(String[] args) {
        int[] arr = new int[] {5,3,2,8,4,9,1,0,7,6};
        System.out.println("排序前:"+Arrays.toString(arr));
        insertSort(arr);
        System.out.println("排序后:"+Arrays.toString(arr));
    }
    public static void insertSort(int[] arr) {
        for(int i=1;i<arr.length;i++) {
            if(arr[i]<arr[i-1]) {
                int temp=arr[i];
                int j;
                for(j=i-1;j>=0&&temp<arr[j];j--)
                    arr[j+1]=arr[j];
                arr[j+1]=temp;
            }
        }
    }
}

运行结果:

排序前:[5, 3, 2, 8, 4, 9, 1, 0, 7, 6]
排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

4.快速排序

  快速排序算法利用的是一趟快速排序,基本内容是选择一个数作为准基数,然后利用这个准基数将遗传数据分为两个部分,第一部分比这个准基数小,都放在准基数的左边,第二部分都比这个准基数大,放在准基数的右边.

import java.util.Arrays;
/**
 * 快速排序:
 *  快速排序算法利用的是一趟快速排序,基本内容是选择一个数作为准基数,
 *  然后利用这个准基数将遗传数据分为两个部分,第一部分比这个准基数小,
 *  都放在准基数的左边,第二部分都比这个准基数大,放在准基数的右边.
 */
public class QuickSort {
    public static void main(String[] args) {
        int[] arr = new int[] {5,3,2,8,4,9,1,0,7,6};
        System.out.println("排序前:"+ Arrays.toString(arr));
        quickSort(arr,0,arr.length-1);
        System.out.println("排序后:"+Arrays.toString(arr));
    }

    public static void quickSort(int[] arr,int begin,int end) {
        //先定义两个参数接收排序起始值和结束值
        int a = begin;
        int b = end;
        //先判断a是否大于b

        if (a >= b) {
            //没必要排序
            return;
        }
        //基准数,默认设置为第一个值
        int x = arr[a];
        //循环
        while (a < b) {
            //从后往前找,找到一个比基准数x小的值,赋给arr[a]
            //如果a和b的逻辑正确--a<b ,并且最后一个值arr[b]>x,就一直往下找,直到找到后面的值大于x
            while (a < b && arr[b] >= x) {
                b--;
            }
            //跳出循环,两种情况,一是a和b的逻辑不对了,a>=b,这时候排序结束.二是在后面找到了比x小的值
            if (a < b) {
                //将这时候找到的arr[b]放到最前面arr[a]
                arr[a] = arr[b];
                //排序的起始位置后移一位
                a++;
            }
            //从前往后找,找到一个比基准数x大的值,放在最后面arr[b]
            while (a < b && arr[a] <= x) {
                a++;
            }
            if (a < b) {
                arr[b] = arr[a];
                //排序的终止位置前移一位
                b--;
            }
        }
        //跳出循环 a < b的逻辑不成立了,a==b重合了,此时将x赋值回去arr[a]
        arr[a] = x;
        //调用递归函数,再细分再排序
        quickSort(arr,begin,a-1);
        quickSort(arr,a+1,end);
    }
}

运行结果:

排序前:[5, 3, 2, 8, 4, 9, 1, 0, 7, 6]
排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

5.归并排序

  归并排序,简单的说把一串数,从中平等分为两份,再把两份再细分,直到不能细分为止,这就是分而治之的分的步骤. 再从最小的单元,两两合并,合并的规则是将其按从小到大的顺序放到一个临时数组中,再把这个临时数组替换原数组相应位置

import java.util.Arrays;

/**
 * 归并排序:
 * 归并操作的工作原理如下:
 * 第一步:申请空间,使其大小为两个已经 排序序列之和,该空间用来存放合并后的序列
 * 第二步:设定两个 指针,最初位置分别为两个已经排序序列的起始位置
 * 第三步:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置重复步骤3直到某一指针超出序列尾
 * 将另一序列剩下的所有元素直接复制到合并序列尾
 *
 */
public class MergeSort {


    public static void main(String[] args) {
        int[] arr = new int[] {5,3,2,8,4,9,1,0,7,6};
        System.out.println("排序前:"+ Arrays.toString(arr));
        mergeSort(arr, 0, arr.length-1);
        System.out.println("排序后:"+ Arrays.toString(arr));

    }

    public static void mergeSort(int[] a,int s,int e){
        int m = (s + e) / 2;
        if (s < e){
            mergeSort(a,s,m);
            mergeSort(a,m+1,e);
            //归并
            merge(a,s,m,e);
        }
    }

    private static void merge(int[] a, int s, int m, int e) {
        //初始化一个从起始s到终止e的一个数组
        int[] temp = new int[(e - s) + 1];
        //左起始指针
        int l = s;
        //右起始指针
        int r = m+1;
        int i = 0;
        //将s-e这段数据在逻辑上一分为二,l-m为一个左边的数组,r-e为一个右边的数组,两边都是有序的
        //从两边的第一个指针开始遍历,将其中小的那个值放在temp数组中
        while (l <= m && r <= e){
            if (a[l] < a[r]){
                temp[i++] = a[l++];
            }else{
                temp[i++] = a[r++];
            }
        }

        //将两个数组剩余的数放到temp中
        while (l <= m){
            temp[i++] = a[l++];
        }
        while (r <= e){
            temp[i++] = a[r++];
        }

        //将temp数组覆盖原数组
        for (int n = 0; n < temp.length; n++) {
            a[s+n] = temp[n];
        }

    }
}

运行结果:

排序前:[5, 3, 2, 8, 4, 9, 1, 0, 7, 6]
排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

6.希尔排序

  **希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”。希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止 . 希尔排序实质上是一种分组插入的方法 . **

/**
 * 希尔排序:
 * 希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”。
 * 希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;
 * 随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止
 */
import java.util.Arrays;
public class ShellSort {
    public static void main(String[] args) {
        int[] arr = new int[] {5,3,2,8,4,9,1,0,7,6};
        System.out.println("排序前:"+Arrays.toString(arr));
        shellSort(arr);
        System.out.println("排序后:"+Arrays.toString(arr));
    }
    public static void shellSort(int[] arr) {
        int k = 1;
        for (int d = arr.length / 2; d > 0; d /= 2) {
            for (int i = d; i < arr.length; i++) {
                for (int j = i - d; j >= 0; j -= d) {
                    if (arr[j] > arr[j + d]) {
                        int temp = arr[j];
                        arr[j] = arr[j + d];
                        arr[j + d] = temp;
                    }
                }
            }
            System.out.println( Arrays.toString(arr));
            k++;
        }
    }
}

运行结果:

排序前:[5, 3, 2, 8, 4, 9, 1, 0, 7, 6]
[5, 1, 0, 7, 4, 9, 3, 2, 8, 6]
[0, 1, 3, 2, 4, 6, 5, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

7.基数排序

  基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。

/**
 * 基数排序:
 * 取得数组中的最大数,并取得位数;
 * arr为原始数组,从最低位开始取每个位组成radix数组;
 * 对radix进行计数排序(利用计数排序适用于小范围数的特点);
 */

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class RadixSort {
    public static void main(String[] args) {
        int[] arr = new int[] {5,3,2,8,4,9,1,0,7,6};
        System.out.println("排序前:"+ Arrays.toString(arr));
        radixSort(arr);
        System.out.println("排序后:"+Arrays.toString(arr));
    }
    /**
     * 基数排序
     */
    public static void radixSort(int[] a) {
        int max = a[0];
        for (int i = 1; i < a.length; i++) {
            if (a[i] > max) {
                max = a[i];
            }
        }
        int time = 0;
        while (max > 0) {
            max /= 10;
            time++;
        }
        List<ArrayList<Integer>> queue = new ArrayList<ArrayList<Integer>>();
        for (int i = 0; i < 10; i++) {
            ArrayList<Integer> queue1 = new ArrayList<Integer>();
            queue.add(queue1);
        }
        for (int i = 0; i < time; i++) {
            for (int j = 0; j < a.length; j++) {
                int x = a[j] % (int) Math.pow(10, i+1)/(int)Math.pow(10, i);
                ArrayList<Integer> queue2 = queue.get(x);
                queue2.add(a[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);
                    a[count] = queue3.get(0);
                    queue3.remove(0);
                    count++;
                }
            }
        }
    }
}

运行结果:

排序前:[5, 3, 2, 8, 4, 9, 1, 0, 7, 6]
排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

8.堆排序

  堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点

/**
 * 堆排序
 * 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
 * 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n];
 * 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,
 * 然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。
 * 不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
 */

import java.util.Arrays;

public class HeapSort {
    public static void main(String[] args) {
        int[] arr = new int[] {5,3,2,8,4,9,1,0,7,6};
        System.out.println("排序前:"+ Arrays.toString(arr));
        heapSort(arr);
        System.out.println("排序后:"+Arrays.toString(arr));
    }

    /**
     * 堆排序
     */
    public static void heapSort(int[] a) {
        int len = a.length;
        for (int i = 0; i < len - 1; i++) {
            buildMaxHeap(a,len - 1 - i);
            swap(a,0,len - 1 - i);
        }
    }

    private static void buildMaxHeap(int[] data, 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(data[biggerIndex] < data[biggerIndex + 1]) {
                        biggerIndex++;
                    }
                }
                //如果K节点的值小于其较大的子节点的值
                if(data[k] < data[biggerIndex]) {
                    //交换他们
                    swap(data, k, biggerIndex);
                    k = biggerIndex;
                } else {
                    break;
                }
            }
        }
    }

    private static void swap(int[] a, int i, int j) {
        int tmp = a[i];
        a[i] = a[j];
        a[j] = tmp;
    }
}

运行结果

排序前:[5, 3, 2, 8, 4, 9, 1, 0, 7, 6]
排序后:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

各种算法的比较

排序法 平均时间 最小时间 最大时间 稳定度 额外空间 备注
冒泡排序 O(n2) O(n) O(n2) 稳定 O(1) n小时较好
选择排序 O(n2) O(n2) O(n2) 不稳定 O(1) n小时较好
插入排序 O(n2) O(n) O(n2) 稳定 O(1) 大部分已排序时较好
基数排序 O(logRB) O(n) O(logRB) 稳定 O(n) B是真数(0-9),R是基数(个十百)
Shell排序 O(nlogn) - O(ns) 1<s<2 不稳定 O(1) s是所选分组
快速排序 O(nlogn) O(n2) O(n2) 不稳定 O(logn) n大时较好
归并排序 O(nlogn) O(nlogn) O(nlogn) 稳定 O(n) 要求稳定性时较好
堆排序 O(nlogn) O(nlogn) O(nlogn) 不稳定 O(1) n大时较好

速度: 快速排序>>归并排序>>>>>插入排序>>选择排序>>冒泡排序
并且可以看到,选择排序,冒泡排序在数据量越来越大的情况下,耗时已经呈指数型上涨,而不是倍数上涨

(1)若n较小(如n≤50),可采用直接插入或直接选择排序。
 当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。
(2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜;
(3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。
 快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
 堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。
 若要求排序稳定,则可选用归并排序。

posted @ 2020-06-28 10:09  洛水良遥  阅读(701)  评论(0编辑  收藏  举报