Java排序算法 【更新ing】

排序算法,是指将一组或多组数据通过特定的算法进行排序处理,使该组数据符合升序或降序,方便对其进行计算和筛选,提高数据处理的效率。排序算法可以在在多种计算机语言中实现,这里我们通过Java来实现几种常见的排序算法。

冒泡排序

冒泡排序是一种比较简单的排序算法,其原理就是从头到尾比较数组中前后两个元素的大小,并根据大小来调换元素的位置,最终按照升序或降序为数组中的元素排序。
在这里插入图片描述

上图是冒泡排序的一个执行逻辑。
首先,我们要遍历数组。假设数组中有N个元素,那么我们就需要遍历N-1次数组,以保证所有的元素都被排列到合适的位置。最后一个元素不需要排列,因为其他的元素已经排列好了。
之后,我们要将每一个元素与其他未排好序的元素相比较,从而将每一个元素的调整到合适位置。按照数组从头到尾的顺序来比较,如果前一个元素比后一个元素大/小,那么就交换这两个元素的位置,直到这个元素无法继续交换为止。此时这个元素将符合升序/降序排列。
最后,我们要重复第二步,每一次交换的步骤都会变短,直到所有的元素都经过比较为止。
冒泡排序通过以下代码来实现。

public static void main(String[] args) {
    int arr[] = {8, 5, 3, 2, 4};
//外层循环控制遍历的次数
for (int i = 0; i < arr.length; i++) {
    for (int j = 0; j < arr.length - i - 1; j++) {
        //内层循环控制交换的位置,每循环一次获取一个最大值,前面的元素比后面大就交换
        if (arr[j] > arr[j + 1]) { //升序排列,'<'为降序
            int temp = arr[j + 1];//保存当前较小值
            arr[j + 1] = arr[j];//较大值赋给下一个数组元素
            arr[j] = temp;//将较小值赋给当前一个元素
    }
        }
    }
}

冒泡排序是一种稳定的算法,其平均时间复杂度为O(n²) 。在如上代码中,要将一个数组进行冒泡排序,就必须执行两个循环,这意味着要经过n²次处理。
事实上,我们可以对冒泡算法做一个改进来加快它的排序处理速度。在数组中经常会出现只有几个元素需要调整或者数组本身就是升序/降序的情况,这时冒泡算法仍然会对整个数组进行第二层循环的交换处理,尽管大部分的处理是没有必要的。在这种情况下,我们可以在第一层循环中添加一个执行条件,来限制是否要执行第二层循环。这样一来,我们就可以减少非必要的元素交换步骤,并提高排序速度。

for (int i = 0; i < arr.length; i++) {
      boolean aa = false;//设置aa为变量
      System.out.print(1+" ");//检查外循环有没有执行
      for (int j = 0; j < arr.length - i - 1; j++) {
        if (arr[j] > arr[j + 1]) { 
            int temp = arr[j + 1];
            arr[j + 1] = arr[j];
            arr[j] = temp;
            aa = true;//当内循环执行时,将检测变量改为true
            System.out.print(0+" ");//检测内循环有没有执行
        }
    }
    if (aa != true){//如果没有执行内循环语句时,代表当前元素不需要进行交换,跳出内循环
        break;
    }
}

通过增加一个boolean变量,我们检测了程序是否经历了第二层循环。如果没有进入第二层循环,说明当前处理的元素不需要进行位置变化,也就不需要与之后的元素进行对比了。理想情况下,不需要交换数据的数组,在冒泡排序中只需要进行一次遍历,其时间复杂度为O(n)。

选择排序

选择排序是一种不稳定的排序算法,其最优和最差时间复杂度都是O(n²)。
选择排序实现的原理是遍历数组并选出数组中的最大值/最小值,并将该元素放到数组的头部,并依次处理完数组中的全部元素。下图是选择排序的处理逻辑。
在这里插入图片描述

首先,我们要遍历数组中的元素。和冒泡排序一样,要遍历N-1次,从而处理数组中的所有元素。
之后,我们要选出数组中的最小元素。将当前元素的值和下标定义为最小值,并通过第二层循环与其他元素比较。如果后面的元素有比当前元素更小的,则将新元素的值和下标赋给最小值。我们通过第二层循环遍历整个数组,并找出其中的最小值。
最后,我们将当前元素与最小值相比较,如果当前元素不是最小值,那么就与最小值元素相交换。通过重复这一步骤,来实现整个数组的排序。
选择排序的代码实现如下。

for(int i = 0;i<arr.length-1;i++){
    int min = arr[i];
    int minindex = i;
    for(int j = i;j<arr.length;j++){
        if(arr[j]<min){  //选择当前最小的数
            min = arr[j];
            minindex = j;
        }
    }
    if(i != minindex){ //若i不是当前元素最小的,则和找到的那个元素交换
        arr[minindex] = arr[i];
        arr[i] = min;
    }
}

插入排序

插入排序是一种稳定的排序算法,其时间复杂度为O(n²)。插入算法由两个循环构成,外部循环控制数组遍历的次数,内部循环负责在数组中向前寻找插入的位置。
在这里插入图片描述

首先,我们要设置数组遍历次数,并将数组中的第二个元素作为起点(第一个元素无法向前插入);
之后,我们将保存当前元素的值和下标,并判断当前元素是否小于其前面的元素;
然后,如果当前元素比前面的元素小,则将当前下标的值替换为前面下标的值,并将下标前移1位,直到找到合适的下标位置,并将当前元素的值插入到数组中。
重复以上流程,直到处理完整个数组中的元素。
实现代码如下。

for(int i = 1; i< arr.length; i++){
    int index = i;
    int value = arr[i];
    while (index > 0 && value < arr[index -1]){
        arr[index] = arr[index -1];
        index--;
    }
    arr[index] = value;
}

本算法同样可以通过设置目标变量的方式进行优化,使算法在最好情况(数组无需排序)下的空间复杂度为O(n)。优化方法和冒泡排序一样,在外循环内设置目标布尔型变量,在内循环中改变其赋值,并在内循环结束后检测目标变量的值是否被改变,从而跳出内循环。

希尔排序

希尔排序的结构借鉴了插入排序,同样是保存插入元素并寻找位置插入,只不过希尔排序需要根据循环次数调整插入和比较的步长。希尔排序是不稳定的排序算法,其时间复杂度平均为O(nlog²n),比直接插入排序要快。
在这里插入图片描述

首先,我们要设置插入数据的步长/间隔,初始间隔要对数组长度进行取模运算(length/2),每个循环后,步长 /= 2 。
之后,我们将当前步长作为循环的起始位置,并保存当前元素的下标和值。
然后,我们比较当前元素与目标元素的大小,如果当前元素小于目标元素,则将当前元素的下标替换为目标元素,并向前移动(步长)个位置,直到找到合适的下标位置,并将当前元素的值插入到数组中。
重复以上步骤,直到调整完所有的元素。
代码实现如下。

for(int gap = a.length/2;gap>0;gap/=2){
    for(int i = gap;i<a.length;i++){
        int index = i;
        int insertValue = a[i];
        while (index-gap >=0 && insertValue < a[index-gap]){
            a[index] = a[index-gap];
            index -= gap;
        }
        a[index] = insertValue;
    }
}

希尔排序也可以使用冒泡排序的方式来进行,但是效率会低于基于插入排序。有兴趣的话可以探索一下,

快速排序

快速排序的原理是分区操作,即在一个数组中找到一个基准点,并按照该基准点重新排序,使该基准点左侧数据比它小,右侧的数据比它大。之后递归调用本方法,使基准点两侧的元素也插入合适的位置,从而实现数组排序。快排平均的时间复杂度为O(nlogn),在最坏情况下为O(n²),是不稳定但速度较快的算法。
快速排序的实现步骤如下:
1.移动右指针,使其小于基准点的值,并左移;
2.移动左指针,使其大于或等于基准点的值,并右移;
3.如果左指针和右指针重合,则将当前元素与基准值交换;
4.如果左右指针都已经调整完位置且不重合,则交换左右指针中的值;
5.递归调用1-4步骤,分别对基准点左右两侧的值进行排序。
在这里插入图片描述

代码实现如下。

   public static void main(String []args) {
        int[] a= {3, 2, 6, 1, 9, 5, 3, 6, 1, 7, 9, 2, 5};
        quicksort(a, 0, a.length - 1);//调用排序方法,设置数组前后起点
        System.out.print(Arrays.toString(a));//输出排序后的数组
    }
        public static void quicksort(int []arr,int left, int right){
            if(left>=right){//设置递归的退出条件
                return;
            }
            int l = left;//保存左指针起始位置
            int r = right;//保存右指针起始位置
           
            while(l<r){//基准点设置为左指针的起始位置
                while(l<r && arr[r]>=arr[left]){//当右指针中的数据小于基准点时,右指针左移
                    r--;
                }
                while(l<r && arr[l]<=arr[left]){//当左指针中的数据小于基准点时,左指针右移
                    l++;
                }
                if(r == l){//当左右指针重合时,将当前元素的值与基准点的值交换
                    int tem = arr[r];
                    arr[r] = arr[left];
                    arr[left] = tem;
                }else{//如果左右指针移动结束后不重合,则交换左右指针的值
                    int temp = arr[r];
                    arr[r] = arr[l];
                    arr[l] = temp;
                }
            }
//递归,对基准点左右两侧的数据进行排序
            quicksort(arr,left,l-1);
            quicksort(arr,r+1,right);
        }
  }

基数排序

基数排序(Radix Sort)是桶排序的拓展,其时间复杂度为O(d(n+r)):d 为位数,r 为基数,n 为原数组个数。基数排序的效率是稳定的,因为其排序过程与数组是否有顺序无关。
与其他排序算法不同,它是一种非比较排序算法,其原理是将数组中的元素按位数切分到不同的分组(桶)中,并根据该位数的大小进行排序,循环排序后再收集到原数组中。
在这里插入图片描述

上图是基数排序的一个流程示范。对于基数排序,其算法逻辑是这样的:
1.遍历数组找出数组中的最大值,并得到其位数,设置为此次基数排序的次数;
2.从个位数开始,将数组中的元素依次装入二维数组(桶)中;
3.将二维数组中的数据按顺序填入原数组,并根据下一个数位进行基数排序。
代码实现如下。

public static void main(String[] args){
    int[] arr = new int[]{4,6,3,34,67,993,57,2,56,843,36,80};
    redixSort(arr);
    System.out.print(Arrays.toString(arr));
}
public static void redixSort(int []a){
 int [][]bucket = new int[10][a.length-1];//桶中存放的具体元素
 int[] bucketCount = new int[10];//设置每个桶所存的元素个数
 int max = a[0];
 for(int i = 1; i<a.length;i++){//找出最大值
     if(max<a[i])max = a[i];
 }
 int maxCount = (max+"").length();//得出最大位数
 for (int i = 0;i<maxCount;i++){//将最大值位数设为基数排序次数
     for(int j = 0;j<a.length;j++){
//调用pow方法,将当前元素除以10的i次方,并对10取余,得出指定位数的值
         int value = a[j]/(int)Math.pow(10,i)%10;
  bucket[value][bucketCount[value]] = a[j];//将该值装入指定桶中的指定位置
         bucketCount[value]++;//该桶的元素个数+1
     }
     int index = 0;
//将元素从桶中装回到数组中
     for(int x = 0;x< bucketCount.length;x++){
         if (bucketCount[x]!=0){
             for(int y = 0;y<bucketCount[x];y++){
                 a[index] = bucket[x][y];
                 index++;
             }
         }
         bucketCount[x] = 0;//数据转移后,清空桶中数据
     }
 }
}

归并排序

归并排序是一种采用了分治法的排序操作,将数组分成若干小块并按照顺序重新合并成新的数组。归并排序的最好和最坏时间复杂度均为O(nlogn),是一种稳定的排序算法。
在这里插入图片描述

归并排序是将数组中的数据一分为二,并依次将数据折中划分为更小的序列数组,直到数组无法继续划分(最大数组元素个数为1)为止。之后,将数组中的元素按大小顺序逐级添加到新的数组中,直到将全部元素填充到新数组后,再将排好序的新数组中的元素传给原数组,实现排序操作。
代码实现如下。

public static void main(String []args) {
        int[] a = new int[]{3, 2, 6, 1, 9, 5, 3, 6, 1, 7, 9, 2, 5};
        int[] temp = new int[a.length];
        mergesort(a,0,a.length-1,temp);//设置数组分解的初始范围
        System.out.print(Arrays.toString(a));//输出数组
    }
    public static void mergesort(int []a, int left,int right,int []temp){
        if(left<right){
            int mid = (left+right)/2;//设置数组分解的中值
            mergesort(a,left,mid,temp);//递归分解前半部分
            mergesort(a,mid+1,right,temp);//递归分解后半部分
            merge(a,left,mid,right,temp);//递归合并数组
        }
    }
    public static void merge(int a[],int left,int mid, int right, int []temp){
        int i = left;//设置左侧数组起点
        int j = mid+1;//设置右侧数组起点
        int t = 0;//设置元素插入坐标
//当两侧数组元素未整理完时,按照左右两侧元素的大小顺序插入临时数组
        while (i<=mid && j<=right){
            if (a[i]<=a[j]){
                temp[t] = a[i];
                t++;i++;
            }else{
                temp[t] = a[j];
                t++;j++;
            }
        }
//处理数组元素为奇数时落单的元素
        while (i<=mid){
            temp[t] = a[i];
            t++;i++;
        }
        while (j<=right){
            temp[t] = a[j];
            t++;j++;
        }
        t = 0;//重置下标
        int templeft = left;
        while (templeft<=right){//将原数组替换为新数组
            a[templeft] = temp[t];
            t++;templeft++;
        }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值