【算法和数据结构】排序

上一篇文章中讲了数组的基础以及三种排序方法,这篇文章将延续上篇的思路,继续介绍八大排序算法中的另外五种。

1.八大排序

1)希尔排序

例:给定一个n个元素的数组,数组下标从0开始,采用希尔排序将数组按照升序排列。

思想:希尔排序是基于插入排序的一种改进算法,思路为先将数组分为n/2组,对每一组进行插入排序;再将新的数组分成n/2/2组,对每一组进行插入排序,依次类推,直到组数为1位为止,插入排序后保证数组变为有序。代码实现如下。

public void shellSort(int[] arr){
    int temp = 0;
    //将数组分为gap组,即为每相隔gap距离的元素为同一组
    for(int gap=arr.length/2;gap>0;gap/=2){
        //循环中实际是一个插入排序
        for(int i=gap;i<arr.length;i++){
            //从i-gap开始与前面每相隔gap距离的元素进行比较,插入排序的思想,只不过这里用了交换而不是位移
            for(int j=i-gap;j>=0;j-=gap){
                if(arr[j]>arr[j+gap]){
                    temp = arr[j];
                    arr[j] = arr[j+gap];
                    arr[j+gap] = temp;
                }
            }
        }
    }
    for(int n=0;n<arr.length;n++){
        System.out.println(arr[n]);
    }
}

2)归并排序

例:给定一个n个元素的数组,数组下标从0开始,采用归并排序将数组按照升序排列。

思想:归并排序的思路是将数组分解为数量为n的最小单元,对每个单元进行排序后将其两两合并为数量为2n的单元;依此类推,直至将所有单元合并。归并排序是一种分而治之的思想。代码实现如下。

public void mergeSort(int[] arr,int l,int r){
    int mid = (l+r)/2;
    if(l<r){
        //先从中间开始递归将数组分解为最小单元
        mergeSort(arr,l,mid);
        mergeSort(arr,mid+1,r);
        //将分解后的单元分别进行排序与合并
        merge(arr,l,mid,r);
    }
}
private void merge(int[] arr,int l,int mid,int r){
    int[] temp = new int[r-l+1];
    int i = l;
    int j = mid+1;
    int c = 0;
    while (i<=mid && j<=r){
        //比较大小,并合并两个数组为temp
        if(arr[i]<=arr[j]){
            temp[c] = arr[i];
            i++;
        }else{
            temp[c] = arr[j];
            j++;
        }
        c++;
    }
    //上面的循环执行完之后,肯定有一个单元a全部完成合并,另一个单元b中会剩下大于单元a中所有元素的值,需要将单元b中剩下的元素也全部合并到temp中。
    while (i<=mid){
        temp[c] = arr[i];
        c++;
        i++;
    }
    while (j<=r){
        temp[c] = arr[j];
        c++;
        j++;
    }
    c=0;
    int tempL = l;
    //按照temp中排好的顺序调整原数组arr中的元素顺序
    while (tempL<=r){
        arr[tempL] = temp[c];
        c++;
        tempL++;
    }
}

3)快速排序

例:给定一个n个元素的数组,数组下标从0开始,采用快速排序将数组按照升序排列。

思想:顾名思义,快排是八大排序算法中性能最好的。先选择一个数作为基准数pivot,以pivot为基准用双指针遍历数组,将小于pivot的元素集中到数组左边,大于pivot的元素集中到数组右边,双指针会合的位置即为pivot应该在的位置;此时将左右两个数组分别递归进行上述操作,直至整个数组变得有序。快速排序也是分而治之的思想。代码实现如下。

public void quickSort(int[] arr,int l,int r){
    //当l>=r时,说明这一轮排序结束
    if(l>=r){
        return;
    }
    //取最左边的元素作为基准数
    int pivot = arr[l];
    int i = l;
    int j = r;
    while (i<j){
        //如果右边的元素大于基准数,将右指针r前移,直到遇到小于基准数的元素为止
        while (arr[j]>=pivot && i<j){
            j--;
        }
        //如果左边的元素小于基准数,将左指针l后移,直到遇到大于基准数的元素为止
        while (arr[i]<=pivot && i<j){
            i++;
        }
        //交换两个元素,保证左边全部元素小于基准值,右边全部元素大于基准值
        if(i<j){
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }
    //循环结束后i即是基准数应该在的位置,由于取了最左边的数为基准数,所以需要将基准数和i位置的元素交换
    arr[l] = arr[i];
    arr[i] = pivot;
    //递归对左右两侧数组分别再进行排序
    quickSort(arr,l,i-1);
    quickSort(arr,i+1,r);
}

4)计数排序

例:给定一个n个元素的数组,数组下标从0开始,采用计数排序将数组按照升序排列。

思想:首先遍历整个数组,找出数组中的最小元素min与最大元素max;声明一个长度为max-min+1的数组counter作为计数器;使min值对应counter数组下标0的位置,再次遍历整个数组,每遍历一个元素就在counter中对应的位置加1;遍历counter数组即可排列原数组的顺序。由该思路可以看出,计数排序并不依赖于元素之间的比较,但是不适用于元素差值过大的数组和元素不是整数的数组。计数排序的代码实现如下。

public void countSort(int[] arr){
    int min = arr[0];
    int max = arr[0];
    //先遍历数组找出数组的最大值和最小值
    for(int i=0;i<arr.length;i++){
        if(arr[i]<min){
            min = arr[i];
        }
        if(arr[i]>max){
            max = arr[i];
        }
    }
    //声明计数器
    int[] counter = new int[max-min+1];
    //遍历数组进行计数
    for(int i=0;i<arr.length;i++){
        counter[arr[i]-min]++;
    }
    int index = 0;
    //遍历计数器,将对应的元素放回原数组
    for(int i=0;i<counter.length;i++){
        while (counter[i]>0){
            arr[index] = i+min;
            index++;
            counter[i]--;
        }
    }
    for(int n=0;n<arr.length;n++){
        System.out.println(arr[n]);
    }
}

5)基数排序
例:给定一个n个元素的数组,数组下标从0开始,采用基数排序将数组按照升序排列。

思想:基数排序思路是将原数组按照位数进行排序,先按个位数的大小将原数组排序,再按十位数的大小排序,依此类推,直到整个数组变得有序。基数排序是一种以空间换时间的思路,当数据的数量级很大时,会非常占用空间。代码实现如下。

public void radixSort(int[] arr){
    //基数,代表位数,1为个位,10为十位,依此类推
    int digit = 1;
    int max = arr[0];
    //遍历获取数组中的最大值
    for(int i=0;i<arr.length;i++){
        if(arr[i]>max){
            max = arr[i];
        }
    }
    //获取最大值的数量级
    int maxDigit = (int)Math.pow(10,(max+"").length());
    //创建数据桶,用来记录每个位数计数时数据所在位置
    int[][] bucket = new int[10][arr.length];
    //临时数组,用来记录每个位数时0-9出现的次数
    int[] temp = new int[10];
    while (digit<maxDigit){
        //以digit为基准记录数据到数据桶中(digit为1说明该次排序是按个位数排序,digit为10说明该次排序是按十位数排序,依此类推)
        for(int i=0;i<arr.length;i++){
            int d = (arr[i]/digit)%10;
            bucket[d][temp[d]] = arr[i];
            temp[d]++;
        }
        //类似计数排序过程,只不过数据桶中记录了该次排序具体的元素值,按照下标取值放回原数组即可
        int index = 0;
        for(int i=0;i<10;i++){
            int indexF = 0;
            //按照temp中记录的数量从数据桶中取回数据
            while (temp[i]>0){
                arr[index] = bucket[i][indexF];
                index++;
                temp[i]--;
                indexF++;
            }
        }
        //位数提高一位,继续排序
        digit*=10;
    }

    for(int n=0;n<arr.length;n++){
        System.out.println(arr[n]);
    }
}

2.习题

1)912. 排序数组

2)88. 合并两个有序数组

3)1037. 有效的回旋镖

4)1232. 缀点成线

文章内容为英雄哥算法星球的个人学习总结,B站英雄哥直播间:https://live.bilibili.com/24513717 ,每日凌晨5点到8点直播刷题。
注:本文章搬运自个人博客。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值