Partition相关整理

1 篇文章 0 订阅

emmm迁移一下我的笔记好了...

partition 和single是对的

double是错误的

快排的partition算法

第一种是单向扫描法 - 算法导论

主要思想是,j 从 left 开始扫描,直到 j<right,这for循环过程中保证的是 i 及其左侧都是比 pivot 小的数

private static int partition(int[] arr, int left, int right) {
    int pivot = arr[right];
    int i=left-1;
    for(int j=left;j<=right-1;j++){  // for j left to right-1
        if(arr[j]<pivot){
            i++;
            if(i!=j)
            swap(arr,i,j);
        }
    }
    i++;
    swap(arr,i,right);
    return i;
}

第二种是双向扫描法,有多种具体实现办法,如下为“哨兵法”  fivejoy.github.io中记录 (当时记录时更理解挖坑填数法可见 也是很神奇哈哈)

private static int partition(int[] nums,int left,int right){
    int i=left;
    int j=right;
    int temp=nums[left];
    while(i<j) {   //!=也可以啊
        while(i<j&&nums[j]>temp)
            j--;
        while(i<j&&nums[i]<=temp)
            i++;
        if(i<j)
            swap(nums,i,j);
    }
    nums[left]=nums[i];
    nums[i]=temp;
    return i;
}

快排

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

private static void quickSort(int[] arr, int left, int right) {
    if(left>=right)
        return;
    int g=partitionMOSINgle(arr,left,right);
    quickSort(arr,left,g-1);
    quickSort(arr,g+1,right);
}

快排时间复杂度

快速排序的最好情况:每次选取的主元可正好将数组a中的元素平均分成两份 每次将问题分为两个等分的问题然后向下递归T(N)=O(NlogN)
快速排序的最坏情况:每次选取的主元都将数组a分为1个元素+N-1个元素两份·

计算方法如下

 

以下为快排中有必要了解的事情:

其中在partition选择pivot时,有多种方法,上面都选择的是取两侧,但是当序列有序时就比较容易出现最坏时间复杂度情况,即每次都是把序列分为N-1,1 的partition 。MOOC给出了其余三种选择方式

① 随机取pivot 但random()并不便宜

private static int selectPivot(int[] arr,int left,int right) {
    int i=left+(int)Math.random()*(right-left);
    swap(arr,left,i);
    return arr[i];
}

② 取 头中尾处元素 的中位数为pivot
③ 同理可以试试 5或者7个数取中位数

为什么插入排序比快排慢?

每次选择完pivot & 子集划分完后 ,pivot可被一次性放到其正确位置【保证最快!】。而在插入排序的每次交换中,元素所在的位置都是待定的,暂时的,可能下次循环又要后错一位【所以插入排序比快排慢咯】

如果有元素正好 = pivot 怎么办?MOOC
①停下并交换
所有元素都相等 eg. 1 1 1 1 1 1 1 ,此时会做出很多无谓的交换。但主元一定会换到比较中间的位置
② 不理它,继续移动。 但是最后主元会放到比较靠近边儿的位置—造成主元选取时①方法可能遇到的很囧的情况
SO所以选择① 吧

数据规模

快排对大规模数据做的不错,虽然归并和堆排也是O(NlgN),但快排的常数项更小(nowcoder)

而在小规模数据中,因为采用递归,所以递归的弊端包括需要占用额外系统堆栈空间,每次调用系统堆栈时需要把很多东西压入栈中,返回时也不断POP。所以对(e.g. N<100)小规模数据可能并不如插入排序块 (插入排序的常数项低,所以当N不大时 O(N^2)<O(NlogN))

所以当数据规模未知时,可以设定一个cutOff阈值,小于阈值则使用简单排序算法,大于则使用快排。这个思想来自如Arrays.sort(),其使用的cutOff是60

 

以上两种partition实现的快排均经过随机数验证,验证方法如下

// for test
public static void comparator(int[] arr) {
    Arrays.sort(arr);
}

// for test
public static int[] generateRandomArray(int maxSize, int maxValue) {
    int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
    for (int i = 0; i < arr.length; i++) {
        arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
    }
    return arr;
}

// for test
public static int[] copyArray(int[] arr) {
    if (arr == null) {
        return null;
    }
    int[] res = new int[arr.length];
    for (int i = 0; i < arr.length; i++) {
        res[i] = arr[i];
    }
    return res;
}

// for test
public static boolean isEqual(int[] arr1, int[] arr2) {
    if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
        return false;
    }
    if (arr1 == null && arr2 == null) {
        return true;
    }
    if (arr1.length != arr2.length) {
        return false;
    }
    for (int i = 0; i < arr1.length; i++) {
        if (arr1[i] != arr2[i]) {
            return false;
        }
    }
    return true;
}

// for test
public static void printArray(int[] arr) {
    if (arr == null) {
        return;
    }
    for (int i = 0; i < arr.length; i++) {
        System.out.print(arr[i] + " ");
    }
    System.out.println();
}
public static void main(String[] args){
    int testTime = 500000;
    int maxSize = 100;
    int maxValue = 100;
    boolean succeed = true;
    int i=0;
    for ( i = 0; i < testTime; i++) {
        int[] arr1 = generateRandomArray(maxSize, maxValue);
        int[] arr2 = copyArray(arr1);
        solution(arr1);
        comparator(arr2);
        if (!isEqual(arr1, arr2)) {
            succeed = false;
            printArray(arr1);
            printArray(arr2);
            break;
        }
    }
    System.out.println("has been tested for "+i+"times");
    System.out.println(succeed ? "Congratulations!" : "Sorry Wrong !");
}

 


以下是partition算法的常见应用

应用一:

数组中出现次数超过一半的数字(就是一个数组中有一个数总是出现,出现次数甚至>arr.length/2)from 剑指offer 面试题29

解法一:先排序,最中间的数,使用快排的话时间复杂度O(nlogn) 

解法二: 基于partition算法,O(n) 会改变数组状态,因为用到了swap

同1中所想,若排序,则数组中间的数字必为出现次数最多的数,同时排序数组最中间的数,也就是整个数组中第length/2大的数

注意下面一句话要好好理解,才能明白怎么用partition做。

先对数组partition,获得选中pivot的下标为g ,如果g=N/2,则表示这个数字就是整个数组中第length./2.那如果g<N/2则表示所求数字一定在g的右侧,否则在左侧

无效情况:①传入数组为null ② 传入数组中最多出现的数出现次数并未超过一半

无效情况可以通过 返回0+使用全局变量 标记

boolean invalidArray=true;
public static int moreThanHalfNums(int[] a){
    int left=0,right=a.length-1,mid=left+(right-left)>>1;
    int g=partition(a,left,right);
    while(g!=mid) {
        if (g < mid) {
            left=g+1;
            g=partition(a,left,right);
        } else  {
            right=g-1;
            g=partition(a,left,right);
        }
    }
        int result=a[g];
        //检查一下这个数是不是真的出现了>N/2次
        checkInvaid(a,g);
        return result;
}

数组中第K的数字 int getKthMax(int[] a,int k)

同理,把上面的方法中的mid替换为K就可以了

解法三:根据数字出现次数的O(N)算法 eMm不是很好理解。。

遍历数组,声明temp,count

保存当前数字为temp,如果下一个数字和temp相同,则次数-1,如果相同,次数+1,如果数字为零,则保存下一个数字为temp,且将次数设为1.

由于要找的数字出现次数比其他所有数字出现次数之和大,因此要找的数一定是最后一个将count设置为1的temp

用例二 输出数组中最小的前K个数

解法一:partition算法 类似用例二中解法二的推广 ,but需要修改数组,否则就得用额外空间

解法二:O(Nlogk)  容器法

创建大小为K的数据容器

每次读一个数,如果容器未满,将数填入容器;如果容器已满,找到容器中最大的数,如果这个最大的数比当前数小,则丢弃当前数,否则替换。

所以对于这个容器来说,需要满足三种操作:① 找到最大数;②删除最大数;③ 插入新数

所以如果使用二叉树实现这个数据容器,则可以满足在O(logk)时间复杂度内实现这三步,对于N个数字,总时间O(Nlogk)

二叉树中的最大堆可以O(1)找到最大值 

解法二同时适用于海量数据输入,

由于内存大小有限,所以不能将海量数据一次性载入内存,这时可以从你辅助存储空间(硬盘每次读入数据),此时只要求内存能够容纳数据容器的大小即可。

如何利用数组实现最大堆。。。。。?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值