Java六大排序

前言

排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。

注意:一般排序是从小到大,从左到右

在一组未排序数据中,两个关键字具有相同属性,即数字相等,再经过排序后两个关键字的顺序不变,叫稳定排序,反之则为不稳定排序。

举例:

9[1] 5 3 7 6 2 4 9[2]
稳定排序:2 3 4 5 6 7 9[1] 9[2]
不稳定排序:2 3 4 5 6 7 9[2] 9[1]
内部排序:待排序的记录全部放到内存中进行排序,时间复杂度也就等于比较的次数

外部排序:数据量很大,内存无法容纳,需要对外存进行访问再排序,把若干段数据一次读入内存使用内部排序的方法进行排序后写入外存,再将这若干个已经排序的数据进行归并,时间复杂度等于IO(访问外存)的次数

注:本文章主要讲解冒泡、快速、直接插入、希尔排序、简单选择、归并这六类排序,其他暂不研究。

排序最好情形平均时间最差情形空间复杂度稳定度
冒泡排序O(n)O(n²)O(n²)O(1)稳定
快速排序O(nlogn)O(nlogn)O(n²)O(nlogn)不稳定
直接插入排序O(n)O(n²)O(n²)O(1)稳定
希尔排序O(nlog²n)O(nlogn)O(nlog²n)O(1)不稳定
直接选择排序O(n²)O(n²)O(n²)O(1)不稳定
堆排序O(nlogn)O(nlogn)O(nlogn)O(1)不稳定
归并排序O(nlogn)O(nlogn)O(nlogn)O(n)稳定
计数排序O(n+k)O(n+k)O(n+k)O(k)稳定
基数排序O(n*k)O(n*k)O(n*k)O(n+k)稳定
桶排序O(n+k)O(n+k)O(n²)O(n+k)稳定

注1:分析排序算法时,最好debug看看具体代码走向,而不是直接背网上代码,这样永远无法深入代码的核心,也不法痛彻心扉地理解代码的真谛。

注2:建议测试用例包括但不限于数组为null,数组只有1、2、3个元素,完全顺序,完全逆序、长而乱的数组,长而只有个别元素位置不对这些情况,唯有按这个标准debug每种排序,才能彻底理解代码的底层逻辑并深刻记忆。 

注3:理解排序需要考虑循环次数,元素比较次数,元素交换次数。

一、交换排序

序列中任意两个元素进行比较,根据比较结果来交换各自在序列中的位置。

1、冒泡排序

一次解释:
从头到尾依次比较相邻的两个数,将较大的数往后移,一次遍历可以将一个数移动到它应该在的位置,最后得到有序序列。
二次解释:
以升序为例(从小到大),每次比较相邻的两个元素,如果左侧元素比右侧的大,则交换两个元素的位置,每次把循环中最大的元素放在循环的最后,像冒泡一样从小到最大。

栗子:初始数组:29,15,14,37,25   长度5 循环次数n-1=4
第一次循环:15,14,29,25,[37]     比较4次    i=0 j=0,1,2,3 【arr[0]和arr[1],arr[1]和arr[2],arr[2]和arr[3],arr[3]和arr[4]】
第二次循环:14,15,25,[29,37]     比较3次    i=1 j=0,1,2 【arr[0]和arr[1]比,arr[1]和arr[2],arr[2]和arr[3]】
第三次循环:14,15,[25,29,37]     比较2次    i=2 j=0,1 【arr[0]和arr[1]比较,arr[1]和arr[2]】
第四次循环:[14,15,29,25,37]     比较1次    i=3 j=0 【arr[0]和arr[1]】

public static void bubbleSort(int[] arr){
    for(int i=0;i<arr.length-1;i++){
        for(int j=0;j<arr.length-1-i; j++){  //注意这里是数组长度-1-i
            if(arr[j] > arr[j + 1]){
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
}

改进代码:若数组已经完整排序,不需要再排序,可以在第一轮最多次比较确认从小到大排序后直接返回

public class First{
    public static void bubbleSort(int[] arr){
        for(int i=0;i<arr.length-1;i++){
            boolean flag = true;
            for(int j=0;j<arr.length-1-i; j++){
                if(arr[j]>arr[j+1]){
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                    flag = false;
                }
            }
            if(flag){
                break;  //可以改成 return;
            }
        }
    }
    public static void main(String[] args){
        int[] arr={1,2,5,7,8,9};
        bubbleSort(arr);
        for(int i:arr){
            System.out.print(i+"\t");
        }
    }
}

上面栗子第一次循环已经将数组各个元素比较了一次,如果该轮循环比较完不需要交换元素,那可以直接退出循环,不用多余的比较;并且面对[1,3,4,5,9,7]这种只需改变1个元素就完全顺序时也只需经历两次循环,极大提高效率。

注:除了测试正常排序外,还需要测试数组{}和数组为null,数组只有1个元素,数组2个元素的debug情况,有助于理解排序。

2、快速排序

快速排序(Quick Sort) 双指针法、horae、挖空法
从数组中选一个基准值(如第一个数,也可以是最后一个数),将基准值与其他元素进行比较,将元素分成两个区域,把所有比基准值小的值放在左侧,所有比基准值大的放在右侧。通过递归上述操作,再次将左右两区域进行区域划分,直到每个分区只有一个元素,排序完成。


进一步理解:[双指针法]
将数组中第一个元素作为基准值,用一个左指针指向数组的第一个元素,用一个右指针指向数组的最后一个元素。
基准先左,先移动右指针,右指针从右向左移动,直到有一个元素小于基准值,交换左右指针的元素;
移完右指针再移动左指针,左指针从左向右移动,直到有一个元素大于基准值,交换左右指针的元素;
重复上述操作直到左指针和右指针相遇为止。

【注意:基准在左,先移动右指针;基准在左则先移动左指针,看下面栗子】

栗子:18[L],7,45,29,15,21,18,34[R]
L是18,R是34 基准P为18[设将其挖掉] 基准在左先移动右指针,R从右到左直到15,15<18,右指针元素交给左指针位置
15[L],7,45,29,[][R],21,18,34    移完右指针移动左指针,L从15开始直到45,45>18,左指针元素交给右指针位置
15,7,[][L],29,[45][R],21,18,34  【重复上面步骤】移动右指针直到L与R相遇,将基准放入该位置
15,7,18[L][R],29,45,21,18,34    此时拆分成15,7和29,45,21,18,34  【至此完成第一次遍历】
先看15[L],7[R] 基准为15,移动右指针,因7<15,左右指针互换变成7,15
再看[][L],45,21,18,34[R] 基准为29,先移动右指针,因18<29,左右指针互换: 18[L],45,21,[][R],34 再移动左指针,因45>29
交换左右指针:18,[][L],21,45[R],34   移动右指针,21<29,交换左右指针位置  18,21[L],[][R],45,34  移动左指针,左右指针一致,将基准放入
18,21,[29][L][R],45,34  此时拆分成18,21和45,34 一排序18,21和34,45  排序完成

总结:每一次遍历,将一个元素放到它该去的位置。遍历次数不确定。与数组长度相关 n/2,n/4,n/8...  比较元素次数视前者为定,交换元素次数不确定

public static void quickSort(int[] arr,int L,int R){
    if(L<R){  //当L==R或者L-R=1时方法结束 可以改成 if(R-L<1) return; 或者 if(L>=R) return;
        int pivot = arr[L];  //把数组中的首位数字作为基准数
        // 记录需要排序的下标
        int low=L;
        int high=R;
        // 循环找到比基准数大的数和比基准数小的数
        while(low<high){
            // 右边的数字比基准数大,基准值在左,先移动右指针,直到找到第一个小于pivot的数的下标
            while (low<high && arr[high]>=pivot) high--;
            arr[low] = arr[high];  // 用右指针的数放入左指针的值
            //左边的数字比基准数小,移完右指针再移动左指针,直到找到第一个大于pivot的数的下标
            while (low<high && arr[low]<=pivot) low++;  //注意是++,low<high防止上面移动后导致low==high
            //用左指针的数替换右指针的数
            arr[high] = arr[low];
        }
        // 把标准值赋给下标重合的位置
        arr[low] = pivot;  //此时low==high 所以可写成arr[high] = pivot;
        quickSort(arr,L,low-1);  //这里low-1效率更高
        quickSort(arr,low+1,R);
    }
}

调用上面方法时会先执行quicksort一次,分出左序列、基准、右序列;然后方法里调用quicksort,即左序列调用一次,右序列调用一次,左序列调用时会接着拆分成两个子序列,左子序列接着拆分,直到左左..左子序列只有两个元素,排序好后才进行左左..右子序列。
即不断拆分直到子序列只有不超过两个元素,每一次轮询都是不断递归调用方法直到L==R或者L>R时才会停止,排好序才下一个子序列,直到所有都是不大于2个有序的元素,排序结束。

下面代码是移动完右指针,再移动左指针,然后左右指针对应的元素交换

public void quickSort(int[] arr, int L, int R){
    // 如果指针在同一位置则直接返回
    if (R - L < 1) return;  // 即L==R或者L>R
    // 将第一个数看作是分界点
    int pivot = arr[L];
    int i = L;
    int j = R;
    // 开始左右指针进行遍历[L+1,R]
    while(i<j){
        // 要先遍历右指针,当右指针遍历到i停止时,i指向到也是小于tmp到位置,
        // 否则i停止时可能等于j,造成大于tmp
        // 遍历右指针,查找到第一个小于tmp的数的下标
        while(i<j && arr[j]>=pivot) j--;
        // 遍历左指针,查找到第一个大于tmp的数的下标
        while(i<j && arr[i]<=pivot) i++;
        if(i<j){  //进行交换
            int t = arr[j];
            arr[j] = arr[i];
            arr[i] = t;
        }
    }
    // 将终点进行交换,即左右指针重合的位置
    arr[L] = arr[i];
    arr[i] = pivot;
    quickSort(arr,L,i-1);
    quickSort(arr,i+1,R);
}

二、插入排序

将一个记录插入到已经排好序的有序表中,使得被插入数的序列同样是有序的。按照此法对所有元素进行插入,直到整个序列排为有序的过程。

1、直接插入排序

一次解释:
对于一个序列,选定一个下标,认为在这个下标之前的元素都是有序的。
将下标所在的元素插入到其之前的序列中。接着再选取这个下标的后一个元素,继续重复操作。
直到最后一个元素完成插入为止。我们一般从序列的第二个元素开始操作。

【相当于把a[n]分割,先排序数组a[0]~a[1],再a[0]~a[2],直到a[0]~a[n-1]】

栗子:
初始数组:      [53] 27 36 15 69 42   排序轮次n-1=5
第一次排序(27):[27 53] 36 15 69 42    [比较1次 27与53比较,交换]
第二次排序(36):[27 36 53] 15 69 42    [比较2次 36与27,53比较,从小到大排序]
第三次排序(53):[15 27 36 53] 69 42    [比较3次 15跟27 36 53比较]
第四次排序(69):[15 27 36 53 69] 42    [比较4次 69跟15 27 36 53比较]
第五次排序(42):[15 27 36 42 53 69]    [比较5次 42跟15 27 36 53 69比较]

二次解释:
每次遍历到第i个数,都要跟前面的遍历过的数进行比较,如果前面的数大于第i个数就要进行交换。

总结:
循环次数n-1,每次循环比较元素次数i[如果i同时作为索引需从1开始遍历,即从第2个元素起与前面元素比较] 交换元素次数最多1个
注意:若是完全顺序,因从第2个元素起与前面比较,仍需循环n-1

public static void insertSort(int[] array){
    for(int i=1; i<array.length; i++){  //遍历次数应该是n-1
        int tmp = array[i];      // 将i下标的元素存起来, 可以认为这样就空出一个空间来移动元素
        int j = 0;           //定义一个j来遍历i前面的元素
        for(j =i-1; j>=0; j--){
            if(array[j]>tmp){   //如果j下标元素大于tmp, 则将j下标元素往前移一位
                array[j+1] = array[j];   
            }else{  //不大于则说明j下标及j前面的元素都小于tmp, 则退出循环(此时从0下标到j下标都已有序)
                break;  //这里可改为flag标记方式,但冒泡却不能改成这种
            }
        }
        array[j+1] = tmp;  //将tmp放到那个空出的位置上
    }
}
//建议写法 i,j需要对应索引值
public void insertSort(int[] arr){
    for (int i=1; i<arr.length; i++){  //遍历次数n-1,i从1开始对应索引
        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;
        }
    }
}

改进代码:上面代码在查找相应位置时效率略微低下,选择二分法查找提高效率

public static void EFinsertSort(int[] arr){
	// 有序区间[0..i)
    // 无序区间[i...n]
    for(int i=1; i<arr.length; i++){
        int left = 0;
        int right = i;
        int val = arr[i];
        //使用二叉查找,找到应插入到的位置
        while(left < right){
            int mid = (left + right) >> 1;
            if (val < arr[mid]){
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        // 搬移left..i的元素
        for (int j=i; i>left; i--){
            arr[j] = arr[j-1];
        }
        // left就是val插入的位置
        arr[left] = val;
    }
}    

2、希尔排序

如一个有序的升序数组,这时再插入一个比最小值还要小的数,也就意味着被插入的数要和数组所有元素比较一次。
我们需要对直接插入排序进行改进。
插入排序对已经排好序的数组操作时,效率很高。因此我们可以试着先将数组变为一个相对有序的数组,然后再做插入排序。
希尔排序把序列按下标的一定增量(步长)分组,对每组分别使用插入排序。随着增量(步长)减少,一直到一,算法结束,
整个序列变为有序。因此希尔排序又称缩小增量排序。
一般来说,初次取序列的一半为增量,以后每次减半,直到增量为一。

三次解释:
将整个数组按照步长进行分组,比如说步长是4数组就被分为ABCDABCD,步长是2则数组就被分为ABABABABAB;若为奇数,最后一个为A
每个组中进行插入排序,直到步长为1时,对整个数组进行一次插入排序。

希尔优点是相对于插入排序,它可以更快地将元素移动到正确的位置,从而减少了比较和交换的次数,
提高了排序效率。缺点是其实现过程较为复杂,增量序列的选择对排序效率有较大的影响,
且不同的增量序列可能导致排序结果不同。

栗子:18,21,37,3,15,7,18,34,9   step = n/2 = 4  
分组: 18[A1],21[B1],37[C1],3[D1],15[A2],7[B2],18[C2],34[D2],9[A3]
i=4 A1与A2比较,15<18,交换;i=5 B1与B2比较,7<21,交换;
i=6 C1与C2比较,18<37,交换;i=7 D1与D2比较,3<34,不变;
i=8 A2与A3比较,9<18【这里的A2上面交换后的】,交换;
第一次轮询结果为:9,7,18,3,15,21,37,34,18
step=n/2/2=2  9[A1],7[B1],18[A2],3[B2],15[A3],21[B3],37[A4],34[B4],18[A5]
i=2 A1与A2比较,9<18,不变;i=3 B1与B2比较,3<7,交换;
i=4 A2与A3比较,15<18,交换;i=5 B2与B3比较,7<21,不变;
i=6 A3与A4比较,15<37,不变;i=7 B3与B4比较,21<34,不变;
i=8 A4与A5比较,18<37,交换;
第二次轮询结果为:9,3,15,7,18,21,18,34,37
step=1最后进行一次整体的插入排序

[18,21,37,3,15,7,18,34,9]   第一次 step==n/2==4 第二次 step/2==2 第三次 step/2==1
step=4 j=4,5,6,7,8 k=4【此时arr[0]和arr[4]比较】,5【arr[1]和arr[5]】,6,7,[8,4]【对应上面A1、A2、A3】
step=2 j=2,3,4,5,6,7,8 k=2[arr[0]和arr[2]],3,[4,2],[5,3],[6,4,2],[7,5,3],[8,6,4,2]
step=1 j=1,2,3,4,5,6,7,8 k=1[arr[0]和arr[1]比较],[2,1],[3,2,1],[4,3,2,1],[5,4,3,2,1],[6,5,4,3,2,1],[7,6,5,4,3,2,1],[8,7,6,5,4,3,2,1]   相当于逆冒泡排序
//建议写法
public void shellSort(int[] arr){
    for(int step=arr.length/2; step>0; step/=2){
        // step表示步长,j表示比较元素次数
        for(int j=step; j < arr.length; j++){
            for (int k=j; k>0 && k-step>=0; k-=step){  //k表示索引,指出需要交换的元素
                if (arr[k-step]>arr[k]){
                    int tmp=arr[k];
                    arr[k]=arr[k-step];
                    arr[k-step]=tmp;
                }else break;
            }
        }
    }
}

三、选择排序

首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

1、简单选择排序

一次解释:
每一趟从未排序的区间找到一个最小元素,并放到第一位,直到全部区间有序为止。

二次解释:
(1)每一次遍历的过程中,都假定第一个索引处的元素是最小值,和其他索引处的值依次进行比较,如果当前索引处的值大于其他某个索引处的值,则假定其他某个索引出的值为最小值,最后可以找到最小值所在的索引。
(2)交换第一个索引处和最小值所在的索引处的值。

栗子: [18,21,37,3,15,7,18,34,9]  长度为9
i=0  minIndex=0 j=1,2,3,4,5,6,7,8 每次arr[j]与arr[minIndex]即arr[0]比较,确定最小值索引j给minIndex
i=1  minIndex=1 j=2,3,4,5,6,7,8 每次arr[j]与arr[minIndex]即arr[1]比较,确定最小值索引j给minIndex
i=2  minIndex=2 j=3,4,5,6,7,8 每次arr[j]与arr[minIndex]即arr[2]比较,确定最小值索引j给minIndex
i=3  minIndex=3 j=4,5,6,7,8 每次arr[j]与arr[minIndex]即arr[3]比较,确定最小值索引j给minIndex
i=4  minIndex=4 j=5,6,7,8 每次arr[j]与arr[minIndex]即arr[4]比较,确定最小值索引j给minIndex
i=5  minIndex=5 j=6,7,8 每次arr[j]与arr[minIndex]即arr[5]比较,确定最小值索引j给minIndex
i=6  minIndex=6 j=7,8 每次arr[j]与arr[minIndex]即arr[6]比较,确定最小值索引j给minIndex
i=7  minIndex=7 j=8 每次arr[j]与arr[minIndex]即arr[7]比较,确定最小值索引j给minIndex
public void selectSort(int[] arr){
    for(int i=0; i<arr.length-1; i++){  //这里应该-1循环次数-1
        int minIndex=i;  //每次循环将i作为最小索引,找到最小值放入
        for(int j=i+1; j<arr.length; j++){
            if(arr[j]<arr[minIndex]){
                minIndex = j;
            }
        }
        //如果最小的数和当前遍历的下标不一致,则交换
        if (i != minIndex){  //若第1个为最小,则不用交换;第2个为第二小也不用交换...  可以改为flag
            int temp = arr[i];
            arr[i] = arr[minIndex];
            arr[minIndex] = temp;
        }
    }
}

代码改进:上面代码是单向选择排序,每次只筛选一个数排序,效率低,可以改成一次遍历筛选出最大值和最小值两个数。【双指针法】

public static void selectSortGai(int[] arr){
    int low = 0;  //最小值的索引
    int high = arr.length-1;  //记录最大值的索引
    while(low <= high){
        int min = low;
        int max = low;
        for(int i=low+1;i<=high; i++){  //筛选出更小的数
            if(arr[i]<arr[min]){
                min = i;
            }
            if(arr[i]>arr[max]){  //筛选出更大的值
                max = i;
            }
        }
        swap(arr, min, low);  //交换数组最左边的值和最小值
        //max和low重合的情况
        if(max == low){
            max=min;  //这里不能return; 
        }
        swap(arr, max, high);  //交换数组最右边的值和最大值
        //移动左右指针
        low++;  //这里++在前面和后面一样
        high--;
    }
}
private static void swap(int[] arr, int i, int j){
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

2、堆排序

暂无

归并排序

归并排序(Merge Sort)采用分治法,把无序数组两两分割,分割数次,然后自下至上将两个子序列进行排序,然后合并成一个有序数组,逐渐向上进行两两合并,直到合并成一个有序数组。
【n/2是int类型,因为是计算索引值,所以奇数个数时左边大于右边】

归并排序步骤:
(1)分解:将待排序的数组不断分成两个子数组,直到每个子数组只有一个元素为止。
(2)合并:将相邻的两个子数组合并成一个有序数组,直到最后只剩下一个有序数组为止。

合并时要用到一个辅助数组来暂存合并后的有序数组。假设待合并的两个有序数组分别为A和B,它们长度分别为n和m,合并后的有序数组为C,合并过程步骤如:
1、定义三个指针i、j和,分别指向数组A、B和C起始位置。
2、比较A[i]和B[j]的大小,将小元素放入C[k]中,并将对应指针向后移动一位。【双指针】
3、重复步骤2,直到其中一个数组的元素全部放入C中。
4、将另一个数组中剩余的元素放入C中。

栗子: 拆分阶段容易理解,将两个已经有序的子序列合并成一个有序序列,如最后一次合并
将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8]

//定义一个新数组,将原数组拆分成子序列填入新数组,最后新数组回填原数组
public class Test{
    public static void merge(int[] arr){
        mergeSort(arr,0,arr.length-1);
    }
    private static void mergeSort(int[] arr, int L, int R){  //L R数组索引最左最右
        if(L >= R) return;
        int M = (L+R)/2;
        mergeSort(arr,L,M);  //往左递归
        mergeSort(arr,M+1,R);  //往右递归
        //将两个子序列合并
        int a = L;
        int b = M+1;
        int size = R-L+1;
        int[] newarr = new int[size];   //定义新数组存放两个子序列
        int j = 0;  //新数组newarr的索引,若写成L则数组索引越界
        //将两个子序列有序放入newarr数组中
        while(a <= M && b <= R){
            if(arr[a] < arr[b]){
                newarr[j++]=arr[a++];
            }else{
                newarr[j++]=arr[b++];
            }
        }
        while(a<=M) newarr[j++] = arr[a++];
       	while(b<=R) newarr[j++] = arr[b++];
        //将arr数组元素重新放入arr数组中
        for(int i=0; i<size; i++){
            arr[i+L]=newarr[i];  //注意这里是i+L
        }
    }
    public static void main(String[] args){
        int[] arr= {5,7,6,9,3,11};
        merge(arr);
        for (int i : arr){
            System.out.print(i+"\t");
        }
    }
}
//容易理解  定义两个新数组,填入原数组数据,两个新数组依次比较大小回填原数组中
public class Test{
    //L数组第一个元素 索引0  R数组最后元素 索引arr.length-1
    public static void mergeSort(int[] arr, int L, int R){
        if(L>=R) return;
        int M = (L+R)/2;  //也可以是>>1
        mergeSort(arr,L,M);  //拆分左边  这里不断拆分直到L=0,R==1,M==0,代入return所以R仍为1 【A】
        mergeSort(arr, M+1, R);  //拆分右边  【B】
        merge(arr, L, M + 1, R);  //合并     【C】
    }
    //L数组第一个元素 R数组最后元素 M数组分割元素
    public static void merge(int[] arr,int L,int M,int R){
        int[] lArr = new int[M-L];  //左数组
        int[] rArr = new int[R-M+1];  //右数组
        //往这两个数组填充数据
        for(int i=L; i<M; i++){
            lArr[i-L]=arr[i];
        }
        for(int i=M; i<=R; i++){
            rArr[i-M]=arr[i];
        }
        int i=0, j=0;  //分别表示lArr[0] 和rArr[0]
        int k=L;  //arr数组的第一个元素
        //比较两个数组的值,较小者放入数组arr,移动指针,继续比较下一个
        while (i<lArr.length && j<rArr.length){
            if (lArr[i] < rArr[j]){
                arr[k++] = lArr[i++];
            }else{
                arr[k++] = rArr[j++];
            }
        }
        //若左数组没比较完,右数组已经完了,那么将左数组抄到大数组中(都是大数字)
        while(i<lArr.length){
            arr[k++] = lArr[i++];
        }
        //若右数组没比较完,左数组已经完了,那么将右数组抄到大数组中(都是大数字)
        while (j<rArr.length){
            arr[k++] = rArr[j++];
        }
    }
    public static void main(String[] args) {
        int[] arr={9,2,5,1,3,2,9,5,2,1,8};
        mergeSort(arr, 0, arr.length-1);
    }
}   

每调用一次非return的【A】则多一次【B】【C】循环
    
代码理解:

只有一个元素{9}时,调用mergeSort因L=R直接return结束;


当有2个元素{9,2}时L=0,R=1,M=0 调用【A】传入的L,M直接return 调用【B】 传入M+1和R [均1] return 调用【C】传入L=0,M+1=1,R=1   lArr:[0] rArr:[0] 填充数据lArr:[9] rArr:[2] 然后左右数组比较存值;

当有3个元素{9,2,7}时L=0,R=2,M=1 调用【A】传入L,M M=0 [**注意这里多一次【B】【C】循环**] 调用【A】传入L,M[均为0] return 调用【B】传入M+1,R[均为1] return 调用【C】 传入值L=0,M+1=1,R=1 lArr[0] rArr[0] lArr[0]=arr[0]=9  rArr[0]=arr[1]=2 lArr和rArr比较存值arr={2,9,7} 此时L=0 M=1 R=1 调用【B】[这里的【B】是上面多一次循环位置切入]此时L=0 R=2 M=1 传入M+1=2 R=2 return 调用【C】lArr[0,0]
rArr[0] 填充数据lArr[2,9] rArr[7] lArr和rArr比较存值arr={2,7,9}


当有4个元素时上面递归过程中【B】会多跑一次循环,其他过程照旧,同理可得多个元素情况

基数排序

按照元素的位数,从低位到高位依次进行排序,每一次排序按照指定位数上的数字进行桶排序,最后得到有序序列。将个、十、百等位数逐个进行排序。

进一步解释:
将整数按位数切割成不同的数字,然后按每个位数分别比较。为此需要将所有待比较的数值统一为同样的数位长度,数位不足的数在高位补零。

public static void radixSort(int[] arr) {
    // 存放数组中的最大数字
    int max = Integer.MIN_VALUE;
    for (int value : arr) {
        if (value > max) {
            max = value;
        }
    }
    //计算最大数字是几位数
    int maxLength = (max + "").length();
    //用于临时存储数据
    int[][] temp = new int[10][arr.length];
    //用于记录在 temp 中相应的下标存放数字的数量
    int[] counts = new int[10];
    // 根据最大长度的数决定比较次数
    for (int i = 0, n = 1; i < maxLength; i++, n *= 10) {
        // 每一个数字分别计算余数
        for (int j = 0; j < arr.length; j++) {
            // 计算余数
            int remainder = arr[j] / n % 10;
            // 把当前遍历的数据放到指定的数组中
            temp[remainder][counts[remainder]] = arr[j];
            // 记录数量
            counts[remainder]++;
        }
        // 记录取的元素需要放的位置
        int index = 0;
        // 把数字取出来
        for (int k = 0; k < counts.length; k++) {
            // 记录数量的数组中当前余数记录的数量不为 0
            if (counts[k] != 0) {
                // 循环取出元素
                for (int l = 0; l < counts[k]; l++) {
                    arr[index] = temp[k][l];
                    // 记录下一个位置
                    index++;
                }
                // 把数量置空
                counts[k] = 0;
            }
        }
    }
}

以上就是本文章的所有内容,后续有需要会加以补充,仅是整理就花了近1小时,创作不易,希望大家能有所收获,以上代码均通过多次验证,读者也可以自行适当修改,谢谢大家!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值