常见排序算法——冒泡排序,快排,归并

冒泡排序

冒泡排序顾名思义就是像水中的泡泡一样,离水面越近,泡泡就会越大。

在数组中的体现就是从数组的一号索引开始逐渐像后比较,遇到比当前值大的数就进行交换,利用两层循环实现全数组的有序。

代码分为三部分:

1.外层循环,每一次循环保证最大的数排在他对应的位置,因为最后一个数不用排所以次数-1

2.内层循环,对已经排好序的数之外的数进行排序,两个相邻数互换位置,直到排好当前最大数的位置为止

3.交换算法,使用位运算的方式交换位置可以提高效率

代码

public int[] bubbleSort(int[] arr){
    for(int i = 0 ;i < arr.length-1;i++){  //最后一个排所以只需要走数组长度-1次
        for(int j = 0;j <= arr.length-i-1;j++){
            //用内层循环来控制两个数的交换和比较
            if(arr[j] > arr[j+1]){
                swap(arr,i,j);            
            }
        }

    }
    return arr;
}

public static void swap(int[] arr,int a,int b){ //将数组中两个数互换位置的方法
     //位运算符号更高效,但局限性就是arr[a]和arr[b]本质上不能是同一个数
    arr[a]=arr[a]^arr[b];
    arr[b]=arr[a]^arr[b];
    arr[a]=arr[a]^arr[b];
} 

下面来详细介绍一下交换方法swap

我们都知道a^0=a,a^a=0,所以我们可以利用位运算的特点完成交换

首先令a=a^b

接着令b=a^b,现在的b=a^b^b=a

最后令a=a^b=a^b^a=b完成了交换操作

注意,这个方法只适用于a和b本质上不相同的情况,如果出现相同情况交换会失败

时间复杂度


冒泡排序的时间复杂度主要取决于两个嵌套循环的迭代次数。外层循环的迭代次数为 (n-1) 次(其中 (n) 是数组的长度)。对于每次外层循环迭代,内层循环的迭代次数会减少一次(因为每次迭代都会将一个元素放到正确的位置)。
在最坏情况下(数组是逆序的),每次内层循环需要比较 (n-i-1) 次,其中 (i) 是外层循环的索引。总的比较次数是:
 [
 (n-1) + (n-2) + (n-3) + \cdots + 1 = \frac{(n-1) \cdot n}{2}
 ]
因此,时间复杂度是 (O(n^2))。
在最好的情况下(数组已经是排序好的),冒泡排序仍然需要进行 (n-1) 次外层循环和内层循环中的部分比较,但可以通过优化(如添加标志位来检测是否发生了交换)来减少比较次数。但在没有优化的情况下,时间复杂度仍然是 (O(n^2))。

空间复杂度

冒泡排序是一种就地排序算法,不需要额外的存储空间来存放临时数据。只需要几个额外的变量来控制循环和交换元素,因此空间复杂度是 (O(1))。

快速排序

快速排序算法是对冒泡排序的一种改进算法,平均时间复杂度为(O(nlogn)),比冒泡排序提高很多

快排的主要思想是在数组中随机挑选一个数作为分界点,将数组分为三个部分,小于区,等于区,大于区,接着递归实现各区的有序从而达到整体的有序

代码

public void quickSort(int[] arr,int l,int r){
    //参数说明,给定数组,数组的最小索引,数组的最大索引
    int n = l + (int)(Math.random() * (r-l+1));
    //在数组中随机位置选取一个索引,使用random函数有助于平均时间复杂度
    swap(arr,n,r); //将选中位置的数与数组最右边的数进行交换作为标准
    int[] p=partition(arr,l,r); //返回等于区的左边界和右边界
    //左区递归,右区递归
    quickSort(arr,l,p[0]-1); //pratition得到的左区边界成为左区的右边界递归去排序
    quickSort(arr,p[1]+1,r); //pratition得到的右区边界成为右区的左边界递归去排序
 
}

public static int[] partition(int[] arr,int l,int r){//l作为指针控制一个个数据交换
    int less=l-1; //作为左区小于部分的界限
    int more=r; //作为右区大于部分的界限
    while(l < more){
        if(arr[l] < arr[r]){
        swap(arr,++less,l++); //左区外扩指针右移,将左区后一个数和当前数做交换
        }else if(arr[l] > arr[r]){
         swap(arr,--more,l); 
        //右区左扩,将右区前一个数据与当前数做交换,指针不变,因为新换过来的数据还没有比较过          
        }else{
        l++;//这是相等的情况,只需要指针右移就好了
        }    
    }
    swap(arr,more,r); //最后将最右侧的标准和右区内第一个数交换就做到了有序
    return new int[]{less+1,more};
} 

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

时间复杂度

随机选择基准元素是为了在平均情况下提高性能。使用 int n = l + (int)(Math.random() * (r - l + 1)); 从当前范围 [l, r] 中随机选择一个元素,将其与数组的最右边的元素交换。这有助于避免最坏情况,并通常使时间复杂度接近 O(nlog⁡n)O(nlogn)。

在 partition 函数中,使用双指针方法对数组进行分区。这个操作的时间复杂度是 O(n),因为每个元素最多被处理一次。

快排的递归调用将问题规模减小。最优情况下,每次划分都能平分数组,递归深度为 log⁡n,每层的分区操作是 O(n)。因此,平均情况下时间复杂度是 O(nlog⁡n)

最坏情况下,快排可能退化成 O(n^2),例如,当每次划分的基准元素是数组的最大或最小元素时,递归深度达到 n,这使得时间复杂度达到 O(n^2)。

空间复杂度

空间复杂度的主要部分是递归调用栈的空间。在最优情况下,递归深度为 log⁡n,因此空间复杂度为 O(log⁡n)。在最坏情况下,递归深度为 n,这会使空间复杂度达到 O(n)。

归并排序

将整个数组分为两部分,保证每个部分都是有序的最后每个部分之间在排序做到整体的有序

代码:

public void MergeSort(int[] arr,int l,int r){
    if(l==r){
            return;
     }
     int mid=l+((r-l)>>1); //利用位运算计算中间数提高效率
     MergeSort(arr,l,mid); //左边递归
     MergeSort(arr,mid+1,r); //右边递归

     merge(arr,l,mid,r);
}
public void merge(int[] arr,int l,int mid,int r){
    int help[]=new int[r-l+1]; //申请一个额外空间用来存放有序的数组
    int i=0; //指针
    int p1=l; //小于部分指针
    int p2=mid+1; //大于部分指针

    while (p1<=mid && p2<=r){
        help[i++]=arr[p1]<arr[p2] ? arr[p1++] : arr[p2++];
        }
    //一定会有p1或者p2后续的节点没添加完但其中一个到头了,那么就用剩下的补齐help数组
    while (p1<=mid){
        help[i++]=arr[p1++];
        }
    while (p2<=r){
        help[i++]=arr[p2++];
        }
   //将排好的数组赋值给arr
    for (int k = 0; k < help.length; k++) {
        arr[l+k]=help[k];
        }
}

时间复杂度

1.分解阶段:MergeSort 将数组分成两半,递归地对每半进行排序,递归深度为 log(n),其中 n 是数组长度。
2.合并阶段:merge 方法在每一层递归中合并两个有序子数组的时间复杂度是 O(n)。

总的时间复杂度是 O(n log n),因为每层递归的合并操作需要 O(n) 时间,递归深度为 log n。

空间复杂度

递归栈空间:递归深度为 log n,每层递归占用常量空间。
额外空间:merge 方法中使用了一个大小为 n 的辅助数组 help。

总的空间复杂度是 O(n),主要是由于辅助数组 help 的使用。

  • 7
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值