43、递归的思想与应用1

递归是一种数学上分而自治的思想

将原问题分解为规模较小的问题进行处理。

分解后的问题与原问题的类型完全相同,但规模较小。通过小规模问题的求解,能够轻易求得原问题的解。

问题的分解是有限的(递归不能无限进行):当边界条件不满足时,分解问题(递归继续进行),当边界条件满足时,直接求解(递归结束)。

地规模型的一般表示法:

f(n)=an并f(n-1)  n>1, a1 n==1

递归在程序设计中的应用

递归函数:函数体中存在自我调用的函数。

递归函数必须有递归出口(边界条件)。

函数的无限递归将导致程序奔溃。

递归思想的应用:

求解sum(n)=1+2+3+..+n

sum(n)=n+sum(n-1) n>1

              1       n==1

unsigned int sum(unsigned int n)
{
if(n>1)
{
return n+sum(n-1);
}
else
{
return 1;
}

}

斐波拉切数列:

数列自身递归定义:1,1,2,3,5,8,13,21,...

fac(n)=fac(n-1)+fac(n-2)  n>=3 

1 n==2

1 n==1

unsigned int fac(unsigned int n)
{
if(n>2)
{
return fac(n-1)+fac(n-2);
}
if((n==2)||(n==1))
{
return 1;
}
return 0;

}

递归调用时不要陷入递归执行的细节,建议递归模型,不要陷入流程。

用递归方法求字符串长度:

strlen(s)=1+strlen(s+1)  *s!='\0'  

                0   *s=='\0'

unsigned int strl(const char* s)
{
if(*s !='\0')
{
return 1+strl(s+1);
}
else
{
return 0;

}}

小结:递归是将问题分而自治的思想,用递归解决问题首先要建立递归的模型,递归解法必须要有边界条件,否则无解,不要陷入递归函数的执行细节,学会通过代码描述递归问题。

44、单链表的转置

reverse(list)=

guard=list->next; ret=reverse(list->next); guard->next=list; list->next=NULL; len(list)>=2;

guard=list  len(list)==1;

guard=NULL  list==NULL;

先找出口:分解成容易解决的小问题

Node* reverse(Node* list)
{
if((list==NULL)||(list->next==NULL))
{
return list;
}
else
{
Node* guard=list->next;
Node* ret=reverse(list->next);
guard->next=list;
list->next=NULL;
return ret;
}

}

单向链表的合并:

list->1-2-3->NULL

list->2-3-4->NULL

递归出口:

list2  list1==NULL

list1  list2==NULL

递归分解:

list1->value<list2->value =>list1->next=merge(list1->next,list2)

list1->value>=list2->value =>list2->next=merge(list1,list2->next)

//合并
Node* merge(Node* list1,Node* list2)
{
if(list1==NULL)
{
return list2;
}
else if(list2==NULL)
{
return list1;
}
else if(list1->value<list2->value)
{
/*Node* list1_=list1->next;
Node* list=merge(list1_,list2);
list1->next=list;
return list1;*/
return(list1->next=merge(list1->next,list2),list1);
}
else
{
/*Node* list2_=list2->next;
Node* list=merge(list1,list2_);
list2->next=list;
return list2;*/
return(list2->next=merge(list1,list2->next),list2);
}

}

3汉诺塔问题:

将木块借助B柱由A柱移动到C柱,每次只能移动一个木块,只能出现小木块在大木块之上。

分解:

将n-1个木块借助c柱由A柱移动到B柱,将最底层的唯一木块直接移动到c柱,将n-1个木块借助A柱由B柱移动到c柱

//汉诺塔问题
void HanoiTower(int n,char a,char b,char c)//n个木块由a移动到c
{
if(n==1)
{
cout<<a<<"->"<<c<<endl;
}
else
{
HanoiTower(n-1,a,c,b);
HanoiTower(1,a,b,c);
HanoiTower(n-1,b,a,c);
}

}

4全排列问题

{a,b,c}->abc,acb,bac,bca,cab,cba

permutation(n)=

找到n-1个元素全排列的方法,就能得到n个元素的全排列  n>2

1个元素的全排列为自身 n==1

void permutation(char* s,char* e)
{
if(*s=='\0')
{
cout<<e<<endl;
}
else
{
int len=strlen(s);
for(int i=0;i<len;i++)
{
if((i==0)||s[0]!=s[i])//避开相同的情况
{
swap(s[0],s[i]);//首先自身跟自身交互,剩下的元素全排列,然后跟第二个元素交互,剩下的全排列,交换回来,依次交换完
permutation(s+1,e);//问题分解,只要n-1个元素的全排列,就能求得n个元素的全排列
swap(s[0],s[i]);
}
}
}

}

找出口,问题分解。

小提示:递归还能用于需要回溯穷举的场合。

45、函数调用过程回顾:

程序运行后有一个特殊的内存区供函数调用使用:

用于保存函数中的实参,局部变量,临时变量,等,从起始地址开始往一个方向增长(如:高地址->低地址),有一个专用“指针”标识当前已使用内存的“顶部”。

程序中的栈区:一段特殊的专用内存区

栈底:main  高地址

栈顶:f(),g()...低地址 g函数返回后,f对应的栈空间数据没有变化

实例分析:逆序打印单链表中的偶数结点

list->1-2-3->NULL

r_print_even(list)=

r_print_even(list->next);print(list->value);  list!=NULL

return    list==NULL

//逆序打印
void r_print_even(Node* list)
{
if(list!=NULL)
{
r_print_even(list->next);
if((list->value)%2==0)
{
cout<<list->value<<endl;
}
}

}

Node* list=create_list(2,5);
print_list(list);
r_print_even(list);
destroy_list(list);
/*从栈的角度分析:从2到6函数调用过程中,会在栈上分配空间每个指针都指向一个数字,
到NULL的时候会退栈,空什么都不会做,函数调用直接结束,结束就会返回,返回就会退栈
于是指向NULL的空间退掉,退到指向6的地方,判断是不是偶数,偶数打印,打印完之后退栈
判读是不是偶数,不是退栈,直到退完,打印的过程就是回溯的过程,打印的内容由栈上的
参数指针得到,在栈上做标记。回溯算法设计时,就是利用栈来保存数据,然后回退的时候

利用数据重新计算。*/

八皇后问题:

在一个8*8的国际象棋棋盘上,有8个皇后,每个皇后占一格,要求皇后间不会出现相互“攻击”的现象(不能有两个皇后处在同一行,同一列或同一对角线上)

现在(00)放皇后,一行一行的放,往上一行下下一列放皇后,只需要看三个方向,左下,正下,右下。如果一行都不能放,说明上一行是错的,上一行放置的就是错的,往上回溯。

关键数据结构定义:

棋盘:二维数组(10*10):0表示位置为空,1表示皇后,2表示边界。

位置:struct Pos;

struct Pos

{int x;

int y;};

关键数据结构定义:方向数据

方向:水平(-1,0),(1,0)

垂直:(0,-1),(0,1)

对角线:(-1,1),(-1,-1),(1,-1),(1,1)

算法思路:

1、初始化:j=1

2、初始化:i=1

3、从第j行开始,恢复i的有效值(通过函数调用栈进行回溯),判断第i个位置

a、位置i可放入皇后:标记位置(i,j),j++转步骤2

b、位置i不可放入皇后:i++,转步骤a

c、当i>8时,j--,转步骤3

结束:第8行有位置可放入皇后。

//8皇后问题
template <int SIZE>
class QueueSolution:public Wobject
{
protected:
enum { N=SIZE+2 }; 
struct Pos :public Wobject
{
Pos(int px=0,int py=0):x(px),y(py){}
int x;
int y;
};
int m_chessboard[N][N];
Pos m_direction[3];
LinkList <Pos> m_solution;
int m_count;
void init()
{
m_count=0;
for(int i=0;i<N;i+=(N-1))
{
for(int j=0;j<N;j++)
{
m_chessboard[i][j]=2;
m_chessboard[j][i]=2;
}
}
for(int i=1;i<=SIZE;i++)
{
for(int j=1;j<=SIZE;j++)
{
m_chessboard[i][j]=0;
}
}
m_direction[0].x=-1;
m_direction[0].y=-1;
m_direction[1].x=0;
m_direction[1].y=-1;
m_direction[2].x=1;
m_direction[2].y=-1;
}
void print()
{
for(m_solution.move(0);!m_solution.end();m_solution.next())
{
cout<<"("<<m_solution.current().x<<","<<m_solution.current().y<<")";
}
cout<<endl;
for(int i=0;i<N;i++)
{
for(int j=0;j<N;j++)
{
switch(m_chessboard[i][j])
{
case 0: cout<<" ";break;
case 1: cout<<"#";break;
case 2: cout<<"*";break;
}
}
cout<<endl;
}
cout<<endl;
}
bool check(int x,int  y,int d)
{
bool flag=true;
do
{
x+=m_direction[d].x; //当前位置上方向数据
y+=m_direction[d].y;
flag=flag&&(m_chessboard[x][y]==0);
}
while(flag);
return (m_chessboard[x][y]==2);
}
void run(int j)
{
if(j<=SIZE)
{
for(int i=1;i<=SIZE;i++)
{
if(check(i,j,0)&&check(i,j,1)&&check(i,j,2))
{
m_chessboard[i][j]=1;
m_solution.insert(Pos(i,j));
run(j+1);//递归调用自己,第j行能放皇后,找j+1位置
m_chessboard[i][j]=0;//如果返回说明不能放皇后
m_solution.remove(m_solution.length()-1);
}
}
}
else
{
m_count++;
print();
//如果j>size,说明找到一个解决方案
}
}
public:
QueueSolution()
{
init();
}
void run()
{
run(1);
cout<<"total  "<<m_count<<endl;
}
};
int main()
{
QueueSolution<4> qs;
qs.run();

return 0;}

小结:程序运行后的栈存储区专供函数调用使用,栈存储区用于保存实参,局部变量、临时变量,等,利用栈存储区能够方便的实现回溯算法,八皇后问题是栈回溯的经典应用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值