左神数据结构学习(基础)——简单排序算法

1.1 选择排序O(n2)

public class SelectSort {

    public static void swap2(int[] arrs,int a,int b){
        int temp = arrs[a];
        arrs[a] = arrs[b];
        arrs[b] = temp;
    }

    public static void selectsort(int[] arrs){
        if(arrs == null || arrs.length <= 2){
            return;
        }
        //每次选择最小数放在前面(每次遍历n-i次)
        for(int i = 0;i < arrs.length-1;i++){
            int minIndex = i;
            for(int j = i+1;j < arrs.length;j++){
                minIndex = arrs[minIndex] <= arrs[j] ? minIndex : j;
                swap2(arrs,i,minIndex);
            }
        }
    }
}

1.2 冒泡排序O(n2)

public class BubbleSort {


    //交换两个数  该方式要满足 a != b ,即内存上不是一块的   否则会为0
    public static void swap(int[] arrs,int a,int b){
        arrs[a] = arrs[a] ^ arrs[b];
        arrs[b] = arrs[a] ^ arrs[b];
        arrs[a] = arrs[a] ^ arrs[b];

    }

    public static void bubblesort(int[] arrs){
        for(int i = 0;i < arrs.length;i++){
            boolean ischange = false;
            for(int j = 0;j < arrs.length - i - 1;j++){  //每次比较相邻两个数的值,大的在后
                if(arrs[j] > arrs[j+1]){
                    swap(arrs,j,j+1);
                    ischange = true;
                }
            }
            if(!ischange){
                break;
            }
        }
    }
}

1.3 异或运算补充

异或运算(^):① 0^a = a; a^a = 0

                           ② a^b^c=a^c^b(交换律)

                               a^(b^c)=(a^b)^c(结合律)

交换两个数的值(不能是同一个内存上的)

//交换两个数  该方式要满足 a != b ,即内存上不是一块的   否则会为0
public static void swap(int[] arrs,int a,int b){
    arrs[a] = arrs[a] ^ arrs[b];
    arrs[b] = arrs[a] ^ arrs[b];
    arrs[a] = arrs[a] ^ arrs[b];
}

异或运算补充

①数列arr中 有一个数的个数为奇数个,其他数为偶数个,找出这个奇数个数的数字。

//数列arr中 有一个数的个数为奇数个,其他数为偶数个,找出这个奇数个数的数字。
public static int findOddTimesNum1(int[] arr){
    int eor = 0;
    for(int cur : arr){
        eor ^= cur;  //运用异或运算的交换律以及 a^a=0
    }
    return eor; //此时为奇数个数的值就是eor
}

②数列arr中 有两个数的个数为奇数个,其他数为偶数个,找出奇数个数的两个数字。

//数列arr中 有两个数的个数为奇数个,其他数为偶数个,找出奇数个数的两个数字。
public static int[] findOddTimesNum2(int[] arr){
    int eor = 0;
    for(int cur : arr){
        eor ^= cur;
    }//此时运算结果为 eor = a^b;为两个奇数个数值的异或
    
    //那么我们可以知道这两个值的异或肯定不是0,必然在其二进制数表达上存在 1
    int rightOne = eor & (~eor + 1);  //提取出两个奇数个数值的异或表达式中 最右边的1
    int Onlyone = 0; //用于存储其中的一个值
    for(int cur : arr){
        if((rightOne & cur) == 1){
            Onlyone ^= cur;
        }
    }//此时OnlyOne = a or b;
    int[] num = {Onlyone,eor ^ Onlyone};
    return num;
}

1.4 插入排序O(n2)~ O(n)

public class InsertSort {

    //交换两个数  该方式要满足 a != b ,即内存上不是一块的   否则会为0
    public static void swap(int[] arrs,int a,int b){
        arrs[a] = arrs[a] ^ arrs[b];
        arrs[b] = arrs[a] ^ arrs[b];
        arrs[a] = arrs[a] ^ arrs[b];
    }

    public static void insertSort(int[] arr){//从小到大
        for(int i = 1;i < arr.length;i++){
            for(int j = i;j > 0;j--){
                if(arr[j] < arr[j-1]){
                    swap(arr,j,j-1);
                }
            }
        }
    }
}

1.5 二分查找(查找的数组需要是一个有序的数组,假设为升序数组)O(log2n)

①在一个有序数组中,找某个数是否存在

//①在一个有序数组中,找某个数是否存在
public static int binarysearch(int[] arr,int target,int size){//size想要进行查找的范围 [0,size-1]
    int left = 0;
    int right = size - 1;   //定义目标寻找的位置 [left,right]
    //①循环条件的判断 ②迭代过程中middle和right的关系
    while(left <= right){
        int middle = (left + right) / 2;
        if(target < arr[middle]){
            right = middle - 1;
        }else if(target > arr[middle]){
            left = middle + 1;
        }else if(target == arr[middle]){
            return middle; //找到返回该元素的下标
        }
    }
    return -1;  //找不到则返回-1
}

②在一个有序数组中,找>=某个数最左侧的位置

//②在一个有序数组中,找>=某个数最左侧的位置
public static int findLeftLocation(int[] arr,int target){
    int t = binarysearch(arr,target,arr.length); //查看该目标数是否在该数组中,并获取到其下标
    if(t == -1){
        return -1;//表示没有
    }
    //该数存在数组中,但不一定是最左的位置,因此需要继续二分
    int temp = -1;//用于保存找到的下标
    while(t != -1){
        temp = t; //t会被下一次迭代掉,因此需要先保存
        t = binarysearch(arr,target,t); //搜索范围[0,t],不能t-1,会导致搜索范围出现问题
    }
    return temp;
}

③局部最小值的问题

//③局部最小值的问题
public static int localMiniMum(int[] arr){//返回的是该元素对应的下标
    //先判断i=0和n-1的情况
    if(arr.length == 1 || arr[0] < arr[1]){
        return 0;
    }
    if(arr.length == 1 || arr[arr.length-1] < arr[arr.length-2]){
        return arr.length-1;
    }
    //排除上两种情况后,在进行二分查找局部最小
    int left = 0;
    int right = arr.length - 1;
    //此时二分查找的条件变化:arr[i-1]>arr[i] && arr[i]<arr[i+1]
    while(left <= right){
        int middle = (left + right) / 2; //存在溢出的可能性,需要优化(left +(right – left)>> 1;
        if(arr[middle-1] < arr[middle] && arr[middle] < arr[middle+1]){
            right = middle - 1;
        }else if(arr[middle+1]> arr[middle] && arr[middle] > arr[middle-1]){
            left = middle + 1;
        }else if(arr[middle-1] >= arr[middle] && arr[middle+1] >= arr[middle]){//没有等于可能在有相同最小值时出现死循环
            return middle; //找到返回该元素的下标
        }
    }
    return -1;  //找不到则返回-1
}

1.6 对数器

①有一个你想要测的方法a

②实现一个绝对正确但是复杂度不好的方法b

③实验一个随机样品产生器

④实现对比算法a和b的方法

⑤把方法a和方法b比对多次来验证方法a是否正确

⑥如果有一个样品使得对比出错,打印样品分析时那个方法出错

⑦当样品数量很多时比对测试依然正确,可以确定方法a已经正确

public class LogarithmicDetector {
    //生成一个随机大小,最大数随机的数组
    public static int[] generatorRandomArray(int maxSize,int maxNum){
        int[] arr = new int[(int)((maxSize+1)*Math.random())];   //random()->[0,1)  生成[1,maxSize]的随机数组
        for(int i = 0;i < arr.length;i++){
            arr[i] = (int)(Math.random()*(maxNum+1)) - (int)(Math.random()*maxNum); //范围(-maxNum,maxNum+1)整数
        }
        return arr;
    }

    //复制当前数组的一个样本
    public static int[] copyArray(int[] arr){
        int[] newArr = Arrays.copyOf(arr, arr.length);
        return newArr;
    }

    //判断两个数是否相同
    public static boolean isEquals(int[] arr1,int[] arr2){
        if(arr1.length != arr2.length){
            return false;
        }
        if(arr1 == null && arr2 != null || arr1 != null && arr2 ==null){
            return false;
        }
        for(int i = 0;i < arr1.length;i++){
            if(arr1[i] != arr2[i]){
                return false;
            }
        }
        return true; //若两个数组都为空,返回的也是true
    }

    @Test
    public void test(){
        int testTimes = 10000;
        int maxSize = 50;
        int maxNum = 100;
        boolean isEqual = true;
        for(int i = 0;i < testTimes;i++){
            int[] arr1 = generatorRandomArray(maxSize, maxNum);
            int[] arr2 = copyArray(arr1);
            BubbleSort.bubblesort(arr1); //冒泡排序  (假设是自己写的方法a)
            InsertSort.insertSort(arr2); //插入排序   (已知正确的方法b)
            if(!isEquals(arr1,arr2)){   //通过对比两种方法运行后的数据结果是否一致,不一致直接跳出用于后续打印分析
                isEqual = false;
                break;
            }
        }
        System.out.println(isEqual ? "Success":"Failed");//Success
    }
}

1.7 剖析递归行为和递归行为时间复杂度的估算

Master公式的使用:T(N)=a*T(N/b)+O(N^d)

① log(b,a)>d --> 复杂度O(N^log(b,a)

② log(b,a)=d --> 复杂度O(N^d*logN)

③ log(b,a)<d --> 复杂度O(N^d)

 1.8 归并排序O(nlogn)

①整体就是一个简单递归,左边排好序,右边排好序,让其整体有序

②让其整体有序的过程用了外排序的方法

③利用master公式来求解时间复杂度

④归并排序的实质:时间复杂度O(nlogn),额外空间复杂度O(n)

public class MergeSort {
    public static void mergeSort(int[] arr){
        if(arr == null || arr.length < 2) {
            return;
        }
        divide(arr,0,arr.length-1);
    }

    //分治  --> 采用递归的方式
    public static void divide(int[] arr,int L,int R){
        if(L == R){
            return;
        }
        int mid = L + ((R - L) >> 1);
        divide(arr,L,mid); //左侧递归
        divide(arr,mid+1,R); //右侧递归
        merge(arr,L,mid,R);
    }

    //将排好序的[L,M]和[M+1,R]合并成一个[L,R]的有序  --->  归并
    public static void merge(int[] arr,int L,int M,int R){
        //需要开辟一个新的空间用于暂时存放合并的数组,长度为R-L+1
        int[] temp = new int[R - L + 1];
        //需要指针分别指向三个数组的索引
        int i = 0;
        int L1 = L;//[L,M]数组的指针
        int R1 = M + 1;//[M+1,R]数组的指针
        //合并
        while (L1 <= M && R1 <= R){
            temp[i++] = arr[L1] < arr[R1] ? arr[L1++] : arr[R1++];//相等则先拷贝右侧的数据
        }//表明有一侧已经排完,还有一侧没有排完
        while (L1 <= M){ //[M+1,R]已经全部排完,只要把[L,M]剩余的依次放入就行
            temp[i++] = arr[L1++];
        }
        while(R1 <= R){  //[L,M]已经全部排完,只要把[M+1,R]剩余的依次放入就行
            temp[i++] = arr[R1++];
        }
        for(i = 0;i < temp.length;i++){//数组原来排序的范围arr[L,R],赋值的时候要注意不能从0开始
            arr[L+i] = temp[i];
        }
    }
}

eg1:小和问题(在一个数组中,每一个数左边比当前数小的数累加起来,叫做数组的小和),求一个数组的小和。 -->  在归并排序的思想上进行添加修改

例子:[1,3,4,2,5]1左边比1小的数,没有;3左边比3小的数,1;4左边比4小的数,1,3;2左边比2小的数,1;5左边比5小的数,1,3,4,2;所以小和为:1+1+3+1+1+3+4+2=16。

public class XiaoHe {
    /**
     * 小和问题(在一个数组中,每一个数左边比当前数小的数累加起来,叫做数组的小和),求一个数组的小和。
     * 例子:[1,3,4,2,5]1左边比1小的数,没有;3左边比3小的数,1;4左边比4小的数,1,3;2左边比2小的数,1;
     * 5左边比5小的数,1,3,4,2;所以小和为:1+1+3+1+1+3+4+2=16。
     */
    //方式一:暴力解法 时间复杂度O(n^2)
    public static int smallSumSoulation1(int[] arr){
        if(arr == null || arr.length < 2){
            return 0;
        }
        int sum = 0;
        for(int i = 0;i < arr.length;i++){
            for(int j = 0;j <= i;j++){
                if(arr[i] > arr[j]){
                    sum += arr[j];
                }
            }
        }
        return sum;
    }

    //方式二:通过归并排序计算,在合并过程中比较计算 --> 时间复杂度O(nlogn)
    public static int smallSumSoulation2(int[] arr){
        if(arr == null || arr.length < 2) {
            return 0;
        }
        return divide(arr,0,arr.length-1);
    }

    //排序的过程,也要求小和
    public static int divide(int[] arr,int L,int R){
        if(L == R){
            return 0;
        }
        int mid = L + ((R - L) >> 1);
        return divide(arr,L,mid) + divide(arr,mid+1,R) + merge(arr,L,mid,R);
    }

    public static int merge(int[] arr,int L,int M,int R){
        int[] temp = new int[R - L + 1];
        int i = 0;
        int L1 = L;//[L,M]数组的指针
        int R1 = M + 1;//[M+1,R]数组的指针
        int result = 0;//小和
        while (L1 <= M && R1 <= R){ //均没有越界
            //相等则先拷贝右侧的数据,并且小和不加
            result += arr[L1] < arr[R1] ? (R - R1 + 1) * arr[L1] : 0;
            temp[i++] = arr[L1] < arr[R1] ? arr[L1++] : arr[R1++];
        }
        while (L1 <= M){
            temp[i++] = arr[L1++];
        }
        while(R1 <= R){
            temp[i++] = arr[R1++];
        }
        for(i = 0;i < temp.length;i++){
            arr[L+i] = temp[i];
        }
        return result;
    }
}

eg2:逆序对问题:在一个数组中,左边的数如果比右边的数大,则这两个数构成一个逆序对,请打印所有逆序对。  -->  在归并排序的思想上进行添加修改

public class ReverseDouble {
    /**
     * 逆序对问题:在一个数组中,左边的数如果比右边的数大,则这两个数构成一个逆序对,请打印所有逆序对。
     */
    public static void reverseDouble(int[] arr){
        if(arr == null || arr.length < 2) {
            return;
        }
        divide(arr,0,arr.length-1);
    }

    public static void divide(int[] arr,int L,int R){
        if(L == R){
            return;
        }
        int mid = L + ((R - L) >> 1);
        divide(arr,L,mid);
        divide(arr,mid+1,R);
        merge(arr,L,mid,R);
    }

    public static void merge(int[] arr,int L,int M,int R){
        int[] temp = new int[R - L + 1];
        int i = 0;
        int L1 = L;//[L,M]数组的指针
        int R1 = M + 1;//[M+1,R]数组的指针
        while (L1 <= M && R1 <= R){ //均没有越界
            if(arr[L1] > arr[R1]) System.out.println("["+arr[L1]+","+arr[R1]+"]");
            temp[i++] = arr[L1] < arr[R1] ? arr[L1++] : arr[R1++];//相等则先拷贝右侧的数据
        }
        while (L1 <= M){
            temp[i++] = arr[L1++];
        }
        while(R1 <= R){
            temp[i++] = arr[R1++];
        }
        for(i = 0;i < temp.length;i++){
            arr[L+i] = temp[i];
        }
    }
}

问题:荷兰国旗问题

一、给定一个数组arr,和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。要求额外空间复杂度O(1),时间复杂度O(n)。

二、(荷兰国旗问题)给定一个数组arr,和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。要求额外空间复杂度O(1),时间复杂度O(n)。

public class HeLanGuoQiProblems {
    /**
     * 一、  给定一个数组arr,和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。
     * 要求额外空间复杂度O(1),时间复杂度O(n)。
     */
    public static void swap(int[] arr,int a,int b){
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

    public static void heLanGuoQi(int[] arr,int num){
        int L = -1;//左边边界条件,用于记录小于等于num数的边界范围
        for(int i = 0;i < arr.length;i++){
            //如果arr[i]<=num,当前数和边界L区的下一个数进行交换,并且边界L区进行右扩,再判断下一个数;没有则直接判断下一个数
            if(arr[i] <= num){
                swap(arr,i,L+1);
                L++;
            }
        }
    }

    /**
     * 二、(荷兰国旗问题)给定一个数组arr,和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。
     * 要求额外空间复杂度O(1),时间复杂度O(n)。
     */
    public static void heLanGuoQiAdvance(int[] arr,int num){
        int L = -1; //小于num数的左边界
        int R = arr.length;//大于num数的右边界
        int i = 0;
        //当i=R-1时,表明大于num的数以及在右侧了,程序以及完成。若没有这个条件,会导致已经在右侧的数重新进入循环进行交换
        while(i < arr.length && i < R){
            if(arr[i] < num){//如果arr[i]<num,当前数和边界L区的下一个数进行交换,并且边界L区进行右扩,再判断下一个数
                swap(arr,i,L+1);
                L++;
                i++;
            }else if(arr[i] > num){//如果arr[i]>num,当前数和边界R区的前一个数进行交换,并且边界R区进行左扩,此时i不变,继续判断当前数
                swap(arr,i,R-1);
                R--;
            }else {//没有则直接判断下一个数
                i++;
            }
        }
    }
}

1.9 快速排序(时间复杂度O(nlogn)~ O(n^2))

不改进的快速排序

1> 把数组范围内中的最后一个数作为划分值,然后把数组通过荷兰国旗问题分成三个部分:左侧<划分值、中间==划分值、右侧>划分值

2>对左侧范围和右侧范围,递归执行

3>时间复杂度为O(n^2)

随机快速排序(改进的快速排序)

1>在数组范围中,等概率随机选一个数作为划分值,然后把数组通过荷兰国旗问题分成三个部分:左侧<划分值、中间==划分值、右侧>划分值

2>对左侧范围和右侧范围,递归执行

3>时间复杂度为O(nlogn)

public class QuickSort {

    public static void swap(int[] arr,int a,int b){
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

    //方式一
    public static void quickSortVersion1(int[] arr,int L,int R){
        if(L >= R){
            return;
        }
        int temp = arr[R];//选取最后一个数为基值作为比较
        int i = L;
        int j = R;
        while(i < j){
            while(arr[i] <= temp && i < j){//选取的基准数为最右,因此探测顺序应为 先左后右
                i++;
            }
            while(arr[j] >= temp && i < j){
                j--;
            }
            swap(arr,i,j);
        }
        arr[R] = arr[i];
        arr[i] = temp;
        quickSortVersion1(arr,L,i-1);
        quickSortVersion1(arr,i+1,R);
    }

    //方式二:推荐(再数组中随机选取基准值,并放在数组最后)
    public static void quicksort(int[] arr){
        if(arr == null || arr.length < 2){
            return;
        }
        quicksort(arr,0,arr.length-1);
    }

    public static void quicksort(int[] arr,int L,int R){
        if(L < R){
            swap(arr,L+(int)(Math.random()*(R-L+1)),R);
            int[] p = partition(arr, L, R);//拿到与基准数相同数的下标数组
            quicksort(arr,L,p[0]-1);//小于基准数再进行快排
            quicksort(arr,p[1]+1,R);//大于基准数的再进行快排
        }
    }

    //荷兰国旗问题,小于的放左边,等于的放中间,大于的放右边,返回中间相同的坐标数组
    public static int[] partition(int[] arr,int L,int R){
        //放两个指针,分别代表左边界和右边界(当前数为判断数,该数已在右边界内)
        int left = L - 1;
        int right = R;
        while(L < right){ //已最右边的数为基准数
            if(arr[L] < arr[R]){//边界left右移,数组指针指向下一个
                swap(arr,++left,L++);
            }else if(arr[L] > arr[R]){//边界right左移,数组指针不变
                swap(arr,--right,L);
            }else {//和判断的数相同,数组指针直接指向下一个
                L++;
            }
        }
        swap(arr,right,R);//将判断的数和大于他的第一个数进行交换
        return new int[]{left+1,right};
    }
}

1.10 堆排序(时间复杂度O(nlogn),空间复杂度O(1))

堆的结构可以分为大根堆小根堆,是一个完全二叉树,而堆排序是根据堆的这种数据结构设计的一种排序。

        大根堆:每个结点的值都大于其左孩子和右孩子结点的值。

        小根堆:每个结点的值都小于其左孩子和右孩子结点的值。

        已知某个索引i的数,其父节点索引:(i-1)/2;左孩子索引:2*i+1;右孩子索引:2*i+2。

堆排序基本思想:

        1.首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端。

        2.将顶端的数与尾端的数交换,此时,末尾的数为最大值,剩余待排序数组的个数为n-1

        3.将剩余n-1个数再构造成大根堆,重复步骤2的操作。

public class HeapSort {

    public static void swap(int[] arr,int a,int b){
        int temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

    public static void heapSort(int[] arr){
        if(arr == null || arr.length < 2){
            return;
        }
        //构造大根堆
        for(int i = 0;i < arr.length;i++){
            heapInsert(arr,i);
        }
        int heapSize = arr.length;
        //固定大根堆的最大值,使其不参与下次大根堆的运算
        swap(arr,0,--heapSize);
        while(heapSize > 0){
            heapify(arr,0,heapSize);
            swap(arr,0,--heapSize);
        }
    }

    //如果当前插入的数比其父位置上的元素大,则需要交换他们的值  -->  构造大根堆(向上)
    public static void heapInsert(int[] arr,int index){
        while(arr[index] > arr[index - 1] / 2){
            swap(arr,index,(index - 1) / 2);
            index = (index - 1) / 2;
        }
    }

    //如果当前插入的数比起孩子结点位置的数小,则需要交换位置   -->  构造大根堆(向下)
    public static void heapify(int[] arr,int index,int heapSize){
        int left = index * 2 + 1;
        while(left < heapSize){
            //先找出左孩子和右孩子之间的最大值,返回最大孩子的下标
            int largetIndex = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
            //比较最大孩子和其父的大小,返回数大的值的下标
            largetIndex = arr[largetIndex] > arr[index] ? largetIndex : index;
            if(largetIndex == index){
                break;
            }
            swap(arr,largetIndex,index);
            index = largetIndex;
            left = index * 2 + 1;
        }
    }
}

堆排序扩展问题:已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的排序算法针对这个数据进行排序。

解法:对数组k个元素进行构造小根堆(PriorityQueue),保证最大移动不超过k。每次把小根堆的最小值放在这k个元素的首位,然后弹出首位元素,再向下进行k个元素构造小根堆,如此反复。

public static void sort_k(int[] arr,int k){
    //PriorityQueue -> 拥有小根堆的特性
    PriorityQueue<Integer> heap = new PriorityQueue<>();
    //添加k个元素,进行小根堆排序
    int index = 0;
    for(;index <= Math.min(arr.length,k);index++){
        heap.add(arr[index]);
    }
    int i = 0;
    for(;index < arr.length;i++,index++){
        heap.add(arr[index]); 
        arr[i] = heap.poll(); //返回根结点元素,并删除源数据
    }
    while(!heap.isEmpty()){
        arr[i++] = heap.poll();//将最后的数依次弹出加入数组中
    }
}

1.11 比较器

1>比较器的实质就是重载比较运算符

2>比较器可以很好的应用在特殊标准的排序上

3>比较器可以很好的应用在根据特殊标准排序的结构上

默认比较器方法compare的性质:返回负数,第一个参数排在前面;返回正数,第二个参数排在前面;返回0,谁放前面都可以。

1.12 桶排序(时间复杂度O(n),空间复杂度O(n))

桶排序之计数排序

public class CountSort {

    public static int[] getMaxAndMin(int[] arr){
        int[] p = new int[]{arr[0],arr[0]}; //用于存放最小值和最大值。初始为数组的第一个元素值
        for(int i = 0;i < arr.length;i++){
            p[0] = arr[i] < p[0] ? arr[i] : p[0];//保存最小值
            p[1] = arr[i] > p[1] ? arr[i] : p[1];//保存最大值
        }
        return p;
    }

    public static int[] countSort(int[] arr){
        //得到数组的最大值和最小值,并确定统计数组的长度 max - min + 1
        int[] p = getMaxAndMin(arr);
        int[] count = new int[p[1] - p[0] + 1]; //统计最小值到最大值之间每个数出现的次数
        //遍历数组,记录出现的次数
        for(int i = 0;i < arr.length;i++){
            count[arr[i] - p[0]]++;
        }
        //统计数组变形,每个元素的值为该元素和前面所有元素之和
        for(int i = 1;i < count.length;i++){
            count[i] += count[i - 1];
        }
        //从右向左遍历原数组,输出;没输出一个,对应计数数组对应位置减一
        int[] help = new int[arr.length];//辅助数组,保存输出结果
        for(int i = arr.length - 1;i >= 0;i--){
            help[count[arr[i] - p[0]] - 1] = arr[i];
            count[arr[i] - p[0]]--;//对应元素的个数减一
        }
        return help;
    }
}

桶排序之基数排序

public class RadixSort {

    public static void radixSort(int[] arr){
        radixSort(arr,0,arr.length-1,getMaxBits(arr));
    }

    //获得数组中最大元素的位数(1000->3位,100->2位)
    public static int getMaxBits(int[] arr){
        int max = arr[0];
        for(int i = 0;i < arr.length;i++){
            max = max < arr[i] ? arr[i] : max;
        }
        int bits = 0; //记录最大数是几位的
        while(max != 0){
            bits++;
            max /= 10;
        }
        return bits;
    }

    //获得数字x对应d为的值。 (117,2) -> x/10%10=1;(12345,3)->x/100%10
    public static int getDigit(int x,int d){
        return (x / ((int)Math.pow(10,d-1)) % 10);
    }

    //digit表示元素的最大位数 --> 将根据这个判断需要进行几次的桶排序
    public static void radixSort(int[] arr,int left,int right,int digit){
        final int radix = 10;//桶的个数,基数排序桶的个数为10,是确定的
        int i = 0,j = 0;
        int[] help = new int[right - left + 1];//准备和要排序数一样大的数组
        for(int d = 1;d <= digit;d++){
            int[] count = new int[radix];
            //记录相同位数的个数
            for(i = left;i <= right;i++){
                j = getDigit(arr[i],d);
                count[j]++;
            }
            //将count变形,每个元素为该元素和前面所有元素的和,用于后续的原数组的倒叙遍历
            for(i = 1;i < radix;i++){
                count[i] += count[i - 1];
            }
            //将原数组从右向左遍历,通过count对应位上的值-1找到在辅助数组的位置
            for(i = right;i >= left;i--){
                j = getDigit(arr[i],d);
                help[count[j] - 1] = arr[i];
                count[j]--;
            }
            //将排序完成的数组重新赋给原数组
            for(i = left,j=0;i <= right;i++,j++){
                arr[i] = help[j];
            }
        }
    }
}

1.13 排序算法的稳定性及其汇总

        同样值的个体之间,如果不因为排序而改变相对次序,就是这个排序是有稳定性的;否则就没有。

        不具备稳定性的排序:选择排序、快速排序、堆排序

        具备稳定性的排序:冒泡排序、插入排序、归并排序、一切桶排序思想下的排序

目前没有找到时间复杂度O(nlogn),额外空间复杂度O(1),又稳定的排序。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值