正文专注于<递归算法和分治思想>[胖虎学习算法系列]

本文专注于<递归算法和分治思想>[胖虎学习算法系列]
本文专注于<递归算法和分治思想>


初衷:博主看到网上有很多人贴出各种OJ上的AC代码,很多都会标注上“递归”两字
我刚开始学习递归算法和分治法的时候,苦于没有人写出递归算法和分治法的详细解析,很难系统地学习
所以小博主冒然写一篇总结性的博文,希望可以帮助到更多的人,当然再强调下,只为像我一样的新人准备,AC牛神们可无视哈!
相信无论你是大牛还是和博主一样的新手,花上10几分钟看下来,肯定也会有一定的收获的!

写在前面:转载请注明出处!
作者:胖虎
CSDN博客地址:http://blog.csdn.net/ljphhj
如果你觉得现在走的辛苦,那就证明你在走上坡路!



正文专注于<递归算法和分治思想>[胖虎学习算法系列]




本文的标题体现了本文主要要关注的知识点,我准备通过以下几部分来阐述我要说明的!


"算法基本介绍"->"典型案例分析讲解"->"笔试题、面试题的案例"


一、算法性质基本介绍(ps: 为没了解过递归算法和分治法的人提供,牛牛可略过!)


▲递归算法:


●递归算法的性质:
是把问题转化为规模缩小了的同类问题的子问题。然后递归调用函数(或过程)来表示问题的解。


●递归算法的特点: 
1.调用自身 
2.必须有一个明确的递归结束条件(递归出口) 
3.递归算法解题通常显得很简洁,但递归算法解题的运行效率较低。
4.在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。
(ps:3、4的特点即平时一般不推荐使用递归的原因)


●什么问题,我们可以用递归算法呢?
使用递归算法的要求:
1、每次调用在规模上都有所缩小(通常是减半(即分治))
2、前一次处理要为后一次处理做准备(通常前一次的输出就作为后一次的输入)
3、在问题的规模极小时必须用直接给出解答而不再进行递归调用,因而每次递归调用都是有条件的(以规模未达到直接解答的大小为条件),无条件递归调用将会成为死循环而不能正常结束。


▲分治思想:
●分治的基本思想:
是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。
求出子问题的解,就可得到原问题的解。


●分治法解题的一般步骤:
(1)分解,将要解决的问题划分成若干规模较小的同类问题;
(2)求解,当子问题划分得足够小时,用较简单的方法解决;
(3)合并,按原问题的要求,将子问题的解逐层合并构成原问题的解。

●什么问题,我们可以用分治法呢?
使用分治法的要求:

当我们求解某些问题时,由于这些问题要处理的数据相当多,或求解过程相当复杂,使得直接求解法在时间上相当长,或者根本无法直接求出。对于这类问题,我们往往先把它分解成几个子问题,找到求出这几个子问题的解法后,再找到合适的方法,把它们组合成求整个问题的解法。如果这些子问题还较大,难以解决,可以再把它们分成几个更小的子问题,以此类推,直至可以直接求出解为止。


二、典型案例分析讲解

实际上更多的情况是分治思想和递归算法的结合,所以以下的一些典型案例就不具体划分!

▲依我现在了解比较典型的案例大概有:


求N的阶乘、欧几里德算法(求最大公约数)、斐波那契数列、汉诺塔问题、树的三种递归遍历方式、快速排序、折半查找、图的遍历、归并排序、八皇后问题(回溯、递归)、棋盘覆盖(分治,递归)、Strassen矩阵乘法(分治)、最近点对问题(分治+递归)、循环赛日程表、凸包问题求解


(ps: 以上典型案例在后面都会讲解, 此博文持续更新哈,如果有哪位网友大神还有经典例子,希望留言哈!)


注意:以下有些算法用其他方式会有更好的效率,但是为了突出递归算法和分治思想,此博文中使用的方法将不会考虑到其他的更优的方法!


-------------------------------------------------------不积跬步无以至千里--------------------------------------------


题目:求N的阶乘

题意:n! = n * (n-1) * (n-2) * ..... * 2 * 1
这便是个典型的递归的算法.设 f(n) 为求n的阶乘的函数,那么可以一个可以等价为 n * f(n-1)
而递归结束的条件为: n == 1时,求1!直接返回数值1


JAVA实现代码:
package csdn.panghu.recursion;


/**
 * @Description:  [递归求N阶乘]
 * @Author:       [胖虎]   
 * @CreateDate:   [2014-3-31 下午9:36:20]
 * @CsdnUrl:      [http://blog.csdn.net/ljphhj]
 */
public class CalNFactorial {
	public static int f(int n){
		/*递归结束条件*/
		if (n == 1){
			return 1;
		}
		/*递归调用*/
		return n * f(n-1);
	}
	public static void main(String[] args) {
		System.out.println(f(5));
	}
}




-------------------------------------------------------不积跬步无以至千里--------------------------------------------


题目:欧几里德算法(求最大公约数)

题意:给出两个数m、n,求出两个数的最大公约数


JAVA实现代码:


package csdn.panghu.recursion;
/**
 * 
 * @Description:  [递归求解gcd欧几里德算法]
 * @Author:       [胖虎]   
 * @CreateDate:   [2014-3-31 下午10:51:47]
 * @CsdnUrl:      [http://blog.csdn.net/ljphhj]
 */
public class Gcd {
	
	public static int gcd(int m, int n){
		/*递归终结条件*/
		if (n == 0){
			return m;
		}
		/*递归调用*/
		return gcd(n, m%n);
	}
	public static void main(String[] args) {
		System.out.println(Gcd.gcd(6,4));
	}
}




-------------------------------------------------------不积跬步无以至千里--------------------------------------------


题目:斐波那契数列

题意:斐波那契数列,又称黄金分割数列,指的是这样一个数列:0、1、1、2、3、5、8、13、21、……

在数学上,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n>=2,n∈N*)

即F(0) = 0, F(1) = 1 为递归的终止条件



JAVA实现代码:


package csdn.panghu.recursion;
/**
 * ps: 只为讲解递归,所以并不是最优的算法,望牛牛们勿喷!
 * 
 * @Description:  [递归方式求解斐波那契数列]
 * @Author:       [胖虎]   
 * @CreateDate:   [2014-3-31 下午11:04:29]
 * @CsdnUrl:      [http://blog.csdn.net/ljphhj]
 */
public class FibonacciSequence {
	
	public static int fibonacci(int n){
		/*递归结束条件*/
		if (n == 0)
			return 0;
		if (n == 1)
			return 1;
		/*递归调用*/
		return fibonacci(n-1) + fibonacci(n-2);
	}
	
	public static void main(String[] args) {
		System.out.println(FibonacciSequence.fibonacci(6));
	}
}




-------------------------------------------------------不积跬步无以至千里--------------------------------------------


题目:汉诺塔问题(汉诺塔问题是经典递归问题)

题意:
有三个塔(分别为A号,B号和C号)。开始时.有n个圆形盘以从下到上、从大到小的次序叠置在A塔上。
现要将A塔上的所有圆形盘,借助B搭,全部移动到C搭上。且仍按照原来的次序叠置。 
移动的规则如下:
这些圆形盘只能在3个塔问进行移动.一次只能移动一个盘子,且任何时候都不允许将较大的盘子压在比它小的盘子的上面。 


如图:

正文专注于<递归算法和分治思想>[胖虎学习算法系列]




算法分析:
1.当n=1时,移动方式: A->C
2.当n=2时,移动方式:  A->B, A->C, B->C
3.当n=3时, 移动方式:  我们需要把上面两个2,借助C塔,移动到B塔上,然后把A塔的最大的盘移动到C塔,再借助A塔把B塔的两个盘移动到C塔(调用f(2)函数)


根据上面的列举,我们可以分析得到下面的解法.(通用性)


我们定义一个递归方法Hannoita(int N,char A,char B,char C)[这里的N代表移动的盘数,A表示盘子来源的塔,B表示移动时借助的塔,C表示盘子要移动到的目标塔]


设初始盘子个数为N
▲若A塔上仅仅只有一个盘子(N=1),则直接从A移动到C问题完全解决。
▲若A塔上有一个以上的盘子(N>1),则进行如下操作:
1.先把(n-1)个盘子从A塔经过移动,叠放到B塔上。在不违反规则情况下,所有(N一1)个盘子不能作为一个整体一起移动,而是要符合要求地从一个塔移到另一个塔上。
2.用Hannoita(N-1,A, C,B)调用递归方法,注意:这里是借助于C塔,将(N一1)个盘子从A塔移动到B塔,A是源塔,B是目标塔。 
3.将剩下的第N个盘子(也就是最底下的一个)直接从A塔叠放到空着的C塔上。 
4.用第一步的方法,再次将B塔上的所有盘子叠放到C塔上。同样,这一步实际上也是由一系列更小的符合规则的移动盘子的操作组成的,用Hannoita(N一1,B,A,C)调用递归方法【注意:这里是借助于A塔,将(N—1)个盘子从B塔移动到C塔,B是源塔,C是目标塔】 




JAVA实现代码:
package csdn.panghu.recursion;


/**
 * 
 * @Description:  [递归方式求解汉诺塔问题]
 * @Author:       [胖虎]   
 * @CreateDate:   [2014-4-1 上午12:16:59]
 * @CsdnUrl:      [http://blog.csdn.net/ljphhj]
 */
public class HannoitaProblem {


	public static void Hannoita(int n, char A, char B, char C){
		/*递归结束条件*/
		if (n == 1){
			System.out.println("n=" + n + " " + A + "-->" + C);
		}else{
			/*递归的调用*/
			Hannoita(n-1,A,C,B);
			System.out.println("n=" + n + " " + A + "-->" + C);
			Hannoita(n-1, B, A, C);
		}
	}
	
	public static void main(String[] args) {
		HannoitaProblem.Hannoita(3, 'A', 'B', 'C');
	}
}




-------------------------------------------------------不积跬步无以至千里--------------------------------------------


题目:二叉树的三种递归遍历方式

题意:二叉树的三种递归遍历方式,分别为先序遍历,中序遍历,后序遍历。我这里稍微讲下中序遍历的递归方式,其余的也一样哈


中序遍历:先遍历根节点的左子树,再遍历根结点,最后遍历根结点的右子树!
递归方式:我们会发现,当我们要遍历根结点的左子树的时候,根结点的left孩子,相当于左子树的根结点,那么遍历左子树又相当于调用了原来中序遍历的函数,同理遍历右子树也是!直接看代码的递归实现哈


JAVA实现代码:


package csdn.panghu.recursion;


/**
 * 
 * @Description:  [递归方式做二叉树的三种遍历]
 * @Author:       [胖虎]   
 * @CreateDate:   [2014-4-1 上午12:29:35]
 * @CsdnUrl:      [http://blog.csdn.net/ljphhj]
 * 
 * 
 *  输出结果:
 *  前序遍历:
	0 1 3 7 8 4 2 5 6 
	中序遍历:
	7 3 8 1 4 0 5 2 6 
	后序遍历:
	7 8 3 4 1 5 6 2 0 
 */
class TreeNode {
	int val;
	TreeNode left;
	TreeNode right;


	TreeNode(int x) {
		val = x;
	}
}
public class BinaryTreeTraversal {
	public static void PreOrderTraversal(TreeNode root){
		/*递归结束条件*/
		if (root == null)
			return ;
		System.out.print(root.val + " ");
		/*递归调用*/
		PreOrderTraversal(root.left);
		PreOrderTraversal(root.right);
	}
	public static void InOrderTraversal(TreeNode root){
		/*递归结束条件*/
		if (root == null)
			return ;
		
		/*递归调用*/
		InOrderTraversal(root.left);
		System.out.print(root.val + " ");
		InOrderTraversal(root.right);
	}
	public static void PostOrderTraversal(TreeNode root){
		/*递归结束条件*/
		if (root == null)
			return ;
		
		/*递归调用*/
		PostOrderTraversal(root.left);
		PostOrderTraversal(root.right);
		System.out.print(root.val + " ");
	}
	public static void main(String[] args) {
		//初始化一棵二叉树, 不影响递归的思想,可忽略
		TreeNode[] nodes = new TreeNode[9];
		for (int i=8; i>=0; --i){
			nodes[i] = new TreeNode(i);
			if (2*i + 1 > 8){
				nodes[i].left = null;
				nodes[i].right = null;
			}else{
				nodes[i].left = nodes[2*i+1];
				if (2*i + 2 > 8){
					nodes[i].right = null;
				}else{
					nodes[i].right = nodes[2*i+2];
				}
			}
		}
		System.out.println("前序遍历:");
		PreOrderTraversal(nodes[0]);
		System.out.println("\n中序遍历:");
		InOrderTraversal(nodes[0]);
		System.out.println("\n后序遍历:");
		PostOrderTraversal(nodes[0]);
	}
}



-------------------------------------------------------不积跬步无以至千里--------------------------------------------


题目:快速排序

它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

设要排序的数组是A[0]……A[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。


快速排序的算法是:
1)设置两个变量i、j,排序开始的时候:i=0,j=N-1;
2)以第一个数组元素作为关键数据,赋值给key,即key=A[0];
3)从j开始向前搜索,即由后开始向前搜索(j--),找到第一个小于key的值A[j],将A[j]赋给A[i];
4)从i开始向后搜索,即由前开始向后搜索(i++),找到第一个大于key的A[i],将A[i]赋给A[j];
5)重复第3、4步,直到i=j; (3,4步中,没找到符合条件的值,即3中A[j]不小于key,4中A[j]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直至找到为止。找到符合条件的值,进行交换的时候i, j指针位置不变。另外,i==j这一过程一定正好是i+或j-完成的时候,此时令循环结束)。


JAVA实现代码:


package csdn.panghu.recursion;


/**
 * 
 * @Description:  [展示快速排序中的递归算法 + 分治思想]
 * @Author:       [胖虎]   
 * @CreateDate:   [2014-4-1 上午12:46:14]
 * @CsdnUrl:      [http://blog.csdn.net/ljphhj]
 * 
 * 待排序列:{7,-1,10,2,0,9,11,3,4}
 * 输出排序结果:{-1,0,2,3,4,7,9,10,11}
 * 
 */
public class QuickSortProblem {


	public static int Partion(int[] arrays, int left, int right){
		if (right==left)
			return left;
		int key = arrays[left];
		while (left < right){
			//从后往前搜索比key小的元素,填入arrays[i]的位置中,并跳出循环
			while (left < right){
				if (arrays[right] < key){
					arrays[left] = arrays[right];
					break;
				}
				right--;
			}
			while (left < right){
				if (arrays[left] > key){
					arrays[right] = arrays[left];
					break;
				}
				left++;
			}
		}
		arrays[left] = key;	//最终取出来的这个比较数的位置
		//此时middle = left = right
		return left;
	}
	
	public static void QuickSort(int[] arrays, int left, int right){
		if (left >= right)
			return ;
		int middle = Partion(arrays, left, right);
		QuickSort(arrays,left,middle);
		QuickSort(arrays,middle+1,right);
	}
	
	public static void main(String[] args) {
		int[] arrays = new int[]{7,-1,10,2,0,9,11,3,4};
		QuickSort(arrays,0,arrays.length-1);
		for (int i=0; i<arrays.length; ++i){
			System.out.print(arrays[i] + " ");
		}
	}
}



-------------------------------------------------------不积跬步无以至千里--------------------------------------------


题目:折半查找

题意:在一个有序的数组中,要查找一个值为key的数是否存在于这个数组中,我们用递归的思想可以把问题分解成两个小的子问题,然后再一直细分下去,直到找到这个值或者已经没办法往下分了,但是在数组中还没找到值等于key的数


在有序表中,把待查找数据值与查找范围的中间元素值进行比较,会有三种情况出现:
1)待查找数据值与中间元素值正好相等,则放回中间元素值的索引。
2)待查找数据值比中间元素值小,则以整个查找范围的前半部分作为新的查找范围,执行1),直到找到相等的值。
3)待查找数据值比中间元素值大,则以整个查找范围的后半部分作为新的查找范围,执行1),直到找到相等的值
4)如果最后找不到相等的值,则返回错误提示信息。


JAVA实现代码:


package csdn.panghu.recursion;


/**
 * 
 * @Description:  [折半查找中使用到的递归和分治思想,O(logN)]
 * @Author:       [胖虎]   
 * @CreateDate:   [2014-4-1 上午1:24:01]
 * @CsdnUrl:      [http://blog.csdn.net/ljphhj]
 */
public class BinarySearch {
	
	public static boolean IsExist(int[] arrays, int key, int left, int right){
		/*递归结束条件*/
		if (left == right){
			if (arrays[left] == key){
				return true;
			}else{
				return false;
			}
		}
		/*递归调用*/
		int middle = (right + left) / 2;
		if (arrays[middle] > key){
			return IsExist(arrays, key, left, middle-1);
		}else if (arrays[middle] < key){
			return IsExist(arrays, key, middle+1, right);
		}else{
			return true;
		}
	}
	
	public static void main(String[] args) {
		int[] arrays = new int[]{-2,-3,1,2,3,4,5,6,7,10};
		System.out.println(BinarySearch.IsExist(arrays, -3, 0, arrays.length-1));
	}
}



-------------------------------------------------------不积跬步无以至千里--------------------------------------------


题目:图的遍历(DFS深度遍历/BFS广度遍历[非递归])  


-------------------------------------------------------基本图知识
图的简单介绍:
▲图的二元组定义:
图G由两个集合V和E组成,记为:G=(V,E)
其中:V是顶点的有穷非空集合,E是V中顶点偶对(称为边)的有穷集。

通常,也将图G的顶点集和边集分别记为V(G)和E(G)。E(G)可以是空集。若E(G)为空,则图G只有顶点而没有边。


-------------------------------------------------------基本图知识
▲有向图(若图G中的每条边都是有方向的,则称G为有向图(Digraph))
1、有向边的表示在有向图中,一条有向边是由两个顶点组成的有序对,有序对通常用尖括号表示。有向边也称为弧(Arc),边的始点称为弧尾(Tail),终点称为弧头(Head)。
【例】<vi,vj>表示一条有向边,vi是边的始点(起点),vj是边的终点。因此,<vi,vj>和<vj,vi>是两条不同的有向边。


2、有向图的表示
  【例】下面(a)图中G1是一个有向图。图中边的方向是用从始点指向终点的箭头表示的,该图的顶点集和边集分别为:
      V(G1)={v1,v2,v3}
      E(G1)={<v1,v2>,<v2,v1>,<v2,v3>}
如图示:(图1)
正文专注于<递归算法和分治思想>[胖虎学习算法系列]


-------------------------------------------------------基本图知识
▲无向图(若图G中的每条边都是没有方向的,则称G为无向图(Undigraph))
1、无向边的表示
无向图中的边均是顶点的无序对,无序对通常用圆括号表示。
【例】无序对(vi,vj)和(vj,vi)表示同一条边。


2、无向图的表示
  【例】下面(b)图中的G2和(c)图中的G3均是无向图,它们的顶点集和边集分别为:
    V(G2)={v1,v2,v3,v4}
    E(G2)={(vl,v2),(v1,v3),(v1,v4),(v2,v3),(v2,v4),(v3,v4)}
    V(G3)={v1,v2,v3,v4,v5,v6,v7}
    E(G3)={(v1,v2),(vl,v3),(v2,v4),(v2,v5),(v3,v6),(v3,v7)}
如图示:(图2)

正文专注于<递归算法和分治思想>[胖虎学习算法系列]


注意:不考虑顶点到其自身的边。即若(v1,v2)或<vl,v2>是E(G)中的一条边,则要求v1≠v2。此外,不允许一条边在图中重复出现,即只讨论简单的图
-------------------------------------------------------进入正题


算法思想:
先说明: 表示一个图的方法有好几种,我们这里采用邻接矩阵的方式来表示图。
▲图的邻接矩阵表示法:
在图的邻接矩阵表示法中:① 用邻接矩阵表示顶点间的相邻关系 Edges[图的顶点数][图的顶点数] ② 用一个顺序表来存储顶点信息 Vexs[图的顶点数]
【例】下图中无向图G5和有向图G6的邻接矩阵分别为Al和A2(图3)


正文专注于<递归算法和分治思想>[胖虎学习算法系列]




▲DFS深度遍历:假设给定图G的初态是所有顶点均未曾访问过。在G中任选一顶点v为初始出发点(源点),则深度优先遍历可定义如下:首先访问出发点v,并将其标记为已访问过(数组visited[index]的值置为"true")。然后依次从v出发搜索v的每个邻接点w。若w未曾访问过(判断数组visited[index] ?= true),则以w为新的出发点继续进行深度优先遍历(递归调用),直至图中所有和源点v有路径相通的顶点(亦称为从源点可达的顶点)均已被访问为止。

若此时图中仍有未访问的顶点,则另选一个尚未访问的顶点作为新的源点重复上述过程,直至图中所有顶点均已被访问为止。



▲BFS广度遍历:设图G的初态是所有顶点均未访问过。在G中任选一顶点v为源点,则广度优先遍历可以定义为:首先访问出发点v,接着依次访问v的所有邻接点w1,w2,…,wt,然后再依次访问与wl,w2,…,wt邻接的所有未曾访问过的顶点。依此类推,直至图中所有和源点v有路径相通的顶点都已访问到为止。此时从v开始的搜索过程结束。
若G是连通图,则遍历完成;否则,在图C中另选一个尚未访问的顶点作为新源点继续上述的搜索过程,直至G中所有顶点均已被访问为止。



图的遍历JAVA实现代码:
package csdn.panghu.recursion;


import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.Queue;


/**
 * 
 * @Description:  [图遍历算法(主要是DFS)中的递归算法]
 * @Author:       [胖虎]   
 * @CreateDate:   [2014-4-1 下午5:58:33]
 * @CsdnUrl:      [http://blog.csdn.net/ljphhj]
 * 
 * 
 * 输入: graph为博文中 图三 示例的有向图的邻接矩阵
 * 输出:
 *  图的深度遍历
	A顶点(V0)被访问了!
	B顶点(V1)被访问了!
	E顶点(V4)被访问了!
	D顶点(V3)被访问了!
	C顶点(V2)被访问了!
	图的广度遍历
	A顶点(V0)被访问了!
	B顶点(V1)被访问了!
	D顶点(V3)被访问了!
	E顶点(V4)被访问了!
	C顶点(V2)被访问了!
 */




class Graph{
	String[] Vexs;
	int[][] Edges;
	int VexsLen;
	public Graph(String[] vexs, int[][] edges) {
		super();
		Vexs = vexs;
		Edges = edges;
		VexsLen = vexs.length;
	}
}
public class GraphTraversal {
	
	public static void main(String[] args) {
		
		/*图的初始化: 我们选取一个博文中图3中的有向图做例子*/
		String[] vexs = new String[5];
		for (int i=0; i<5; ++i){
			vexs[i] = (char)('A' + i ) + "顶点(V" + i + ")";
		}
		int[][] edges = new int[][]{
			{0,1,0,1,0},
			{1,0,0,0,1},
			{0,1,0,1,0},
			{1,0,0,0,0},
			{0,0,0,1,0}
		};
		Graph graph = new Graph(vexs, edges);
		
		/*图的深度遍历(递归)*/
		System.out.println("图的深度遍历");
		DFSTraversal(graph);
		
		
		/*图的广度遍历(无关递归,但居然涉及到图遍历就也写下)*/
		System.out.println("图的广度遍历");
		BFSTraversal(graph);
		
	}
	
	public static void DFSTraversal(Graph graph){
		/*初始化visited[]数组*/
		boolean[] visited = new boolean[graph.VexsLen];
		for (int i=0; i<graph.VexsLen; ++i){
			visited[i] = false;
		}
		
		/*深度遍历*/
		for (int i=0; i<graph.VexsLen; ++i){
			if (!visited[i]){
				DFS(graph, i, visited);
			}
		}
	}
	
	/**
	 * 
	 * @param graph 图对象
	 * @param i		要访问的顶点下标i
	 */
	public static void DFS(Graph graph, int i, boolean[] visited){
		/*访问该顶点*/
		visitedMethod(graph,i);
		/*设置成已访问*/
		visited[i] = true;
		for (int k=0; k<graph.VexsLen; ++k){
			/*寻找还没有被访问过的邻接点*/
			if (graph.Edges[i][k] == 1 && !visited[k]){
				DFS(graph, k, visited);
			}
		}
		
	}
	
	public static void BFSTraversal(Graph graph){
		/*初始化visited[]数组*/
		boolean[] visited = new boolean[graph.VexsLen];
		for (int i=0; i<graph.VexsLen; ++i){
			visited[i] = false;
		}
		
		/*广度遍历,寻找源点*/
		for (int i=0; i<graph.VexsLen; ++i){
			if (!visited[i]){
				BFS(graph,i,visited);
			}
		}
	}
	
	public static void BFS(Graph graph, int i,boolean[] visited){
		if (graph.VexsLen < 0)
			return ;
		Queue<Integer> queue = new LinkedList<Integer>();
		queue.add(i);	//把源点的下标i加入到队列中
		
		while (!queue.isEmpty()){
			int index = queue.remove();
			visitedMethod(graph, index);
			visited[index] = true;	//设置下标index的顶点为已访问过
			for (int k=0; k<graph.VexsLen; ++k){
				/*寻找还没有被访问过的邻接点*/
				if (graph.Edges[index][k] == 1 && !visited[k]){
					queue.add(k);	//该顶点下标加入到队列中
				}
			}
		}
	}
	public static void visitedMethod(Graph graph, int i){
		System.out.println(graph.Vexs[i] + "被访问了!");
	}
}




-------------------------------------------------------不积跬步无以至千里--------------------------------------------


题目:归并排序(稳定的排序方法)

题意:给出一个数组大小为N,通过分治法把它排成有序的数组,复杂度O(logN)
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
算法思想:将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

归并,归并,按字面意思很容易理解,即通过 递归 + 合并(分治法)的排序方式


归并排序的JAVA实现代码:


package csdn.panghu.recursion;
/**
 * 
 * @Description:  [归并排序:体现递归和分治]
 * @Author:       [胖虎]   
 * @CreateDate:   [2014-4-1 下午9:18:42]
 * @CsdnUrl:      [http://blog.csdn.net/ljphhj]
 * 
 *  输入: 归并排序前的序列 {-7,1,0,10,9,-20,100,8,1}
 *  输出: 归并排序后的序列 {-20,-7,0,1,1,8,9,10,100}
 */
public class MergeSortProblem {
	
	public static void main(String[] args) {
		int[] arrays = new int[]{-7,1,0,10,9,-20,100,8,1};
		MergeSort(arrays);
	}
	
	public static void MergeSort(int[] arrays){
		int len = arrays.length;
		if (len < 2)
			return ;
		int left = 0;
		int right = len-1;
		
		Sort(arrays, left, right);
		
		System.out.println("归并排序后的序列");
		for (int i : arrays){
			System.out.print(i + " ");
		}
	}
	//排序
	private static void Sort(int[] arrays, int left, int right) {
		/*递归结束条件*/
		if(right == left){
			return ;
		}
		int middle = (right + left) / 2;  //二分
		/*递归调用(大问题分解成小问题)+分治思想(先左右两边排序)*/
		Sort(arrays, left, middle);
		Sort(arrays, middle+1, right);
		Merge(arrays, left, middle, right);/*结果的合并*/
	}
	
	//合并
	public static void Merge(int[] arrays, int left, int middle, int right){
		int len = right - left + 1;
		int[] temp = new int[len];
		int k = 0;
		int i = left;
		int j = middle+1;
		while (i <= middle && j <= right){
			if (arrays[i] < arrays[j]){
				temp[k++] = arrays[i];
				i++;
				continue;
			}else{
				temp[k++] = arrays[j];
				j++;
				continue;
			}
		}
		while (i <= middle){
			temp[k++] = arrays[i];
			i++;
		}
		while (j <= right){
			temp[k++] = arrays[j];
			j++;
		}
		k = k - 1;
		for (int m=right; m>=left; --m){
			arrays[m] = temp[k--];
		}
	}
}


-------------------------------------------------------不积跬步无以至千里--------------------------------------------


题目:八皇后问题(回溯、递归)[详细讲解N皇后问题(超详细注释),讲解的时候以八皇后的问题来讲,实现的时候实现一个N皇后的JAVA代码]


▲八皇后问题的介绍


八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8X8格的国际象棋上摆放八


个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种解法!


▲八皇后问题算法思路:


回溯的方法解决8皇后问题的步骤为:
 
1)从第一行开始,为皇后找到安全位置,然后跳到下一行
 
2)如果在第m行出现死胡同(找不到安全位置),如果该列为第一行,棋局失败,否则后退到上一行,在进行回溯(找这一行的下一个满足的皇后位置)
 
3)如果在第8行上找到了安全位置,则棋局成功。(8行上全部的皇后不会打架了,就是一种解法)
 
8个皇后都找到了安全位置代表棋局的成功,用一个长度为8的整数数组SafeQueens[8]代表成功摆放的8个皇后,数组下标代表棋盘的row向量,而数组的值为棋盘的col向量,所以(row,col)的皇后可以表示为(SafeQueens[row] = col)



【这个问题的解法,博主会做一定的讲解,放在代码的注释里个人觉得会比较好】
N皇后的JAVA实现代码:

package csdn.panghu.recursion;
/**
 * 
 * @Description:  [详细讲解N皇后问题(超详细注释)]
 * @Author:       [胖虎]   
 * @CreateDate:   [2014-4-2 上午12:40:49]
 * @CsdnUrl:      [http://blog.csdn.net/ljphhj]
 */
public class NQueenProblem {
	
	private final int SAFE_POSITION = 0;//表示当前位置为安全位置
	private int QueenCount;				//皇后的个数
	private int result = 0;				//解法的个数
	/**
	 * 表示棋盘的位置,数组里的值表示为“冲突的皇后数”
	 * 如:Position[i][j] = 2   表示 i 行, j列这个位置有两个皇后和它冲突
	 * 所以你放在这个位置,就会有两个皇后和你冲突!!即不满足条件
	 * 
	 * Position[i][j] = SAFE_POSITION = 0; 这样表示0个皇后和它冲突
	 * 所以你可以把皇后放在这个位置!
	 */
	private int[][] Position;			//表示棋盘的数组
	
	/* 
	 * 数组下标代表棋盘的row向量,
	 * 而数组的值为棋盘的col向量。
	 * 如:皇后(row, col) 可表示为(SafeQueens[row] = col)
	 * */
	private int[] SafeQueens;			//表示已经找到安全位置的皇后(为了输出解)
	
	private long time;					//用来计算算法所用时间
	
	public NQueenProblem(int n) {
		this.QueenCount = n;			//皇后的个数
		this.Position = new int[n][n];	//n*n棋盘
		this.SafeQueens = new int[n];
	}
	
	public void Solve() {
		if (QueenCount <= 0)
			return ;
		time = System.currentTimeMillis();
		FindSafeQueen(0);
	}
	/**
	 * 这个是要递归的函数
	 * @param row 表示要找的是第row行的皇后(row: 0 ~ N-1)
	 */
	public void FindSafeQueen(int row){
		for (int col=0; col<QueenCount; ++col){
			/*如果此位置现在是安全的位置*/
			if (Position[row][col] == SAFE_POSITION){
				SafeQueens[row] = col;//设置row行的皇后位置
				
				/*为棋盘添加冲突数: (以下注释较多,方便理解,也自己可以去除掉!!)*/
				/* 下面要做处理:把跟这个row行皇后处于
				 * “竖直方向”,“斜线方向”,“反斜线方向”
				 * 的位置标记一下
				 * 我们只处理row+1行 ~ N-1行所不能放皇后的位置
				 * 而不考虑小于等于row行的那些情况,因为我们取皇后的时候是一行行往下的
				 * */
				for (int dealRow=row+1; dealRow<QueenCount; ++dealRow){
					/* "|" (竖直方向)所波及到的位置,冲突皇后数+1个*/
					Position[dealRow][col]++;
					
					/* "/"(斜杠方向)所波及到的位置, 由于我们只考虑大于 row + 1 ~ 第N行,
					 * 只需要考虑斜线的左下方的那些位置(这样才能保证大于row行)
					 * 即条件:列 (col - (dealRow - row)) >= 0
					 * "列"不要越过下边界 0
					 * */
					if ((col - (dealRow - row)) >= 0){
						/* "/"(斜杠方向)所波及到的位置,冲突皇后数+1个*/
						Position[dealRow][(col - (dealRow - row))]++;
					}
					
					/* "\"(反斜杠方向)所波及到的位置, 由于我们只考虑大于 row + 1 ~ 第N(即QueenCount)行
					 * 我们只需要考虑反斜线的右下方的那些位置(这样才能保证大于row行)
					 * 即条件:(col + (dealRow - row)) < QueenCount
					 * "列"不要越过上边界 N(即QueenCount)
					 * */
					if ((col + (dealRow - row)) < QueenCount){
						/* "/"(斜杠方向)所波及到的位置,冲突皇后数+1个*/
						Position[dealRow][(col + (dealRow - row))]++;
					}
				}
				
				/*最后一行*/
				if (row == QueenCount-1){
					printOneResult(++result);
				}else{
					//递归调用,去求row+1行的N皇后位置
					FindSafeQueen(row+1);
				}
				
				/*为棋盘减少冲突数(跟增加冲突数相反)*/
				for (int dealRow=row+1; dealRow<QueenCount; ++dealRow){
					/* "|" (竖直方向)所波及到的位置,冲突皇后数 -1个*/
					Position[dealRow][col]--;
					if ((col - (dealRow - row)) >= 0){
						/* "/"(斜杠方向)所波及到的位置,冲突皇后数 -1个*/
						Position[dealRow][(col - (dealRow - row))]--;
					}
					if ((col + (dealRow - row)) < QueenCount){
						/* "/"(斜杠方向)所波及到的位置,冲突皇后数 -1个*/
						Position[dealRow][(col + (dealRow - row))]--;
					}
				}
			}
		}
		if(row == 0){
			System.out.println(QueenCount + "皇后共有 " + result + " 个解\n包括printf,共耗时:"
					+ (System.currentTimeMillis() - time) + "毫秒");
		}
	}
	
	private void printOneResult(int result) {
		System.out.println(QueenCount + "皇后的第 " + result + " 个解:(皇后: ▲  其他空位置: - )");
		for (int i = 0; i < QueenCount; i++) {
			for (int j = 0; j < QueenCount; j++) {
				System.out.print(SafeQueens[i] == j ? " ▲ " : " - ");
			}
			System.out.println();
		}
		System.out.println();
	}


	public static void main(String[] args) {
		int N = 8;	//定义N皇后问题
		NQueenProblem queenProblem = new NQueenProblem(N);
		queenProblem.Solve();
	}
}




-------------------------------------------------------不积跬步无以至千里--------------------------------------------


题目: 棋盘覆盖(分治)

题意:在一个2^k×2^k (k≥0)个方格组成的棋盘中,恰有一个方格与其他方格不同,称该方格为特殊方格。显然,特殊方格在棋盘中可能出现的位置有4^k种,因而有4k种不同的棋盘,棋盘覆盖问题(chess cover problem)要求用图1(b)所示的4种不同形状的L型骨牌覆盖给定棋盘上除特殊方格以外的所有方格,且任何2个L型骨牌不得重叠覆盖。


图1:

正文专注于<递归算法和分治思想>[胖虎学习算法系列]


图2:

正文专注于<递归算法和分治思想>[胖虎学习算法系列]


如何应用分治法求解棋盘覆盖问题呢?
算法思想:分治的技巧在于如何划分棋盘,使划分后的子棋盘的大小相同,并且每个子棋盘均包含一个特殊方格,从而将原问题分解为规模较小的棋盘覆盖问题。
当k>0时,可将2^k×2^k的棋盘划分为4个2^(k-1)×2^(k-1)的子棋盘,如图2(a)所示。这样划分后,由于原棋盘只有一个特殊方格,所以,这4个子棋盘中只有一个子棋盘包含该特殊方格,其余3个子棋盘中没有特殊方格。为了将这3个没有特殊方格的子棋盘转化为特殊棋盘,以便采用递归方法求解,可以用一个L型骨牌覆盖这3个较小棋盘的会合处,如图2(b)所示,从而将原问题转化为4个较小规模的棋盘覆盖问题。递归地使用这种划分策略,直至将


棋盘分割为1×1的子棋盘。(递归结束条件)


T(n)满足如下递推式:
T(n) = 1 当n=0时
T(n) = 4T(n-1) 当n>0时
解此递推式可得T(n)=O(4^n)


事先说明:
1、棋盘:可以用一个二维数组board[size][size]表示一个棋盘,其中,size=2^k。在递归处理的过程中使用同一个棋盘。
2、子棋盘:整个棋盘用二维数组board[size][size]表示,其中的子棋盘由棋盘左上角的下标leftRow、leftCol和棋盘大小size表示。
3、特殊方格:用board[specialRow][specialCol]表示特殊方格,specialRow和specialCol是该特殊方格在二维数组board中的下标;
4、L型骨牌:一个2^k×2^k的棋盘中有一个特殊方格,所以,用到L型骨牌的个数为(4^k-1)/3,将所有L型骨牌从1开始连续编号,用type表示。



棋盘覆盖问题的JAVA实现代码:


package csdn.panghu.recursion;
/**
 * 
 * @Description:  [棋盘覆盖问题:涉及递归]
 * @Author:       [胖虎]   
 * @CreateDate:   [2014-4-2 上午2:06:51]
 * @CsdnUrl:      [http://blog.csdn.net/ljphhj]
 * 
 * 呜呜~~我要睡觉!!!!!!!!
 */
public class ChessBoardProblem {
	
	private int[][] Board;	//模拟棋盘
	private int specialRow;
	private int specialCol;
	private int size;
	private int type = 0;
	
	public ChessBoardProblem(int specialRow, int specialCol, int size) {
		//初始化棋盘 和 特殊方格位置
		this.size = (int) Math.pow(2, size);
		this.Board = new int[this.size][this.size];
		this.specialRow = specialRow;
		this.specialCol = specialCol;
	}
	
	/**
	 * 
	 * @param specialRow  特殊方格所在位置的行标
	 * @param specialCol  特殊方格所在位置的列标
	 * @param leftRow	    要处理的子棋盘的左上方行标
	 * @param leftCol     要处理的子棋盘的左上方列标
	 * @param size		    要处理的子棋盘的大小(size * size)
	 */
	private void ChessBoard(int specialRow, int specialCol, int leftRow, int leftCol,int size){
		if (size == 1)
			return ;
		int subSize = size / 2;
		type = type % 4 + 1;
		int t = type;
		/*处理划分四个子棋盘的左上方那个棋盘*/
		if (specialRow < leftRow + subSize && specialCol < leftCol + subSize){
			//表示特殊方格存在于子棋盘的左上角
			ChessBoard(specialRow,specialCol,leftRow,leftCol,subSize);	//本来就有特殊方格,递归调用咯!
		}else{
			//如果特殊方格不在子棋盘的左上角,那么必定被划分之后要补充的特殊方格在左上角区域中的右下方那个方格
			Board[leftRow + subSize-1][leftCol + subSize-1] = t;	//设置为type类型的骨牌(即这个子棋盘也有了特殊方格)
			ChessBoard(leftRow + subSize-1,leftCol + subSize-1,leftRow,leftCol,subSize);	//有了这个特殊方格之后,


递归调用咯!
		}
		
		/*处理划分四个子棋盘的右上方那个棋盘*/
		if (specialRow < leftRow + subSize && specialCol >= leftCol + subSize){
			//表示特殊方格存在于子棋盘的右上角
			ChessBoard(specialRow,specialCol,leftRow,leftCol + subSize,subSize);	//本来就有特殊方格,递归调用咯!
		}else{
			//如果特殊方格不在子棋盘的右上角,那么必定被划分之后要补充的特殊方格在右上角区域中的左下方那个方格
			Board[leftRow + subSize-1][leftCol + subSize] = t;	//设置为type类型的骨牌(即这个子棋盘也有了特殊方格)
			ChessBoard(leftRow + subSize-1,leftCol + subSize,leftRow,leftCol + subSize,subSize);	//有了这个特殊方


格之后,递归调用咯!
		}
		/*处理划分四个子棋盘的左下方那个棋盘*/
		if (specialRow >= leftRow + subSize && specialCol < leftCol + subSize){
			//表示特殊方格存在于子棋盘的左下角
			ChessBoard(specialRow,specialCol,leftRow + subSize,leftCol,subSize);	//本来就有特殊方格,递归调用咯!
		}else{
			//如果特殊方格不在子棋盘的左下角,那么必定被划分之后要补充的特殊方格在左下角区域中的右上方那个方格
			Board[leftRow + subSize][leftCol + subSize-1] = t;	//设置为type类型的骨牌(即这个子棋盘也有了特殊方格)
			ChessBoard(leftRow + subSize,leftCol + subSize-1,leftRow+subSize,leftCol,subSize);	//有了这个特殊方

格之后,递归调用咯!
		}
		
		/*处理划分四个子棋盘的右下方那个棋盘*/
		if (specialRow >= leftRow + subSize && specialCol >= leftCol + subSize){
			//表示特殊方格存在于子棋盘的右下角
			ChessBoard(specialRow,specialCol,leftRow+subSize,leftCol+subSize,subSize);	//本来就有特殊方格,递归

调用咯!
		}else{
			//如果特殊方格不在子棋盘的右下角,那么必定被划分之后要补充的特殊方格在右下角区域中的左上方那个方格
			Board[leftRow+subSize][leftCol+subSize] = t;	//设置为type类型的骨牌(即这个子棋盘也有了特殊方格)
			ChessBoard(leftRow+subSize,leftCol+subSize,leftRow+subSize,leftCol+subSize,subSize);	//有了这个特殊方

格之后,递归调用咯!
		}
	}
	
	public void solve(){
		ChessBoard(specialRow,specialCol,0,0,size);
		printfResult();
	}
	
	private void printfResult() {
		for (int i=0; i<size; ++i){
			for (int j=0; j<size; ++j){
				System.out.print(Board[i][j] + " ");
			}
			System.out.println("");
		}
	}


	public static void main(String[] args) {
		int N = 2;
		int specialRow = 0;
		int specialCol = 1;
		ChessBoardProblem chessBoardProblem = new ChessBoardProblem(specialRow, specialCol, N); 
		chessBoardProblem.solve();
	}
}





-------------------------------------------------------不积跬步无以至千里--------------------------------------------


题目:Strassen矩阵乘法(分治 + 递归)

Strassen矩阵乘法的复杂度t(n) = (nlog27)。因为log27 ≈2 . 8 1,所以与直接计算方法的复杂性(n3)相比,分而治之矩阵乘法算法有较大的改进。
题意:矩阵乘法是一种高效的算法可以把一些一维递推优化到log( n ),还可以求路径方案等,所以更是是一种应用性极强的算法。矩阵,是线性代数中的基本概念之一。一个m×n的矩阵就是m×n个数排成m行n列的一个数阵。


我们来看看矩阵相乘的说明:
设A,B,C都为2*2的矩阵
那么求矩阵C = A*B,可写为
C11 = A11*B11 + A12*B21
C12 = A11*B12 + A12*B22
C21 = A21*B11 + A22*B21
C22 = A21*B12 + A22*B22
则共需要8次乘法和4次加法。
如果阶大于2,可以将矩阵分块进行计算。耗费的时间是O(nE3)


要改进算法计算时间的复杂度,必须减少乘法运算次数。
按分治法的思想,Strassen提出一种新的方法,用7次乘法完成2阶矩阵的乘法,
算法如下:
M1 = A11 * (B12 - B12)
M2 = (A11 + A12) * B22
M3 = (A21 + A22) * B11
M4 = A22 * (B21 - B11)
M5 = (A11 + A22) * (B11 + B22)
M6 = (A12 - A22) * (B21 + B22)
M7 = (A11 - A21) * (B11 + B12)
完成了7次乘法,再做如下加法:
C11 = M5 + M4 - M2 + M6
C12 = M1 + M2
C21 = M3 + M4
C22 = M5 + M1 - M3 - M7
全部计算使用了7次乘法和18次加减法,计算时间降低到O(nE2.81)。计算复杂性得到较大改进。(当n越大越明显)


递归方程为T(n) = 7*T(n/2) + O(n^2) (n > 2),优于传统的划分T(n) = 8*T(n/2) + O(n^2) (n > 2)。


Strassen矩阵乘法的JAVA实现:

package csdn.panghu.recursion;


/**
 * 
 * @Description:  [Strassen矩阵乘法]
 * @Author:       [胖虎]   
 * @CreateDate:   [2014-4-2 上午9:13:16]
 * @CsdnUrl:      [http://blog.csdn.net/ljphhj]
 * 
 *  输入的矩阵:A
	1	2	3	4	
	2	4	6	8	
	3	6	9	12	
	4	8	12	16	
	输入的矩阵:B
	1	2	3	4	
	2	4	6	8	
	3	6	9	12	
	4	8	12	16	
	输出的矩阵:C
	30	60	90	120	
	60	120	180	240	
	90	180	270	360	
	120	240	360	480	
 */
public class StrassenProblem {
	
	public void solve(int N, int[][] A, int[][] B, int[][] C){ 
		if (N <= 0)
			return ;
		strassen(N,A,B,C);
	}
	
	/**
	 * 
	 * @param N 矩阵的规模
	 * @param A 矩阵A
	 * @param B 矩阵B
	 * @param C 结果矩阵C
	 */
	private void strassen(int N, int[][] A, int[][] B, int[][] C){
		//定义一些中间变量
        int [][] M1=new int [N][N];
        int [][] M2=new int [N][N];
        int [][] M3=new int [N][N];
        int [][] M4=new int [N][N];
        int [][] M5=new int [N][N];
        int [][] M6=new int [N][N];
        int [][] M7=new int [N][N];
         
        int [][] C11=new int [N][N];
        int [][] C12=new int [N][N];
        int [][] C21=new int [N][N];
        int [][] C22=new int [N][N];
         
        int [][] A11=new int [N][N];
        int [][] A12=new int [N][N];
        int [][] A21=new int [N][N];
        int [][] A22=new int [N][N];
         
        int [][] B11=new int [N][N];
        int [][] B12=new int [N][N];
        int [][] B21=new int [N][N];
        int [][] B22=new int [N][N];
         
        int [][] temp=new int [N][N];
        int [][] temp1=new int [N][N];


		
		
		/*递归结束条件: 如果矩阵为2*2规模的,直接算!*/
		if (A.length == 2){
			MatrixMul(A,B,C);
		}else{
            //首先将矩阵A,B 分为4块
            for(int i = 0; i < A.length/2; i++) {
                for(int j = 0; j < A.length/2; j++) {
                     A11[i][j]=A[i][j];
                     A12[i][j]=A[i][j+A.length/2];
                     A21[i][j]=A[i+A.length/2][j];
                     A22[i][j]=A[i+A.length/2][j+A.length/2];
                     B11[i][j]=B[i][j];
                     B12[i][j]=B[i][j+A.length/2];
                     B21[i][j]=B[i+A.length/2][j];
                     B22[i][j]=B[i+A.length/2][j+A.length/2];
                }
            }
            //计算M1
            MatrixSub(B12, B22, temp);
            MatrixMul(A11, temp, M1);
            //计算M2
            MatrixAdd(A11, A12, temp);
            MatrixMul(temp, B22, M2);
            //计算M3
            MatrixAdd(A21, A22, temp);
            MatrixMul(temp, B11, M3);
            //M4
            MatrixSub(B21, B11, temp);
            MatrixMul(A22, temp, M4);
            //M5
            MatrixAdd(A11, A22, temp1);
            MatrixAdd(B11, B22, temp);
            MatrixMul(temp1, temp, M5);
            //M6
            MatrixSub(A12, A22, temp1);
            MatrixAdd(B21, B22, temp);
            MatrixMul(temp1, temp, M6);
            //M7
            MatrixSub(A11, A21, temp1);
            MatrixAdd(B11, B12, temp);
            MatrixMul(temp1, temp, M7);
             
            //计算C11
            MatrixAdd(M5, M4, temp1);
            MatrixSub(temp1, M2, temp);
            MatrixAdd(temp, M6, C11); 
            //计算C12
            MatrixAdd(M1, M2, C12);
            //C21
            MatrixAdd(M3, M4, C21);
            //C22
            MatrixAdd(M5, M1, temp1);
            MatrixSub(temp1, M3, temp);
            MatrixSub(temp, M7, C22);
             
            //结果送回C中
            for(int i = 0; i < C.length/2; i++) {
                for(int j = 0; j < C.length/2; j++) {
                    C[i][j]=C11[i][j];
                    C[i][j+C.length/2]=C12[i][j];
                    C[i+C.length/2][j]=C21[i][j];
                    C[i+C.length/2][j+C.length/2]=C22[i][j];
                }
            }
		}
	}
	/**
     * 矩阵乘法,此处只是定义了2*2矩阵的乘法
     * */
    public void MatrixMul(int[][] first, int[][] second, int[][] resault){
        for(int i = 0; i < 2; ++i) {
            for(int j = 0; j < 2; ++j) {
                resault[i][j] = 0;
                for(int k = 0; k < 2; ++k) {
                    resault[i][j] += first[i][k] * second[k][j];
                }
            }
        }
 
    }
 
    /**
     * 矩阵的加法运算
     * */
    public void MatrixAdd(int[][] first, int[][] second, int[][] resault){
        for(int i = 0; i < first.length; i++) {
            for(int j = 0; j < first[i].length; j++) {
                resault[i][j] = first[i][j] + second[i][j];
            }
        }
    }
     
    /**
     * 矩阵的减法运算
     * */
    public void MatrixSub(int[][] first, int[][] second, int[][] resault){
        for(int i = 0; i < first.length; i++) {
            for(int j = 0; j < first[i].length; j++) {
                resault[i][j] = first[i][j] - second[i][j];
            }
        }
    }
	public static void main(String[] args) {
		int N = 4;	//矩阵的大小
		int[][] A = new int[N][N];
		int[][] B = new int[N][N];
		int[][] C = new int[N][N];
		/*A, B矩阵的初始化,具体值无所谓哈!也可以自己录入*/
		for (int i=0; i<N; ++i){
			for (int j=0; j<N; ++j){
				A[i][j] = (i+1) * (j+1);
				B[i][j] = (i+1) * (j+1);
			}
		}
		
		System.out.println("输入的矩阵:A");
		printfMatrix(A);
		System.out.println("输入的矩阵:B");
		printfMatrix(B);
		StrassenProblem strassenProblem = new StrassenProblem();
		strassenProblem.solve(N, A, B, C);
		System.out.println("输出的矩阵:C");
		printfMatrix(C);
	}


	private static void printfMatrix(int[][] matrix) {
		for (int i=0; i<matrix.length; ++i){
			for (int j=0; j<matrix.length; ++j){
				System.out.print(matrix[i][j] + "\t");
			}
			System.out.println("");
		}
	}
}




-------------------------------------------------------不积跬步无以至千里--------------------------------------------


题目:最近点对问题(分治 + 递归)

题意:给出平面上的 N 个二维点,求出距离最小的 2 个点对。


算法思想:

我们设想如果将所有点按照X的坐标进行划分,这样所有点可以被模拟在一条直线上。这时候根据X轴来把所有点划分成两半(不断划分,直到被划分成的区域是只包含两个点或者三个点的时候,直接蛮力掉,求出这个区域的最近点对!)
1、当n<=3时,我们就可以直接算出最近点对!
2、当n>3时,我们将大问题k转换为求它左右两半区域的最近点对,再求跨越它左右两半的情况中的最近点对,进行比较三个最近点对值,就可以得到整个大问题k的最近点对值!
(当n>3时,划分集合区域S为SL(左区域)和SR(由区域),使得SL中的每一个点位于SR中每一个点的左边,并且SL和SR中点数相同。分别在SL和SR中解决最近点对问题(递归),得到LMin和RMin,分别表示SL和SR中的最近点对的距离。令CurrentMin=Min{LMin,RMin};。如果S中的最近点对(P1,P2)。P1、P2两点一个在SL和一个在SR中,那么P1和P2一定在以L为中心的间隙内,以L-d和L+d的区域内)。


算法步骤:
1、为了可以进行分治,我们先把所有点按照它的X坐标进行排序,使得所有点在X轴上可以找到相应的位置。
2、根据点的个数,把X轴这些点划分成两半,然后分别求出左右两半的最小距离。(LMin: 左半部分最小距离, RMin: 右半部分最小距离)
3、CurrentMin = Min{LMin, RMin}; 由于最小点对也可能是处于两半区域的中间,所以我们还需要在考虑两点处于中间的情况(具体的看上面算法思路中的分析)



最近点对问题的JAVA实现:

package csdn.panghu.recursion;


import java.util.Arrays;
import java.util.Comparator;


/**
 * 
 * @Description:  [求解最近点对问题]
 * @Author:       [胖虎]   
 * @CreateDate:   [2014-4-2 上午9:57:22]
 * @CsdnUrl:      [http://blog.csdn.net/ljphhj]
 * 
 */
class Point{
	int x;
	int y;
	public Point(int x, int y){
		this.x = x;
		this.y = y;
	}
	@Override
	public String toString() {
		return "[x=" + x + ", y=" + y + "]";
	}
}
public class PointMinDistanceProblem {
	private Point leftPoint = new Point(-1, -1);
	private Point rightPoint = new Point(-1, -1);
	
	private static Point[] points = new Point[]{
		new Point(1,3),
		new Point(5,6),
		new Point(-1,10),
		new Point(10,6),
		new Point(2,5),
		new Point(4,9),
		new Point(3,2),
		new Point(7,7),
	};
	public static void main(String[] args) {
		PointMinDistanceProblem p = new PointMinDistanceProblem();
		//1. 蛮力法求解:O(n^2)
//		double dis1 = p.getMinDistance1(points,0,points.length-1);
//		System.out.println("直接暴力: " + dis1);
		
		//2. 分治法求解
		double dis2 = p.getMinDistance2(points);
		System.out.println("分治法求解最近点对距离: " + dis2 + " Point1:" + p.leftPoint + " Point2:" + p.rightPoint);
	}
	
	/*蛮力法*/
	public double getMinDistance1(Point[] points, int left, int right){
		
		double minDistance = Double.MAX_VALUE;
		for (int i=left; i<=right; ++i){
			for (int j=i+1; j<=right; ++j){
				double distance = getDistance(points[i],points[j]);
				if (minDistance > distance){
					leftPoint = points[i];
					rightPoint = points[j];
					minDistance = distance;
				}
			}
		}
		return minDistance;
	}
	class xcomparator implements Comparator<Point>{


		public int compare(Point p1, Point p2) {
			return p1.x > p2.x ? 1 : p1.x == p2.x ? 0 : -1;
		}
	}
	
	/*分治法*/
	public double getMinDistance2(Point[] points){
		
		int len = points.length;
		if (len == 1){
			return Double.MAX_VALUE;
		}
		/*根据所有点的x值进行排序*/
		Arrays.sort(points,new xcomparator());
		return getMinDistance(points, 0, len-1);
	}
	
	public double getMin(double leftdis, double rightdis){
		return leftdis > rightdis ? rightdis : leftdis;
	}
	
	public double getMinDistance(Point[] points, int left, int right){


		if (right - left < 3){
			return getMinDistance1(points,left,right);
		}
		
		//求出中间
		int mid = (left + right) / 2;
		double leftMin = getMinDistance(points, left, mid);
		double rightMin = getMinDistance(points, mid+1, right);
		double currentMin = leftMin > rightMin ? rightMin : leftMin;
		//求出左边
		int leftR = 0;
		int rightR = 0;
		for (int i=left; i<mid; ++i){
			if (points[mid].x - points[i].x < currentMin){
				leftR = i;
				break;
			}
		}
		for (int i=right; i>mid; --i){
			if (points[i].x - points[mid].x < currentMin){
				rightR = i;
				break;
			}
		}
		double midMin = getMinDistance1(points, leftR, rightR);
		
		return midMin > currentMin ? currentMin : midMin;
	}
	
	
	/*求两点间的距离*/
	public double getDistance(Point p1, Point p2){
		
		return Math.sqrt(((p2.y - p1.y)*(p2.y - p1.y)) + ((p2.x - p1.x)*(p2.x - p1.x)));
	}
}






-------------------------------------------------------不积跬步无以至千里--------------------------------------------


题目:循环赛日程安排表(分治)

题意:
设有n个运动员要进行循环赛。现要设计一个满足以下要求的比赛日程表:       
1.每个选手必须与其他n-1个选手各赛一次;       
2.每个选手一天只能参赛一次;        
3.n是偶数时,循环赛在n-1天内结束。n是奇数时,循环赛进行n天.



JAVA实现代码:

package csdn.panghu.recursion;
/* 赛程表安排问题,有2的k次方(n)支球队,在n-1天两两比赛,每支球队每天只进行一场比赛,求安排日程表。
 * 可用分治递归的方法求解,由样例图分析可知,可将整个日程表矩阵划分为四个同等大小部分,若左上角填充完毕,
 * 则,右上角每个元素为左上角中同行对应元素再加上小矩阵长度,左下角矩阵每个元素为左上角矩阵中同列对应元
 * 素再加上矩阵长度,而右下角矩阵和左上角矩阵一致。所以整个问题就在于左上角的填充,左上角的填充依然和原
 * 问题一致,可以递归调用,递归结束条件是当矩阵长度为1时,将矩阵第一行第一列元素赋为1*/
/**
 * 
 * @Description:  [求解循环赛日程表安排问题(分治+递归)]
 * @Author:       [胖虎]   
 * @CreateDate:   [2014-4-2 上午10:12:50]
 * @CsdnUrl:      [http://blog.csdn.net/ljphhj]
 */
public class Schedule {
	public void scheduleTable(int[][]t,int n){
		if(n==1){
			t[0][0]=1;
		}else{
			int m=n/2;
			scheduleTable(t,m);
			//填充右上角矩阵,每个元素为同一行,列下标减m的元素再加m
			for(int i=0;i<m;i++){
				for(int j=m;j<n;j++){
					t[i][j]=t[i][j-m]+m;
				}
			}
			
			//填充左下角矩阵,每个元素为同一列,行下标减m的元素再加m
			for(int i=m;i<n;i++){
				for(int j=0;j<m;j++){
					t[i][j]=t[i-m][j]+m;
				}
			}


			//填充右下下角矩阵,右下角矩阵与左上角矩阵一致
			for(int i=m;i<n;i++){
				for(int j=m;j<n;j++){
					t[i][j]=t[i-m][j-m];
				}
			}
		}
	}
	
	public static void main(String[]args){
		int[][]num=new int[8][8];
		int n=8;
		Schedule s=new Schedule();
		s.scheduleTable(num, n);
		int c=0;
		for(int i=0;i<n;i++){
			for(int j=0;j<n;j++){
				System.out.print(num[i][j]+" ");
				c++;
				if((c%n)==0){
					System.out.println();
				}
			}
		}
	}
}



-------------------------------------------------------不积跬步无以至千里--------------------------------------------


题目:凸包问题求解(递归方式)

题意:
凸包:令S是平面上的一个点集,封闭S中所有顶点的最小凸多边形,称为S的凸包。 


如下图1中由红线段表示的多边形就是点集Q={p0,p1,...p12}的凸包。 
图1:

正文专注于<递归算法和分治思想>[胖虎学习算法系列]


正文专注于<递归算法和分治思想>[胖虎学习算法系列]

正文专注于<递归算法和分治思想>[胖虎学习算法系列]




凸包问题求解JAVA实现代码:

package csdn.panghu.recursion;


import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;


/**
 * 详细注释,分治解决凸包问题
 * @Description:  [分治+递归解决凸包问题]
 * @Author:       [胖虎]   
 * @CreateDate:   [2014-4-2 下午1:59:36]
 * @CsdnUrl:      [http://blog.csdn.net/ljphhj]
 */


/*凸包上的点*/
class TuPoint {
	double x;
	double y;
	public TuPoint(double x, double y) {
		this.x = x;
		this.y = y;
	}
	@Override
	public String toString() {
		return "TuPoint [x=" + x + ", y=" + y + "]";
	}
	
}
/*凸包上的两点的连线*/
class TuLine {
	TuPoint point1, point2;
	public TuLine(TuPoint point1, TuPoint point2) {
		this.point1 = point1;
		this.point2 = point2;
	}
	//两点的距离(线的长度)
	public double getDistance() {
		double dx = point1.x - point2.x;
		double dy = point1.y - point2.y;
		return Math.sqrt(dx * dx + dy * dy);
	}
	@Override
	public String toString() {
		return "TuLine [point1=" + point1 + ", point2=" + point2 + "]";
	}
	
}


public class TuBaoProblem {
	//要处理的点集合
	private List<TuPoint> pointList = null;
	//点集pointList对应的凸包结果。
	private List<TuLine> lineList = new ArrayList<TuLine>();
	
	public TuBaoProblem(List<TuPoint> pointList) {
		super();
		this.pointList = pointList;
	}
	//求解凸包,并把结果存入到lineList中。
	public void solve(){
		//初始化:clear
		lineList.clear();
		if (pointList == null | pointList.isEmpty())
			return ;
		/*左凸包中的点集合*/
		List<TuPoint> leftPointList = new ArrayList<TuPoint>();	
		/*右凸包中的点集合*/
		List<TuPoint> rightPointList = new ArrayList<TuPoint>();	
		
		/*根据point的x坐标来排序*/
		Collections.sort(pointList, new xcomparator());
		
		/*找到x最小的点,即最左边的点*/
		TuPoint leftPoint = pointList.get(0);
		
		/*找到x最大的点,即最右边的点*/
		TuPoint rightPoint = pointList.get(pointList.size() - 1);
		
		/*leftPoint ~~ rightPoint的连线把大的凸包问题,分解成两个小的凸包问题*/
		/*把总的点集,分成两半,对应放到leftPointList(左凸包点集) 或者 rightPointList(右凸包点集)*/
		for (int i = 0; i < pointList.size(); i++) {// 穷举所有的点,
			TuPoint tempPoint = pointList.get(i);
			//判断点tempPoint所在区域为左凸包还是右凸包
			double result = findArea(leftPoint, rightPoint, tempPoint);
			if (result > 0) {
				//tempPoint属于左边
				leftPointList.add(tempPoint);
			} else if (result < 0) {
				//tempPoint属于右边
				rightPointList.add(tempPoint);
			}
		}
		
		//分别求解左右两个凸包
		dealTuBao(leftPoint, rightPoint, leftPointList);
		dealTuBao(rightPoint, leftPoint, rightPointList);
	}
	private void dealTuBao(TuPoint p1, TuPoint p2, List<TuPoint> subPointList){
		if (subPointList.isEmpty()){
			/*递归结束条件*/
			//这两个点所连成的线将是最终结果凸包上的一条!
			lineList.add(new TuLine(p1, p2));
			return ;
		}
		//subPointList不为空的话,我们要去找博文中图示上写的 Pmax 点
		
		double maxArea = Double.MIN_VALUE;
		TuPoint pMax = null;
		for (int i = 0; i < subPointList.size(); i++) {
			// 最大面积对应的点就是Pmax
			double area = findArea(p1, p2, subPointList.get(i));
			if (area > maxArea) {
				pMax = subPointList.get(i);
				maxArea = area;
			}
		}
		
		/*下面的处理跟之前solve()函数中的处理一样*/
		
		// 找出位于(p1, pMax)直线左边的点集s1
		// 找出位于(pMax, p2)直线右边的点集s2
		/*左凸包中的点集合*/
		List<TuPoint> leftPointList = new ArrayList<TuPoint>();	
		/*右凸包中的点集合*/
		List<TuPoint> rightPointList = new ArrayList<TuPoint>();
		
		/*把点集subPointList,分成两半,对应放到leftPointList(左凸包点集) 
		 * 或者 rightPointList(右凸包点集)*/
		for (int i = 1; i < subPointList.size(); i++) {// 穷举所有的点,
			TuPoint tempPoint = subPointList.get(i);
			//判断点tempPoint所在区域为左凸包还是右凸包
			/*线: p1 ~ pMax*/
			double result1 = findArea(p1, pMax, tempPoint);
			/*线: p2 ~ pMax*/
			double result2 = findArea(pMax, p2, tempPoint);
			if (result1 > 0) {
				//tempPoint属于左边
				leftPointList.add(tempPoint);
			} else if (result2 > 0) {
				//tempPoint属于右边
				rightPointList.add(tempPoint);
			}
		}
		//递归调用咯~~
		dealTuBao(p1, pMax, leftPointList);
		dealTuBao(pMax, p2, rightPointList);
	}
	/* 函数的功能: 1. 判断点在子凸包的左边或者右边  2.用来算三角形的面积,来找到Pmax点
	 * 三角形的面积等于返回值绝对值的二分之一
	 * 点p3位于直线(p1, p2)左侧时,表达式的结果为正
	 * 点p3位于直线(p1, p2)右侧时,表达式的结果为负
	 * */
	private double findArea(TuPoint p1, TuPoint p2, TuPoint p3) {
		return p1.x * p2.y + p3.x * p1.y + p2.x * p3.y - p3.x * p2.y - p2.x
				* p1.y - p1.x * p3.y;
	}
	
	public static void main(String[] args) {
		List<TuPoint> arrays = new ArrayList<TuPoint>();
		arrays.add(new TuPoint(2, 4));
		arrays.add(new TuPoint(3, 4));
		arrays.add(new TuPoint(3, 3));
		arrays.add(new TuPoint(4, 3));
		arrays.add(new TuPoint(4, 4));
		arrays.add(new TuPoint(5, 4));
		arrays.add(new TuPoint(5, 2));
		arrays.add(new TuPoint(3.5, 2));
		arrays.add(new TuPoint(2, 2));
		TuBaoProblem t = new TuBaoProblem(arrays);
		t.solve();
		t.printResult();
	}
	/*输出结果*/
	public void printResult() {
		for (Object i : lineList.toArray()){
			System.out.println(i);
		}
	}
	/*x比较器*/
	class xcomparator implements Comparator<TuPoint>{


		public int compare(TuPoint p1, TuPoint p2) {
			return p1.x > p2.x ? 1 : p1.x == p2.x ? 0 : -1;
		}
	}
}




三、笔试题、面试题案例(等待更新)



5楼xiananliu昨天 23:44
好复杂啊啊啊啊啊啊,接触java两年了,感觉底层不是很清楚,也好像用不到,谁告诉我我是怎么了。
Re: u011133213昨天 23:47
回复xiananliun我博文中反复出现了“不积跬步无以至千里”,希望可以帮助你??
4楼u012799778昨天 21:36
支持一下
Re: u011133213昨天 22:25
回复u012799778n谢谢,博主花了两天的时间写的这个东西,最希望的就是对别人有用处,感谢肯定!
3楼lfx_kobe昨天 17:51
mark一下
Re: u011133213昨天 17:51
回复lfx_koben谢谢!
2楼xxm282828昨天 16:38
学习了
Re: u011133213昨天 17:36
回复xxm282828nthank~
1楼u013617734昨天 09:25
赞,学习了
Re: u011133213昨天 10:02
回复u013617734n谢谢!共同学习~