算法 (二)递归相关算法的时间复杂度分析(master公式引入)、归并思路练习:小和问题和逆序对问题

1、递归相关算法的时间复杂度分析(master公式引入)

一切递归行为都可以变成非递归行为,递归就是系统帮你压栈,子程序运行完之后弹栈,还原,继续往下执行

在学排序算法的时候,首先学习的一定是选择、插入(希尔排序是插入排序的一种)、冒泡三个排序,这个三个排序的时间复杂度均为O(N^2),空间复杂度为O(1)(关于这几个算法,请看:选择排序、插入排序、希尔排序冒泡排序

对于涉及到递归的,如紧接着这三个排序下面的归并排序(归并排序),这里要引入一个公式:master公式(这是最简形式,不考虑其他复杂变种,并且看递归次数只看第一级)

在这里插入图片描述

比如归并排序(自顶向下)算法,对半开递归+merge:2*T(N/2) + O(N),对照master公式算一下为:O(N*logN),额外空间复杂度O(N)(因为用到了辅助数组)

2、归并思路练习:小和问题和逆序对问题

Question1:小和问题

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


=======================================================================

Question2:逆序对问题

在一个数组中,左边的数如果比右边的数大,则折两个数构成一个逆序对,请打印所有逆序
对。

刚开始想这个问题的时候,有个问题困扰我,就是为什么要用归并排序的思路来做这个问题,为什么还要弄个辅助数组,这个辅助数组好像没有用啊,我又不是在排序,我是在做求和问题,所以我之前一直在想这个辅助数组的问题,直接比较arr[i]和arr[j],然后求和不就行了,后来发现,不管我怎么做,都是要加上这个辅助数组的

  1. 这里涉及到一个加快这个小和计算效率的方法,当排好序后,如果你比右边的某个数小,那么你就比(比这个数大的右边的数)都要小,一次性地帮你算完,就没你的事了
  2. 而且这个因为是递归的,会从相临近的两个数开始比较,不必担心顺序的问题,因为它会帮你自顶向下全部计算一下
  3. 辅助数组是提高它计算效率的关键,这个思路除了排序、计算小和、还可以得出逆序数组逆序
  4. 如果你既要用递归,又不用辅助数组,那么你就会少算一些东西,比如你算了比一个数小,指针拨过去了,这个数就不会再被计算了

所以你只要在归并排序的基础上,再添加一些东西就行了

小和问题代码(我这里用的是《算法_第四版》上面的归并排序思路,用的是if语句,可能和其他的文章不太一样,个人觉得更好理解:归并排序):

package cn.nupt.sort;

/**
 * @Description: 小和问题和逆序对问题
 *
 * @author PizAn
 * @date 2019年1月22日 下午12:14:50 
 * 
 */
public class SmallSum {
	
	

	public void mergeSort(int[] arr) {
		int lo = 0;
		int hi = arr.length - 1;
		int sort = mergeSort(arr, lo, hi);
		System.out.println(sort);

	}

	private int mergeSort(int[] arr, int lo, int hi) {
		if (lo == hi)
			return 0;

		int mid = lo + ((hi - lo) >> 1); // 用位运算符,更有逼格
		
		return mergeSort(arr, lo, mid) + mergeSort(arr, mid + 1, hi) + merge(arr, lo, mid, hi);

	}

	==========================小和问题=========================================

	private int merge(int[] arr, int lo, int mid, int hi) {

		int i = lo;
		int j = mid + 1;
		int[] arrSup = new int[arr.length];
		int sum = 0;

		for (int k = lo; k <= hi; k++) {
			arrSup[k] = arr[k];
		}

		for (int k = lo; k <= hi; k++) {
			if (i > mid) {                      //左半边用尽,用右半边元素
				arr[k] = arrSup[j++];
			} else if (j > hi) {				//右半边用尽,用左半边元素					
				arr[k] = arrSup[i++];
			} else if (arrSup[i] < arrSup[j]) { 
			
           ============主要添加的地方===========================
				sum += arrSup[i] * (hi - j + 1);
				
				arr[k] = arrSup[i++];
			} else {							//用完再用左半边	
				arr[k] = arrSup[j++];
			}
		}
		return sum;

	}

	=========================逆序对求解====================================

	private int merge(int[] arr, int lo, int mid, int hi) {

		int i = lo;
		int j = mid + 1;
		int[] arrSup = new int[arr.length];
		int sum = 0;

		for (int k = lo; k <= hi; k++) {
			arrSup[k] = arr[k];
		}

		for (int k = lo; k <= hi; k++) {
			if (i > mid) {                      //左半边用尽,用右半边元素
				arr[k] = arrSup[j++];
			} else if (j > hi) {				//右半边用尽,用左半边元素					
				arr[k] = arrSup[i++];
			} else if (arrSup[i] > arrSup[j]) { 
			
				===========主要添加的地方=============
				System.out.println("[" + arrSup[i] + ":" + arrSup[j] + "]");
				
				arr[k] = arrSup[j++];
			} else {							//用完再用左半边	
				arr[k] = arrSup[i++];
			}
		}
		return sum; //这里是为了不报错,删掉然后把上面的稍微改一下就行了

	}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值