文章目录

  • 六、计数排序
  • 七、桶排序
  • 八、基数排序
  • 九、快速排序
  • 十、堆排序


六、计数排序


java 实现比赛数据排名考虑并列_算法

算法描述:

计数排序不是基于比较的排序算法,其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中。 作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。

这就是典型的拿空间换时间,只要空间够大,最多可见次数的遍历就能排好序,代码大概长下面这个样子:

//其实也可以先遍历一次数组获取最大值和最小值,但是我比较懒,手动狗头
    public static void sort(int[] list, int max) {
    	//这就是空间法了,先开辟一个足够大的数组
        int[] arr = new int[max + 1];
        //把数字都先存进去,因为int数组初始化好就是0,所以相同位置直接+1
        for (int i : list) {
            arr[i] += 1;
        }
        //index,原来数组的下标,把比0大的全部覆盖原数组
        int index= 0;
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr[i]; j++) {
                list[index] = i;
                index++;
            }
        }
    }

总的来说,这还算是一种比较简单的空间换时间方法,不过这样排序的缺点非常明显,那就是一旦数字之间的差距较大,这种方式就显得太笨比了。

七、桶排序


java 实现比赛数据排名考虑并列_数据结构_02

算法描述:

桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。

算法步骤:

  1. 第一步先分桶
  2. 把集合中的数各自存到分段的桶中
  3. 把每个分桶中的数排序
  4. 把分桶中所有数取出再次存回到数组中

这就是计数排序2.0了,都是拿空间换时间,桶排序可以在一定程度上减少对于大数组的遍历,计数排序只能一个一个判断过去,桶排序只要看这个桶里的长度即可,代码大概长下面这个样子:

public static void sort(int[] list) {
     	//分桶数
        int bucketCount = 10;
     	//使用二维数组分桶
     	//为什么使用Integer,其实是怕数组中有0的存在
     	//数组初始化之后都是0,这样会很麻烦
     	//如果可以确定数组无重复,那么二维数组的两个维度数相乘就是可排序的最大值-1
        Integer[][] bucket = new Integer[bucketCount][list.length];
    	//把不同大小段的数字分到不同的桶中
        for (int i = 0; i < list.length; i++) {
        	//这里就是简单的映射关系,你可以自己随便定义
        	//只不过要注意最大值是否越界,如果越界要么提高分桶数要么提高映射
            int quotient = list[i] / 10; 
            //把符合的数存入到桶中
            for (int j = 0; j < list.length; j++) {
                if (bucket[quotient][j] == null) {
                    bucket[quotient][j] = list[i];
                    break;
                }
            }
        }
        //刚开始用来交换数字,后面覆盖原数组的时候用来做下标移动
        int index;
     	//开始遍历整个桶进行分别排序
        for (int i = 0; i < bucket.length; i++) {
        	//这里就是每个小桶的排序了,因为被分的很小,而且都在范围,
        	//某种程度上来说其实插入排序可能会更好
        	//不过我没写,主要是前面写了插入排序这里不能用,就写了个最好写的
            //冒泡排序,没有任何难度,但要注意是否为null
            //不然会抛出空指针异常
            for (int j = 0; j < bucket[i].length; j++) {
                for (int k = 0; k < bucket[i].length - 1 - j; k++) {
                    if (bucket[i][k] != null && bucket[i][k + 1] != null) {
                        if (bucket[i][k] > bucket[i][k + 1]) {
                            swp = bucket[i][k];
                            bucket[i][k] = bucket[i][k + 1];
                            bucket[i][k + 1] = swp;
                        }
                    }
                }
            }
        }
        //把index归零,准备覆盖原数组
        index = 0;
     	//排序结束,把排好序的数字放回到原数组中
     	for (int i = 0; i < bucket.length; i++) {
     		//这里就很自然的判断了桶子的长度,不需要一个一个遍历过去
            for (int j = 0; j < bucket[i].length; j++) {
                if (bucket[i][j] != null) {
                    list[index] = bucket[i][j];
                    index++;
                } else {
                    break;
                }
            }
        }
}

桶排序其实最大的难点还是在于自己想的太好,因为他的效率取决于映射关系,所以想要自动找一个比较好的分桶方式,甚至想实现智能判断怎么分(说的就是当时的我)。然后写不出来,后面就干脆全部定死了,发现好写了很多。

八、基数排序


java 实现比赛数据排名考虑并列_数组_03

算法描述:

基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。

我愿称它为计数排序3.0,其实想想感觉差不多,基数排序是根据每一位数来排一个临时的空间,直到每一位数都排完,而且基数排序也会用到桶排序,其实关系没那强烈,只不过看起来像,也确实有点关系。代码大概长下面这个样子:

public static void sort(int[] list) {
        //判断最大位数有多少位
        int max = MaxNum(list);
        //先从最低位开始,每次根据某一位排序,从最低位到最高位
        //当最高位排序结束的时候,就是整个数组有序的时候
        for (int i = 0; i < max; i++) {
            sortBucket(list, i);
        }
    }

    public static void sortBucket(int[] list, int value) {
        //获得本次遍历最小的数字例如 0,10,100
        int base = (int) Math.pow(10, value);
        //下面就是桶排序了,还是老样子
        Integer[][] result = new Integer[10][list.length];
        //把数字加入进去
        for (int i : list) {
            //获取这个数本次排序的那一位是多少
            //也就是应该把这位数放到那个数字的桶子里
            //因为基数排序是每次根据不同位的数来排序
            int number = i / base % 10;
            //放到桶中
            for (int j = 0; j < result[number].length; j++) {
                if (result[number][j] == null) {
                    result[number][j] = i;
                    break;
                }
            }
        }
        //再把数字放回的原数组中
        //为什么不需要排序,因为放入桶的过程就是在排序
        //只不过每次都是只排一点
        int index = 0;
        for (int i = 0; i < result.length; i++) {
            for (int j = 0; j < result[i].length; j++) {
                if (result[i][j] != null) {
                    list[index++] = result[i][j];
                }
            }
        }
    }

    public static int MaxNum(int[] list) {
        //默认是都是个位数
        int max = 1;
        for (int i : list) {
            int length = 1;
            while (i / 10 != 0) {
                i /= 10;
                length++;
            }
            if (length > max) {
                max = length;
            }
        }
        //返回获取到的最大位数
        return max;
    }

总结一下,其实基数排序最难的地方是怎么把数放进去,而且每次都放到合适的位置,解决这个问题之后,就好写多了。

九、快速排序


java 实现比赛数据排名考虑并列_算法_04

算法描述:

通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。

算法步骤:

  1. 确定第一个或者最后一个数为要排序的数字
  2. 把大于他的数和小于它的数分别存放到两边(这样这个数字就排好了)
  3. 对这两个分区分别执行上述操作,也就是不断分区,不断找到一个数字排好
  4. 最后,当所有分区都只剩一个数字的时候排序就结束了

快排其实没什么好说的,虽然直到怎么回事,但是写过的次数太少,所以导致每次都不用快排,渐渐生疏了,如果我没记错的话,应该是长下面这个样子的:

public static void sort(int[] list, int start, int end) {
        if (start < end) {
            //把排好的数字的坐标获取到
            int mid = getMid(list, start, end);
            //把前半部分进行排序
            sort(list, start, mid - 1);
            //把后半部分也排序
            sort(list, mid + 1, end);
        }
    }

	//把选定的数字排好,并返回他的下标或坐标
    public static int getMid(int[] list, int start, int end) {
        //选定第一个元素为要排序的元素
        //因为这个值最终会被排好序,而且一般是中间,所以叫mid
        int mid = list[start];
        //当start=end的时候,就确定好要排序元素的下标
        while (start < end) {
            //当后面的元素大于选定元素的时候,把end减小
            while (start < end && list[end] > mid) {
                end--;
            }
            //当后面的元素小于等于选定元素的时候,把两个元素替换,由于已经记录了要排序的元素
            //所以只要记录好位置(end下标),把值赋给原坐标就行了
            list[start] = list[end];
            //当前面元素小于要选定元素的时候,把start++
            while (start < end && list[start] < mid) {
                start++;
            }
            //当前面的元素大于选定元素的时候,替换前面的元素
            list[end] = list[start];
        }
        //当排序结束,要排好的数还没赋值,也就是在上面的移动过程中,为了节省代码,没有选择交换
        //而使用直接赋值,所以需要最后把正确的数赋值回去
        list[start] = mid;
        //返回start和end其实一样
        return start;
    }

十、堆排序


java 实现比赛数据排名考虑并列_java 实现比赛数据排名考虑并列_05

算法描述:

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

堆排序,其实就是利用类似于完全二叉树的方式,先构建,大顶堆,然后把堆顶元素和最大位置的元素,或者说最后一个子节点交换,这样之后最后一个元素,数组就排好了。代码大概长下面这个样子:

public static void sort(int[] list){
		//构建大顶堆,大顶堆可以简单的理解为就是比较大的元素都在上面
        for (int i = list.length/2-1 ; i >= 0 ; i--){
            Heap(list,i,list.length);
        }
        for (int i = list.length-1; i>0;i--){
        	//交换元素
            swp(list,i,0);
            //继续构建不完全的大顶堆
            Heap(list,0,i);
        }
    }
    
    //由于构建大顶堆是从最小父节点开始的,所以会把较大的值移动上来
    private static void Heap(int[] list,int later,int length){
    	//记录父节点元素
        int res=list[later];
       	//遍历左节点
       	//如果不是最小的父节点,那么就去看他更小的节点
        for (int i = later*2+1 ; i < length ; i++){
        	//在这里,比较左右节点,大的留下
            if(i+1<length && list[i]<list[i+1]){
                i++;
            }
            //子节点大于父节点就把父节点赋值,并记录交换之后的位置
            if(res<list[i]){
                list[later]=list[i];
                later=i;
            }
        }
        //把元素赋值给它应该在的位置
        lister[later]=res;
    }
    
    //交换元素
    private static void swp(int[] list,int a,int b){
        int n;
        n = list[a];
        list[a] = list[b];
        list[b] = n;
    }

堆排序的话,写起来还是挺难的,理解的也一般般,其实还是二叉树这种数据结构掌握的太差了。

总结:从我写这几个排序算法的时候,我发现对我来说,知道这个算法在代码里是怎么结束的,会很有利于我写出来,不过还是自己太菜了,哈哈,希望对你有帮助