排序算法总结

排序算法是一类重要的算法,也是最基础的一类算法,在学习排序算法的时候,涉及到大量的各种核心算法概念,例如大O表示法,分治法,堆和二叉树之类的数据结构,随机算法,最佳、最差和平均情况分析,时空权衡以及上限和下限,其重要性不言而喻,下面就介绍一些常见的排序算法

时间复杂度
时间复杂度是衡量一个算法的重要指标,通常用大O表示法来表示,其反应了算法对数据的操作量级

空间复杂度
空间复杂度表示一个算法小号空间的大小,如果一个算法不需要额外的空间开销,我们称之为原地算法,此时其空间复杂度为O(1)

关于排序算法的稳定性
如果在数组中有两个元素是相等的,在经过某个排序算法之后,原来在前面的的那个元素仍然在另一个元素的前面,那么我们就说这个排序算法是稳定的。如果在排序之后,原来的两个相等元素中在前面的一个元素被移到了后面,那么这个算法就是不稳定的。

比较排序、非比较排序
如果一个算法需要在排序的过程中使用比较操作来判断两个元素的大小关系,那么这个排序算法就是比较排序,大部分排序算法都是比较排序,比如冒泡排序、插入排序、堆排序等等,这种排序算法的平均时间复杂度最快也只能是O(nlogn)O(nlogn)。

非比较排序比较典型的有计数排序、桶排序和基数排序,这类排序能够脱离比较排序时间复杂度的束缚,达到O(n)O(n)级别的效率。

冒泡排序
冒泡排序是最为基础的排序算法,代码也比较简洁明了,且是一种稳定的算法(相等的元素顺序不会发生变化)

public void BubbleSort(int[] nums){
    for(int i=nums.length;i>=0;i--){//第一层循环相当于冒泡的过程,没经过一次循环“待排序”的数组长度减一
        for(int j=0;j<i;j++){
            if(nums[j]>nums[j+1]){
            swap(nums[j],nums[j+1]);//交换得到正确顺序
            }
        }
    }
}

时间复杂度为O(n^2),空间复杂度为O(1)

选择排序
选择排序的思路和冒泡排序差不多,每一轮都选出一个最大的值和最后一个元素交换,其实整个过程和冒泡排序差不多,都是要找到最大的元素放到最后,不同点是冒泡排序是不停的交换元素,而选择排序只需要在每一轮交换一次。

public void selectionSort(int[] nums){
    for(int i=nums.length;i>0;i--){//做n轮选择
    int MaxIndex=0;
    for(int j=0;j<=i;j++){
       if(nums[MaxIndex]<nums[j]){maxIndex=j;}  
}
swap(nums, maxIndex, i);   // 把这个最大的元素移到最后
    
}

时间复杂度和空间复杂度与冒泡排序相同

插入排序
插入排序的核心思想是遍历整个数组,保持当前元素左侧始终是排序后的数组,然后将当前元素插入到前面排序完成的数组的对应的位置,使其保持排序状态。有点动态规划的感觉,类似于先把前i-1个元素排序完成,再插入第i个元素,构成i个元素的有序数组

public void insertionSort(int[] nums){
      for(int i=1;i<nums.length;i++){//从第二项开始,其左边只有一个元素,所以可认为是排好序的
      int j=i;
      //while循环相当于对左边序列的一个排序
      while(j>=0&&nums[j-1]>nums[j]){
      swap(nums, j, j-1);
      j--
}
}
}

时间复杂度上,插入排序在最好的情况,也就是数组已经排好序的时候,复杂度是O(n)O(n),在其他情况下都是O(n^2)
空间复杂度是O(1)O(1),不需要额外的空间,是原地算法。

插入排序是稳定排序,每次交换都是相邻元素的交换,不会有选择排序的那种跳跃式交换元素。

归并排序
归并排序很好地体现了分治算法的思想,从而大大提高了算法的效率

private void mergeSort(int[] nums, int left, int right) {  // 需要左右边界确定排序范围
    if (left >= right) return;//递归出口
    int mid = (left+right) / 2;

    mergeSort(nums, left, mid);                           // 先“分”,即先对左右子数组进行排序
    mergeSort(nums, mid+1, right);

    int[] temp = new int[right-left+1];                   // 临时存放合并结果的数组
    int i=left,j=mid+1;
    int cur = 0;
    //再“合”,合并排序后的两个数组
    while (i<=mid&&j<=right) {                            
        if (nums[i]<=nums[j]) temp[cur] = nums[i++];
        else temp[cur] = nums[j++];
        cur++;
    }
    while (i<=mid) temp[cur++] = nums[i++];
    while (j<=right) temp[cur++] = nums[j++];

    for (int k = 0; k < temp.length; k++) {             // 合并数组完成,拷贝到原来的数组中
        nums[left+k] = temp[k];
    }
}


归并排序代码看似复杂,实则掌握好其过程,考虑递归的出口(left>=right)先“分”(先对左右子序列排序),再“合”(将排序好的子序列合并,实则也是再次排序),然后考虑特殊的情况(即有一部分子序列为被合并完全,需要再次用循环加入),最后拷贝入原数组.
时间复杂度上归并排序能够稳定在O(nlogn)的水平,在每一级的合并排序数组过程中总的操作次数是n,总的层级数是logn,相乘得到最后的结果就是O(nlogn)。

空间复杂度是O(n)O(n),因为在合并的过程中需要使用临时数组来存放临时排序结果。

归并排序是稳定排序,保证原来相同的元素能够保持相对的位置。

快速排序
快速排序(有时称为分区交换排序)是一种高效的排序算法。其核心的思路是取第一个元素(或者最后一个元素)作为分界点,把整个数组分成左右两侧,左边的元素小于或者等于分界点元素,而右边的元素大于分界点元素,然后把分界点移到中间位置,对左右子数组分别进行递归,最后就能得到一个排序完成的数组。当子数组只有一个或者没有元素的时候就结束这个递归过程。

int quicksort(int a[], int m, int p)
{
	int i = m, j = p,v=a[m];//v就是基准值
	while (i < j) {
		while (i<j&&a[j]>v) {
			j--;
		}
		while (i < j&&a[i] <= v) {
			i++;
		}
		if(i<j){
		  swap(a[i],a[j]);
		}
    a[m]=a[i];
    a[i]=v;//将基准值归位
	}
	quicksort(m,i-1);//递归处理左边的
	quicksort(i+1,p);//递归处理右边的
}

时间复杂度在最佳情况是O(nlogn),但是如果分界点元素选择不当可能会恶化到O(n^2),快速排序是不稳定的,因为包含跳跃式交换元素位置。

非比较排序:计数排序
该算法的核心思想是建立一个映射表,通过映射原数组中的每个数字在映射表中出现的次数来依次“写入”到原始表中,所需的映射表的大小最小是(max-min+1)保证原数组中每个数据都能被构造相应的映射
由于数组的下标都是非负的,所以我们将映射数组的下标index=nums[i]-min(nums)。
下面根据一个示例来讲解,比如现在有个待排序的整数序列A={-1, 2, 0, 4, 3, 6, 5, 8, -2, 1, 3, 0, 3,6, 5, 2}。首先我们花O(n)的时间扫描一下整个序列,可以得到max=8,min=-2。然后我们建立一个新的数组C,长度为(max-min+1)=11
在这里插入图片描述
此时我们再扫描一下数组A,比如对于-1,我们的操作是:-1-min=-1-(-2)=1;C[1]++。对于2,我们的操作是:2-(-2)=4;C[4]++。这样我们又花了O(n)的时间。操作结果是:在这里插入图片描述
然后我们可以根据count数组中出现值的个数来向写入相应个数的数字。

class Solution {
    public int[] sortArray(int[] nums) {
        int max = -65535, min = 65535;
        for (int num: nums) {//先找出最大最小值
            max = Math.max(num, max);
            min = Math.min(num, min);
        }


        int[] counter = new int[max - min + 1];//新建一个count表,count的大小至少是max-min+1
        for (int num: nums) {
            counter[num - min]++;//遍历原数组,找出每个元素出现的次数
        }

        int idx = 0;
        for (int num = min; num <= max; num++) {//由于这里要从min到max,保证被写入的num是原数组中的数字
            int cnt = counter[num - min];//num-min是对应count数组中的下标
            while (cnt-- > 0) {
                nums[idx++] = num;//如果cnt>0,则依次写入原数组,否则代表未出现过
            }
        }
        return nums;
    }
}


最后贴上不同排序算法之间的比较
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Pareto排序算法是一种用于多目标优化问题的排序方法,它可以将解集中的解按照非支配关系进行排序。在Pareto排序算法中,首先需要确定一个基准解,然后将其他解与基准解进行比较,根据非支配关系将解分为不同的层次。具体的排序方法有很多种,包括庄家法、擂台赛法、递归方法以及快速排序方法等\[2\]。 快速排序方法是目前普遍使用的Pareto排序算法之一。它通过将解集划分为两个子集,然后对每个子集进行递归排序,最终将解集按照非支配关系进行排序。在快速排序方法中,首先选择一个解作为基准解,然后将其他解与基准解进行比较,将非支配的解放入一个新的集合中。接下来,对新集合进行递归排序,直到所有解都被排序为止\[2\]。 通过多轮排序,Pareto最优解集可以被找到。在排序的过程中,解集中的解按照非支配关系被分为不同的层次。最终,我们可以得到一个非支配层次关系的种群个体集合\[3\]。 总结来说,Pareto排序算法是一种用于多目标优化问题的排序方法,它可以将解集按照非支配关系进行排序。其中,快速排序方法是一种常用的Pareto排序算法,通过递归地将解集划分为子集并进行排序,最终得到Pareto最优解集\[2\]\[3\]。 #### 引用[.reference_title] - *1* *2* *3* [多目标进化算法(二)——非支配排序/NSGA-II](https://blog.csdn.net/qq_38537501/article/details/123360414)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值