十大排序算法(part2-Java实现)

5.希尔排序

思想:希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法。希尔排序的本质是插入排序,同时它也称为缩小增量排序。将记录的下标按照一定的增量dk进行分组,对于每一组的记录我们使用直接插入排序算法。而增量dk的选择初始值一半设置为数组长度的一半,序列{dk,dk/2,dk/4...1},称之为增量序列。

代码实现如下:

public class Test5 {
    public static void main(String[] args) {
        int a[]=new int[]{10,9,8,7,6,5,4,3,2,1};
        System.out.print("排序前的结果为: ");
        for(int i=0;i<a.length;i++){
            System.out.print(a[i]+" ");
        }
        Test5 test5=new Test5();
        int dk=a.length/2; //初始的增量设置为数组长度的一半
        while(dk>=1){
            test5.shellSort(a,dk);
            dk/=2;  //增量序列每次除以2,当增量序列为1时候等同于直接插入排序
        }
        System.out.print("排序后的结果为: ");
        for(int i=0;i<a.length;i++){
            System.out.print(a[i]+" ");
        }
    }
    public void shellSort(int []a,int dk){
        int len=a.length,i,j;
        for(i=dk;i<len;i++){
            int temp=a[i];
            for(j=i-dk;;){
                while(j>=0 && a[j]>temp){
                    a[j+dk]=a[j];
                    j=j-dk;
                }
                a[j+dk]=temp;
                break;
            }
        }
    }
}

排序前的结果为: 10 9 8 7 6 5 4 3 2 1

排序后的结果为: 1 2 3 4 5 6 7 8 9 10

该代码与直接插入排序的代码几乎相同,注意增量dk每次要除以2,用上递归即可解决。

结论:在不同的插入排序过程中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,所以shell排序是不稳定的。希尔排序与直接插入排序一样,最好的时间复杂度为o(n),最坏的时间复杂度是o( n^{2})。而希尔排序的平均时间复杂度是o( n^{s}), 1<s<2, s根据所选的分组而定。希尔排序同直接插入排序一样,不需要借助额外的辅助空间,空间复杂度为o(1)

6.归并排序

思想:归并排序法是将两个(或者两个以上)有序表合成新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

代码如下:

public class Test6 {
    public static void main(String[] args) {
        int a[]=new int[]{10,9,8,7,6,5,4,3,2,1};
        System.out.print("排序前的结果为: ");
        for(int i=0;i<a.length;i++){
            System.out.print(a[i]+" ");
        }
        Test6 test6=new Test6();
        test6.mergeSort(a,0,a.length-1);
        System.out.print("排序后的结果为: ");
        for(int i=0;i<a.length;i++){
            System.out.print(a[i]+" ");
        }
    }
    public void mergeSort(int a[],int l,int r){
        if(l<r){//当子序列中只有一个元素时结束递归
            int m=(l+r)/2;//划分为两个子序列
            mergeSort(a, l, m);//对左侧子序列进行递归排序
            mergeSort(a, m+1, r);//对右侧子序列进行递归排序
            merge(a,l, m, r);//合并
        }
    }
    public void merge(int a[] ,int l,int m,int r){//两个排好序的子序列合并为一个子序列
        int temp[]=new int[a.length];//辅助数组
        int p1=l,p2=m+1;
        int k=l;
        while(p1<=m && p2<=r){
            if(a[p1]>a[p2]){
                temp[k++]=a[p2++];
            }else{
                temp[k++]=a[p1++];
            }
        }
        while(p1<=m){  //左边的序列没有合并完
            temp[k++]=a[p1++];
        }
        while(p2<=l){  //右边的序列没有合并完
            temp[k++]=a[p2++];
        }
        for(int i=l;i<=r;i++){ //将合并好有序的数组复制到原数组中去
            a[i]=temp[i];
        }
    }
}

排序前的结果为: 10 9 8 7 6 5 4 3 2 1

排序后的结果为: 1 2 3 4 5 6 7 8 9 10

结论:归并排序是一个稳定的排序算法,并且最好最坏平均时间复杂度均为o(n\log n),由于排序过程时候需要额外的数组空间,故其空间复杂度为o(n)

7.桶排序

思想:把数组a划分为n个相同大小的相同的子区间(桶),每个子区间各自排序,最后合并。计数排序是桶排序的一种特殊情况,可以把计数排序当成每个桶只有一个元素的情况。

代码如下:

import java.util.ArrayList;
import java.util.Collections;

public class Test7 {
    public static void main(String[] args) {
        int a[]=new int[]{21,8,6,11,36,50,27,42,0,12};
        System.out.print("排序前的结果为: ");
        for(int i=0;i<a.length;i++){
            System.out.print(a[i]+" ");
        }
        Test7 test7=new Test7();
        test7.bucketSort(a);
        System.out.print("排序后的结果为: ");
        for(int i=0;i<a.length;i++){
            System.out.print(a[i]+" ");
        }
    }
    public void bucketSort(int a[]){
        int min=Integer.MAX_VALUE;
        int max=Integer.MIN_VALUE;
        int len=a.length;
        for(int i=0;i<len;i++){ //获得数组中的最大值和最小值
            max=Math.max(max,a[i]);
            min=Math.min(min,a[i]);
        }
        int bucketNum=(max-min)/len+1; //注意一定要加1
        ArrayList<ArrayList<Integer>> bucketArr=new ArrayList<>(bucketNum);
        for(int i=0;i<bucketNum;i++){
            bucketArr.add(new ArrayList<Integer>());
        }
        for(int i=0;i<len;i++){
            int num=(a[i]-min)/len;  //每个元素应该放在桶中的位置
            bucketArr.get(num).add(a[i]);
        }
        //对某个桶进行排序
        for (int i=0;i<bucketArr.size();i++){
            Collections.sort(bucketArr.get(i));
        }
        int count=0;
        for (int i=0;i<bucketArr.size();i++){ //将桶内元素复制到数组中
            for(int j=0;j<bucketArr.get(i).size();j++)
            a[count++]=bucketArr.get(i).get(j);
        }
    }
}

排序前的结果为: 21 8 6 11 36 50 27 42 0 12

排序后的结果为: 0 6 8 11 12 21 27 36 42 50

结论:N为关键字的数量,M为桶的数量。桶排序的平均时间复杂度为线性的O(N+C),其中C=N*(logN-logM)。如果相对于同样的N,桶数量M越大,其效率越高,N=M时,即极限情况下每个桶只有一个数据时。桶排序的最好效率能够达到O(N)。桶排序的空间复杂度为O(N+M),如果输入数据非常庞大,而桶的数量也非常多,则空间代价无疑是昂贵的。此外,桶排序是稳定的。

8.计数排序

思想:找出待排序的数组中最大和最小的元素,并且统计数组中每个值为i的元素出现的次数,存入数组a的第i项,对所有的计数累加(从a中的第一个元素开始,每一项和前一项相加),最后将每个元素i放在新数组的第a(i)项,每放一个元素就将a(i)减去1。

代码如下:

public class Test8 {
    public static void main(String[] args) {
        int a[]=new int[]{21,8,6,11,36,50,27,42,0,12};
        System.out.print("排序前的结果为: ");
        for(int i=0;i<a.length;i++){
            System.out.print(a[i]+" ");
        }
        Test8 test8=new Test8();
        test8.countSort(a);
        System.out.print("排序后的结果为: ");
        for(int i=0;i<a.length;i++){
            System.out.print(a[i]+" ");
        }
    }
    public void countSort(int a[]){
        //1.得到数列的最大值和最小值,并算出差值d
        int max =a[0];
        int min =a[0];
        for(int i=1; i<a.length; i++) {
            if(a[i] > max) {
                max = a[i];
            }else if(a[i] < min) {
                min = a[i];
            }
        }
        //2.创建统计数组并统计每个元素的个数,注意统计数组的长度
        int[]countArray = new int[max-min+1];
        for(int i=0; i<a.length; i++) {
            countArray[a[i]-min]++;
        }
        //3.统计数组做变形,后面的元素等于前面的元素之和
        int sum = 0;
        for(int i=0;i<countArray.length;i++) {
            sum += countArray[i];
            countArray[i] = sum;
        }
        //4.倒序遍历原始数列,从统计数组找到正确位置,输出到结果数组
        int[] sortedArray = new int[a.length];
        for(int i=a.length-1;i>=0;i--) {
            sortedArray[countArray[a[i]-min]-1]=a[i];
            countArray[a[i]-min]--;
        }
        //5.将排完序的数组赋值到原数组里
        for(int i=0;i<a.length;i++){
            a[i]=sortedArray[i];
        }
    }
}

排序前的结果为: 21 8 6 11 36 50 27 42 0 12

排序后的结果为: 0 6 8 11 12 21 27 36 42 50

结论:计数排序是一个非基于比较的排序算法,假如数列的原始规模是n,最大最小整数的差值是m,则它的时间复杂度为o(n+m),快于任何比较排序算法,当然这是一种牺牲空间换取时间的做法。只考虑统计数组的大小的话,它的空间复杂度为o(m)。同时计数排序算法是一个稳定的排序算法。

9.基数排序(桶子法)

思想:现将所有比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行依次排序。这样从最低位排序一直到最高位排序完成后,数列就变成了一个有序序列。

import java.util.ArrayList;

public class Test9 {
    public static void main(String[] args) {
        int a[]=new int[]{21,80,6,1100,36,50,27,42,340,0,12};
        System.out.print("排序前的结果为: ");
        for(int i=0;i<a.length;i++){
            System.out.print(a[i]+" ");
        }
        Test9 test9=new Test9();
        test9.radixSort(a);
        System.out.print("排序后的结果为: ");
        for(int i=0;i<a.length;i++){
            System.out.print(a[i]+" ");
        }
    }
    public void radixSort(int a[]){
        //确定排序的趟数,即最大整数的位数
        int max=a[0];
        int len=a.length;
        for(int i=1;i<len;i++){
            if(a[i]>max){
                max=a[i];
            }
        }
        int time=String.valueOf(max).length();
        ArrayList<ArrayList<Integer>>queue=new ArrayList<>(10);
        for (int i=0;i<10;i++){
            queue.add(new ArrayList<>());
        }
        //进行time次分配和收集元素
        for(int i=0;i<time;i++){
            //分配数组元素
            for(int j=0;j<a.length;j++){
                //得到数字的第i+1位数
                int x=a[j]%(int)Math.pow(10,i+1)/(int)Math.pow(10,i);
                ArrayList<Integer>queue1=queue.get(x);
                queue1.add(a[j]);
                queue.set(x,queue1);
            }
            int count=0;//元素计数器
            //收集队列元素
            for(int k=0;k<10;k++){
                while (queue.get(k).size()>0){
                    ArrayList<Integer>queue2=queue.get(k);
                    a[count++]=queue2.get(0); //将队列中第i+1次排完序的结果赋值给数组
                    queue2.remove(0);
                }
            }
        }
    }
}

排序前的结果为: 21 80 6 1100 36 50 27 42 340 0 12

排序后的结果为: 0 6 12 21 27 36 42 50 80 340 1100

结论:基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。时间效率 :设待排序列为n个记录,d个关键码(d表示最大数字的位数),关键码的取值范围为r,则进行链式基数排序的时间复杂度为O(d(n+r)),其中,一趟分配时间复杂度为O(n),一趟收集时间复杂度为O(r),共进行d趟分配和收集。 空间效率:需要2*r个指向队列的辅助空间,以及用与静态链表的n个指针。此外,基数排序是稳定的排序算法。

10.堆排序

思想:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后去掉该最大元素,将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次大值。如此反复执行,便能得到一个有序序列了。

代码如下:

public class Test10 {
    public static void main(String[] args) {
        int a[]=new int[]{21,80,6,1100,36,50,27,42,340,0,12};
        System.out.print("排序前的结果为: ");
        for(int i=0;i<a.length;i++){
            System.out.print(a[i]+" ");
        }
        Test10 test10=new Test10();
        test10.heapSort(a);
        System.out.print("排序后的结果为: ");
        for(int i=0;i<a.length;i++){
            System.out.print(a[i]+" ");
        }
    }
    public void heapSort(int[] a) {
        int len=a.length;
        //索引从0开始,最后一个非叶子结点的下标是len/2-1
        for(int i=len/2-1;i>=0;i--){
            adjustHeap(a,i,len);
        }
        //建堆结束之后,开始调整堆
        for(int j=len-1;j>0;j--){
            //把大根堆的根元素与堆最后一个元素交换位置
            swap(a,0,j);
            //每一次的堆调整之后,都会有一个元素到达自己的最终位置
            adjustHeap(a,0,j);
        }
    }
    //交换元素
    public void swap(int a[],int i,int j){
        int temp=a[i];
        a[i]=a[j];
        a[j]=temp;
    }
    //i为要调整的根结点的元素下标,len为要调整的那部分的数组长度
    public void adjustHeap(int a[],int i,int len){
        //先取出当前元素
        int temp=a[i];
        //2*i+1为左子树i的左子树
        for(int k=2*i+1;k<len;k=2*k+1){
            //让k指向子节点中最大的结点
            if(k+1<len && a[k]<a[k+1]){
                k++;
            }
            if(a[k]>temp){
                swap(a,i,k);
                i=k;
            }else{
                break;
            }
        }
    }
}

排序前的结果为: 21 80 6 1100 36 50 27 42 340 0 12

排序后的结果为: 0 6 12 21 27 36 42 50 80 340 1100

结论:堆排序是一种不稳定的排序算法,它的最好最坏以及平均的时间复杂度均为o(n\log n),由于不需要借助额外的辅助空间,它的空间复杂度为o(1)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值