排序——选择排序(直接选择排序和堆排)

26 篇文章 0 订阅
5 篇文章 0 订阅
本文详细介绍了直接选择排序和堆排序算法,包括基本原理、优化方法、时间复杂度分析(O(n^2)),以及它们的空间复杂度(O(1))和稳定性特点。着重讨论了如何通过优化直接选择排序来减少比较次数,但时间复杂度不变。
摘要由CSDN通过智能技术生成

本专栏和大家分享关于排序的算法,其中有插入排(直接插入排序和希尔排序)、选择排序(直接选择排序和堆排)、交换排序(冒泡排序和快速排序)、归并排序以及其他非基于比较的排序

本文与大家分享选择排序

目录

 1.直接选择排序

优化:

时空复杂度:

稳定性:

2.堆排

向下调整的代码如下:

那么我们将数组向下调整后有什么用呢??

具体代码:

时空复杂度:

稳定性:

感谢您的访问!!!期待您的关注!!!


 1.直接选择排序

实际上就是在遍历的时候,每次记录i下标之后的最小值,放到i下标位置,这样最后就能形成一个有序数组,比较容易理解,我们直接通过代码演示

public static void selectSort(int[] array){
        for(int i = 0; i < array.length - 1; i++){
            int maxIndex = i;
            for(int j = i + 1; j < array.length; j++){
                if(array[j] > array[maxIndex]){
                    maxIndex = j;
                }
            }
            if(maxIndex != i){
                int tmp = array[maxIndex];
                array[maxIndex] = array[i];
                array[i] = tmp;
            }
        }
    }

但是时间复杂度太大!为O(N^2),是不稳定的排序

优化:

基于前面,我们可以在每一次遍历中不只是找最小值,而是最大值和最小值一起找

如图所示,我们定义一个左指针和一个右指针,接着利用i在left ~ right 里面遍历,找到一个最小值的下标和一个最大值的下标,将最小值与left的值进行交换,最大值与right的值进行交换,使得该区间的最小值和最大值分别在最左边和最右边的位置;接着 left ++,right--,缩小范围继续查找

但是有一个细节问题需要考虑:

如果我们某一段区间出现这种情况,我们找出最小值为1,最大值为10,交换left的元素与最小值:

接着我们要交换最大值与right的元素就会发现最大值原本是left上面的10,现在变成了1.这是因为我们的最大值刚好是left上的元素,而原来left上的元素又被最小值交换到minIndex的位置去了,因此当我们的maxIndex == left的时候,我们在交换完最小值后,需要将maxIndex = minIndex,去交换后的地方找原来的最大值

    public static void OptimizedSelectSort(int[] array){
        int left = 0;
        int right = array.length-1;
        while(left < right){
            int minIndex = left;
            int maxIndex = left;
            for(int i = left+1; i <= right; i++){
                if(array[i] < array[minIndex]){
                    minIndex = i;
                }
                if(array[i] > array[maxIndex]){
                    maxIndex = i;
                }
            }
            swap(array,left,minIndex);
            if(maxIndex == left){
                maxIndex = minIndex;
            }
            swap(array,right,maxIndex);
            left++;
            right--;
        }
    }

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

时空复杂度:

但是实际上时间复杂度还是O(N^2),分析时间复杂度的一种方法是考虑比较的次数。内层循环中,每个元素都可能与最大值和最小值进行比较,所以比较的次数仍然是 O(n^2)。虽然通过同时找到最大和最小值,每个元素可能会被比较两次,但这并不改变算法的时间复杂度。

空间复杂度为O(1)

稳定性:

是不稳定的排序:主要是因为在选择最小元素的过程中,相同元素的相对位置可能被打乱。


2.堆排

实际上认识了优先级队列的向下调整就能够理解堆排序

如果我们有如上图这样的数据,我们怎么将它设置为大根堆呢??

我们采用从最后一棵子树开始,依次向下调整的方法

假设每一棵子树的父亲节点下标为parent,子节点为child,那么最后一棵子树的子节点下标为 arr.length - 1,就能得到最后一棵子树的父亲节点下标为parent = (child - 1) / 2,即(arr.length - 1 ) /2 ;那么我们就从(arr.length - 1 ) /2 这个节点往前遍历,直到parent < 0,每次遍历都要针对每课子树进行向下调整

由于我们要建立的是大根堆,那么要求每棵子树的父亲节点都要大于任意一个子节点,那么我们就要在子节点里面找到一个最大的子节点,判断这个子节点是否大于父亲节点,如果大于,则交换

以4下标为根节点的子树已经判断完了,接下来parent--,判断以3下标为节点的所有子树

找到子节点中最大的一个,即为8,判断 9 > 4,那么9 与 4交换

那么以3下标为根节点的子树也就判断完了,接下来判断以2为节点的子树

可以看到,此时子树就不止一棵了,我们先判断以2为父亲节点的树,找到子节点中最大的,为10, 10 > 2,那么10与2交换,

此时我们会发现,交换完后,以4下标为父亲节点的树就不满足大根堆的要求了,因此我们要让parent = child,再继续向下调整,直到每课子树都满足向下调整的要求,交换完后是这样的

 

然后parent继续--,继续重复上述的流程即可

向下调整的代码如下:

public static void heap(int[] array){
         for(int parent = (array.length - 1 - 1) / 2; parent >= 0; parent--){
             siftDown(array,parent);
         }
     }
 ​
     private static void siftDown(int[] array,int parent){
         int child = 2 * parent + 1;
         while(child < array.length){
             if(child + 1 < array.length && array[child] < array[child+1]){//找到子节点最大的一个
                 child++;
             }
             if(array[parent] < array[child]){
                 swap(array,parent,child);
                 parent = child;
                 child = 2 * parent + 1;
             }else{
                 break;//不需要交换则说明这颗子树满足条件
             }
 ​
         }
     }
 ​
     private static void swap(int[] array,int i,int j) {
         int tmp = array[i];
         array[i] = array[j];
         array[j] = tmp;
     }

那么我们将数组向下调整后有什么用呢??

如图所示,我们以数组{6,4,7,5,8,3,9,2,10,1}建立完大根堆后,堆顶元素就是数组里面最大的元素,那么我们如果要得到增序列,就可以将堆顶元素与最后一个元素进行交换,就是将最大的数往后放接着再次进行向下调整,但是此时的向下调整不包括刚刚放进去的最大值,这样我们就能将第二大的元素通过向下调整放到堆顶,再次进行交换...依次类推

具体代码:

 public static void heapSort(int[] array){
        if(array.length == 0){
            return;
        }
        for(int parent = (array.length-1-1) / 2; parent >= 0; parent--){
            siftDown(array,parent,array.length);
        }
        int end = array.length - 1;
        while(end > 0){
            swap(array,0,end);
            siftDown(array,0,end);
            end--;
        }
    }

    private static void siftDown(int[] array,int parent,int end){
        int child = 2 * parent + 1;
        while(child < end){
            if(child + 1 < end&& array[child] < array[child+1]){//找到子节点最大的一个
                child++;
            }
            if(array[parent] < array[child]){
                swap(array,parent,child);
                parent = child;
                child = 2 * parent + 1;
            }else{
                break;//不需要交换则说明这颗子树满足条件
            }

        }
    }

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

时空复杂度:

堆排序包括两个主要步骤:建堆和排序。建堆过程的时间复杂度为O(n),排序过程,需要遍历每个节点,将第一个节点(最大的节点)放到末尾,同时每次都要进行向下调整,时间复杂度为O(n log n)。因此,整体的时间复杂度为O(n + n log n),简化后为O(n log n)。

空间复杂度为O(1)

稳定性:

不稳定的排序

在堆排序中,每次从堆中取出最大或最小元素时,都会与堆中的最后一个元素交换,然后重新调整堆以维持堆的性质。这个交换过程可能会导致相同元素的相对顺序发生改变,因此堆排序是不稳定的。

感谢您的访问!!!期待您的关注!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值