【java算法】排序算法合集

今天笔试,排序考的好多,我才整理了两个,笔试完决定全部整理完
在这里插入图片描述
上图是我们所有排序算法的分类:
分析算法的术语说明:

  • 稳定:如果a原本在b前面,而a=b,排序之后仍然在b的前面;
  • 不稳定:如果a原本在b前面,而a=b,排序之后a可能会出现在b的后面
  • 内排序:所有排序操作都在内存中完成
  • 外排序:由于数据太大,因此把数据放在磁盘中,而排序通过磁盘和内存的数据传输才能进行;(涉及文件操作以及归并排序思想)
  • 时间复杂度:一个算法执行所耗费的时间
  • 空间复杂度:运行完一个程序所需内存大小

一、交换排序

1、 冒泡排序

冒泡排序是一种简单的排序算法。

算法描述
  • 比较相邻元素,如果第一个比第二个大,就交换他们两个;
  • 对每一对相邻元素作相同的工作,从开始第一对到结尾最后一对,这样在最后的元素会是最大的数;
  • 针对所有的元素重复以上步骤,直到排序完成;
代码实现

根据算法描述;使用双重for循环
经过刷题总结,时间复杂度是O(n^2)的算法通常来说并不是最优解;

普通实现
  public static void bubbleSort(int []arr){
        if(arr==null){
            return;
        }
        for(int i=0;i<arr.length;i++){
            for(int j=0;j<arr.length-1;j++){
                if(arr[j]>arr[j+1]){
                    swap(arr,j,j+1);
                }
            }
        }
        System.out.println(Arrays.toString(arr));
    }
  

    private static void swap(int[] arr, int j, int i) {
        int temp=arr[j];
        arr[j]=arr[i];
        arr[i]=temp;
    }
优化:
  • 改变j的范围(因为每次遍历一遍后都确定了一个位置,所以length-1-i)
  • 定义flag标志位,如果它是true说明遍历一遍没有需要调整的,也就是数据是有序的,直接退出
 public static void bubbleSort2(int []arr){
        if(arr==null){
            return;
        }
        boolean flag;
        for(int i=0;i<arr.length;i++){
            flag=true;
            for(int j=0;j<arr.length-1-i;j++){
                if(arr[j]>arr[j+1]){
                    swap(arr,j,j+1);
                    flag=false;
                }
            }
            if(flag){
                break;
            }
        }
        System.out.println(Arrays.toString(arr));
    }

泛型实现
 public static <T extends Comparable<T>>void bubbleSort1(T [] arr){
        if(arr==null){
            return;
        }
        boolean flag;
        for(int i=0;i<arr.length;i++){
            flag=true;
            for(int j=0;j<arr.length-1-i;j++){
                if(arr[j].compareTo(arr[j+1])>0){
                    swap(arr,j,j+1);
                    flag=false;
                }
            }
            if(flag){
                break;
            }
        }

    }
    private static<T> void swap(T[]arr,int j,int i){
        T temp=arr[j];
        arr[j]=arr[i];
        arr[i]=temp;
    }

算法分析
  • 时间复杂度:
    最优时间复杂度:O(n)
    最差时间复杂度:O(n^2)
    平均时间复杂度:O(n^2)
  • 空间复杂度:
    平均空间复杂度:O(1)
  • 稳定性:
    稳定

2、快速排序

快排主要是通过选择一个关键值作为基准值。
比基准值小的放在左边;
比基准值大的放在右边;
依次递归。

算法描述
  • 选择基准
  • 分割操作
  • 递归

(我记得之前写递归与分治的时候就写过快排)

   public static int Partition(int []arr,int left,int right){
        int temp=arr[left];
        while (left<right){
            while (left<right&&arr[right]>temp)--right;
            arr[left]=arr[right];
            while (left<right&&arr[left]<=temp)++left;
            arr[right]=arr[left];
        }
        arr[left]=temp;
        return left;
    }
    public static void QuickSort(int []arr,int left,int right){
        if(left<right){
            int pos=Partition(arr,left,right);
            QuickSort(arr,left,pos-1);
            QuickSort(arr,pos+1,right);
        }
    }

当我按照思想写出代码的时候,感觉很懵;
这就是递归,代码简洁,不容易理解;
然后我进行一步步的调试;
理清楚了;

  int arr[]={1,5,6,8,2,3};
        QuickSort(arr,0,arr.length-1);

测试用例
在这里插入图片描述

上图将调用顺序画的很清楚,可以看到程序跑完,所有的都排好序了,
而每进行一次Partition函数,就确定一个pos的位置,同时pos左边的都是小于ar[pos]的,右边的全是大于pos的;
每次控制参数的时候,pos-1,pos+1;因为每次排序的时候pos位置确定了,直接缩小范围就行了;

递归的调用其实就是树;
在使用递归的时候有时不需要那么抠细节,明白每个函数的意义;
QuickSort(arr,left,pos-1);排pos左边
QuickSort(arr,pos+1,right);排pos右边

二、交换排序

1、简单选择排序

选择排序的特点:

  • 不稳定;
  • 无论对什么数据进行排序都是O(n^2)的时间复杂度(较大)
  • 数据规模越小越好
算法描述

首先在未排序的序列中找到最小(或最大)的元素(按需查找),存放在排序序列的起始位置,然后,再从剩余的元素中继续寻找最小(或最大)的元素,和已排序好的队列末尾进行交换,以此类推,直到所有元素都排序完成。

重点:
数据结构区分为已排序和未排序,注意下标控制。
注意选择的条件判定

代码实现

笔试题中经常有排序的顺序问题
初始:5,8,7,3,2
1:2,8,7,3,5
2:2,3,7,8,5
3:2,3,5,8,7
4:2,3,5,7,8

public class SelectSort {
    public static void selectSort(int []arr){
        if(arr==null||arr.length==0){
            return;
        }
        for(int i=0;i<arr.length;i++){
            int minIndex=i;
            for(int j=i;j<arr.length;j++){
                if(arr[j]<arr[minIndex]){
                    minIndex=j;
                }
            }
            swap(arr,minIndex,i);
        }
        System.out.println(Arrays.toString(arr));

    }

    private static void swap(int[] arr, int minIndex, int i) {
        int temp=arr[minIndex];
        arr[minIndex]=arr[i];
        arr[i]=temp;
    }

    public static void main(String[] args) {
        int []arr={5,8,7,3,2};
        selectSort(arr);
    }

这个代码很容易理解,但处理很巧妙,掌握边界的控制方法

算法分析

时间复杂度:
最优时间复杂度:O(n^2)
平均时间复杂度:O(n^2)
最坏时间复杂度:O(n^2)

空间复杂度:
平均空间复杂度:O(1)

稳定性:不稳定

2、堆排序

堆是一颗顺序存储的完全二叉树。完全二叉树中所有非终端节点的值均不大于(或不小于)其左右孩子节点的值。
小根堆:其中每个节点的值小于等于其左右孩子的值
大根堆:其中每个节点的值大于其左右孩子的值

堆排序就是利用堆这种数据结构进行排序算法。堆排序利用了大根堆(或小根堆)堆顶记录关键字最大(或最小)这一特征,使得在当前无序区中**选取最大(或最小)**关键字的记录变得简单

算法描述
  • 先将初始数组建成一个大根堆,此堆为初始的无序
  • 再将关键字最大的记录(堆顶)和无序区的最后一个记录交换,此时得到新的无序区和有序区。调整后的堆可能违反堆结构的性质,再将当前无序区调整为堆。然后循环
  • 直到无序区只有一个元素为止

堆排序首先是根据元素构建堆。然后将堆的根节点取出(一般是与最后一个节点进行交换),将前面len-1个节点继续进行堆调整的过程,然后再将根节点取出,这样一直到所有节点取出。

代码实现

基本思想:
一定要注意父节点和子节点的下标之间的关系;
i 2i+1 2i+2
注意控制下标

 public static void heapSort(int []arr){
        if(arr==null){
            return;
        }
        for(int i=(arr.length-1-1)/2;i>=0;i--){
            //i代表要调整的根节点的下标
            adjust(arr,i,arr.length-1);
            //构建大根堆
        }

        for(int i=0;i<arr.length;i++) {
            //0下标 根  保存最大值
            //arr[0]和堆中
            swap(arr, 0, arr.length - 1-i);//控制下标
            adjust(arr,0,arr.length-1-i-1);
        }
        System.out.println(Arrays.toString(arr));
    }

    private static void adjust(int[] arr, int start, int end) {
        int temp=arr[start];
        for(int i=2*start+1;i<=end;i=2*i+1){
            if(i+1<=end&&arr[i]<arr[i+1]){
                i++;//保存左右孩子较大值的下标
            }
            if(temp<arr[i]){
                //父节点小于孩子节点的值,交换
                arr[start]=arr[i];
                //选取孩子节点的左孩子节点,继续向下筛选
                start=i;
            }else {
                break;
            }

        }
        arr[start]=temp;//将值赋回来
    }

    private static void swap(int[] arr, int i, int j) {
        int temp=arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
    }

    public static void main(String[] args) {
        int []arr={5,8,6,3,2,1,4,8,9};
        heapSort(arr);
    }
算法分析

时间复杂度分析:
最优时间复杂度:O(nlog2n)
平均时间复杂度:O(nlog2n)
最坏时间复杂度:O(nlog2n)
空间复杂度分析:
O(1)
稳定性分析:
不稳定

三、插入排序

1、直接插入排序

通过构建有序序列,对于未排序的数据,在已排序的序列中从后向前进行查找操作,找到满足条件的元素之后进行位置插入操作
(为什么不从前向后进行查找相应的位置呢)

算法描述
  • 从第一个元素开始,该元素可以认为已经是排序好的元素;
  • 取出下一个元素,在已经排序好了的元素中从后向前进行查找
  • 如果已经排序好的元素大于新元素,将该元素移动到下一个位置;
  • 重置上一步,直到找到已排序的元素小于或等于新元素,进行该元素的插入动作
代码实现
 public static void insertSort(int []arr){
        if(arr==null||arr.length==0){
            return;
        }
        //
        for(int i=0;i<arr.length;i++){
            int minIndex=i;
            for(int j=i+1;j<arr.length;j++){
                if(arr[j]<arr[minIndex]){
                    minIndex=j;
                }
            }
            int temp=arr[minIndex];
            int z=0;
            for( z=minIndex;z>i;z--){
                arr[z]=arr[z-1];
            }
            arr[z]=temp;
        }

    }
算法分析

时间复杂度分析:
最优时间复杂度:O(n)
平均时间复杂度:O(n^2)
最差时间复杂度:O(n^2)
空间复杂度:
平均空间复杂度:O(1)
稳定性分析:
稳定

2、希尔排序

希尔排序也是一种插入排序,它是简单插入排序的一种算法改进方式。
也成为见效增量排序,希尔排序的时间复杂度相比直接插入排序的时间复杂度要小
他与直接插入排序的不同在于它会优先比较距离远的元素。
希尔排序是按照一定的增量进行分组排序,对每一组进行直接插入排序,随着分组个数的减少,每组中的元素就会越来越多,当增量减少为1时,排序结束。

算法描述

选择增量gap=length/2,缩小增量继续以gap=gap/2的方式进行分组。(经常选5,3,1)

  • 选择一个增量序列,按照增量序列个数为m,进行m趟排序;
  • 每趟排序根据对应的增量次数分别进行元素的分组操作,对组内进行直接插入排序操作;
  • 继续下一个增量,分别进行分组直接插入操作;
  • 重复第三个步骤,直到增量变为1,所有元素在一个分组内,希尔排序结束
    例:26,53,67,48,57,13,48,32,60,50
    增量5,3,1
    1:13,48,32,48,50,26,53,67,60,57
    2:13,26,32,48,50,48,53,57,60,67
    3:13,26,32,48,48,50,53,57,60,67
代码实现
public static void shell(int []arr,int gap){
        for(int i=gap;i<arr.length;i++){
            int temp=arr[i];
            int j=0;
            for( j=i-gap;j>=0;j-=gap){
                if(temp<arr[j]){
                    arr[j+gap]=arr[j];
                }else {
                    break;
                }
            }
            arr[j+gap]=temp;
        }
    }
    public static void shellSort(int []arr){
        int []partition={5,3,1};
        for(int i=0;i<partition.length;i++){
            shell(arr,partition[i]);
        }
        System.out.println(Arrays.toString(arr));
    }

    public static void main(String[] args) {
        int []arr={5,8,6,3,2,1,4,9};
        shellSort(arr);
    }
算法分析

时间复杂度分析:
平均时间复杂度:O(n1.3~n1.5)
空间复杂度分析:
O(1)
稳定性分析:
不稳定

四、归并排序

归并排序建立在归并的有效操作上进行排序,主要采用分治法将已有序的子序列合并,得到完全有序的序列,即先让每一小段有序,再让小段之间变得有序。若将两个有序合成一个有序段,这又称为二路归并。

算法描述
  • 开始以间隔为1的进行归并,也就是说,第一个元素跟第二个进行归并。第三个与第四个进行归并;(每一个元素是有序的)
  • 然后,再以间隔为2的进行归并,1-4进行归并,5-8进行归并;
  • 再以22的间隔,同理,直到2k超过数组长度为止;
  • 当不够两组进行归并的时候,如果超过k个元素,仍然进行归并;如果剩余元素不超过k个元素,那么直接复制给中间数组。
代码实现
  public static void mergeSort(int []arr){
        int []brr=new int[arr.length];
        mergePass(arr,brr,0,arr.length-1);
        System.out.println(Arrays.toString(arr));
    }

    private static void mergePass(int[] arr, int[] brr, int left, int right) {
        if(left<right){
            int mid=(right-left)/2+left;
            mergePass(arr,brr,left,mid);
            mergePass(arr,brr,mid+1,right);
            merge(brr,arr,left,mid,right);
            copy_Arr(arr,brr,left,right);
        }
    }

    private static void merge(int[] brr, int[] arr, int left, int mid, int right) {
        int i=left,j=mid+1;
        int k=left;
        while (i<=mid&&j<=right){
            brr[k++]=arr[i]<arr[j]?arr[i++]:arr[j++];
        }
        while (i<=mid){
            brr[k++]=arr[i++];
        }
        while (j<=right){
            brr[k++]=arr[j++];
        }
    }

    private static void copy_Arr(int[] arr, int[] brr, int left, int right) {
        for(int i=left;i<=right;i++){
            arr[i]=brr[i];
        }
//        for(int i=0;i<arr.length;i++){
//            arr[i]=brr[i];
//        }
    }

    public static void main(String[] args) {
        int []arr={5,7,8,9,3,6,1,2};
        mergeSort(arr);
    }
算法分析

时间复杂度分析:
平均时间复杂度:O(nlog2n)
最坏时间复杂度:O(nlog2n)
空间复杂度分析:O(n)
稳定性分析:
不稳定

五、基数排序

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值