本系列所有代码https://github.com/YIWANFENG/Algorithm-github
递归思想
递归函数无非是自己调用自己而已,其必含2部分,第一结束条件,第二循环体,所以递归类似于一个循环,其实绝大部分递归都可以利用堆栈写成非递归方式。
分治法的思想是将一个难以解决的大问题分隔为一些规模较小的相同问题,以便各个击破。
其实分治思想所得方法大部分为递归实现,所以二者在一定程度上交叉,下面两个分析也不应该完全互相独立。
一般递归形式:
Function(a,b...) {
//递归结束
if(结束条件) func_end();
Else {
//每次递归的实际执行代码(“栈上循环”)
Function(c,d..);
...
}
}
(依托整数划分问题来大概说明)
整数划分
即将整数n表示成一系列正整数之和。
N = n1+n2+n3+... (n1 >= n2 >= n3 >= ... >= nk >= 1,k >= 1)
正整数n的这种表示称为整数n的划分
首先拿到一个题目你应该可以感觉到这问题是一个递归的问题,如果没感觉的话一般非递归方法相对简单可解。(感觉真是玄学/(ㄒoㄒ)/~~。多见一些就会有改善)
例如此问题:
1:一个整数可以划分为一些整数(6=5+1=4+2=...),而一些整数可以划分为另一些不大于前者的整数(5=4+1=3+2=...),是不是感觉有种自己调用自己的赶脚?所以可以采用递归解决。
并且函数参数大致为DivideInteger(intn,int m);/*n要划分的数,m最大的分解数,在循环体处分析可能会引进更多参数*/
2:寻找结束递归的条件。结束条件是n划分结果相加正好等于n以及最小的划分为1,即1不可再划分。
3:寻找循环体即每次递归一般会执行的地方。
来开始分析,
6 = 6
6 = 5 +1,5 = 4 +1,4 = 3 + 1, 3 = 2 + 1, 2 = 1 + 1
6 = 4 + 2, 4与2的分解参照上一行
6 = 3 + 3 3的分解参照上面
...
那么可见,n可被1到n划分,
而且每次划分必须不大前一个划分
即for(int i=m;i>0; --i) {
DivideInteger(n-i,m);
}
但是若上次分解为4,2而划分2时,m=4,但是要划分的数为2,所以上面需要改为
for(int i=(m>n?n:m); i>0; --i) {
DivideInteger(n-i,m);
}
再分析,如何保存输出呢?即把每次分解的数存入数组,然后每次划分结束输出数组,在此可用全局数组Array[100],全局变量end_out=-1 保存其含有几个分解数。明显每次划分写入数组,每次进入递归调用end_out++,退出需要end_out--来确保每次划分输出本层递归与之前的值。
for(int i=(m>n?n:m); i>0; --i) {
end_out++;
DivideInteger(n-i,m);
end_out--;
}
PS:本人感觉在此“循环体”时的返回值或者结果操作是需要注意的地方,在下面Permutation的python实现代码中体会。
结合结束条件则合法输出是
If(n < 1)
For(int i=0; i<=end_out; ++i) {
Cout<<Array[i]<<’‘;
Cout<<endl;
}
综上完整程序为:
int Array[100];
int end_out = -1;
voidPrintDevideInteger(int n,int m){
///输出组成元素不大于m的关于n的划分
if(n < 1) {
for(int i=0;i<=end_out; ++i)
cout<<Array[i]<<' ';
//cout<<"\nend_out:"<<end_out+1<<endl;
cout<<endl;
}
else {
for(inti=(n>m?m:n); i>0; --i) {
++end_out;
Array[end_out] = i;
PrintDevideInteger(n-i, i);
--end_out;
}
}
}
如果只分析有几种结果,那么
算法思路及相关公式:
在正整数n的不同划分中,将最大加数n1不大于m的划分个数记为q(n,m)。可以建立起
1.q(n,1)=1,n>=1,即当最大加数n1不可大于1时只有一种全1的划分。
2.q(n,m)=q(n,n),m>=n.最大加数n1不可大于n,所以q(1,m)=1
3.q(n,n)=1+q(n,n-1)正整数n的划分由n1=n的划分与n1<=n-1的划分组成
4.q(n,m)=q(n,m-1)+q(n-m,m),n>m>1.正整数n的最大加数n1不大于m的划分由n1=m的划分与n1<=m-1的划分组成。
int DevideInteger(int n,int m){
///输出组成元素不大于m的关于n的划分个数
if(n<1 || m<1) return 0;
if(n==1 || m==1) return 1;
if(n<m) return DevideInteger(n, n);
if(n==m) return DevideInteger(n, m-1)+1;
return DevideInteger(n, m-1) + DevideInteger(n-m, m);
}
Fibonacci数列
关于那个公式法需要参考卢卡斯数列
int Fib(int n) {
if(n<=1) return 1;
return Fib(n-1) + Fib(n-2);
}
int Fib2(int n) {
int i=1, j=1, c;
while(n-- >0) {
c = i+j;
i = j;
j = c;
}
return i;
}
int Fib3(int n) { //SP
return (pow((1+sqrt(5))/2, n+1) - pow((1-sqrt(5))/2, n+1))
/sqrt(5);
}
Ackerman函数
A(1,0) = 2
A(0,m) = 1 //m>=0
A(n,0) = n + 2 //n>=2
A(n,m) = A(A(n-1,m),m) //n,m>=1
long long Ackerman(intn, int m) {
if(n==1 && !m) return 2;
if(!n && m>=0) return 1;
if(n>=2 && !m) return n+2;
if(n>=1 && m>=1) return Ackerman(Ackerman(n-1,m), m-1);
}
Permutation问题
R={r1,r2,r3,r4...} //共n元素
Ri = R-{ri}
集合X中元素的全排列记为Perm(x).
N = 1 时,Perm(R) = (r) r为R中唯一元素
N > 1 时,Perm(R) = (r1Perm(R1), r2Perm(R2), r3Perm(R3)...)
其中(ri)Perm(X)表示全排列Perm(X)的每一个排列前加上前缀ri得到的排列。
即我们可以每次去除一个元素,然后再求剩下元素的全排列再在结果最前加上该去除的元素。
由上可得知当有重复元素时, 在递归产生全排列的那段程序开始之前, 需要加一个判断:判断第i个元素是否在list[start,i-1]中出现过,如果出现过,则放弃所有以该元素开头的排列。
因为在第一题中,我们将剔除的元素交换到数组头上,这样一来,如果前面的数组含有目前排列的第一个元素,说明该元素开头的所有排列已经排过,可以略过此次。
C++:
template<typenameT_>
inline void Swap(T_&a, T_ &b){
T_ item = a;
a = b;
b = item;
}
template<typenameT_>
void Permutation(T_list[], int i_start, int i_end){
if(i_start == i_end){
for(int i=0;i<=i_end; ++i)
cout<<list[i]<<' ';
cout<<endl;
}
else {
for(int i=i_start;i<=i_end; ++i){
Swap<T_>(list[i_start], list[i]);
Permutation<T_>(list, i_start+1,i_end);
Swap<T_>(list[i_start], list[i]);
}
}
}
Python3:
def Permutation(list,end):
result = []
re = []
#print("list", list)
if 0 == end:
re.append(list)
else:
for i in list:
list_tmp = list[:]
list_tmp.remove(i)
#print("list_tmp",list_tmp)
for x in (Permutation(list_tmp,end-1)):
result = []
result.append(i)
result.extend(x)
#print("result",result)
re.append(result)
#print("re", re)
return re
pass
def main():
list = [1,2,3]
for x in (Permutation(list, 2)):
print(x)
pass
Hanoi问题
(汉诺塔问题)
void hanoi2(int n, intx, int y, int z) {
//将塔x上按直径从大到小且自上而下编号为1-n的n个原盘按规则
//搬到z,y辅助
if(n==1) move(1,x,z);
else {
hanoi2(n-1, x, z,y);
move(n, x, z);
hanoi2(n-1, y, x,z);
}
}