面试中常考的排序算法比较

在这里插入图片描述
在这里插入图片描述
排序算法之(9)–八种常用排序算法效率对比

1、插入排序

思路:从位置1开始往后遍历,每个值插入到前面的合适位置。对于比他大的值,往后移一位,否则就插入

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

public static void insertSort(int[] nums) {
        //从1开始遍历
        for (int i = 1; i < nums.length; i++) {
            int temp = nums[i];
            //将前面大于num【i】的数往后移一位,给要插入的数腾位子
            int j = i - 1;
            //如果该数是此时最小的,遍历到位置0就结束。或者如果遇到不比他大的,插入即可
            while (j >= 0 && nums[j] > temp) {
                nums[j + 1] = nums[j];
                j--;
            }
            //插入该值
            nums[j + 1] = temp;
        }
    }

2、冒泡排序

思路:从前往后遍历nums.length次,每次遍历把前后两个相邻的数大小排序。这样每一趟都会把剩余数组中最大的数排到最后面。直到有序。相当于冒泡泡

优化:对于原本有序,或者还没遍历完nums.length次就已经有序了,不需要再遍历了。所以每一次遍历用flag记录,如果没有交换,说明有序,返回即可

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

public static void bubbleSort(int[] nums) {
        //遍历nums.length-1次
        for (int i = nums.length - 1; i > 0; i--) {
            //每次设置一个flag记录是否发生交换
            boolean flag = false;
            //剩余未排序数组冒泡一趟。把最大的值交换到后面
            for (int j = 0; j < i; j++) {
                //需要交换
                if (nums[j] > nums[j + 1]) {
                    int temp = nums[j];
                    nums[j] = nums[j + 1];
                    nums[j + 1] = temp;
                    flag = true;
                }
            }
            if (!flag) return;
        }
    }

3、快速排序法

思路:递归。
取剩余数组第一个值作为中枢,依次从后往前遍历,如果不比他小,跳过,否则把值放到最前面取值的位置。然后换成从前往后遍历。同理。交换遍历,最后指针相撞时结束。左右指针相等指向的位置把中枢值放到那里。中枢值把数组分成了左右两节。分别调用本方法进行排序。

递归结束条件:当传入的左指针>=右指针时,结束递归。

时间复杂度:O(nlog2n),空间复杂度:O(log2n)


```java
public static void quickSort(int[] nums, int left, int right) {
        //递归一定要有结束条件
        //当左指针等于右指针,说明数组只有一个元素。无需排序。当左指针大于右指针,说明前一个递归排序的中枢值就是第一个值。无序排序
        if (left >= right) return;

        //记录原始的左右开始的位置,用于后面递归调用
        int cp_left = left;
        int cp_right = right;
        //用一个标志来判断此时指针是从后往前移动还是从前往后移动,刚开始右指针从后往前移动。
        boolean isRight = true;
        //记录中枢值
        int temp = nums[left];
        //当左右指针相撞时停止
        while (left < right) {
            //判断移动方向。类似于加锁的思想。cas
            if (isRight) {
                if (nums[right] >= temp) right--;   //右指针的值不比中枢值小,不需要放到前面
                else {  //右指针的值比中枢值小,需要放到前面去
                    nums[left++] = nums[right];
                    isRight = false;    //更新标志
                }
            } else {    //否则左指针从前往后移动
                if (nums[left] <= temp) left++;
                else {
                    nums[right--] = nums[left];
                    isRight = true;
                }
            }
        }
        //最后当左右指针相等时,指向的位置就是中枢值的位置
        nums[left] = temp;

        //中枢值将数组分为左右两部分。递归调用本方法
        quickSort(nums, cp_left, left - 1);
        quickSort(nums, right + 1, cp_right);
    }

4、选择排序法

思想:遍历数组nums.length-2次,每次找到最小的元素,和剩余数组的值交换。剩余数组为数组前面排好序后面部分.

优点是思路简单,代码简单。

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

public static void selectSort(int[] nums) {
        //遍历遍历数组nums.length-2次,因为最后一次只剩两个元素,找到小的放到前面。那最后一个数字就是最大的
        for (int i = 0; i < nums.length-1; i++) {
            //每次记录最小值和数组下标。因为要交换
            int minVal = nums[i];
            int minIndex = i;
            for (int j = i; j < nums.length; j++) {
                if (nums[j] < minVal) {
                    minVal = nums[j];
                    minIndex = j;
                }
            }
            //交换
            nums[minIndex] = nums[i];
            nums[i] = minVal;
        }
    }

5、归并排序

基本思想
  归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
  归并排序的最好,最坏,平均时间复杂度均为O(nlogn)

分而治之
在这里插入图片描述
可以看到这种结构很像一棵完全二叉树,本文的归并排序我们采用递归去实现(也可采用迭代的方式去实现)。分阶段可以理解为就是递归拆分子序列的过程,递归深度为log2n。

合并相邻有序子序列
再来看看治阶段,我们需要将两个已经有序的子序列合并成一个有序序列,比如上图中的最后一次合并,要将[4,5,7,8]和[1,2,3,6]两个已经有序的子序列,合并为最终序列[1,2,3,4,5,6,7,8],来看下实现步骤。
在这里插入图片描述
在这里插入图片描述
代码实现

public static void sort(int []arr){
        int []temp = new int[arr.length];//在排序前,先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
        sort(arr,0,arr.length-1,temp);
    }
    private static void sort(int[] arr,int left,int right,int []temp){
        if(left<right){
            int mid = (left+right)/2;
            sort(arr,left,mid,temp);//左边归并排序,使得左子序列有序
            sort(arr,mid+1,right,temp);//右边归并排序,使得右子序列有序
            merge(arr,left,mid,right,temp);//将两个有序子数组合并操作
        }
    }
    private static void merge(int[] arr,int left,int mid,int right,int[] temp){
        int i = left;//左序列指针
        int j = mid+1;//右序列指针
        int t = 0;//临时数组指针
        while (i<=mid && j<=right){
            if(arr[i]<=arr[j]){
                temp[t++] = arr[i++];
            }else {
                temp[t++] = arr[j++];
            }
        }
        while(i<=mid){//将左边剩余元素填充进temp中
            temp[t++] = arr[i++];
        }
        while(j<=right){//将右序列剩余元素填充进temp中
            temp[t++] = arr[j++];
        }
        t = 0;
        //将temp中的元素全部拷贝到原数组中
        while(left <= right){
            arr[left++] = temp[t++];
        }
    }

最后
  归并排序是稳定排序,它也是一种十分高效的排序,能利用完全二叉树特性的排序一般性能都不会太差。java中Arrays.sort()采用了一种名为TimSort的排序算法,就是归并排序的优化版本。从上文的图中可看出,每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)。

6、计数排序

时间复杂度:O(N),空间复杂度:O(N)
02 基础版算法步骤
第一步:找出原数组中元素值最大的,记为max。

第二步:创建一个新数组count,其长度是max加1,其元素默认值都为0。

第三步:遍历原数组中的元素,以原数组中的元素作为count数组的索引,以原数组中的元素出现次数作为count数组的元素值。

第四步:创建结果数组result,起始索引index。

第五步:遍历count数组,找出其中元素值大于0的元素,将其对应的索引作为元素值填充到result数组中去,每处理一次,count中的该元素值减1,直到该元素值不大于0,依次处理count中剩下的元素。

第六步:返回结果数组result。

04 优化版
基础版能够解决一般的情况,但是它有一个缺陷,那就是存在空间浪费的问题。

比如一组数据{101,109,108,102,110,107,103},其中最大值为110,按照基础版的思路,我们需要创建一个长度为111的计数数组,但是我们可以发现,它前面的[0,100]的空间完全浪费了,那怎样优化呢?

将数组长度定为max-min+1,即不仅要找出最大值,还要找出最小值,根据两者的差来确定计数数组的长度。

详细内容:https://www.cnblogs.com/xiaochuan94/p/11198610.html

7堆排序

堆排序

堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。首先简单了解下堆结构。

堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。如下图:
  在这里插入图片描述
堆排序基本思想及步骤
堆排序的基本思想是:将待排序序列构造成一个大顶堆,此时,整个序列的最大值就是堆顶的根节点。将其与末尾元素进行交换,此时末尾就为最大值。然后将剩余n-1个元素重新构造成一个堆,这样会得到n个元素的次小值。如此反复执行,便能得到一个有序序列了

复杂度
堆排序是一种选择排序,整体主要由构建初始堆+交换堆顶元素和末尾元素并重建堆两部分组成。其中构建初始堆经推导复杂度为O(n),在交换并重建堆的过程中,需交换n-1次,而重建堆的过程中,根据完全二叉树的性质,[log2(n-1),log2(n-2)…1]逐步递减,近似为nlogn。所以堆排序时间复杂度一般认为就是O(nlogn)级。

图解排序算法(三)之堆排序

8希尔排序

希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。
希尔排序中对于增量序列的选择十分重要,直接影响到希尔排序的性能。我们选择的增量序列{n/2,(n/2)/2…1}(希尔增量),其最坏时间复杂度依然为O(n2),一些经过优化的增量序列如Hibbard经过复杂证明可使得最坏时间复杂度为O(n3/2)
在这里插入图片描述
在这里插入图片描述
图解排序算法(二)之希尔排序

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值