七大基础排序-Java版

486 阅读6分钟

前言

七大排序作为排序的基础,也是大厂面试管喜欢提问的方式,很多同学只懂得部分排序的思想,却不一定能在面试的过程中手撕代码,秋招临近,借此机会手撕一遍以巩固思想

冒泡排序

核心思想:对于长度为n的数组,在每一次遍历的过程中,都将较大的数往后进行交换,再遍历到最后一位的过程中,最大的数就排列在末尾了。注意:一边遍历一遍交换数字顺序,是冒泡的思想,不要与下面的插入排序混淆(网上很多排序出现了错误,不要被误导).附上偷来的动图

冒泡排序

    static void maopao(int []a){
        for (int i = a.length - 1; i > 0 ; i--) {
            boolean change = false;//优化,记录每次遍历过中是否发生过交换
            for (int j = 0; j < i; j++) {
                if (a[j] > a[j+1]){
                    swap(a,j,j+1);
                    change = true;
                }
            }
            if (!change) return;//若无交换,说明排序成功
        }
    }

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

快速排序

核心思想:选用递归思想。先选定一个数字(一般选择末尾数),找到其位置,将该位置上的数字与末尾选定数交换。再对该数左右两侧重复同样的动作。难点在于,是如何找到其位置的:设定两个指针,从数组头尾进行遍历,头部指针一旦找到比选定数字大的,停下,同时尾部指针找到比选定数字小的,停下,将指针所指向数字进行交换。重复上述操作,直到头尾指针重合。上述操作进行的结果是:比选定数字小的,均在其左侧,比选定数字大的,均在其右侧,以及收尾指针相交处,即是该选定数字最终所在的位置。附上偷来的图

    static void Kuaipai(int[] a, int start, int end) {
        if (start >= end) return;
        int mid = fenzu(a, start, end);
        Kuaipai(a, start, mid - 1);
        Kuaipai(a, mid, end);
    }

    static int fenzu(int[] a, int start, int end) {
        int num = a[end];
        int end1 = end;//记录末尾数位置
        while (start < end) {
            while (a[start] < num && start != end) start++;
            while (a[end] >= num && end != start) end--;
            if (start != end) {
                swap(a, start, end);
            }
        }
        swap(a, end1, start);
        return start;
    }
    
    static void swap(int []a,int i,int j){
        int b = a[i];
        a[i] = a[j];
        a[j] = b;
    }

插入排序

核心思想:假定前面的数列已经排序完毕,将后面的数字插入到前面已排序的数组中适合他的位置。注:图中数字在下往前移动的时候,并非是交换了位置,不要错误理解

    static void Charu(int[] a) {
        for (int i = 1; i < a.length; i++) {
            int j = i;
            int temp = a[j];
            while (j > 0 && a[j - 1] > temp) {
                a[j] = a[j - 1];
                j = j - 1;
            }
            a[j] = temp;
        }
    }

希尔排序

核心思想:希尔排序是在插入排序的基础上,人为的将数组进行了逻辑的的划分,每一次划分之后使用插入排序进行该逻辑上的分组,最后在划分间隔为1时,相当于做了一次插入排序(在基本有序的基础上)。

    static void Xier(int[] a) {
        for (int step = a.length / 2; step >= 1; step /= 2) {
            Charu(a, step);
        }
    }

    static void Charu(int[] a, int step) {
        for (int i = step; i < a.length; i = i + 1) {
            int j = i;
            int temp = a[j];
            while (j - step >= 0 && a[j - step] > temp) {
                a[j] = a[j - step];
                j = j - step;
            }
            a[j] = temp;
        }
    }

简单选择排序

核心思想:遍历一遍数组,找到最大的数字(或者最小数),将其放在最后(最前),依次类推,完成排序。注:注意与冒泡排序的区别,虽然每一趟都是完成了最大数的摆放,但是冒泡排序过程中包含了一边遍历,一遍将其有序化的操作(同样是遍历一次,但对数组进行的影响不同)

    static void xuanze(int []a){
        for (int i = 0; i < a.length; i++) {
            int min = i;
            for (int j = i; j < a.length; j++) {
                if (a[j] < a[min]) min = j;
            }
            swap(a,i,min);
        }
    }

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

堆排序

核心思想:构造大根堆(小根堆):根节点大于(小于)左右字节点,整个树状结构都满足规律------>根节点为i,那么左子节点为2i + 1,右子节点为2i+2。以大根堆为例,从最后一个包含子节点的父节点开始(即非叶子节点的最后一个节点,i = length/2),遍历到根节点,依次完成大根堆构造,完成整个树状结构的大根堆结构。再将根节点与最后节点交换(相当于找到了最大数,放在了他应该在的位置),以0到n-1为堆,重新构造大根堆,依次循环,完成排序。

    static void duipaixu(int []a){
        //从非叶子节点的最后一个节点开始,下标为i/2,循环遍历至0号节点
        for (int i = a.length / 2; i >= 0 ; i--) {
            adjust(a,i,a.length);
        }
        for (int i = a.length - 1; i > 0; i--) {
            //交换首尾
            swap(a,0,i);
            //调整第一个节点
            adjust(a,0,i);
        }
    }

    private static void adjust(int[] a, int start,int length) {
        int temp = a[start];
        int child = start * 2 +1 ;//左孩子
        while (child < length) {
            //如果存在右孩子,并且有孩子比左孩子大,则使用右孩子作为比较对象
            if (child + 1 < length && a[child] < a[child + 1]) {
                child = child + 1;
            }
            //如果孩子较大,交换,并且根据交换后是否有孩子判断是否要继续
            if (temp < a[child]) {
                swap(a, start, child);
                //如果交换后的节点存在孩子节点,则重新赋值并继续循环
                if (child * 2 + 1 < length){
                    start = child;
                    temp = a[start];
                    child = start * 2 + 1;
                }else break;
            } else {
                //当前节点已经符合根节点最大,无须继续
                break;
            }
        }
    }
    
    static void swap(int []a,int i,int j){
        int b = a[i];
        a[i] = a[j];
        a[j] = b;
    }

并归排序

核心思想:将数组分为左右两个已经排序好的数组,并对两个数组进行合并。难点在于递归的思想以及寻找出口:当且仅当首下标大于尾下标时,进行分组并合并,否则视为已完成。

    static void bingguipaixu(int []a,int start,int end){
        if (start < end){
            int mid = (start + end) /2;
            bingguipaixu(a,start,mid);
            bingguipaixu(a,mid + 1,end);
            merge(a,start,mid,end);
        }
    }

    //左右两边已经排序完成,代码可简化(简化后思路不够清晰)
    static void merge(int []a,int start,int mid,int end){
        int []b = new int[end - start + 1];
        int p = start;
        int q = mid + 1;
        int r = 0;
        while (p <= mid && q <= end){
            if (a[p] <= a[q]){
                b[r] = a[p];
                p = p + 1;
            }else{
                b[r] = a[q];
                q = q + 1;
            }
            r = r + 1;
        }
        if (p > mid) {
            while (q <= end) {
                b[r] = a[q];
                r = r + 1;
                q = q + 1;
            }
        }
        if (q > end){//这边判断可以不写,因为如果上面成立,q一定 > end
            while (p <= mid) {
                b[r] = a[p];
                r = r + 1;
                p = p + 1;
            }
        }
        for (int i = start ,j = 0; i <= end; i++,j++) {
            a[i] = b[j];
        }
    }

结束语:

本文总结了Java版七大排序的写法,如有问题希望能不吝文字,多多交流。PS:都看到这里了,给个赞呗,鼓励一下^v^