Java实现:排序算法汇总

目录

O(n^2)

冒泡

选择

插入

O(nlogn)

希尔排序

堆排序

快速排序

归并排序

O(n)

计数排序

基数排序

桶排序


O(n^2)

  • 选择排序是不稳定的,冒泡排序、插入排序是稳定的;
  • 在这三个排序算法中,选择排序交换的次数是最少的;
  • 在数组几乎有序的情况下,插入排序的时间复杂度接近线性级别。

冒泡

一边比较一边向后两两交换,将最大值 / 最小值冒泡到最后一位;

经过优化的写法:使用一个变量记录当前轮次的比较是否发生过交换,如果没有发生交换表示已经有序,不再继续排序;

public static void bubbleSort(int[] arr) {
    // 初始时 swapped 为 true,否则排序过程无法启动
    boolean swapped = true;
    for (int i = 0; i < arr.length - 1; i++) {
        // 如果没有发生过交换,说明剩余部分已经有序,排序完成
        if (swapped==false) break;
        // 设置 swapped 为 false,如果发生交换,则将其置为 true
        swapped = false;
        for (int j = 0; j < arr.length - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                // 如果左边的数大于右边的数,则交换,保证右边的数字最大
                swap(arr, j, j + 1);
                // 表示发生了交换
                swapped = true;
            }
        }
    }
}
// 交换元素
private static void swap(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

选择

简单选择排序

选择排序的思想是:双重循环遍历数组,每经过一轮比较,找到最小元素的下标,将其交换至首位


public static void selectionSort(int[] arr) {
    int minIndex;
    for (int i = 0; i < arr.length - 1; i++) {
        minIndex = i;
        for (int j = i + 1; j < arr.length; j++) {
            if (arr[minIndex] > arr[j]) {
                // 记录最小值的下标
                minIndex = j;
            }
        }
        // 将最小元素交换至首位
        int temp = arr[i];
        arr[i] = arr[minIndex];
        arr[minIndex] = temp;
    }
}

二元选择排序

class Solution {
    public int[] sortArray(int[] nums) {
        //二元选择排序
        //同时找最大最小值
        int min;
        int max;
        for(int i=0;i<nums.length/2;i++){
            min=i;
            max=i;
            //获取最大最小值下标
            for(int j=i+1;j<nums.length-i;j++){
                if(nums[j]<=nums[min]){
                    min=j;
                }
                if(nums[j]>nums[max]){
                    max=j;
                }
            }
            //当max=min时,必定为max=min=i,且[i,length-i]都等于nums[i]
            if(min==max) break;

            //交换最小值
            swap(nums,i,min);

            //当max=i时,此时i的值已经交换到min处
            if(max==i) max=min;

            //交换最大值
            swap(nums,nums.length-i-1,max);
        }
        return nums;
    }
    
    //交换
    public void swap(int[] nums,int i,int j){
        int temp=nums[i];
        nums[i]=nums[j];
        nums[j]=temp;
    }
}

插入

在新数字插入过程中,与前面的数字不断比较,前面的数字不断向后挪出位置,当新数字找到自己的位置后,插入一次即可。

class Solution {
    public int[] sortArray(int[] nums) {
        if(nums.length<2) return nums;
        //交换法插入排序
        //从第二个数开始
        for(int i=1;i<nums.length;i++){
            int cur=nums[i];
            //插入位置之前的数字
            int j=i-1;
            //把比插入数大的数都移到后面
            while(j>=0 && nums[j]>cur){
                //插入的数比它前面的数小
                //把前面的数移到后面
                nums[j+1]=nums[j];
                j--;
            }
            // 两种情况会跳出循环:
            //1. 遇到一个小于或等于cur的数字,跳出循环,cur就坐到它后面。
            //2. 已经走到数列头部,仍然没有遇到小于或等于cur的数字,也会跳出循环,此时j等于 0,cur就坐到数列头部。   
            nums[j+1]=cur;
        }
        return nums;
    }
}

O(nlogn)

希尔排序

本质是插入排序,时间复杂度由经验得出o(n^1.25)-o(1.6n^1.25)

public void shellSort(int[] nums){
    //间隔序列,在希尔排序中我们称之为增量序列
    for(int gap=nums.length/2;gap>0;gap/=2){
        //插入排序
        //从gap开始,按照顺序将每个元素依次向前插入自己所在的组
        for(int i=gap;i<nums.length;i++){
            //cur站起来,开始找位置
            int cur=nums[i];
            //该组前一个数字的索引
            int j=i-gap;
            while(j>=0 && nums[j]>=cur){
                nums[j+gap]=nums[j];
                j-=gap;
            }
            //cur插入找到了自己的位置,坐下
            nums[j+gap]=cur;
        }
    }
}

堆排序

class Solution {
   //堆排序
    public int[] sortArray(int[] nums) {
        //建立初始大根堆
        buildMaxHeap(nums);
 
        //调整大根堆
        for(int i=nums.length-1;i>0;i--){
            //将大根堆顶与最后一个元素交换,通过不断交换,最后得到一个升序数组
            swap(nums,0,i);
            // 调整剩余数组,使其满足大顶堆
            maxHeapify(nums, 0, i);
        }
 
        return nums;
    }
 
    //建立初始大根堆
    public void buildMaxHeap(int[] nums){
        //从第一个非叶子节点开始
        for(int i=nums.length/2-1;i>=0;i--){
            //调整每一个子树为大根堆
            maxHeapify(nums,i,nums.length);
        }
    }
 
    //调整大根堆,第二个参数为堆顶,第三个参数为,参与调整的最后一个数的下标+1
    public void maxHeapify(int[] nums,int i,int heapSize){
        //左子树
        int l=2*i+1;
        //右子树
        int r=l+1;
        //记录根结点、左子树结点、右子树结点三者中的最大值下标
        int largest=i;
        // 与左子树结点比较
        if(l<heapSize && nums[l]>nums[largest]){
            largest=l;
        }
        // 与右子树结点比较
        if(r<heapSize && nums[r]>nums[largest]){
            largest=r;
        }
        if(largest!=i){
            // 将最大值交换为根结点
            swap(nums,i,largest);
            // 再次调整交换数字后的大顶堆
            maxHeapify(nums,largest,heapSize);
        }
    }
 
    //交换
    public void swap(int[] nums,int i,int j){
        int temp=nums[i];
        nums[i]=nums[j];
        nums[j]=temp;   
    }    
}

快速排序

class Solution {
    //快排
    public int[] sortArray(int[] nums) {
        quickSort(nums,0,nums.length-1);
        return nums;
    }

    public void quickSort(int[] nums,int start,int end){
        //如果区域内的数字少于2个,退出递归
        if(start>=end) return;
        //获得轴
        int middle=getPivot(nums,start,end);
        //递归
        quickSort(nums,start,middle-1);
        quickSort(nums,middle+1,end);
    }

    // 将nums从start到end分区,左边区域比基数小,右边区域比基数大,然后返回中间值的下标
    public int getPivot(int[] nums,int start,int end){
        //取第一个数为基数
        int pivot=nums[start];
        //左边界
        int left=start+1;
        //右边界
        int right=end;

        //移动两边界
        while(left<right){
            while(left<right && nums[left]<=pivot) left++;
            while(left<right && nums[right]>=pivot) right--;
            //此时左边界>pivot,右边界<pivot
            //交换左右边界
            if(left<right){
                swap(nums,left,right);
                left++;
                right--;
            }  
        }

        //此时两种情况,left==right     left>right
        //单独判断left==right,nums[right]>pivot情况
        if(left==right && nums[right]>pivot){
            right--;
        }
        //此时都是left>right,轴right将数组分成两边界(start,right],(right,end]
        //将基数和轴交换
        swap(nums,start,right);
        return right;
    }

    //交换方法
    public void swap(int[] nums,int i,int j){
        int temp=nums[i];
        nums[i]=nums[j];
        nums[j]=temp;        
    }
}

归并排序

class Solution {
   //归并排序
    public int[] sortArray(int[] nums) {
        //临时数组result
        int[] result=new int[nums.length];
        //归并排序
        mergeSort(nums,0,nums.length-1,result);
        //此时nums与result相同
        return result;//此时nums与result相同
    }

    // 对 nums 的 [start, end] 区间归并排序
    public void mergeSort(int[] nums,int start,int end,int[] result){
        // 只剩下一个数字,停止拆分
        if(start==end) return;
        int middle=(start+end)/2;
        // 拆分左边区域,并将归并排序的结果保存到 result 的 [start, middle] 区间
        mergeSort(nums,start,middle,result);
        // 拆分右边区域,并将归并排序的结果保存到 result 的 [middle + 1, end] 区间
        mergeSort(nums,middle+1,end,result);
        // 合并左右区域到 result 的 [start, end] 区间
        merge(nums,start,end,result);
    }

    // 将 nums 的 [start, middle] 和 [middle + 1, end] 区间合并
    public void merge(int[] nums,int start,int end,int[] result){
        //分割
        int middle=(start+end)/2;
        // 数组 1 的首尾位置
        int start1=start;
        int end1=middle;
        // 数组 2 的首尾位置
        int start2=middle+1;
        int end2=end;
        // 用来遍历数组的指针
        int index1=start1;
        int index2=start2;
        // 结果数组的指针
        int resultIndex=start1;
        //比较插入结果数组
        while(index1<=end1 && index2<=end2){
            if(nums[index1]<=nums[index2]){
                result[resultIndex++]=nums[index1++];
            }else{
                result[resultIndex++]=nums[index2++];
            }
        }
        // 将剩余数字补到结果数组之后
        while(index1<=end1){
            result[resultIndex++]=nums[index1++];
        }
        while(index2<=end2){
            result[resultIndex++]=nums[index2++];
        }
        // 将 result 操作区间的数字拷贝到 arr 数组中,以便下次比较
        for(int i=start;i<=end;i++){
            nums[i]=result[i];
        }
    }
    
}

O(n)

计数排序(倒序遍历计数数组)

class Solution {
   //计数排序(倒序遍历计数数组)
    public int[] sortArray(int[] nums) {
        //判空及越界
        if(nums==null || nums.length<=1) return nums;

        //最大最小值
        int min=0;
        int max=0;
        for(int num:nums){
            if(num<min) min=num;
            if(num>max) max=num;
        }

        //计数
        int range=max-min+1;
        int[] counting=new int[range];
        for(int element:nums){
            counting[element-min]++;
        }

        //记录每种元素最后一个的下标
        //此处记录的下标,需要用记录的数字-1
        counting[0]--;
        for(int i=1;i<range;i++){
            //位置 = 前面比自己小的数字的总数 + 自己的数量 - 1
            //由于counting[0]已经减了1,所以后续的减1可以省略
            counting[i]+=counting[i-1];
        }

        //计数排序
        int[] result=new int[nums.length];
        //从后往前遍历数组,通过counting中记录的下标位置,将nums中的元素放到result数组中
        for(int i=nums.length-1;i>=0;i--){
            result[counting[nums[i]-min]]=nums[i];
            //更新 counting[nums[i] - min],指向此元素的前一个下标
            counting[nums[i]-min]--;
        }

        //将结果赋值回原数组
        System.arraycopy(result,0,nums,0,nums.length);
        return nums;
    }
}

基数排序

class Solution {
   //基数排序(基数:-9~9)
    public int[] sortArray(int[] nums) {
        //判空
        if(nums==null) return nums;

        //最长数字
        int max=0;
        for(int num:nums){
            if(Math.abs(num)>max){
                max=Math.abs(num);
            }
        }

        //计算最长数字的长度
        int maxDigitLength=0;
        while(max!=0){
            maxDigitLength++;
            max/=10;
        }

        //通过计数排序进行基数排序
        int[] counting=new int[19];//下标 [0, 18] 对应基数 [-9, 9]
        int[] result=new int[nums.length];
        int dev=1;//用来计算基数
        for(int n=0;n<maxDigitLength;n++){//最外面循环使用变量n,内层循环可以用i
            //计算基数,计数
            for(int element:nums){
                int radix=element/dev%10+9;
                counting[radix]++;
            }

            //记录每种元素最后一个的下标
            counting[0]--;//计算的为下标=当前个数+之前个数-1
            for(int i=1;i<counting.length;i++){
                counting[i]+=counting[i-1];
            }

            //对基数进行排序
            //使用倒序遍历的方式完成计数排序!!!,从前往后会报错!!!
            for(int i=nums.length-1;i>=0;i--){
                //下标调整
                int radix=nums[i]/dev%10+9;
                result[counting[radix]]=nums[i];
                counting[radix]--;
            }

            //计数排序完成后,将结果拷贝回nums数组
            System.arraycopy(result,0,nums,0,nums.length);
            //将计数数组重置为 0
            Arrays.fill(counting,0);
            //将计算基数dev上升一位
            dev*=10;
        }
        
        return nums;
    }
}

桶排序

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值