标签:技术分享 分享 需要 使用 pos 补充 toc 循环 技术
在学习Java的过程中,我的一个基友扔给了我一道算法题,为了检验自己对Java的学习情况我决定使用Java解决这道算法题。
现有一株K叉树,我们知道其前序遍历与后序遍历,也知道K的值,求该K叉树有多少种可能形态。如一13叉树,前序遍历为abejkcfghid,后序遍历为jkebfghicda,则其可能形态有207352860种。
根据遍历的定义我们可以知道:
前序遍历的第一个字母和后序遍历的最后一个字母是他的根。
前序遍历的根的后一个字母是它最左边的子树。
后续遍历的根的前一个字母是它最右边的子树。
以题目为例:(这里我们从后序遍历入手)
a为该树的根,d为a的最右子树,其余字母都是a的后裔;
以d为根,在前序遍历中d右边的为d的后裔(无);d左边的为d的兄弟及其后裔(即bejkcfghi),其中c为除了d外a的最右子树。
以c为根,在前序遍历中c右边的为c的后裔(即fghi), c左边的为c左边的兄弟及其后裔(即bejk),c的后裔的前序遍历为fghi,后序遍历为fghi,i为c的最右子树;c的左边的兄弟及其后裔的前序遍历为bejk,后序遍历为jkeb。b为除了d,c外a的最右子树;
先处理c左边的兄弟及其后裔,可知b为a的子树,c、d的兄弟;e为b的子树;j、k为e的子树;
处理c的后裔,根据上述处理方法易得f、g、h、i都是c的子树;
如图所示,可见a有3个子树,b有1个子树,c有4个子树,e有2个子树。因为是13叉树,当一个根节点有n个子树,则该结点与子树的排列可能性为C(n,13),故该13叉树总的可能形态有C(3,13)* C(1,13)* C(4,13)* C(2,13) = 207352860。
总结以上过程,我们需要的东西是一个能将K叉树不断拆分,能求出每一个根节点子树数量的方法。
首先,我们要明白我们需要什么:
一个main类,里面能初始化我们所需要的数据,包括题目给出的K,前序遍历和后序遍历,最好还能最后处理结果并输出。
在分析过程中我们需要用到大量的C(n,k),如果每个C(n,k)都单独运算的话非常麻烦,我们可以用一个数组计算并存储它们。
下面自然是重头戏了,通过方法实现算法。这个方法:
1.需要能根据给定的前序遍历和后序遍历判定出根节点;
2.通过根节点拆分前序遍历和后序遍历,同时计算每个根节点子树的数量;
3.能够调用自己,因为拆分自己是要不断循环的,此外我们还需要一个数组用于记录有多少个根,每个跟有多少个子树,这个数组显然是应该独立在方法外的。
public class qianhou { //主类
public static void main(String[] args){
String qian = "abejkcfghid"; //初始化输入
String hou = "jkebfghicda";
byte k = 13;
long poss = 1; //结果
char [] dlr = qian.toCharArray(); //Java中对String类型利用复杂,转化成char数组方便操作
char [] lrd = hou.toCharArray(); //dlr前序遍历,lrd后序遍历
Jisuan a1 = new Jisuan();
int[] zuh=a1.setarray(k); //将方法计算的组合数结果
a1.find(dlr,lrd);
for (int i=1; i<a1.ccount+1; i++){ //计算结果
poss = poss * zuh[a1.acco[i]];
}
System.out.println(poss);
}
}
class Jisuan{
int[] acco = new int[10000];
//新建数组记录根节点子树个数(也可以用ArrayList,虽然给ArrayList数组内的值++比较麻烦,但可以控制数组大小。注意不能写在方法中
int ccount = 0; //acco数组指针
public int[] setarray(byte k){ //记录C(n,k)值的数组
int[] zuhe = new int[k+1];
zuhe[0] = 1;
for (int i=1; i<=k; i++){
zuhe[i] = 1;
zuhe[i] =zuhe[i-1]*(k+1-i)/i;
}
return zuhe;
}
public void find(char[] dlr , char[] lrd){ //调用对象应为前序遍历与后序遍历,因为有完整的信息
int i1 = 0, i2=0, i3 = 0; //初始化指针
int l = dlr.length; //初始化遍历长度
while (dlr[i1] != lrd[l-1]){ //寻找前序遍历中的最右子树位置
i1++;
}
//此处由于时间关系,未使用try校验初始条件是否合理,应该补充
if (l != 1){ //如长度为1则无子树,不需要继续分割
if (i1!=0){ //如果最右子树在前序遍历中位置不为0,说明无左侧兄弟及其后裔,左侧无需分割
char[] ldlr = new char[i1]; //将左侧兄弟及其后裔的前序遍历与后序遍历分割分割
char[] llrd = new char[i1];
for (i2=0; i2<i1;i2++){
ldlr[i2] = dlr[i2];
llrd[i2] = lrd[i2];
}
acco[ccount] = acco[ccount]+1; //根下子树数量+1
find(ldlr,llrd); //先分割左侧兄弟及其后裔,先遍历根的所有兄弟,无需大量调控记录数组的指针
}
if (i1!=l-1){ //如果最右子树在前序遍历中位置不为末尾,说明无后裔,右侧无需分割
char[] rdlr = new char[l-i1-1];//将其后裔的前序遍历与后序遍历分割分割
char[] rlrd = new char[l-i1-1];
for (i3=0; i3<l-i1-1;i3++){
rdlr[i3] = dlr[i3+1+i1];
rlrd[i3] = lrd[i3+i1];
}
ccount++; //数组指针指向下一个根
acco[ccount] = 1; //根下子树数量+1
find(rdlr,rlrd);
}
}
}
}
Java虽然并不适合用来写算法,但是就是因为这重重障碍和限制条件,写起算法来能遇到更多的问题,在解决这些问题的过程中,反而能更加深入的理解Java的内在逻辑和自身的缺陷。
标签:技术分享 分享 需要 使用 pos 补充 toc 循环 技术
原文地址:http://www.cnblogs.com/squallmoon/p/6720165.html