Java学习之浅谈递归的基础应用

递归的巧妙应用可谓妙不可言,也是大大增加了程序结局问题的能力。递归在数学和算法历史上也是久负盛名,例如我们都知道的斐波那契堆、汉诺塔。在计算机算法的多种类型数据结构中都应用到了递归的精髓,如树和图的遍历算法,也是在不断递归的过程中做到了穷尽遍历整个数据框架。在编程之美中,记得有个翻馅饼的例子,实质上与汉诺塔的操作是一致的,书中说到盖茨唯一一篇发行的论文就是涉及到这几块神秘的印度馅饼。那我今天就结合今日所学与算法导论中的内容对递归进行一下经典案例展示。

递归的概述:

方法的递归调用:

1.基于方法可以自己调用自己,这种情况就叫:方法的递归调用

注意事项:
要有出口,否则就是死递归
次数不能太多,否则就内存溢出
构造方法不能递归使用

public class Demo {
	
	private static int num = 1;
	public Demo(){
	//	this();
	}
	public static void main(String[] args) {
		show();
	}
	
	private static void show() {
		System.out.println("show() num = " + num++);
		if(num == 5){
			return;
		}
		show();
		
	}
	
}

使用递归求解阶乘:

例如:求5的阶乘:5!:1 * 2 * 3 * 4 * 5

用递归怎么做?
1.将问题分解:5!
5 * 4!
5 * 4 * 3!
5 * 4 * 3 * 2!
5 * 4 * 3 * 2 * 1!

public class Demo {
	public static void main(String[] args) {
		int startNum = 1;
		int maxNum = 5;
		int result = 1;
		/*
		while(startNum <= maxNum){
			result *= startNum;
			startNum++;
		}
		System.out.println(maxNum + "的阶乘为:" + result);*/
		
		System.out.println("递归求阶乘的结果:" + jieCheng(maxNum));
	}
	
	public static int jieCheng(int num){//5,4,3,2,1
		if(num == 1){
			return 1;
		}
		int result = num * jieCheng(num -1);
		return result;
	}
}

斐波那契神兔的三种方式求解:

有一对兔子,从第三个月开始,每月生一对兔子,新生的兔子,从第三个月开始,每月又生一对兔子,问20个月后,一共多少对兔子?

问题描述:
1:1
2:1
3:2
4:3
5:5
6:8


找规律:
从第3个月开始,每月的兔子数是前两个月兔子数的和

推算到第20个月就可以了:

计算方式一:使用数组:
1,1,2,3,5,8,13…
计算方式二:使用两个相邻的变量:a = 1 , b = 1
1 : a = 1 , b = 1;
2 : a = 1 , b = 1;
3 : a = 1 , b = 2
4 : a = 2 , b = 3
5 : a = 3 , b = 5;

计算方式三:使用递归:

import java.util.Arrays;
public class Demo {
	public static void main(String[] args) {
		//方式一:使用数组:
		int[] arr = new int[20];
		//填充前两个月
		arr[0] = 1;
		arr[1] = 1;
		for(int i = 2 ;i < arr.length ; i++){
			arr[i] = arr[i - 1] + arr[i - 2];
		}
		//遍历数组
		System.out.println(Arrays.toString(arr));//6765;
		
		//方式二:使用相邻变量
		int a = 1;
		int b = 1;
		
		for(int i = 3 ; i <= 20 ; i++){
			int temp = a;
			a = b;
			b = temp + a;
		}
		System.out.println("使用相邻变量的结果:" + b);
		
		//方式三:使用递归
		System.out.println("使用递归结果:" + fib(6));
	}
	
	public static int fib(int n){//6
		//如n是1月或2月时,就返回1对
		if(n == 1 || n == 2){
			return 1;//
		}
		return fib(n - 1) + fib(n - 2);//fib(5)  + fib(4)
	}
}


递归的查找一级目录下同文件后缀的文件
递归输出指定目录下所有的java文件的绝对路径案例

思路:
1.封装初始目录:File对象
2.获取目录下的所有文件和目录:File[ ] listFiles()
3.遍历File[ ]数组;
4.判断每个File是否是文件:
是:判断是否是.java文件,是:打印,否则:下一个循环;
否:回到2

import java.io.File;
public class Demo {
	public static void main(String[] args) {
		//1.封装初始目录
		File file = new File("D:\\20150118\\day19\\");
		listFile(file);
	}
	public static void listFile(File file){
		//2.获取目录下所有的文件和目录
		File[] fileArray = file.listFiles();
		//注意:如果当前File对象下面没有文件和目录,listFiles()方法返回的是null
		//3.遍历
		if(fileArray!= null){
			for(File f : fileArray){
				//4.判断是否是文件
				if(f.isFile() && f.getName().endsWith(".java")){
					System.out.println(f.getAbsolutePath());
				}else{//是一个目录,递归调用
					listFile(f);
				}
			}
		}
	}
}

图利用递归实现的遍历算法:

public class Graph
{
    private readonly int verticals;//顶点个数
    private int edges;//边的个数
    private List<int>[] adjacency;//顶点联接列表

    public Graph(int vertical)
    {
        this.verticals = vertical;
        this.edges = 0;
        adjacency=new List<int>[vertical];
        for (int v = 0; v < vertical; v++)
        {
            adjacency[v]=new List<int>();
        }
    }

    public int GetVerticals ()
    {
        return verticals;
    }

    public int GetEdges()
    {
        return edges;
    }

    public void AddEdge(int verticalStart, int verticalEnd)
    {
        adjacency[verticalStart].Add(verticalEnd);
        adjacency[verticalEnd].Add(verticalStart);
        edges++;
    }

    public List<int> GetAdjacency(int vetical)
    {
        return adjacency[vetical];
    }
}


递归实现深度优先算法

在谈论深度优先算法之前,我们可以先看看迷宫探索问题。迷宫探索在很多的算法书籍上都有所设计,例如严蔚敏老师的《数据结构》一书上就谈到这个问题,这次我就在博客上结合Java基础知识,结合递归知识来扒一扒这个问题。

迷宫中的每一个交会点代表图中的一个顶点,每一条通道对应一个边。
迷宫探索(maze exploration)即:
•在身后放一个绳子
•只能上下左右四个方向尝试着走
•访问到的每一个地方放一个绳索标记访问到的交会点和通道
•当遇到已经访问过的地方,沿着绳索回退到之前没有访问过的地方:

深度优先搜索算法模拟迷宫探索。在实际的图处理算法中,我们通常将图的表示和图的处理逻辑分开来。所以算法的整体设计模式如下:
•创建一个Graph对象
•将Graph对象传给图算法处理对象,如一个Paths对象
•然后查询处理后的结果来获取信息

下面是深度优先的基本代码,我们可以看到,递归调用dfs方法,在调用之前判断该节点是否已经被访问过。

public class DepthFirstSearch
{
    private bool[] marked;//记录顶点是否被标记
    private int count;//记录查找次数

    private DepthFirstSearch(Graph g, int v)
    {
        marked = new bool[g.GetVerticals()];
        dfs(g, v);
    }

    private void dfs(Graph g, int v)
    {
        marked[v] = true;
        count++;
        foreach (int vertical in g.GetAdjacency(v))
        {
            if (!marked[vertical])
                dfs(g,vertical);
        }
    }

    public bool IsMarked(int vertical)
    {
        return marked[vertical];
    }

    public int Count()
    {
        return count;
    }
}

广度优先算法

通常我们更关注的是一类单源最短路径的问题,那就是给定一个图和一个源S,是否存在一条从s到给定定点v的路径,如果存在,找出最短的那条(这里最短定义为边的条数最小)
深度优先算法是将未被访问的节点放到一个堆中(stack),虽然在上面的代码中没有明确在代码中写stack,但是 递归 间接的利用递归堆实现了这一原理。

和深度优先算法不同, 广度优先是将所有未被访问的节点放到了队列中。其主要原理是:
•将 s放到FIFO中,并且将s标记为已访问
•重复直到队列为空
1.移除最近最近添加的顶点v
2.将v未被访问的节点添加到队列中
3.标记他们为已经访问

广度优先是以距离递增的方式来搜索路径的。

class BreadthFirstSearch
{
    private bool[] marked;
    private int[] edgeTo;
    private int sourceVetical;//Source vertical

    public BreadthFirstSearch(Graph g, int s)
    {
        marked=new bool[g.GetVerticals()];
        edgeTo=new int[g.GetVerticals()];
        this.sourceVetical = s;
        bfs(g, s);
    }

    private void bfs(Graph g, int s)
    {
        Queue<int> queue = new Queue<int>();
        marked[s] = true;
        queue.Enqueue(s);
        while (queue.Count()!=0)
        {
            int v = queue.Dequeue();
            foreach (int w in g.GetAdjacency(v))
            {
                if (!marked[w])
                {
                    edgeTo[w] = v;
                    marked[w] = true;
                    queue.Enqueue(w);
                }
            }
        }
    }

    public bool HasPathTo(int v)
    {
        return marked[v];
    }

    public Stack<int> PathTo(int v)
    {
        if (!HasPathTo(v)) return null;

        Stack<int> path = new Stack<int>();
        for (int x = v; x!=sourceVetical; x=edgeTo[x])
        {
            path.Push(x);
        }
        path.Push(sourceVetical);
        return path;
    }
}

总结

– --我认为递归也许只能在计算机世界能实现,要是人能预见未来,这也就使得他能看到未来的自己对未来的预见。这就像两块镜片相对时,由于镜像里的镜像使得你能看见无数个镜像。也就是说,当你看着未来时,未来就发生了变化。这不只是一次分枝,由于你能递归的看见未来的自己,于是对未来的改变也将是无限次的,这使未来仍然进入了不可知的状态。未知,因为递归,而重复进行递归调用未知!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值