排序篇

排序篇

Java库函数实现


Java.util里自带给数组排序的方法Arrays.sort();

​ 头文件 import java.util.Arrays;

​ 相关API:Arrays.sort(arys[])

​ Arrays.sort(arys[],int from,int to) //排序范围:from --> (to - 1)

​ Arrays.sort(arys[],new MyComparator())

​ Arrays.sort(arys[],int from,int to,new MyComparator())

java是一门面向对象的语言, 所以java里面的排序也是以对象为单位, 无论是数组还是java提供的数据结构ArrayList , HashSet, 想要里面的元素实现排序对象类都必须实现 java.lang.Comparable接口或者使用比较器Comparator接口.

方式一: Comparable接口

待排序的元素需要实现 Java 的 Comparable 接口,该接口有 compareTo() 方法,可以用它来判断两个元素的大小关系。

class Customer implements Comparable<Customer>{

    int age;
    public Customer(int age){
        this.age = age;
    }

    @Override
    public int compareTo(Customer c) { // c1.compareTo(c2);
        //return this.age - c.age;  // 升序
        return c.age - this.age;    //降序
        //放回正数表示此对象大于参数对象c
        //这里升序表示 this.age - c.age 表示this对象大于对象c
        //说明排序时取元素this在后先取,在与前面的c比较
    }

    public String toString(){
        return "Customer[age="+age+"]";
    }
}

方式二 : 使用比较器Comparator接口

Comparator接口的设计符合OCP原则。

int compare(Object o1, Object o2) 返回一个基本类型的整型

o1代表后一个元素, o2代表前一个元素

如果要按照升序排序,要使o1 > o2 , 则返回 o1.val - o2.val.
如果o1 小于o2,返回-1 (交换),相等返回0,01大于02返回1(不变)
如果要按照降序排序 , 即 o1 < o2 , 则返回 o2.val - o1.val.
则o1 小于o2,返回1(正数),相等返回0,01大于02返回-1(负数)

// 乌龟
class WuGui{
    int age;
    public WuGui(int age){
        this.age = age;
    }
    @Override
    public String toString() {
        return "小乌龟[" +
                "age=" + age +
                ']';
    }
}

// 单独在这里编写一个比较器
// 比较器实现java.util.Comparator接口。(Comparable是java.lang包下的。Comparator是java.util包下的。)
class WuGuiComparator implements Comparator<WuGui> {

    @Override
    public int compare(WuGui o1, WuGui o2) {
        return o1.age - o2.age;
    }
}

public class TreeSetTest06 {
    public static void main(String[] args) {
      
        //在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象。
				TreeSet<WuGui> wuGuis = new TreeSet<>(new WuGuiComparator());
				/*
        // 大家可以使用匿名内部类的方式(这个类没有名字。直接new接口。)
        TreeSet<WuGui> wuGuis = new TreeSet<>(new Comparator<WuGui>() {
            @Override
            public int compare(WuGui o1, WuGui o2) {
                return o1.age - o2.age;
            }
        });
				*/
        wuGuis.add(new WuGui(1000));
        wuGuis.add(new WuGui(800));
        wuGuis.add(new WuGui(810));

        for(WuGui wuGui : wuGuis){
            System.out.println(wuGui);
        }
    }
}
Arrays.sort底层算法原理

Java 主要排序方法为 java.util.Arrays.sort(),底层采用了双轴快速排序

总结:

Arrays.sort对升序数组、降序数组和重复数组的排序效率有了很大的提升,这里面有几个重大的优化。

  • 对于小数组来说,插入排序效率更高,每次递归到小于47的大小时,用插入排序代替快排,明显提升了性能。
  • 双轴快排使用两个pivot,每轮把数组分成3段,在没有明显增加比较次数的情况下巧妙地减少了递归次数。
  • pivot的选择上增加了随机性,却没有带来随机数的开销。
  • 对重复数据进行了优化处理,避免了不必要交换和递归。

排序算法


排序分类

按排序的位置份可以分为: 内部排序和外部排序

内部排序可以分为: 比较排序和非比较排序

比较排序包括: 插入类排序、选择类排序、交换类排序 和 归并排序

插入排序包括: 直接插入、希尔排序

选择排序包括: 直接选择、堆排序

交换排序包括: 冒泡排序、快速排序

非比较排序包括: 计数排序、基数排序、桶排序

在这里插入图片描述

算法稳定性: 2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序是否相同

记忆口诀:

情绪不稳定,快些(希)选人来聊天吧

快些(希) n l o g n nlogn nlogn 的速度归队(堆)

排序算法怎么选择

数据量规模较小,考虑直接插入或直接选择。当元素分布有序时直接插入将大大减少比较和移动记录的次数,如果不要求稳定性,可以使用直接选择,效率略高于直接插入。

数据量规模中等,选择希尔排序

数据量规模较大,考虑堆排序(元素分布接近正序或逆序)、快速排序(元素分布随机)和归并排序(稳定性)。

一般不使用冒泡。

一、冒泡排序(Bubble Sort)

稳定,平均/最坏时间复杂度 O(n²),元素基本有序时最好时间复杂度 O(n),空间复杂度 O(1)。

比较相邻的元素,如果第一个比第二个大就进行交换,对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对,每一轮排序后末尾元素都是有序的,针对 n 个元素重复以上步骤 n -1 次排序完毕。

public int[] bubbleSort(int[] nums){
    for(int i = 0 ; i < nums.length - 1 ; i++){
        boolean flag = false; //增加一个判断是否发生过交换的标记
        for(int index = 0 ; index < nums.length - 1 - i ; index++){
            if(nums[index] > nums[index + 1])
                swap(nums, index , index +1);
                flag = true;
        }
        if(!flag) //如果扫描一遍发现没有发生交换则说明序列已经有序,退出循环
            break;
    }
    return nums;
}
二、快速排序(Quicksort)

是对冒泡排序的一种改进,不稳定,平均/最好时间复杂度 O(nlogn),元素基本有序时最坏时间复杂度 O(n²),空间复杂度 O(logn)。

首先选择一个基准元素,通过一趟排序将要排序的数据分割成独立的两部分,一部分全部小于等于基准元素,一部分全部大于等于基准元素,再按此方法递归对这两部分数据进行快速排序。

快速排序的一次划分从两头交替搜索,直到 low 和 high 指针重合,一趟时间复杂度 O(n),整个算法的时间复杂度与划分趟数有关。

最好情况是每次划分选择的中间数恰好将当前序列等分,经过 log(n) 趟划分便可得到长度为 1 的子表,这样时间复杂度 O(nlogn)。

最坏情况是每次所选中间数是当前序列中的最大或最小元素,这使每次划分所得子表其中一个为空表 ,这样长度为 n 的数据表需要 n 趟划分,整个排序时间复杂度 O(n²)。

public class QuickSort {

    public static void main(String[] args) {
        int[] nums = {1,8,2,4,3};
        new QuickSort().quickSort(nums,0,nums.length-1);
        for(int i = 0 ; i < nums.length ; i++){
            System.out.println(nums[i]);
        }
    }

    public void quickSort(int[] nums, int start , int end){
        if(start < end){
            int middle = partition(nums, start ,end);
            quickSort(nums,start,middle - 1);
            quickSort(nums, middle + 1,end);

        }
    }


    public int partition(int[] nums , int low ,int high){
        int temp = nums[low]; // 数组的第一个作为中轴
        while(low < high){
            while(low < high && nums[high] >= temp){
                high--;
            }
            nums[low] = nums[high]; //此时nums[high]小于中轴,移动
            while(low < high && nums[low] <= temp){
                low++;
            }
            nums[high] = nums[low];
        }
        nums[low] = temp;
        return low; //返回中轴位置
    }
}

三、选择排序(Selection sort)

选择排序是一种简单直观的排序方法, 在未排序序列中找到最小元素, 存放到排序序列的起始位置再从剩余未排序元素中继续寻找最小元素,然后放到排序序列起始位置。 以此类推,直到所有元素均排序完毕。

public static int[] selectionSort(int[] a){
    for(int i = 0 ; i < a.length ; i++){
        for(int j = i + 1 ; j < a.length)
            if(a[i] > a[j])
                swap(a,i,j);
    }
    return a; 
}
四、插入排序(InsertSort)

插入排序的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。其具体步骤参见代码及注释。

/** 
*  插入排序  
*  
*  从第一个元素开始,该元素可以认为已经被排序
*  取出下一个元素,在已经排序的元素序列中从后向前扫描 
*  如果该元素(已排序)大于新元素,将该元素移到下一位置  
*  重复步骤3,直到找到已排序的元素小于或者等于新元素的位置  
*  将新元素插入到该位置中  
*  重复步骤2 
*/
public static int[] insertSort(int[] a) {
    for (int i = 1; i < a.length; i++) {
        int temp = a[i];
        int j = i;
        while (j > 0 && temp < a[j - 1]) {
            a[j] = a[j - 1];
            j--;
        }
        a[j] = temp;
    }
    return a;
}
五、归并排序(MergingSort)

​ 归并排序是建立在归并操作上的一种稳定的排序算法, 任何情况时间复杂度都为 O(nlogn),空间复杂度为 O(n)。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

public class MergingSort {
    public static void main(String[] args) {
        int[] nums = {1,8,2,4,3};
        mergingSortRecursion(nums,0,nums.length-1);
        for(int i = 0 ; i < nums.length ; i++){
            System.out.println(nums[i]);
        }
    }

    public static void mergingSortRecursion(int[] nums ,int left, int right){
        if(left < right){
            int middle = (left + right) / 2;
            mergingSortRecursion(nums, left , middle);
            mergingSortRecursion(nums, middle + 1, right);
            merge(nums,left,middle, right);
        }
    }

    public static void merge(int[] nums, int left , int middle, int right){
        int[] tempArray = new int[nums.length];
        int i = left; //左边序列的游标
        int j = middle + 1; //右边序列的游标
        int k = left ;  //临时序列的游标

        //从两个数组中取出最小的放入中间数组
        while(i <= middle && j <= right){
            if(nums[i] <= nums[j]){
                tempArray[k++] = nums[i++];
            }else{
                tempArray[k++] = nums[j++];
            }
        }

        //剩余部分依次放入中间数组
        while(j <= right){
            tempArray[k++] = nums[j++];
        }
        while(i <= middle){
            tempArray[k++] = nums[i++];
        }

        //将中间数组中的内容复制回原数组
        while(left <= right){
            nums[left] = tempArray[left++];
        }
    }
}
六、希尔排序(ShellSort)

希尔排序(Shell Sort)是插入排序的一种。也称缩小增量排序,是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

public static void shellSort(int []arr){
    //增量gap,并逐步缩小增量
    for(int gap=arr.length / 2; gap > 0; gap /= 2){
        //从第gap个元素,逐个对其所在组进行直接插入排序操作
        for(int i = gap;i < arr.length; i++){
            int j = i;
            int temp = arr[j];
            if(arr[j] < arr[j-gap]){
                while(j-gap>=0 && temp<arr[j-gap]){
                    //移动法
                    arr[j] = arr[j-gap];
                    j-=gap;
                }
                arr[j] = temp;
            }
        }
    }
}
七、堆排序(HeapSort)

堆排序是利用这种数据结构而设计的一种排序算法,堆排序是一种选择排序, 它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。

堆排序参考

import java.util.Arrays;

public class HeapSort {

    public static void main(String[] args) {
        int[] nums = {5,2,6,3,4,7,1,9,8};
        heapSort(nums);
        System.out.println(Arrays.toString(nums));
    }

    public static int[] heapSort(int[] nums){
        //1、构建大顶堆
        for(int i = (nums.length - 1) / 2 ; i >= 0 ; i--){
            adjustMaxHeap(nums, i, nums.length);
        }
        //2、调整堆结构 + 交换堆顶元素与末尾元素
        for(int j = nums.length - 1 ; j > 0 ; j--){
            swap(nums, 0 , j);
            adjustMaxHeap(nums,0,j);
        }

        return nums;
    }

    //调整堆,从当前节点 i 出发,调整它的子树为最大堆
    public static void adjustMaxHeap(int[] nums, int i , int length){
        /*
        * 通常堆是通过一维数组来实现的。在数组起始位置为 0 的情形中:
        * 父节点 i 的左子节点在位置 (2*i+1);
        * 父节点 i 的右子节点在位置 (2*i+2);
        * 子节点 i 的父节点在位置 floor((i-1)/2);
        */
        for(int k = i*2 + 1 ; k < length; k = k * 2 + 1){ //从i节点的左子节点开始,
          //如果左子节点小于右子节点,k指向右子节点  
            if(k+1 < length && nums[k] < nums[k+1]){ 
                k++;
            }
            if(nums[k] > nums[i]){ //如果子节点大于父节点,交换
                swap(nums,i,k);
                i = k;
            }else{
                break;
            }
        }
    }

    public static void swap(int[] nums,int i ,int j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值