八大排序(JAVA)

直接插入排序

在这里插入图片描述

基本思想:我们平时玩扑克牌时,摸牌阶段的排序就用到了插入排序的思想

1、当插入第n个元素时,前面的n-1个数已经有序

2、用这第n个数与前面的n-1个数比较,找到要插入的位置,将其插入(原来位置上的数不会被覆盖,因为提前保存了)

3、原来位置上的数据,依次后移

     /**
     * 直接插入排序
     */
    public void insertSort(int[] array) {
        for(int i = 1; i < array.length; i++) {
            int j = i-1;
            int temp = array[i];
            while(j >= 0) {
                if(array[j] > temp) {
                    array[j+1] = array[j];
                    j--;
                }else {
                    break;
                }
            }
            array[j+1] = temp;
        }
    }

直接插入排序总结:

①元素越接近有序,直接插入排序的效率越高

②时间复杂度:O(N^2)

最坏的情况下,每次插入一个数字,前面的数字都要挪动一下,一共需要挪动1+2+3+……+n=n(n+1)/2

③空间复杂度:O(1)

没有借助额外的空间,只用到常数个变量

希尔排序

在这里插入图片描述

基本思想:

1、先选定个小于n的数字作为gap,所有距离为gap的数分为一组进行预排序(直接插入排序)

2、再选一个小于gap的数,重复①的操作

3、当gap=1时,相当于整个数组就是一组,再进行一次插入排序即可整体有序

具体实现:

多组并排

在这里插入图片描述

多次预排序(gap>1)+ 一次插入排序(gap==1)

(1)gap越大,预排越快,越不接近于有序

(2)gap越小,预排越慢,越接近有序

/**
     * 希尔排序
     * 假设有10个元素,刚开始分为5组,一组2个元素,每个元素之间相隔5个元素
     * 假设有n个元素,刚开始分为gap组,一组 n/gap 个元素,每个元素之间相隔 gap 个元素
     */
    public void shellSort(int[] array) {
        int gap = array.length; 

        while(gap > 1) {
            //这两句的先后顺序比较重要,影响着gap == 1时会不会进行shell排序
            gap = gap / 2;
            //gap一定会等于1的
            shell(array,gap);
        }
    }

    public void shell(int[] array, int gap) {
        for (int i = gap; i < array.length; i++) {
            int temp = array[i];
            int j = i-gap;
            for(; j >= 0;j -= gap) {
                if(array[j] > temp) {
                    array[j+gap] = array[j];
                }else{
                    break;
                }
            }
            array[j+gap] = temp;
        }
    }

在这里插入图片描述

希尔排序总结:

①希尔排序是对直接插入排序的优化

②时间复杂度:O(N^1.3)

③空间复杂度:O(1)

选择排序

在这里插入图片描述

基本思想:

每次从数组中选出最大的或者最小的,存放在数组的最右边或者最左边,直到全部有序

形式一:

时间复杂度是:O(n^2)

不稳定

public void slectSort(int[] array) {
        for (int i = 0; i < array.length; i++) {
            int j = i+1;
            int min = i;
            for(; j < array.length; j++) {
                if(array[j] < array[min]) {
                    min = j;
                }
            }
            if(min != i) {
                int temp = array[min];
                array[min] = array[i];
                array[i] = temp;
            }
        }
    }

形式二:

时间复杂度是:O(n^2)

不稳定

在这里插入图片描述

/**
     * 拓展
     * 选择排序的第二种做法
     */
    public void slectSort2(int[] array) {
        int left = 0;
        int right = array.length - 1;
        while(left < right) {
            int min = left;
            int max = left;
            //找最大的与最小的
            for (int j = left+1; j <= right; j++) {
                //如果一个数即使最大的又是最小的,那么要么就是这组数都是一样的,要么就剩一个数了
                if(array[j] > array[max]) {
                    max = j;
                }
                if(array[j] < array[min]) {
                    min = j;
                }
            }
            swap(array,min,left);
            //[200,0,8,6,4,1,10]
            //max的下标是left;  min的下标是1
            //将min的值与left交换,此时array[left] = 0 ; array[min] = 200;
            //再将max的的值与right交换,此时array[max] = 0;并不是200; 所以会出错
            //应该将max = min 
            if(max == left) {
                max = min;
            }
            swap(array,max,right);
            left++;
            right--;
        }
    }
    //交换函数
    public void swap(int[] array,int i,int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

总结:

对于数组来说找下表比找数值要好

堆排序

基本思想:

1、将待排序的序列构造成一个大堆,根据大堆的性质,当前堆的根节点(堆顶)就是序列中最大的元素;

2、将堆顶元素和最后一个元素交换,然后将剩下的节点重新构造成一个大堆;

3、重复步骤2,如此反复,从第一次构建大堆开始,每一次构建,我们都能获得一个序列的最大值,然后把它放到大堆的尾部。最后,就得到一个有序的序列了。

小结论:

排升序,建大堆

排降序,建小堆

时间复杂度:O(n*logn)

空间复杂度O(1)

不稳定

public void heapSort(int[] array) {
        //建一个大根堆
        createHeap(array);
        //交换
        int end = array.length-1;
        while(end > 0) {
            swap(array,0,end);
            shiftDown(array,0,end);
            end--;
        }
    }

    public static void createHeap(int[] array) {
        int end = array.length;
        int child = array.length-1;
        int parent = (child-1)/2;
        while(parent >= 0) {
            shiftDown(array,parent,end);
            parent--;
        }
    }
    public static void shiftDown(int[]array,int parent,int end) {
        int child = parent*2+1;
        while(child < end) {
            if(child+1 < end && array[child+1] > array[child]) {
                child = child+1;
            }
            if(array[child] > array[parent]){
                swap(array,child,parent);
            }else {
                break;
            }
            parent = child;
            child = parent*2+1;
        }
    }
public static void swap(int[] array,int i,int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
}

冒泡排序

在这里插入图片描述

冒泡排序的基本思想:

一趟过程中,前后两个数依次比较,将较大的数字往后推,下一次只需要比较剩下的n-1个数,如此往复

时间复杂度O(n^2)

稳定的

public void bubbleSort(int[] array) {
        //最外层是趟数
        for(int i = 0; i < array.length - 1; i++) {
            boolean flg = false;
            for(int j = 0; j < array.length-1-i; j++) {
                if(array[j] > array[j+1]) {
                    flg = true;
                    swap(array,j,j+1);
                }
            }
            if(flg == false) {
                break;
            }
        }
}

快速排序

在这里插入图片描述

Hoare

时间复杂度:n*log n (理想状态下,接近一个满二叉树)

空间复杂度:logn (左子树完成之后 回收空间 再完成右子树)

不稳定

当我们给的数据是有序的时候,时间复杂度是n^2 , 空间复杂度是 n

思路:

找基准,基准的左边一定要与基准,基准的右边一定大于基准。

二分

递归

hoare的单趟思想:

1、左边作key,右边先走找到比key小的值

2、左边后走找到大于key的值

3、然后交换left和right的值

4、一直循环重复上述1 2 3步

5、两者相遇时的位置,与最左边选定的key值交换

这样就让key到达了正确的位置上

在这里插入图片描述

    public void quick(int[] array) {
        int begI = 0;
        int endI = array.length-1;
        QuickSort(array,begI,endI);
    }
    public void QuickSort(int[] array, int begI, int endI) {
        //能不能不写>号,预防 1 2 3 4 5, 直接没有左树,或右树,
        if(begI >= endI) {
            return;
        }
        //找基准小标,基准左边的一定小于等于基准,基准右边的一定大于等于基准
        int benchmark = partition(array,begI,endI);
        QuickSort(array,begI,benchmark-1);//对基准左边的数据进行排序
        QuickSort(array,benchmark+1,endI);//对基准右边的数据进行排序
        
    }
    //找基准下标
    public int partition(int[] array,int left,int right) {
        int temp = array[left]; //把左边的作为基准
        int index = left; //记录一下数组的起始位置
        while(left < right) {
            //left < right &&这个条件不能少,预防后面的right都大于left,数组越界
            while (left < right && array[right] >= temp) {
                //注意这里的等于号,当array[right] == temp,不要交换,否则会陷入死循环。
                right--;
            }
            //right下标的值小于temp
            while(left < right && array[left] <= temp) {
                left++;
            }
            //left下标的值大于temp
            //交换
            swap(array,left,right);
        }
        //交换和原来的left
        swap(array,index,left);
        return left;
    }

时间复杂度分析

时间复杂度:n*log n

在这里插入图片描述

partition函数的时间复杂度是 n ,因为里面的while循环并不是嵌套的。

递归的第一层:执行partition函数 1 次,时间复杂度为 n

递归的第二层:执行partition函数 2 次,时间复杂度为 n

递归的第三层:执行partition函数 4 次,时间复杂度为n

递归的第四层:执行partition函数 8 次,时间复杂度为n

递归的第 log n 层:执行partition函数 2^(logn-1)次,时间复杂度为n

总的时间复杂度是: log n *n 每一层时间复杂度是n,一共logn层

在这里插入图片描述

注意点:

(1)

在这里插入图片描述

内置的while(left < right && array[right] >= pivot) 需要注意的是这个大于等于号改写成大于号可不可以。

答案是不可以,因为当【(left下标所指的值)(right下标所指的值) (pivot的值)】时,会陷入死循环。

(2)

当把数组最左侧的值当作基准值时,要先从右边开始进行比较。否则会把大于基准值的数放在基准的左侧。

当把数组最右侧的值当作基准值时,要先从左边开始进行比较。

快排优化

挖坑法

挖坑法单趟思想:

1、先将最左边第一个数据存放在临时变量key中,形成一个坑位

2、右边先出发找到小于key的值,然后将该值丢到坑中去,此时形成一个新坑位

3、左边后出发找到大于key的值,将该值丢入坑中去,此时又形成一个新的坑位

4、一直循环重复1 2 3步

5、直到两边相遇时,形成一个新的坑,最后将key值丢进去

这样key就到达了正确的位置上了

在这里插入图片描述

     /**
     * 快速排序的优化
     * 挖坑发
     */
    public void quick(int[] array) {
        int begI = 0;
        int endI = array.length-1;
        QuickSort(array,begI,endI);
    }
    public void QuickSort(int[] array, int begI, int endI) {
        if(begI >= endI) {
            return;
        }
        int benchmark = partition(array,begI,endI);
        QuickSort(array,begI,benchmark-1);
        QuickSort(array,benchmark+1,endI);
    }
    public int partition(int[] arr, int left,int right) {
        int pivot = arr[left];//起始坑位
        while(left < right) {
            while(left < right && arr[right] >= pivot) {
                right--;
            }
            arr[left] = arr[right];

            while(left < right && arr[left] <= pivot) {
                left++;
            }
            arr[right] = arr[left];
        }
        arr[left] = pivot;
        return left;
    }

前后指针法

前后指针的思想:

1、初始时选定prev为序列的开始,cur指针指向prev的后一个位置,同样选择最左边的第一个数字作为key

2、cur先走,找到小于key的值,找到就停下来

3、++prev

4、交换prev和cur为下标的值

5、一直循环重复2 3 4步,停下来后,最后交换key和prev为下标的值

这样key同样到达了正确的位置

public int partition2(int[] arr, int left,int right) {
        int privot = arr[left];//基准
        int prev = left;
        int cur = left+1;
        for(; cur <= right; cur++) {
            if(arr[cur] < privot && ++prev != cur) {
            /**
            ++prev != cur  防止cur和prev相等时,相当于自己和自己交换,可以省略
            同时注意:++prev这个地方非常的巧妙,因为这么个原理,cur只要找到小的,prev就要向前走,不管cur与prev的位置到底重不             重合,比如说cur找到了5个小的,那么prev就应该放5个小的,然后再最后再与privot进行交换。所以说cur只要找到小的prev             就应该++。
            */
            /**
            prev这个地方非常的巧妙,当cur找到小的时候,prev才会加加,然后交换。cur找到小的之前,prev一直都指向小的或基准位               置。所以最后cur遍历完了之后,prev可以直接与基准进行交换。
            prev从left进行开始的优点是 【6 7 1 2 5 3 9 8】当第二个元素大与基准时可以进行移动。如果prev与cur都从left+1开             始,那么当第二个元素大于基准时,是跳过的。
            */
                //交换
                swap(arr, cur, prev);
            }
        }
        swap(arr, prev, left);
        return prev;
}

public static void swap(int[] array,int i,int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
}

在这里插入图片描述

在这里插入图片描述

三数取中法

快速排序对于数据是敏感的,如果这个序列是非常无序,杂乱无章的,那么快速排序的效率是非常高的,可是如果数列有序,时间复杂度就会从O(N*logN)变为O(N^2),相当于冒泡排序了

若每趟排序所选的key都正好是该序列的中间值,即单趟排序结束后key位于序列正中间,那么快速排序的时间复杂度就是O(NlogN)

但是这是理想情况,当我们面对一组极端情况下的序列,就是有序的数组,选择左边作为key值的话,那么就会退化为O(N^2)的复杂度,所以此时我们选择首位置,尾位置,中间位置的数分别作为三数,选出中间位置的数,放到最左边,这样选key还是从左边开始,这样优化后,全部都变成了理想情况

在这里插入图片描述

在这里插入图片描述

    public void quickS(int[] array) {
        QuickSorts(array,0,array.length-1);
    }
    public void QuickSorts(int[] array, int begI, int endI) {
        if(begI >= endI) {
            return;
        }
        /**
         * 最坏的情况是排列一组有序的数据,时间复杂度是n^2,空间复杂度是n,
         * 我们采用三数取中法进行划分,这样可以使数据分布的更均匀。
         * 【1,2,3,4,5,6,7】--> 【4,2,3,1,5,6,7】--> 【1,2,3,4,5,6,7】
         *       初始数组                  优化后数组           找到基准是4,而不是1
         */
        int mid = findMidIndex(array,begI,endI);
        swap(array,mid,begI);
        int benchmark = partition4(array,begI,endI); //找基准
        QuickSort(array,begI,benchmark-1);
        QuickSort(array,benchmark+1,endI);
    }
    //寻找大小介于中位的下标
    public int findMidIndex(int[] array,int begI,int endI) {
        int mid = (begI+endI)/2;
        if(array[endI] > array[begI]) {
            if(array[mid] < array[begI]) {
                return begI;
            }else if(array[mid] > array[endI]) {
                return endI;
            }else{
                return mid;
            }
        }else{
            if(array[mid] > array[endI]) {
                return endI;
            }else if(array[mid] < array[begI]) {
                return begI;
            }else {
                return mid;
            }
        }
    }
    public int partition4(int[] arr, int left,int right) {
        int cur = left+1;
        int prev = left;//prev必须从left开始,避免当第二个元素大于基准时,跳过。
        int priot = arr[left];//基准
        while(cur <= right) {
            if(arr[cur] < priot && prev != cur) {
                prev++;
                swap(arr,prev,cur);
            }
            
            cur++;
        }
        swap(arr,left,prev);
        return prev;
    }

递归到小区间

随着递归深度的增加,递归次数以每层2倍的速度增加,这对效率有着很大的影响,当待排序序列的长度分割到一定大小后,继续分割的效率比插入排序要差,此时可以使用插排而不是快排

我们可以当划分区间长度小于10的时候,用插入排序对剩下的数进行排序

在这里插入图片描述

public void quick(int[] array) {
        QuickSort(array,0,array.length-1);
    }
    public void QuickSort(int[] array, int begI, int endI) {
        if(begI >= endI) {
            return;
        }
        if(endI-begI+1 <= 10) {
            insertSort(array,begI,endI);
            return;
        }
        int mid = findMidIndex(array,begI,endI);
        swap(array,mid,begI);
        int benchmark = partition4(array,begI,endI); //找基准
        QuickSort(array,begI,benchmark-1);
        QuickSort(array,benchmark+1,endI);
    }
    public void insertSort(int[] array, int left, int right) {
        for(int i = left+1; i<= right; i++) {
            int temp = array[i];
            int j = i-1;
            while(j >= left) {
                if(array[j] > temp) {
                    array[j+1] = array[j];
                }else { // 出现if就想想有没有else
                    break;
                }
                j--;
            }
            array[j+1] = temp;
        }
    }
    public int findMidIndex(int[] array,int begI,int endI) {
        int mid = (begI+endI)/2;
        if(array[endI] > array[begI]) {
            if(array[mid] < array[begI]) {
                return begI;
            }else if(array[mid] > array[endI]) {
                return endI;
            }else{
                return mid;
            }
        }else{
            if(array[mid] > array[endI]) {
                return endI;
            }else if(array[mid] < array[begI]) {
                return begI;
            }else {
                return mid;
            }
        }
    }
    public int partition4(int[] arr, int left,int right) {
        int cur = left+1;
        int prev = left;
        int priot = arr[left];//基准
        while(cur <= right) {
            if(arr[cur] < priot && prev != cur) {
                if(arr[prev] > priot) {
                    swap(arr,prev,cur);
                }else {
                    prev++;
                    swap(arr,prev,cur);
                }
            }
            cur++;
        }
        swap(arr,left,prev);
        return prev;
    }
    public static void swap(int[] array,int i,int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

递归版本的优化完整版

public void quick(int[] array) {
        QuickSort(array,0,array.length-1);
    }
    public void QuickSort(int[] array, int begI, int endI) {
        if(begI >= endI) {
            return;
        }
        if(endI-begI+1 <= 10) {
            insertSort(array,begI,endI);
            return;
        }
        int mid = findMidIndex(array,begI,endI);
        swap(array,mid,begI);
        int benchmark = partition4(array,begI,endI); //找基准
        QuickSort(array,begI,benchmark-1);
        QuickSort(array,benchmark+1,endI);
    }
    public void insertSort(int[] array, int left, int right) {
        for(int i = left+1; i<= right; i++) {
            int temp = array[i];
            int j = i-1;
            while(j >= left) {
                if(array[j] > temp) {
                    array[j+1] = array[j];
                }else { // 出现if就想想有没有else
                    break;
                }
                j--;
            }
            array[j+1] = temp;
        }
    }
    public int findMidIndex(int[] array,int begI,int endI) {
        int mid = (begI+endI)/2;
        if(array[endI] > array[begI]) {
            if(array[mid] < array[begI]) {
                return begI;
            }else if(array[mid] > array[endI]) {
                return endI;
            }else{
                return mid;
            }
        }else{
            if(array[mid] > array[endI]) {
                return endI;
            }else if(array[mid] < array[begI]) {
                return begI;
            }else {
                return mid;
            }
        }
    }
    public int partition4(int[] arr, int left,int right) {
        int cur = left+1;
        int prev = left;
        int priot = arr[left];//基准
        while(cur <= right) {
            if(arr[cur] < priot && prev != cur) {
                if(arr[prev] > priot) {
                    swap(arr,prev,cur);
                }else {
                    prev++;
                    swap(arr,prev,cur);
                }
            }
            cur++;
        }
        swap(arr,left,prev);
        return prev;
    }
    public static void swap(int[] array,int i,int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }

非递归版本

递归的算法主要是在划分子区间,如果要非递归实现快排,只要使用一个栈来保存区间就可以了。一般将递归程序改成非递归首先想到的就是使用栈,因为递归本身就是一个压栈的过程。

非递归的基本思想:

  1. 申请一个栈,存放排序数组的起始位置和终点位置。

  2. 将整个数组的起始位置和终点位置入栈。

  3. 由于栈的特性是:后进先出,right后进栈,所以right先出栈。

    定义一个end接收栈顶元素,出栈操作、定义一个begin接收栈顶元素,出栈操作。

  4. 对数组进行一次单趟排序,返回key关键值的下标。

  5. 这时候需要排基准值key左边的序列。

​ 如果只将基准值key左边序列的起始位置和终点位置存入栈中,等左边排序完将找不到后边的区间。所以先将右边序列的起始位 置和终点位置存入栈中,再将左边的起始位置和终点位置后存入栈中。

6.判断栈是否为空,若不为空 重复4、5步、若为空则排序完成。

在这里插入图片描述

     /**
     * 快速排序的非递归实现
     */
 public void quick(int[] array) {
        quickSort(array,0,array.length-1);
 }
 public void quickSort(int[] array, int begI,int endI) {
        Stack<Integer> stack = new Stack<>();
        int piovt =  partition4(array,begI,endI);//找到基准下标
        // 1: 判断右面有没有多于两个元素
        //这个条件必须要有,否则就死循环(1个元素)或者越界(0个元素)。
        if(endI-1 > piovt) {
            stack.push(piovt+1);
            stack.push(endI);
        }
        //判断左边有没有多于两个元素
        if(begI+1 < piovt) {
            stack.push(begI);
            stack.push(piovt-1);
        }

        while(!stack.isEmpty()) {
            endI = stack.pop();
            begI = stack.pop();
            piovt =  partition4(array,begI,endI);//找到基准下标
            if(endI-1 > piovt) {
                stack.push(piovt+1);
                stack.push(endI);
            }
            if(begI+1 < piovt) {
                stack.push(begI);
                stack.push(piovt-1);
            }
        }
    }
 public int partition4(int[] arr, int left,int right) {
        int cur = left+1;
        int prev = left;
        int priot = arr[left];//基准
        while(cur <= right) {
            if(arr[cur] < priot && prev != cur) {
                if(arr[prev] > priot) {
                    swap(arr,prev,cur);
                }else {
                    prev++;
                    swap(arr,prev,cur);
                }
            }
            cur++;
        }
        swap(arr,left,prev);
        return prev;
   }
 public static void swap(int[] array,int i,int j) {
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
 }

归并排序

时间复杂度:O(N*logN)

空间复杂度:O(N) 因为你要在合并函数的时候要开辟一个新的数组,这个数组最坏情况下长度是N。

稳定性:稳定

归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题

递归实现

在这里插入图片描述

归并排序的基本思想(分治思想):

1、(拆分)将一段数组分为左序列和右序列,再将左序列细分为左序列和右序列,如此重复该步骤,直到细分到区间不存在或者只有一个数字为止

2、(合并)将第一步得到的数字合并成有序区间

在这里插入图片描述

从思想上来说和二叉树很相似,所以我们可以用递归的方法来实现归并排序

    public void mergeSort(int[] array) {
        mergeS(array,0,array.length-1);
    }
    public void mergeS(int[] array, int left, int right) {
        if(left >= right) {
            return;
        }
        int mid = (left+right)/2;
        mergeS(array,left,mid);
        mergeS(array,mid+1,right);
        //合并
        combine(array,left,right,mid);
    }
    //合并函数
    public void combine(int[] array,int left ,int right, int mid) {
        //将两个有序数组合并为一个有序数组
        int sl = left;
        int el = mid;
        int sr = mid+1;
        int er = right;

        int[] arr = new int[right-left+1];
        int k =0;

        while(sl <= el && sr <= er) {
            if(array[sl] < array[sr]) {
                arr[k] = array[sl];
                k++;
                sl++;
            }else{
                arr[k] = array[sr];
                k++;
                sr++;
            }
        }
        while(sl <= el) {
            arr[k] = array[sl];
            sl++;
            k++;
        }
        while(sr <= er) {
            arr[k] = array[sr];
            sr++;
            k++;
        }
        for(int j = 0; j < k; j++) {
            array[j+left] = arr[j]; 
            /**
            注意:arr数组中的下标是从0-k的,而array数组的下标是从left到right的,
            所以赋值的时候用array[j+left] = arr[j]; 
            */
            array[j+left] = arr[j]; 
        }
    }

非递归实现

我们知道,递归实现的缺点就是会一直调用栈,而栈内存往往是很小的。所以,我们尝试着用循环的办法去实现

由于我们操纵的是数组的下标,所以我们需要借助数组,来帮我们存储上面递归得到的数组下标,和递归的区别就是,递归要将区间一直细分,要将左区间一直递归划分完了,再递归划分右区间,而借助数组的非递归是一次性就将数据处理完毕,并且每次都将下标拷贝回原数组

归并排序的基本思路是将待排序序列a[0…n-1]看成是n个长度为1的有序序列,将相邻的有序表成对归并,得到n/2个长度为2的有序表;将这些有序序列再次归并,得到n/4个长度为4的有序序列;如此反复进行下去,最后得到一个长度为n的有序序列。

在这里插入图片描述

但是我们这是理想情况下(偶数个),还有特殊的边界控制,当数据个数不是偶数个时,我们所分的gap组,势必会有越界的地方

第一种情况:

在这里插入图片描述

第二种情况:

在这里插入图片描述

 public void mergeSor(int[] array) {
        int gap = 1;
        while(gap < array.length) {
            for(int i = 0; i< array.length; i = i+gap*2) { 
                /**
                这里的i就是left,进行一堆一堆的合并
                left是不会越界的。但是mid与right可能会存在越界
                */
                int left = i;
                int mid = left+gap-1;
                int right = mid + gap;
                //如果mid越界就调整一下
                if(mid >= array.length) {
                    mid = array.length-1;
                }
                //如果right越界就调整一下
                if(right >= array.length) {
                    right = array.length-1;
                }
                combine(array,left,right,mid);
            }
            gap = gap*2;
        }
  }

 public void combine(int[] array,int left ,int right, int mid) {
        //将两个有序数组合并为一个有序数组
        int sl = left;
        int el = mid;
        int sr = mid+1;
        int er = right;

        int[] arr = new int[right-left+1];
        int k =0;

        while(sl <= el && sr <= er) {
            if(array[sl] < array[sr]) {
                arr[k] = array[sl];
                k++;
                sl++;
            }else{
                arr[k] = array[sr];
                k++;
                sr++;
            }
        }
        while(sl <= el) {
            arr[k] = array[sl];
            sl++;
            k++;
        }
        while(sr <= er) {
            arr[k] = array[sr];
            sr++;
            k++;
        }
        for(int j = 0; j < k; j++) {
            array[j+left] = arr[j]; 
            /**
            注意:arr数组中的下标是从0-k的,而array数组的下标是从left到right的,
            所以赋值的时候用array[j+left] = arr[j]; 
            */
            array[j+left] = arr[j]; 
        }
  }

海量数据的排序问题

外部排序:排序过程需要在磁盘等外部存储进行的排序
前提:内存只有 1G,需要排序的数据有 100G
因为内存中因为无法把所有数据全部放下,所以需要外部排序,而归并排序是最常用的外部排序

  1. 先把文件切分成 200 份,每个 512 M
  2. 分别对 512 M 排序,因为内存已经可以放的下,所以任意排序方式都可以
  3. 进行 2路归并,同时对 200 份有序文件做归并过程,最终结果就有序了

计数排序

又叫非比较排序,又称为鸽巢原理,是对哈希直接定址法的变形应用

①统计相同元素出现的个数

对于给定的任意数组a,我们需要开辟一个计数数组count,a[i]是几,就对count数组下标是几++

在这里插入图片描述

这里我们用到了绝对映射,即a[i]中的数组元素是几,我们就在count数组下标是几的位置++,但是对于数据比较聚集,不是从较小的数字开始,例如1001,1002,1003,1004这样的数据,我们就可以用到相对映射的方法,以免开辟数组空间的浪费,count数组的空间大小我们可以用a数组中最大值减去最小值+1来确定(即:range=max-min+1),我们可以得到count数组下标 j =a[i]-min
在这里插入图片描述

②根据count数组的结果,将数据拷贝回a数组

count[j]中数据是几,说明该数出现了几次,是0就不用拷贝

在这里插入图片描述

 public void JiShuSort(int[] array) {
        //1:遍历数组找到最大值与最小值
        int maxVal = array[0];
        int minVal = array[0];
        for (int i = 0; i < array.length; i++) {
            if(array[i] > maxVal) {
                maxVal = array[i];
            }
            if(array[i] < minVal) {
                minVal = array[i];
            }
        }
        //2:确定数组的长度
        int len = maxVal-minVal+1;
        /**
        确定数组的长度:len = 最大值-最小值 + 1 :最坏的情况是介于最大值与最小值之间不同的数都有,我们要分别对其进行计数
        这个len代表着种类的个数。
        */
        int[] arrayCount = new int[len];
        //3: 计数
        for (int i = 0; i < array.length; i++) {
            int index = array[i] - minVal;//相对映射
            arrayCount[index]++;
        }
        //4: 排序
        int size = 0;
        for(int i = 0; i < len; i++) {
            while(arrayCount[i] != 0) {
                array[size] = i + minVal;
                size++;
                arrayCount[i]--;
            }
        }
    }
  1. 计数排序在数据范围集中时,效率很高,但是适用范围及场景有限。
  2. 时间复杂度:O(MAX(N,范围))
  3. 空间复杂度:O(范围)
  4. 稳定性:稳定

八大排序的稳定性总结:

稳定的排序有:直接插入排序,冒泡排序、归并排序 计数排序

不稳定的排序有:希尔排序、选择排序、堆排序、快速排序、

部分图片和文字参考 " Hero 2021 " .

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HackerTerry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值