左神算法初级班二 笔记

荷兰国旗问题:将一个数组组织成为为左边<bias,中间=bias,右边>bias。

思路:借鉴类似快排的paration过程实现。

public static int[] paration(int[] arr,int L,int R){//paration过程后 数组变为 <bias =bias >bias
        int Less=L-1,More=R+1,cur=L;
        int bias=0;
        while(L<More){
            if(arr[cur]<bias){
                noProblemSwap(arr,++Less,cur++);//这里cur++是因为cur是从左到右的 已经考虑了[L,cur]的所有数
                //具体来说,因为我们的算法可以保证最后的数据状况为<bias =bias >bias,而在这中间的每一个过程 会分为三个可能存在的部分和一个必存在的待定区域
                //因此中间的状态为 <bias =bias 待定区域 >bias
                //如果说不存在=bias,那么cur=Less+1
                //如果存在=bias,那么arr[Less+1]=bias
                //所以我们可以保证cur扫过的数据一定会满足<=bias
            }
            else if(arr[cur]>bias){
                noProblemSwap(arr,--More,cur);//这里cur不动是因为我们仅仅知道arr[cur]>bias但是并不知道交换过来的arr[--More]和bias的大小关系
            }else {//arr[cur]==bias
                ++cur;
            }
        }
        return new int[]{Less+1,More-1};//等于的区间
    }

使用荷兰国旗问题优化经典快排:经典快排每一次只固定bias的位置,而通过荷兰国旗问题,如果在遇到重复数的情况,就可以大幅减少可能发生的递归,每次只需要对<bias的部分和>bias的部分递归即可,中间部分已经归为,不需要再处理。

使用随机索引来避开数据状况导致的快排复杂度高:

    public static int[] paration(int[] arr,int L,int R){
        int Less=L-1,More=R;
        while(L<More){
            if(arr[L]<arr[R]){
                noProblemSwap(arr,++Less,L++);
            }
            else if(arr[L]>arr[R]){
                noProblemSwap(arr,--More,L);
            }
            else {
                ++L;
            }
        }
        noProblemSwap(arr,More,R);
        return new int[]{Less+1,More};
    }

    public static void quickSort(int[] arr,int L,int R){
        if(L<R){
            noProblemSwap(arr,L+(int)(Math.random()*(R-L+1)),R);//将基准交换到右边界去
            int[] p=paration(arr,L,R);
            quickSort(arr,L,p[0]-1);
            quickSort(arr,p[1]+1,R);
        }
    }

    public static void Sort(int[] arr){
        if(arr==null||arr.length<2)return;
        quickSort(arr,0,arr.length-1);
    }

    public static void noProblemSwap(int[] arr,int i,int j){
        int temp=arr[i]^arr[j];
        arr[j]^=temp;
        arr[i]^=temp;
    }

    public static void main(String[] args) {
        int[] arr=new int[]{1,3,2,56,3436,6768,23,67};
        Sort(arr);
        for (int i : arr) {
            System.out.println(i+" ");
        }
    }

随机快排的长期期望度可以达到O(N*logN),而代码简洁说明快排的常数项低。

随机快排的额外空间复杂度为期望O(logN)。每一次paration需要一个长度为2的数组记录=区间的左右边界,而在随机快排下,递归层数的期望为logN,所以额外空间复杂度的长期期望是O(logN)的。

其他的优化方式:据我所知,在C++的STL里,对于sort函数,有针对不同数据规模使用不同的排序方式,但是它的快排,使用的是三中取值或九中取值的方式。三中取值即在L,Mid,R三个位置取中位数,用这个中位数去划分数组,相比于随机选取,划分过程会更好些,但是取中位数也是一个有消耗的操作,需自行取舍。

堆排序:将数组建立成堆结构帮助排序。

大根堆:对于一棵二叉树,满足任何一个非叶子节点的值大于它的两个孩子。

小根堆:对于一棵二叉树,满足任何一个非叶子节点的值小于它的两个孩子。

我们将以大根堆为例,在数组上组织树结构,索引为i的点,它的孩子是i*2+1,i*2+2,它的父亲为i-1/2。索引从0开始,0作为自己的父亲(在计算机里,-1/2等于0!但是注意,如果-1>>1的话,>>是带符号右移,-1的补码是111111.........111111,-1>>1的结果依然是-1,注意!!!!!!!)(一般是终止条件)。

建立堆结构过程:

    public static void heapInsert(int[] arr){
        for (int i = 0; i < arr.length; i++) {
            int index=i;
            while(arr[index]>arr[(index-1)>>1]){
                noProblemSwap(arr,index,(index-1)>>1);
                index=(index-1)>>1;
            }
        }
    }

为了保证堆结构支持元素的改变,所以需要设计堆中某一个元素改变后,能够重新调整称为堆的函数:

这里以大根堆,堆中某元素变小导致该元素要下沉为例:

    //大根堆中元素变小导致下沉
    public static void heapify(int[] arr,int index,int heapsize){//当前arr数组的0~heapsize-1为堆结构
        int left=(index<<1)+1;
        while(left<heapsize){
            int largest=left+1<heapsize&&arr[left+1]>arr[left] ? left+1:left;//如果右孩子处在堆内且右孩子更大 选择右孩子 否则左孩子
            largest=arr[largest]>arr[index]?largest:index;
            if(largest==index)break;//如果自己已经是最大的,无需再下沉
            noProblemSwap(arr,largest,index);
            index=largest;
            left=(largest<<1)+1;
        }
    }

而如果大根堆中一个元素变大,需要调用的过程和建立大根堆的方法是一样的。

堆排序:对于大根堆来说,每次把堆顶和堆尾的元素置换,让heapsize-1,即交换到堆尾的元素脱离堆结构,然后从堆顶开始做heapify重新调整为大根堆,继续此过程,直到heapsize=0。

public class Heap {

    public static void noProblemSwap(int[] arr,int i,int j){
        int temp=arr[i]^arr[j];
        arr[i]^=temp;
        arr[j]^=temp;
    }

    public static void heapInsert(int[] arr,int index){
        while(arr[index]>arr[(index-1)/2]){
            noProblemSwap(arr,index,(index-1)/2);
            index=(index-1)/2;
        }
    }

    //大根堆中元素变小导致下沉
    public static void heapify(int[] arr,int index,int heapsize){//当前arr数组的0~heapsize-1为堆结构
        int left=(index<<1)+1;
        while(left<heapsize){
            int largest=left+1<heapsize&&arr[left+1]>arr[left] ? left+1:left;//如果右孩子处在堆内且右孩子更大 选择右孩子 否则左孩子
            largest=arr[largest]>arr[index]?largest:index;
            if(largest==index)break;//如果自己已经是最大的,无需再下沉
            noProblemSwap(arr,largest,index);
            index=largest;
            left=(largest<<1)+1;
        }
    }

    public static void heapSort(int[] arr){
        if( arr == null || arr.length < 2)return;
        for (int i = 0; i < arr.length; i++) {
            heapInsert(arr,i);
        }
        int heapSize=arr.length;//现在数组全都是堆结构
        noProblemSwap(arr,0,--heapSize);
        while(heapSize>0){
            heapify(arr,0,heapSize);//0~heapsize-1重新建立为堆
            noProblemSwap(arr,0,--heapSize);
        }
    }

    public static void main(String[] args) {
        int[] arr=new int[]{1,3,2,56,3436,6768,23,67};
        heapSort(arr);
        for (int i : arr) {
            System.out.println(i+" ");
        }
    }
}

堆的应用:有一个流,有一个需求是在任意时刻拿到到目前为止,流输出的所有数的中位数。

方法:使用一个大根堆和一个小根堆,假设流当前吐出了N个数,我们想办法让较大的N/2的数放在小根堆,让较小的N/2个数放在大根堆,所以我们需要保持大根堆和小根堆的元素个数差不大于1,这样最后小根堆的顶和大根堆的顶一定处于中间。

1.初始化两个空堆,从流中读取数据,先放进大根堆

2.读取一个数,如果小于大根堆堆顶,丢近大根堆,否则丢进小根堆

3.如果两个堆的元素个数插值>1,此使从元素较多的堆中,将堆顶弹出放进另一个堆

4.要取中位数的时候,如果当前已经读取的个数为奇数,那么两个堆必然有一个堆,它的元素个数比另一个多1,直接弹出该堆的堆顶就是答案;如果已经读取了偶数个数,那么中位数是(小根堆堆顶+大根堆堆顶/2)。

import java.util.Comparator;
import java.util.PriorityQueue;

public class getMidNum {

    public static class smallerComparator implements Comparator<Integer>{
        @Override
        public int compare(Integer o1, Integer o2) {//小的在前
            return o1-o2;
        }
    }

    public static class biggerComparator implements Comparator<Integer>{
        @Override
        public int compare(Integer o1, Integer o2) {//大的在前
            return o2-o1;
        }
    }

    public static void main(String[] args) {
        PriorityQueue<Integer> bigger=new PriorityQueue<>(new biggerComparator());
        PriorityQueue<Integer> smaller=new PriorityQueue<>(new smallerComparator());
        int[] arr=new int[]{1,3,2,56,3436,6768,23,67};
        for (int i = 0; i < arr.length; i++) {
            if(bigger.isEmpty())bigger.add(arr[i]);
            else{
                if(bigger.peek()>arr[i])bigger.add(arr[i]);
                else smaller.add(arr[i]);
            }
            if(bigger.size()-smaller.size()>1){
                smaller.add(bigger.poll());
            }else if(smaller.size()-bigger.size()>1){
                bigger.add(smaller.poll());
            }
        }
        if(bigger.size()==smaller.size()) System.out.println((bigger.peek()+smaller.peek())/2);
        else System.out.println(bigger.size()>smaller.size()?bigger.peek():smaller.peek());
    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值