图的深度遍历及应用

一、深度优先搜索遍历 思考:1.深度优先遍历的遍历形式就像是原来的for循环里加入递归语句,这样的问题在退回到上一层时,往往要清空上一次的操作。          2.我们往往需要构造出一个图用来描述实际问题,我们在描述图时,建立一个二维数组表示节点之间的关系即可,不用像原来先对二维数组进行初始化(表示两点之间没有路),然后调用方法利用边集数组进行对数组实际赋值,我们直接对二维数组进行操作即可,有时候两个节点之间不仅有关于路是否存在和路的长度的信息,还有其他的信息比如耗费的时间和金钱等,我们这个时候可以在相应的位置加入对象,对象里存在时间和金钱和长度这些属性。

从图中某顶点V0出发,访问此顶点,然后依次从V0各个未被访问的邻接点出发深度优先搜索遍历,直至图中所有和V0相通的顶点都被访问到。 具体实现代码如下:

连通图的深度遍历

private void dfs(int i, boolean[] visited){ System.out.print(i+" "); visited[i]=true; for(int j=0; j<n; j++){if(a[i][j]!=0 && a[i][j]!=MaxValue && !visited[j]){ dfs(j,visited);} }}
public void depthFirstSearch(int v){          boolean []visited=new boolean[n];      for(int i=0; i<n; i++) visited[i]=false;      dfs(v, visited);      System.out.println();}

代码的实现中有两点:一是访问i节点和所有与i节点相连的节点,并且跟i相连的节点也要访问与其相连的全部节点,所以我们利用递归来实现             二是我们事先用一个数组来存储节点的状态,通过数组里的值来判断节点是否访问过

//  非连通图的遍历
   for(int i=0; i<n; i++)
            if(!visited[i])
                dfs1(GA,i,n,visited);  // }

非连通图的遍历在加上这些语句即可

二、广度优先搜索遍历图

private void bfs(int i, boolean[] visited){
      Queue q=new SequenceQueue();
      System.out.print(i+"  ");
      visited[i]=true;
      q.enter(i);
      while(!q.isEmpty()){
	int k=(Integer)q.leave();
	for(int j=0; j<n; j++){
	      if(a[k][j]!=0 && a[k][j]!=MaxValue && !visited[j])
	     {
		System.out.print(j+"  ");
		visited[j]=true;
		q.enter(j);
	      }
	}
       } 
}

《图的深度遍历及应用》

例题一:踩方格问题 有一个方格矩阵,矩阵边界在无穷远处。我们做如下假设   1,每走一步时,只能从当前方格移动一格,走到某个相邻的方格上;2,走过的格子无法走第二次

3,只能向北,东,西三个方向走  问:如果允许在方格矩阵上走n步(n<=20),共有多少种不同的方案。两种走法只要有一步不一样,即被认为是不同的方案 思路:递归方法的方法参数是i,j和n,返回条件是n==0,return 1;n步的方案可以先走一步,走一步有三种选择,所以n步的方案总数就等于三种n-1步的方案的总数,相当于三叉树的遍历。跟之前的n个数的排列有些不同,那些题是先进行某种操作才能让n种方案变成n-1种方案,而此题跟爬楼梯类似,都是将n步的方案分开(只不过多了判断是否走过的算法)。并且因为每种可能都需要判断方格是否走过,所以我们利用一个变量将不同的可能加在一起

代码如下:

public int ways(int i,int j,int n){ if(n==0){ return 1;}
visited[i][j]=1;
int num=0;
if(!visited[i][j-1]){ //向西走 num+=ways(i,j-1,n-1);}
if(!visited[i][j+1]){ //向东走  num+=ways(i,j+1,n-1);}
if(!visited[i+1][j]){ //向北走  num+=ways(i+1,j,n-1);}
visited[i][j]=0;
return num;}
最后将visited[i][j]=0;因为如果程序走到这句,是因为走完了一种可能的长度为n的路径,这个时候将走过的路径上的数组值重新设置,使得下一次的路径可以尝试上一次走过的方格

思考:递归中有多种选择可以理解为先解决完第一个选择在解决第二个……(因为顺序对其无影响),比如二叉树的遍历中可以先解决左子树,后解决跟节点和右子树,本题是一开始一直向西走,走到n==0时return回来,到了选择最后一步怎么走的时候我们这时候不能向西走了,因为东也是走过的,所以这时我们走到了
visited[i][j]=0;处,将标记后的位置重新赋值。

例题二:生日蛋糕问题 要制作一个体积为Nπ的M层生日蛋糕,每层都是一个圆柱体。设从下往上数第i(1<=i<=M)层蛋糕是半径为ri,高度为hi的圆柱。当i<M时,要求ri>ri+1且hi>hi+1。由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)的面积Q最小。令Q=Sπ。请编程对给出的N和M,找出蛋糕的制作方案(适当的ri和hi的值),使S最小。(除Q外,以上所有数据皆为正整数)

思考:我们是要找到每一层可以得到最小外表面的可能的ri和hi,所以我们应该枚举出所有的每一层可能的高度和半径(状态就是半径和高度和蛋糕体积和蛋糕层数  这个问题就是从一个状态跳到另一个状态) 如何确定搜索范围,我们可以确定底层蛋糕的最大可能半径和最大可能高度。 我们应该从底层开始搭蛋糕,体现了搜索顺序,而我们最后为了提高程序的运行速度,往往要进行剪枝。    代码如下: 这个时候我们应该满足两个条件,体积为nπ和外表面面积最小,体积问题就将体积看成参数在返回条件处考虑即可,外表面是对每种满足条件的可能进行比较得到最小面积的外表面。

public void jisuan(int M,int v,int n,int r,int h) {
		//表示要用n层去凑体积为v的蛋糕,最底层半径不能超过r,高度不能超过h,M是蛋糕的最大层数
		 //求出最小表面积放在miarea里
		
		 if(n==0) {
			 if(v!=0) return;
			 else if(minarea>area) minarea=area; return;
		 }
		 if(v<=0) return;
	//我们应该先走一步,先遍历所有能搭最底层蛋糕的hi和ri的可能,得到他们所对应的area值
		 for(int rr=r;rr>=n;rr--) {
			 if(n==M) area=rr*rr;
			 for(int hh=h;hh>=n;hh--) {
				 area+=2*rr*hh;
				 jisuan(M,v-rr*rr*hh,n-1,rr-1,hh-1);
				 area-=2*rr*hh;  //此时是蛋糕回退到更下一层,所以area就要减去在之前那一层所测试的表面积
			 }
		 }
		 
	}

思考:我们会有多种剪枝的方式 剪枝一:搭建过程中发现已经建好的面积已经超过目前所求得的最优表面积,或者预见到搭完后面积一定会超过目前最优表面积(最优性剪枝) 剪枝二:搭建过程中预见到再往上搭,高度或者半径已经无法安排,则停止搭建(可行性剪枝) 剪枝三:   搭建过程中发现还没搭的那些层的体积,一定会超过还缺的体积,则停止搭建(可行性剪枝) 剪枝四:搭建过程中发现还没搭的那些层的体积,最大也到不了还缺的体积,则停止搭建(可行性剪枝)

例题:roads N个城市,编号1到N。城市间有R条单向道路。每条道路连接两个城市,有长度和过路费两个属性。

Bob只有K块钱,他想从城市1走到城市N。问最短共需要走多长的路。如果到不了N,输出-1

2<=N<=100

0<=K<=10000

1<=R<=10000

每条路的长度L,1<=L<=100

每条路的过路费T,0<=T<=100

输入:

K

N

R

s1 e1 L1 T1

s1 e2 L2 T2

sR eR LR TR

s e是路起点和终点


代码如下:

public class lian {
     static int minlen=0;
	public void jisuan(road a[][],int n,int totallen,int k, int visited[]) {
		if(n==4) {
			if(k>=0) {
				if(minlen==0) {
					minlen=totallen;
				}
				else if(minlen>totallen) {
					minlen=totallen;
				}
			}
			return;
		}
			
	
		for(int i=0;i<5;i++) {
			if(a[n][i].money!=0&&k>=a[n][i].money&&visited[i]==0) {
				
				visited[i]=1;
		    	jisuan(a,i,totallen+a[n][i].length,k-a[n][i].money,visited);
				visited[i]=0;
			}
		}
	}
		
	public static void main(String[] args) {
	
    lian b=new lian();
    road a[][]=new road[5][5];
    road r=new road(0,0);
    for(int i=0; i<5; i++)
		for(int j=0; j<5; j++){
			a[i][j]=r;
		}

    
    road r1=new road(2,1);
    a[0][1]=r1;
    road r2=new road(4,2);
    a[0][2]=r2;
    road r3=new road(1,1);
    a[1][2]=r3;
    road r4=new road(3,3);
    a[1][3]=r4;
    road r5=new road(2,2);
    a[2][3]=r5;
    road r6=new road(2,2);
    a[3][4]=r6;
    int visited[]= {0,0,0,0,0,0};
    
    b.jisuan(a,0, 0,11,visited);
    System.out.print(minlen);
	}

}


class road{
	int length;
	int money;
	public road(int a,int b) {
		length=a;
		money=b;
	}
}

思考:大体想法是从点1开始,开始用深度的形式进行遍历所有到目标点的可能,在所有的可能中都计算出最小路程和最小价格。然后得到结果。 容易出现的错误:1.因为二维数组里放的是引用变量,而且我们有时是不会将所有数组上都放上变量,所以会出现nullpointerexception错误,为了防止此错误,我们应该将二维数组初始化,并且我们可以根据引用的某个值确定这个引用变量是不是实际有用的还是它仅仅是来凑数的。

                           2.我们还容易出现数组对应下标指定错误,比如此题中城市编号和数组下标就不统一。

最优性剪枝:1.如果当前已经找到的最有路径长度为L,那么在以后的搜索中,总长度大于等于L的走法,就可以不用考虑

                    2.保存中间计算结果用于最优性剪枝:如果到达某个状态a时,发现以前也曾经到达过a,且前面那次到达a所花的代价更少,则剪枝,这要求保存到达状态a的到目前为止的最小代价。(跟动态规划有点类似)                 用mid【k】【m】表示走到城市k时总路费为m的条件下,最优路径的长度。若在后续的搜索中,再次走到k时,如果总路费恰好为m,且此时路径的长度已经超过
mid【k】【m】,则不必再走下去。

    原文作者:数据结构之图
    原文地址: https://blog.csdn.net/qq_40497403/article/details/79384224
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞