java---排序(重要)---原地排序

一、排序的稳定性
两个相等的数据,如果经过排序后,排序算法能保证其相对位置不发生变化,则称该算法是具备稳定性的排序算法
在这里插入图片描述
二、七大基于比较的排序原地排序
在这里插入图片描述
1、插入排序此处以升序为例
给定一个无序数组,将其分为已排序区间和待排序区间(初始情况下整个数组都为待排序区间,已排序区间为空) ,一般可将第一个元素看作是在已排序区间内

//1、插入排序
    //原地排序
    public static void insertSort(int[] arr){
        //设定一个bound标记,用来区分已排序区间和待排序区间
        //[0,bound)是已排序区间
        //[bound,length)是待排序区间
        int bound=1;
        //外层循环是更新待排序区间和已排序区间
        for(;bound<arr.length;bound++){
            //执行每次比较插入的过程
            //创建一个引用v指向待排序区间最开始的元素
            int v=arr[bound];
            int cur=bound-1;
            //内循环是实现比较,将v与其前一个元素(已排序区间最后一个元素)进行比较
            for(;cur>=0;cur--){
                //如果v比前一个元素小,则应把v插入到arr[cur]之前
                //把cur的位置往后搬运一个格子(画图理解)
                if(arr[cur]>v){
                   arr[cur+1]=arr[cur];
                }else{
                    //触发else即说明已经找到合适的位置(即cur的后面位置)了,循环结束(画图理解)
                    break;
                }
            }
            //找到合适的位置之后,将v插入该位置
            arr[cur+1]=v;
        }
    }

  1. 注意:插入排序是一个稳定排序,但一定要注意代码实现细节(不能是>=)
    在这里插入图片描述
    2)时间复杂度:O(N²)
    空间复杂度:O(1)–(额外开辟的空间)
    3**)插入排序的两个特点:**
    …如果数组长度比较短,排序效率很高
    …如果数组相对有序,排序效率也很高

二、希尔排序依据插入排序的两个特点进行的改进

  • 排序过程:针对序列进行分组,对每一组分别进行插入排序,随时调整分组的变化,逐渐使整个数组逼近有序的状态

  • 使用间隔系数gap来作为分组的依据
    eg:令gap=3,即下标差值为3的元素被分到一组
    在这里插入图片描述
    再令gap=2分组…
    再令gap=1,此时的希尔排序完全等价于插入排序

  • 前面的分组(组数多但长度短,效率高)+排序做好了准备工作,让原本的数组更加接近有序数组,使最后一步插入排序的效率更高

  • 实际选择时的常见序列:size/2,size/4,size/8…1 ------->希尔序列

  • 时间复杂度:O(N²)
    空间复杂度:O(1)
    稳定性:不稳定
    注意:并不是针对每个组分别进行插排,而是交替进行,先处理第0组的第一个数据,再处理第一组的第一个数据…处理第0组第二个数据,第一组第二个数据…

   //希尔排序
    public static void shellSort(int[] arr){
        //先设定一个gap值作为分组依据
        //希尔序列:size/2,size/4,size/8...1
        int gap=arr.length/2;
        while(gap>=1){
            //设定一个辅助方法,进行插排
            _shellSort(arr,gap);
            gap=gap/2;
        }

        
    }

    public static void _shellSort(int[] arr, int gap) {
        //设定一个边界值bound,用来区分待排序区间和已排序区间
        int bound=gap;

        for(;bound<arr.length;bound++){
            int v=arr[bound];
            int cur=bound-gap;
            for(;cur>=0;cur-=gap){
                if(v<arr[cur]){
                    //满足条件,搬运
                    arr[cur+gap]=arr[cur];
                }else{
                    //触发else即说明已经找到合适的位置,跳出循环
                    break;
                }
            }
            找到合适的位置之后,将v插入该位置
            arr[cur+gap]=v;
        }
    }

三、选择排序

  • 实现思路:
    先将整个数组分为两个区间(待排序区间和已排序区间),遍历待排序区间,通过打擂台的方式,找出这个区间中最小的元素(以待排序区间的开始位置作为擂台),则擂台位置的元素就相当于在已排序区间中了

  • 时间复杂度:O(N²)
    空间复杂度:O(1)
    稳定性:不稳定

    //选择排序
    public static void select   Sort(int[] arr){
        //设定一个bound标记,用来区分已排序区间和待排序区间
        //[0,bound)是已排序区间
        //[bound,length)是待排序区间
        //待排序区间起始位置为擂台(找出最小元素)
        int bound=0;
        for(;bound<arr.length;bound++){
            for(int cur=bound+1;bound<arr.length;cur++){
                if(arr[cur]>arr[bound]){
                   //如果挑战者比擂主小,交换两个元素
                    swap(arr,cur,bound);
                }
            }
        }

    }
    public  static void swap(int[] arr,int x,int y){
        int tmp=arr[x];
        arr[x]=arr[y];
        arr[y]=tmp;
    }


四、堆排序本质上是一种优化的选择排序

  • 直观上的理解:如果想进行升序排序,可以建立一个小堆,每次取出堆顶元素,依次取N次,放入一个新的数组中,就可的到一个升序数组,但是,我们此处研究的是原地排序,因此,该方法不可用
  • 基于原地排序的前提下:
    我们可以建立一个大堆,每次取出堆顶元素,让其和堆的最后一个元素进行交换,交换完成之后将最后一个元素删掉(此处的删除只是将其从堆中删除,而不是从数组中删除,即size–),要此时,这个最大值虽然从堆上删除了,但是就正好来到了这个数组的末尾。然后从0号元素进行向下调整(注意不是从最后一个非叶子节点调整), 使前面的元素重新称为堆(不算数组最末尾的元素,因为删除堆顶元素时要进行size–),再将0号元素和堆的最后一个元素进行交换(就相当于此时的0号元素来到了数组上的倒数第二个位置上了),重复上述过程
    //堆排序
    public static void heapSort(int[] arr){
        //先建立一个堆
        createHeap(arr);
        int heapSize=arr.length;  //堆的大小
        //循环取出堆顶元素,让其和堆的最后一个元素进行交换
        for(int i=0;i<arr.length;i++){
            swap(arr,0,heapSize-1);
            //交换完成之后,将最后一个元素从堆中删掉
            heapSize--;
            //从0号元素开始进行向下调整
            shiftDown(arr,heapSize,0);
        }

    }
    //向下调整
    public static void shiftDown(int[] arr, int heapSize, int index) {
        int parent=index;
        int child=2*parent+1;
        while(child<heapSize){
            //找出左右子树中比较大的
            if(child+1<heapSize&&arr[child+1]>arr[child]){
                //让child指向较大的元素
                child=child+1;
            }
            //再取比较child和parent
            if(arr[parent]<arr[child]){
                swap(arr,parent,child);
            }else{
                break;
            }
           //更新循环变量
            parent=child;
            child=2*parent+1;
        }
    }

    //建堆
    public static void createHeap(int[] arr) {
        //从数组的最后一个非叶子节点开始进行向下调整
        for(int i=(arr.length-1-1)/2;i>=0;i--){
            shiftDown(arr,arr.length,i);
        }
    }


五、冒泡排序

  • 时间复杂度:O(N²)
  • 空间复杂度:o(1)
  • 稳定性:稳定
//冒泡排序--->此处以升序排序为例(从后往前遍历)
    public static void bubbleSort(int[] arr){
        //设定一个bound标记,用来区分已排序区间和待排序区间
        //[0,bound)是已排序区间
        //[bound,length)是待排序区间
        int bound=0;
        for(;bound<arr.length;bound++){
            for(int cur=arr.length-1;cur>bound;cur--){
                if(arr[cur]<arr[cur-1]){
                    swap(arr,cur,cur-1);
                }
            }
        }
    }
     public  static void swap(int[] arr,int x,int y){
        int tmp=arr[x];
        arr[x]=arr[y];
        arr[y]=tmp;
    }

六、快速排序

-核心操作:partition:先在待排序数组中选取一个**“基准值”**(一般取第一个元素或者最后一个元素作为基准值),然后把这个数组整理成左侧比基准值小,右侧比基准值的的形式

  • 调整方式:使用左右下标从两边往中间走的方式来实现—>从左往右找一个比基准值大的数,从右往左找一个比基准值小的数,交换这两个数,重复上述过程,直到left于right重合,此时整理完毕,将重合位置元素与基准值进行交换,就会得到一个以基准值为中心,左侧比基准值小,右侧比基准值大的数组,然后递归地将基准值两侧的两个区间进行partition操作
 //快速排序
    public static void quickSort(int[] arr) {
        //创建一个辅助递归方法,明确指定针对哪个区间进行递归
        //[0,length-1]
        _quickSort(arr, 0, arr.length - 1);
    }

    public static void _quickSort(int[] arr, int left, int right) {
        while (left >= right) {
            return;
        }
        //符合条件时,进行partition操作,将基准值保存在一个变量中(即左右两侧区间分割线)
        int index = partition(arr, left, right);
        //对基准值左右两侧区间进行递归(快速排序)
        _quickSort(arr, left, index - 1);
        _quickSort(arr, index + 1, right);
    }

    public static int partition(int[] arr, int left, int right) {
        //先确定一个基准值,此处以最右侧元素作为基准值
        int v = arr[right];
        //创建两个下标
        int l = left;
        int r = right;
        //从左往右找到比基准值大的元素,从右往左找比基准值小的元素
        //交换
        while (l < r) {
            //先从左往右找一个比基准值大的数字
            //循环结束时,l就指向了一个比基准值大的数字
            while(l<r&&arr[l]<=v){
                l++;
            }
            //再从右往左找一个比基准值小的数字
            //循环结束时,r就指向了一个比基准值小的数字
            while(l<r&&arr[r]>=v){
                r--;
            }
            //找到之后,交换两个元素
            swap(arr,l,r);
        }
        //当l和r重合时,交换重合元素和基准值
        swap(arr,l,right);
        //返回基准值的位置
        return l;


    }
  • 注意:如果找最左侧元素为基准值,那就要先从右往左找比基准值小的元素,再从左往右找比基准值大的元素,然后交换(先后顺序很重要)
    - 时间复杂度
    平均:O(NlogN)—>每次选的基准值都是当前区间中比较接近中位数的数组
    最坏:O(N²)—>每次选的基准值都是最大/最小值

- 空间复杂度(取决于递归深度)
平均:O(logN)
最坏:O(N)

  • 稳定性:不稳定
    - 快速排序的优化手段
    1、三数取中:找到第一个元素、最后一个元素和中间位置元素,看三者哪一个更接近于中间值
    2、当待处理区间已经比骄小时,就不再进行递归了,直接针对该区间进行插入排序
    3、当递归深度达到一定深度,并且当前待处理区间还是比较大时,还可以使用堆排序
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值