Algorithm--分治算法

递归 ==> 迭代

递归就是系统帮自己压栈:所有递归都可以用迭代解决
系统压栈用的空间比较大。自己压栈性能会好很多。

public static int getMax(int[] arr, int L, int R){
        if(L == R){  //结束递归
            return arr[L];
        }
        
        int mid = L + (R - L) / 2;  //左边界
        int maxLeft = getMax(arr, L, mid);
        int maxRight = getMax(arr, mid + 1, R);  //防止左边界不收缩。mid + 1
        return Math.max(maxLeft, maxRight);
    }

先递归在求解。

交换

public static void swap(int [] nums, int i, int j){  
        nums[i] = nums[i] ^ nums[j];
        nums[j] = nums[i] ^ nums[j];
        nums[i] = nums[i] ^ nums[j];
    }

public static void swap(int [] nums, int i, int j){
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}

1. 快速排序

排序中最重要的
期望时间复杂度: Θ \Theta Θ(nlgn)
最坏时间复杂度: Θ \Theta Θ( n 2 n^2 n2)
空间复杂度: O ( l g n ) O(lgn) O(lgn) ~ O ( n ) O(n) O(n)

QUICKSORT(A,p,r)
	if  p < r
		q = PARTITION(A, p, r)
		QUICKSORT(A, p, q - 1)
		QUICKSORT(A, q + 1, r)

为了排序一个数组A的全部元素, 初始调用是QUICKSOER(A, 1, A.length).

  • 算法的关键部分是PARTITION过程,它实现了对子数组A[q…r]的原址重排。
PARTITION(A, p, r)
	x = A[r]
	i = p -1
	for j = p to r-1
		if A[j] <= x 
			i = i + 1
			exchange A[i] with A[j]
	exchange A[i + 1] with A[r]
	return i + 1

i+1和j之间的元素是比pivot element 大的元素

快速排序的随机化版本

PARTITION(A, p, r)
	x = A[r]
	i = p -1
	for j = p to r-1
		if A[j] <= x 
			i = i + 1
			exchange A[i] with A[j]
	exchange A[i + 1] with A[r]
	return i + 1

RANDOMIZED-PARRTITION(A, p ,r)
	i = RANDOM(p, r)
	exchange A[r] with A[i]
	return PARTOTION(A, p , r)


QUICKSORT(A,p,r)
	if  p < r
		q = RADOMMIZED-PARTITION(A, p, r)
		QUICKSORT(A, p, q - 1)
		QUICKSORT(A, q + 1, r)

代码如下:常规思路

public static void quickSort(int[] nums){
        if(nums == null || nums.length == 0)
            return;
        quickSort1(nums, 0, nums.length - 1);
    }

    /**
     * 时间复杂度:
     * 最好:O(nlogn)
     * 最坏: O(n^2)
     * 平均: O(nlogn)
     * 空间复杂度:O(logn ~ n)
     * 稳定性: 不稳定
     * @param nums
     * @param left
     * @param right
     */

    public static void quickSort1(int[] nums, int left, int right) {
        if(left < right) {
        	swap(nums, left, left + (int)(Math.random() *(right - left + 1));
            int pivot = partition(nums, left, right);
            quickSort1(nums, left, pivot - 1);
            quickSort1(nums, pivot + 1, right);
        }
    }

    public static int partition(int[] nums, int left, int right) {
        int pivotIndex = left;
        int pivot = nums[pivotIndex];
        int l = left + 1;
        int r = right;
//        System.out.println("pivot: " + pivot + " 当前结果: " + Arrays.toString(nums));
//        System.out.println("pivotIndex: " + pivotIndex + ";  l,r: " + l + " " + r);
        while(l < r) {
            if(nums[l] > pivot && nums[r] < pivot){
                swap(nums, l++, r--);
//                System.out.println("排序过程:" + Arrays.toString(nums));
            }
            if(nums[l] <= pivot) l++;
            if(nums[r] >= pivot) r--; //与pivot小的元素交换,返回r
        }

        swap(nums, pivotIndex, r);
//        System.out.println("此轮结束:" + Arrays.toString(nums));
//        System.out.println();
        return r;
    }

快排优化代码如下:(荷兰国旗问题的延申)

	public static void newQuickSort(int[] nums){
        if(nums == null || nums.length == 0)
            return;
        newQuickSort1(nums, 0, nums.length - 1);
    }

    public static void newQuickSort1(int[] nums, int left, int right){
        if(left < right){
       		swap(nums, left + (int)(Math.random() *(right - left + 1)), right);
            int[] p = partition(nums, left, right);
            newQuickSort1(nums, left, p[0] - 1);
            newQuickSort1(nums, p[1] + 1, right);
        }
    }
	
	//划分的数:nums[right]
	//小于:左边界:0 右边界less   0~less
	//等于:左边界less + 1, 右边界left   less + 1 ~ left
	//待定区域:left + 1 ~ more - 1   
	//大于: more ~ right - 1
	//左后一步:swap(more, right) nums[right]是划分数组的数,最后在more的左边界
	//所以最后返回的是left + 1, more.(more为等于nums[right]->划分的数
    public static int[] partition(int[] nums, int left, int right){ //用left划分,省一个变量
        int less = left - 1;  //less代表左边小于nums[right]的索引
        int more = right; //more代表右边大于nums[right]的索引,右边right为比较的数,不能交换
        while(left < more){ 
            if(nums[left] < nums[right]){
                swap(nums, ++less, left++);
            } else if(nums[left] > nums[right]){
                swap(nums, --more, left);  //交换more前面一个数
            } else{
                left++;
            }
        }
        swap(nums, more, right); //more的索引是大于nums[right]
        return new int[]{less + 1, more};
    }

	//划分的数nums[right]
	//小于:0~less
	//等于:less+1 ~ cur - 1
	//待定:cur: cur ~ more - 1
	//大于: more - 1 ~ right - 1
	//最后一步 swap(more, right) ==> 下雨边界:0~ less, 等于边界:less+1 ~ more 大于边界:more + 1 ~ right   
    public static int[] partition2(int[] nums, int left, int right){
        int less = left - 1;
        int more = right;
        int num = nums[right];
        while(left < more){
            if(nums[left] < num){
                swap(nums, ++less, left++);
            } else if(nums[left] > num){
                swap(nums, --more, left);
            } else{
                left++;
            }
        }
        swap(nums, more, right);
        return new int[]{less + 1, more};
    }

最初版本划分Hoare划分

Hoare-PARTITION(A, p, r)
	x = A[p]
	i =  p + 1
	j = r
	while TRUE
		wile A[j] > x
			j = j - 1
		while A[i] < x
			i = i + 1
		if i < j
			exchange A[i] with A[j]
		return j

例题:荷兰国旗问题

给定一个数组arr,和一个数num, 请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。
返回的是等于区域的边界

public class NetherlandsFlags {
    public static int[] partition(int [] nums, int left, int right, int num){
        int less = left - 1;
        int more = right + 1;
        int cur = left;     //可以用left 代替cur
        while(cur < more){
            if(nums[cur] < num)
                swap(nums, ++less, cur++);
            else if(nums[cur] > num)
                swap(nums, --more, cur); // 大于nums的数放在后面,换回来的元素大小不确定
            else
                cur++;
        }
        return new int[]{less + 1, more - 1};
    }
}
public static int[] partition(int[] nums, int left, int right){
        int less = left - 1;
        int more = right;
        while(left < more){
            if(nums[left] < nums[right]){
                swap(nums, ++less, left++);
            } else if(nums[left] > nums[right]){
                swap(nums, --more, left);
            } else{
                left++;
            }
        }
        swap(nums, more, right);
        return new int[]{less + 1, more};
    }

2. 归并排序

时间复杂度: Θ \Theta Θ(nlgn)
空间复杂度: O ( n ) O(n) O(n) (辅助数组占用的空间,固定的)

MERGE-SORT(A, p, r)
	if p < r
		q = (p + r)/2
		MERGE-SORT(A, p, q)
		MERGE-SORT(A, q + 1, r)
		MERGE(A, p, q, r)  //合并

MERGE(A, p, q, r)
	n1 = q - p + 1
	n2 = r - q
	L[n1], R[n2]
	for i = 0 to n1
		L[i] = A[p + i]
	for j = 0 to n2
		L[j] = A[q + j + 1]
	i = 0; j = 0
	for k = p to r
		if(L[i] <= R[j]
			A[k] = L[i]
			i = i + 1
		else 
			A[k] = A[j]
			j = j + 1
	while i < n1 - 1
		A[k++]	= A[p + i]
		i = i + 1
	while j < n2-1
		A[k++] = A[q+1 + j]
		j = j + 1 
 public static void mergeSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        mergeSort(arr, 0, arr.length - 1);
    }

    public static void mergeSort(int[] arr, int left, int right) {
        if (left < right) { //继续条件
	        int mid = left + ((right - left) >> 1);
	        mergeSort(arr, left, mid);   //划分
	        mergeSort(arr, mid + 1, right);  //划分
	        merge(arr, left, mid, right);  //求解
	    }
	    return 0;
    }
//    public static int mergeSort(int[] nums, int left, int right){
//        if(left == right) {//递归结束条件
//            return 0;
//        }
//        int mid = left + (right - left) / 2;
//        int sumLeft = mergeSort(nums, left, mid);
//        int sumRight = mergeSort(nums, mid + 1, right);
//        return sumLeft + sumRight + merge(nums, left, mid, right);
//    }

    public static void merge(int[] arr, int left, int mid, int right) {
        int[] help = new int[right - left + 1];
        int i = 0;
        int p1 = left;
        int p2 = mid + 1;
        while (p1 <= mid && p2 <= right) {
            help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }
        while (p1 <= mid) {
            help[i++] = arr[p1++];
        }
        while (p2 <= right) {
            help[i++] = arr[p2++];
        }
        for (i = 0; i < help.length; i++) {
            arr[left + i] = help[i];
        }
    }

模板

mergeSort(int[] A, int left, int right){
	if(left == right)
		return 0;
	mid = left + (right - left) / 2;   //取左边界
	mergeSort(A, left, mid);   
	mergeSort(A, mid + 1,right);
	merge(A, left, mid, right); //求解
}

merge(int[] arr, int left, int mid, int right) {
        int[] help = new int[right - left + 1];
        int i = 0;
        int p1 = left;
        int p2 = mid + 1;
        while (p1 <= mid && p2 <= right) {
        	// your code, 求解方法	
            help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }
        while (p1 <= mid) {
            help[i++] = arr[p1++];
        }
        while (p2 <= right) {
            help[i++] = arr[p2++];
        }
        for (i = 0; i < help.length; i++) {
            arr[left + i] = help[i];
        }
    }

例题小和问题:在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求解一个数组的小和。

右边数组比左边某一个某数大的数目, 某数乘以数目。依次分批求解

代码:方法

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

    public static int mergeSort(int[] arr, int l, int r) {
        if (l == r) {
            return 0;
        }
        int mid = l + ((r - l) >> 1);
        return mergeSort(arr, l, mid) + mergeSort(arr, mid + 1, r) + merge(arr, l, mid, r);
    }

    public static int merge(int[] arr, int l, int m, int r) {
        int[] help = new int[r - l + 1];
        int i = 0;
        int p1 = l;
        int p2 = m + 1;
        int res = 0;
        while (p1 <= m && p2 <= r) {
            res += arr[p1] < arr[p2] ? (r - p2 + 1) * arr[p1] : 0; //求解代码
            help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }
        while (p1 <= m) {
            help[i++] = arr[p1++];
        }
        while (p2 <= r) {
            help[i++] = arr[p2++];
        }
        for (i = 0; i < help.length; i++) {
            arr[l + i] = help[i];
        }
        return res;
    }

正确的参考方法

    // for test
    public static int comparator(int[] arr) {
        if (arr == null || arr.length < 2) {
            return 0;
        }
        int res = 0;
        for (int i = 1; i < arr.length; i++) {
            for (int j = 0; j < i; j++) {
                res += arr[j] < arr[i] ? arr[j] : 0;
            }
        }
        return res;
    }

对数器检查算法是否正确

对数器产生随机数组

    // 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;
    }

    // 判断排序数组是否一样
    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;
    }

    // 打印数组
    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;
        for (int i = 0; i < testTime; i++) {
            int[] arr1 = generateRandomArray(maxSize, maxValue);
            int[] arr2 = copyArray(arr1);
            if (smallSum(arr1) != comparator(arr2)) {
                succeed = false;
                printArray(arr1);
                printArray(arr2);
                break;
            }
        }
        System.out.println(succeed ? "Nice!" : "Fucking fucked!");
    }

逆序对个数的问题

从大到小排序,找出比nums[p1]小的元素的个数

public static int reverse(int[] nums){
        if(nums == null || nums.length < 2)
            return 0;
        return mergeSort(nums, 0, nums.length - 1);
    }

    public static int mergeSort(int[] nums, int left, int right){
        if(left == right){ //结束条件
            return 0;
        }
        int mid = left + (right - left) / 2;
        int sumLeft = mergeSort(nums, left, mid);
        int sumRight = mergeSort(nums, mid + 1, right);
        return sumLeft + sumRight + merge(nums, left, mid, right);
    }

    public static int merge(int[] nums, int left, int mid, int right){
        int[] help = new int[right - left + 1];
        int i = 0;
        int p1 = left;
        int p2 = mid + 1;
        int sum = 0;
        while(p1 <= mid && p2 <= right){// 左右数组已经是从大到小排序了,找出比nums[p1]的数量
            sum += (nums[p1] > nums[p2] ?  (right - p2 + 1) : 0);
            help[i++] = nums[p1] > nums[p2] ? nums[p1++] : nums[p2++];  //从大到小排序,先确定大的元素
        }
        while(p1 <= mid){
            help[i++] = nums[p1++];
        }
        while(p2 <= right){
            help[i++] = nums[p2++];
        }
        for(i = 0; i < help.length; i++)
        {
            nums[left + i] = help[i];
        }
        return sum;
    }

    public static int[] generatorArray(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 + 1) * Math.random());
        }
        return arr;
    }

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

    public static int rightMethod(int[] nums){
        if(nums == null || nums.length < 2)
            return 0;
        int sum = 0;
        for(int i = 1; i < nums.length; i ++) //第i个数,前面有几个比他大的数
            for(int j = 0; j < i; j++) {
                sum += nums[j] > nums[i] ? 1 : 0;
            }
        return sum;

    }

    public static boolean isEqual(int sum1, int sum2){
        return sum1 == sum2 ? true : false;
    }

    public static void printArrary(int[] nums){
        if(nums == null || nums.length == 0 )
            return;
        for(int i = 0; i < nums.length; i++){
            System.out.print(nums[i] + " ");
        }
        System.out.println();;
    }

    public static void main(String[] args) {
        int maxSize = 100;
        int maxValue = 100;
        int times = 5000;
        boolean success = true;
        for(int i = 0; i < times; i++){
            int[] arr1 = generatorArray(maxSize, maxValue);
            int[] arr2 = copyArray(arr1);
            int sum1 = reverse(arr2);
            int sum2 = rightMethod(arr1);
            if(!isEqual(sum1, sum2)){
                System.out.println("reveser" + sum1);
                success = false;
                printArrary(arr1);
                printArrary(arr2);
                break;
            }
        }
        System.out.println(success ? "nice!" : "wrong");
    }

总结:快速排序是先排序在划分,所以后面的排序过程可以不要划分的数;归并排序是先划分后排序,所以每一个数都要在划分的子数组里面,而且要防止陷入死循环(防止死递归爆栈问题):取左中位数就要左边界收缩,取右中位数就要右边界收缩。

左边界:
mid = left + (right - left) / 2;
mid = (left + right)  >>> 1;
左边界收缩:
left = mid + 1

我们看极端的情况,left 和 high 都是整型最大值的时候,注意,此时 3232 位整型最大值它的二进制表示的最高位是 00,它们相加以后,最高位是 11 ,变成负数,但是再经过无符号右移 >>>(重点是忽略了符号位,空位都以 00 补齐),就能保证使用 + 在整型溢出了以后结果还是正确的。

右边界:
mid = left + (right - left + 1) / 2;
mid = (left + right + 1)  >>> 1;
右边界收缩:
right = mid - 1

快速排序和归并排序的时间复杂度都是O(nlogn), 但是快速排序要优于归并排序,快排赢在常数项上,快排排序过程只需要一次遍历就可以了,而归并排序需要两次遍历。但快排是不稳定的,而归并排序是稳定的(等于时,左边数组先插入辅助数组)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值