算法?从这里开始(2)——递归,归并排序,小和问题,逆序对问题

1.递归

在一个数组中找最大值,最直观的方式的是遍历,当然可以,但我们这次要用递归的方式。怎么做哪?

假设我们有一个数组,最左边元素我们记为L,第右边元素我们记为R,那么(L+R)/2即为中间元素,我们记为mid。

那么以中间元素mid为分界线,我们只要求出两边最大元素,两边最大元素进行比较,最大的就是这个数组最大元素。至于怎么求两边最大元素,和求原数组最大元素,寻找中间元素切分即可。当L=R,不能切分的时候,再逆序返回即可。

java代码如下:

public class test222 {
    
    
    public static int getMax(int[] arr,int L,int R){
    
    
        if(L==R){
    
    
            return arr[L];
        }
        int mid = (L+R)/2;
        int maxLeft = getMax(arr,L,mid);
        int maxRight = getMax(arr,mid+1,R);
        return Math.max(maxLeft,maxRight);
    }
    public static void main(String[] args) {
    
    
        int[] arr = {
    
    4,3,2,1};
        System.out.println(getMax(arr,0,arr.length-1));
    }
}

也可以这样理解这代码,相信大家都看过动漫,动漫一开始是主线情节,发展发展就开始支线情节,看似与主线没啥关系,但都是为了给主线情节打下铺垫。最后再回归主线情节,因为有支线情节的存在,所以主线情节才显得如此合乎常理。如果没有支线情节的过度,火影忍者鸣人一开始各种丸子,显得就非常牵强了。当然,即使如此,忍界大战那些回忆和博人传的支线,也是不能忍的。

回到主题,这段代码同样如此,先主线情节,把数组分成两段,保存全部主线情节后,进入支线情节发展,两段分别最大值,支线情节结束后,再回归主线情节,主线和支线贯通,直到排序完成,结束代码运行。

那么问题来了:递归不像我们平常的循环,那我们要怎么判断它的时间复杂度呢?

首先,我们需要认识一个公式:master公式
它的概念是这样的:

T(N) = a*T(N/b) + O(N^d)

  1. log(b,a) > d -> 复杂度为O(N^log(b,a))
  2. log(b,a) = d -> 复杂度为O(N^d *logN)
  3. log(b,a) < d -> 复杂度为O(N^d)

这里的a表示的是代表代码被分成几段,b表示子过程样本量,d表示去子过程调用之外,剩下的时间复杂度是多少

以上面的代码为例,int[] arr = {4,3,2,1};寻找这个数组的最大值。首先判断a,我们把这个数组中间为界,平分成两断,即a=2(注:只看主线被切分成多少支线,不看支线被切分成支线的部分),再判断b,这个数组有4个元素,被切分后,每一部分有两个元素,即b=2,再判断d,子过程调用之位,只剩下了常数项操作,常数项操作一般忽略,记为0。

根据a,b,d,可以得到T(N) = 2*T(N/2)+O(1)
因为log(2,2)=1>d,故时间复杂度为O(N^log(2,2)),化简为 O(N)

总结

T(N):样本量为 N 的情况下,时间复杂度 N:父问题的样本量
a:子问题发生的次数(父问题被拆分成了几个子问题,不需要考虑递归调用,只考虑单层的父子关系)
b:被拆成子问题,子问题的样本量(子问题所需要处理的样本量),比如 N 被拆分成两半,所以子问题样本量为 N/2
O(N^d):剩余操作的时间复杂度,除去调用子过程之外,剩下问题所需要的代价(常规操作则为 O(1))

注意:master公式,只有在子问题划分规模一样才能用,如果划分规模不一样,则需要用相对应的数学公式计算。

补充阅读

2.归并排序

明白了上面的求最大值的递归原理,其实归并排序就很简单了。
先把右边的排好序,再把左边的排好顺,按照外排,再整体排好序,即可。
怎么理解这句话。举个例子:

我们有一个数组,里面元素是3,7,5,2,6,4
首先,我们中间在中间切分,左边为3,7,5,右边为2,6,4.
左边排好序为3,5,7,右边为2,4,6.
两边都设置一个指针,初始都指向第一个元素,再设置一个辅助数组。
指针指向的数进行比较,小的记入辅助数组,指针向右移一位,继续比较,当有一方到头,另一方按顺序去不计入辅助数组,再把辅助数组copy给原数组覆盖即可

演示:
3>2,2计入辅助数组,指向向右移动在这里插入图片描述

3<4,3计入辅助数组,指针向右移动一位,剩下 的以此类推,不做演示。在这里插入图片描述

那归并排序的时间复杂度是多少?

一个数组被切分成两份,一个为N的样本,单个样本量为N/2,外排的时间复杂度为O(N),我们可以得到,T(N) = 2T(N/2)+O(N),套用master公式,可得时间复杂度为O(N*logN)

java代码(归并排序+对数器):

package basic_class_01;

import java.util.Arrays;

public class Code_05_MergeSort {
    
    

	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 l, int r) {
    
    
		if (l == r) {
    
    
			return;
		}
		int mid = l + ((r - l) >> 1);
		mergeSort(arr, l, mid);
		mergeSort(arr, mid + 1, r);
		merge(arr, l, mid, r);
	}

	public static void 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;
		while (p1 <= m && p2 <= r) {
    
    
			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];
		}
	}

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

	// for test
	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);
			mergeSort(arr1);
			comparator(arr2);
			if (!isEqual(arr1, arr2)) {
    
    
				succeed = false;
				printArray(arr1);
				printArray(arr2);
				break;
			}
		}
		System.out.println(succeed ? "Nice!" : "Fucking fucked!");

		int[] arr = generateRandomArray(maxSize, maxValue);
		printArray(arr);
		mergeSort(arr);
		printArray(arr);

	}
}

小结:

一个排序的重要性,绝对不局限于它的功能,更重要的是理解它的思路。归并排序。其实就是让你理解分治这件事情。
在后面的文章中,我将写快排和堆排等排序算法,功能是占一边的,更重要的是他们实现的思路。 那怎么体现出,它的思路有用呢?
下面我们看两个具体题目:

3.小和问题

在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。
例子:
[1,3,4,2,5]
1左边比1小的数,没有;
3左边比3小的数,1;
4左边比4小的数,1、3;
2左边比2小的数,1;
5左边比5小的数,1、3、4、2;
所以小和为1+1+3+1+1+3+4+2=16

笨办法很直接,就是遍历一个数左边的元素,确实,这没有错误,但是时间复杂度为O(N*N),复杂度太高,写对数器应该这样写。一个特别好想,特别容易实现的方法。

这个题目可以用归并排序的思路,做出题目。

拿例子说事:[1,3,4,2,5]
把这个数组平分成两份,由于是奇数,所以无所谓哪边多一个,少一个。
然后继续下分,看下图:
在这里插入图片描述
如上图所示,1先和3比较,有1个小和,4在和1,3比较,有两个小和,左边的数组就找出所有小和。同理可得出,右侧小和。(注意:如果在找某一次小和时,乱序,那么找完小和后,将这段数组顺序)此时两侧数组都是顺序的,设置两个指针,让指针分别指向两侧数组第一个元素,然后进行比较,如果右边指针指向的数字大于左侧指针指向的数字,那么就有右侧指针后面有多少元素+1个左侧指针指向的数字的小和。这样说有点抽象,还是这个题目,2>1,所以1是2的小和,也是2后面元素5的小和,然后左侧指针右移,循环往复,直到结束为止。

java代码(小河问题+对数器):

package basic_class_01;

public class Code_12_SmallSum {
    
    

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

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

	// for test
	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!");
	}

}

4.逆序对问题

原理和归并排序,小和问题原理相同,不做做解释,上代码:

public class 逆序对问题 {
    
    
    public static void main(String[] args) {
    
    
        int[] arr = {
    
    5,78,36,32,87,777,5};
        s(arr,0,arr.length-1);
    }

    private static void s(int[] arr, int left, int right) {
    
    
        if(left==right){
    
    
            return;
        }
        int mid = (left+right)/2;
        s(arr,left,mid);
        s(arr,mid+1,right);
        w(arr,left,mid,right);
    }

    private static void w(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){
    
    
            int s = arr[p1]<arr[p2]?1:0;
            int x = p2;
            if(s==1){
    
    
               //(right-p2+1)
                for(int p=0;p<right-p2+1;p++){
    
    
                    System.out.println(arr[p1]+"和"+arr[x++]+"为逆序对");
                }
            }
            help[i++] = arr[p1]>arr[p2]?arr[p2++]:arr[p1++];
        }
        while (p1<=mid){
    
    
            help[i++] = arr[p1++];
        }
        while (p2<=right){
    
    
            help[i++] = arr[p2++];
        }
        for(i=0;i<help.length;i++){
    
    
            arr[i+left] = help[i];
        }
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_43912367/article/details/105533464
今日推荐