今天我们来认识一下------------------递归!
看看这个你会更快的理解:当你往镜子前面一站,镜子里面就有-一个你的像。 但你试过两面镜子一起照吗?如果A、B两面镜子相互面对面放着,你往中间- -站,嘿,两面镜子里都有你的千百个“化身”。为什么会有这么奇妙的现象呢?原来,A镜子里有B镜子的像,B镜子里也有A镜子的像,这样反反复复,就会产生-连串的“像中像”。这是一种递归现象.
1.程序调用自身的编程技巧称为递归。递归是算法在设计语言中广泛使用。通常把一个大型的问题层层转换为一个与原问题相似的规模较小的问题来求解,递归策略只需要少量的程序就可描述出解题的过程所需要的多次重复计算,减少了代码量。递归的能力在于用于有限的语句定义对象无线的集合。一般情况,递归需要有边界条件,递归前进段和递归反回段,当边界条件不满足时,递归前进,当边界条件满足时,递归返回。
- 递归的表现:函数调用函数自己
- 递归的用处:将大型复杂问题化解为若干小问题进行求解
- 递归的好处:代码量少
- 递归的弊端:占空间,函数是基于栈内存运行的
那么我们看看递归的引用:
1.求和 1+2+3+4+.....................+(n-1)+(n);
前n项的和 f(n) = 1+2+3+4+.....n;
则 f(100)= f(99) +100 , f(3) = f(2)+3; 特殊的 f(1) = 1;
public int Nsum(int n){
if(n = 1){
return 1;
}else{
return Nsum(n-1) +n;
}
}
2.n的阶乘
设函数f(n) = n * f(n- 1);
则 f(3) = 3 * f(2);
特殊 f(1) = 1;
public int Njie(int n){
if(n == 1){
return 1;
}else{
return Njie(n-1) * n;
}
}
3.斐波那契数列前20项:1,1,2,3,5,8,13,21.......................(递归编程 对于 这个数 分支 随着 数 越来越大 分支多。导致 时间过程 显得 递归不适用)
设函数f(n) = f(n -1) + f(n-2)
则 f(4) = f(3) + f(2) , f(3) = f(2) + f(1)
特殊的f(1) = 1, f(2) = 1
解法1:递归
public int fbnqsl(int n){
if(i == 2 || i == 1){
return 1;
}else{
return fbnqsl( n-1) + fbnqsl( n-2);
}
}
解法2:迭代(相比之下,和递归相比,速度快了许多)
public int ddfbqnsl(int n){
int n =1;
int m = 1;
int out =0;
for(int i = 1 ; i <= n ; i++){
if(i == 1 || i == 2)
System.out.println("1");
}else{
out = n+ m;
System.out.println(out);
n = m;
m = out;
}
}
4.汉诺塔问题
将最左边的盘子移动 最 指定的盘子上。( 条件不能将 大的放在 小的上面)
那么先简单看看 3 个如何移动
第一步
第二步:
第3步:
第4步
第5步:
第6步
最后:
那么想一想: 100个 50 个 20 个 10 个 5 个 1个
其实实质都是大化小去解决的。
首先思想是 , 如何理解 怎么处理 盘子的移动问题 , 那么我们用 这样的思想 ,去思考 我们只靠率 从哪到哪就行(自己规定就可以了)。
那么怎么决定 移动的方向----》
接下来 用递归问题,就是 考虑边界问题, 那么边界是 : 剩最后一个盘子 从 哪 移动 到 哪 就结束了。
那么 看这个 3 个的就可以看出 最后只用 从 开始的地方(x) 移动 要去的地方(y)。
既然要用大划小来处理问题,那么 就问你 。
public void move( int n , String form , String mid , String to){
if(n == 1){
System.out.println(form +"------------>"+to);
}else{
move(n -1 , from , to , mid); //1.每一步都执行到最后一步 直到 n == 1 时 才会执行
//第二步 (将 x - 1 移动 y上面)
System.out.println(form +"------------>"+to); //第二步
move(n -1 , mid , from , to); //将 y 上面移动到 z上面
}
}
5.八皇后问题:
在一个8 x 8 的棋盘,放入8个皇后棋子,
要求同行、同列、同斜线不能有重复的皇后棋子
1.分析 对于棋盘上的8个棋子去判断
首先我们了解一下 这个八皇后位置不唯一对吗? 好好思考一下,与数读不一样的是, 我们所要的是只要满足条件情况,不管这八个皇后在那个位置。所以答案不唯一。 在每一行可行的棋盘下都,都是同一个数组,因为每一行的皇后唯一。
那么 每一次 都需要 从上一个基础上 从新 的开始因为每一次都有可能是八皇后的位置。 (每一从上一次基础上再去向下重新开始每一个位置的变化),再继续去变化。
意思是:
这是在第一行开始的其他情况:
这是第一行第一个中的情况 ,还有其他的所有情况未画出。
了解了八皇后问题了后,我们来看看如何编写代码?
public class eightwfwing{
public staitc int count = 0; //用来定义 打印输出多少种情况
public static void main (String[] args){
int[][] board = new int[8][8]; //创建一个棋盘 默认值为 0 1表示皇后。
eightQueue(0, board);
}
private static void eightQueue(int row ,int[][] board){
if( row >= 8){
count++;
System.out,println("第"+count+"次");
for(int i = 0 ; i<board.length ; i++ ){
for(int j = 0; j < board[i].length ; j++){
System.out.print(board[i][j] + " " );
}
System.out.println();
}
}else{
//1.复制 因为 每一个起始点开始,往下都有可能的 ,为了不共享棋盘复制很方便。
int[][] newboard = new int[8][8] ;
for(int i = 0 ; i<board.length ; i++ ){
for(int j = 0; j < board[i].length ; j++){
newboard[i][j] = board[i][j];
}
}
//在复制后的棋盘 开始从0 位置 到 8位置
for( int col = 0 ; col < 8 ; i++){ //从复制完的这一行开始 按列去添加
if(NoDanger(row , col , newboard)){ //递归的边界值
//先清除当前的行
for(int c = 0 ; c < 8 ; c++){ //为了处理同行中前一个写了皇后使
//下面的 行 不满足 所以从新列的开
//始 填入皇后前 先处理掉之前的皇后
newboard[row][c] = 0;
}
//放皇后
newboard[row][col] = 1;
eightQueue(row + 1 , newboard);
}
}
}
}
private staic boolean NoDanger(int row , int col , int[][] newboard){
//正上
for(int r=row-1;r>=0;r--){
if(newBoard[r][col]==1){
return false;
}
}
//左上
for(int r=row-1,c=col-1;r>=0&&c>=0;r--,c--){
if(newBoard[r][c]==1){
return false;
}
}
//右上
for(int r=row-1,c=col+1;r>=0&&c<8;r--,c++){
if(newBoard[r][c]==1){
return false;
}
}
return true;
}
)
添加皇后的思想:
例如:
大概过程如上------》;
------------------------------------------------------------------------------------------------------------------------------------------------------------------------
程序调用自身的编程技巧称为递归。递归是算法在设计语言中广泛使用。通常把一个大型的问题层层转换为一个与原问题相似的规模较小的问题来求解,递归策略只需要少量的程序就可描述出解题的过程所需要的多次重复计算,减少了代码量。递归的能力在于用于有限的语句定义对象无线的集合。一般情况,递归需要有边界条件,递归前进段和递归反回段,当边界条件不满足时,递归前进,当边界条件满足时,递归返回。
- 递归的表现:函数调用函数自己
- 递归的用处:将大型复杂问题化解为若干小问题进行求解
- 递归的好处:代码量少
- 递归的弊端:占空间,函数是基于栈内存运行的
对于前进段与分治算法如何理解?
当我们求解某些问题时,由于这些问题要处理的数据相当多,或求解过程相当复杂,使得直接求解方法在时间上相当长,或者根本无法直接求出。对于这类问题,我们往往先把它分解成几个子问题,找到求出这几个子问题的解法后,再找到合适的方式,把它们组合成求整个问题的解法。如果这些子问题还较大,难以解决,可以再把它们分成几个更小的子问题,以此类推,直至可以直接求出解为止。这就是分治算法的基本思想,分而治之。
分治算法应用举例--------文件夹遍历
需求:遍历一个文件夹中所有的文件
public class diguimulu {
public static void main(String[] args) {
String p = "E:/"; //传入一个磁盘
lookmulu(p); //调用 函数
}
public static void lookmulu(String path) {
File file = new File(path); //创建一个对象 用来获取磁盘中的文件
String[] s = file.list(); //list() 返回夹文件夹的名字的数组
//listFile() 返回文件夹对象的数组
if(s != null) { //判断文件名字符串是否为空
for (String string : s) { //遍历
String nextPath = path +"/" +string; // 组合文件夹的名字
File f = new File(nextPath); //创建一个对象 获取新文件夹对象
if(f.isFile()) { //判断是否说文件对象
System.out.println(f.getAbsolutePath()); //打印出文件的路劲
}else {
lookmulu(nextPath); //如果这个文件对象不是文件是文件夹 就继续调
//用自身
}
}
}
}
}
分治算法应用举例----二分查找
需求:在有序数组中查找指定元素。
看看如何去是实现:
public class Testerfen {
public static void main(String[] args) {
int[] n = {1,2,3,4,5,6,7,8,9,10,11,12};
int l = 0;
int r = n.length-1;
int cher = 11;
while(l <= r) {
int mid = r+l/ 2 ;
if(mid > cher) {
r = mid - 1;
}else if (mid < cher){
l = mid+ 1;
}else {
System.out.println(mid);
break;
}
}
}
}
对于二分查找用循环去写,那么能迭代写的,一定能递归,能用递归写的,迭代不一定能写。
返回段与回溯算法
那么意思是 递归函数直到不能去调用函数时,往回返的过程就是回溯,简单的说就和进山洞一样,进了一层又一层,那么进到不能再进去的时候,往回走的过程就是回溯。
回溯算法实际上是一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某-步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就回退再走的技术称为回溯法,而满足回溯条件的某个状态的点称为“回溯”点。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法"的美称。
那么我们来理解下回溯的应用:
数读求解:
首先先说一下:
玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9,不重复。
数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次,所以又称“九宫格”。
那么了解了数读如何玩,我们来分析一下,怎样是满足数读要求的呢
首先 行 不能 重复 列也不能重复, 在属于自己的小九宫格不能重复出线相同的数字。
数读的解是唯一的;
那么放数字,按照行开始按顺序 依次按(1----9)去填空 如果 那个空能填写 那么就依次去填写如果填到 行的数下一个空无法在在太填写数时,那么需要回溯。就像如下情况。
那么如何处理回溯情况呢?先将此空的数清除,再接着去向此空的数向后循环填入去填。
那么先这样去填,看看如何?
在从第二行开始去填,依次类推。(必须要判断的是 行 不能重复 列 不能重复 小九宫格不能重复)
如下 : 在假设的情况下,该空不能填其他数了,那么就要回溯到该空前面的空, 将 原来数 基础上 向后 加一 ,在填入到空格中,在进行判断。 依次类推。
那么,如何填数读,差不多就了解完了。
二、编译阶段
1.分析行,列问题,填数字的问题 对于每列去填空 (列 加 1)%对 列的长度取余.(列的下标从 0 开始 0-----8 编号 9 列)
行数根据 列数 去 改变(行数也从 0---8 开始 9行)
例如 ,开始是 i = 0; 当列大于的长度 大于 9 时 int 类型 10 / 9 = = 1 此时 10/9 = 2 第二行 i = 10/9
列数一直从 0--------81 个数
row = 0;
col = 0;
row = row + (col+1)/9; 换行时;
col = (col + 1)% 9; 列加 1;
分析出 列是 一直加 的数。
数读中的元素,可以将数字事前导入进去, 将空白的地方输入成零,那么
005300000
800000020
070010500
400005300
010070006
003200080
060500009
004000030
000009700
1.首先读取一个数读
标准输入:
public class A{
public static int[][] board = new int[9][9];
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
for(int i = 0 ; i < 9 ; i++){
// 005300000
String line = sc.nextLine(); //获取数读上本应有的基础的数读
for(int j = 0 ; j < 9 ; j++){
//将输入的字符串 中 每个字符获取到 line.charAt(j)
//在用Integer.parseInt(//填字符串);
//所以 line.charAt(j)+"" 字符转 字符串
board[i][j] = Integer.parseInt(line.charAt(j)+");
}
}
print(); //打印出 整个数读
}
//打印方法
private static void print(){
for(int i = 0 ; i < 9 ; i ++){
for(int i = 0 ; i < 9 ; i ++){
System.out.print(board[i][j]+" ");
}
}
solve(0,0); //开始求解数读
}
//填写数字
private static void solve( int row , int col){
if( row >= 9){
print();
System.exit(0); //为什么要用这样的方式去关闭 ,针对程序的关闭方式
//应为递归会一直往下去执行 所以将程序关闭即可。
}else{
if(board[row][col] == 0){
for(int n = 1 ; n <= 9 ; n++){
//判断这个空能填什么数字
if(!isExist( row , col ,n)){
board[row][col] == n;
//会一直执行 ,直到 不能执行时
//根据 是否能填数 作为 这个递归的边界
solve( row+(col+ 1) / 9 ,(col + 1) % 9);
}
//从if中跳出来 将这个空 清零
board[ row][col] = 0;
}
}else{
//如果 这个空有 填的数 就像下继续填
solve(row+(col+ 1) / 9 ,(col + 1) % 9);
}
}
}
private static boolean isExist(int row , int col , int n){
//先看行
for(int c = 0 ; c < 9 ; c++){
if(board[row][c] == n){
return true;
}
}
//再看列
for(int r = 0 ; r < 9 ; r++){
if(board[r][col] == n){
return true;
}
}
//后看小九宫格
// 1.先确定 由行决定 小九宫格 的位置 0-----2 3---5 6----8
// 2.在同样的去确定列 去判断小九宫格的 位置 0-----2 3---5 6----8
// 3.因为 如果我们用 二维数组 去判断。
// 4.在行数上知道到行的最小 和最大值
// 5.在列数上知道列的最小和最大
// 6.用双重for循环 去 将这个数在 属于他的小 九宫格中 去判断 是否能填 这个数 n
int rowMin = 0;
int rowMax = 0;
int colMin = 0;
int colMax = 0;
if(r >= 0 && r <= 2){
romMin = 0;
romMax = 2;
}
if(r >= 3 && r <= 5){
romMin = 3;
romMax = 5;
}
if(r >= 6 && r <= 8){
romMin = 6;
romMax = 8;
}
if(c >= 0 && c <= 2){
colMin = 0;
colMax = 2;
}
if(c >= 3 && c <= 5){
colMin = 3;
colMax = 5;
}
if(c >= 6 && c <= 8){
colMin = 6;
colMax = 8;
}
for(int xr = rowMin ; xr <= rowMax ; xr ++){
for(int xc = colMin ; xc <= colMax ; xc++ ){
if(board[xr][[xc] == n){
return true;
}
}
}
return false;
}
}
数读编程思路: 如何填数字
首先:对于 递归的算法需要,先进行边界考虑。(不能无限递归)
1.那么没在边界时候的考虑, 需要判断 空 是否有数字? 没数字 就考率需要填啥 ,有数字就向下递归
需要考虑到所有数字 都要判断一次 再 for循环 里 先判断 是否 1-----9 之间的数啥 填完数字 从这个空 再向下 递归 ,,直到 不能递归了,就将 回溯 一层 一层 的回溯 直到 能 向下继续递归位置 。
2. 判断空需要填啥 ,那么就 新建方法 (判断是否 行 列 小九宫格 能填 这个数字)