Java实现算法设计与分析实验(VLSI芯片检测算法 + 正则表达式匹配 +黄金矿工 + 单词接龙)总结

实验一

实验名称

VLSI芯片检测算法

实验内容

在这里插入图片描述

实验代码

import java.util.*;
public class FINDGOOD {
	public static void main(String[] args){
		// TODO Auto-generated method stub
		Scanner in = new Scanner(System.in);
		while(true){
			int count = 0;
			System.out.println("Input the total number of VLSIs");
			//System.out.println("Input the state of VLSIs(1:Good 0:Bad)");
			int n = in.nextInt();
			Random r = new Random();
			int a[] = new int[n];
			for(int i=0; i<n; i++)
			{	int a0 = r.nextInt(100);
				if(a0<25){
					a[i] = 0;
				}
				else{
					a[i] = 1;
				}
				System.out.print(a[i]);
			}
			count = find(n,a);
System.out.println( );
			System.out.println("Test Times" + " " + count);
			break;
		}
	}

	static int find(int n,int a[]){
		int count = 0;
		if(n == 3){
			return 1;
		}
		else if(n == 1|n == 2){
			return 0;
		}
		if(n%2 == 0){
			int k = 0;
			int[] a1 = new int[n/2];//将偶数个芯片进行两两分组
			for(int i=1; i<n; i+=2){
				if(a[i-1] == a[i]){
					a1[k] = a[i];
					k++;
				}
				count++;
			}
		return count + find(k,a1);
		}
		else{
			int m = 0;//确定(好,好)的次数
			for(int i = 1;i<n;i++){
				if(a[0] == a[i]){
					m++;
					count++;
				}
			}
			if(m >= n/2){
				return count;//此芯片为好
			}
			else{
				int k = 0;
				int[] a1 = new int[n/2];//丢掉第一个芯片后,将剩下的偶数个进行两两分组测试
				for(int i=1; i<n; i+=2){
					if(a[i] == a[i+1]){
						a1[k] = a[i];
						k++;
					}
					count++;
				}
			return count + find(k,a1);
			}
		}
	}
}

实验结果

1:芯片个数为3时
Input the total number of VLSIs
3
1 1 0 
Test Times 1

2:芯片个数为100时
Input the total number of VLSIs
100
1 0 1 1 1 1 1 0 1 1 1 0 1 1 0 1 1 1 0 1 0 0 1 0 1 1 1 0 1 1 0 1 0 0 1 1 0 					1 1 0 0 1 1 0 1 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 0 1 0 1 1 0 1 1 1 1 0 1 1 1 0 		1 0 1 1 1 1 1 1 1 1 1 0 1 1 1 0 1 1 1 1 1 1 0 1 1 
Test Times 74

3:芯片个数为1000时
Input the total number of VLSIs
1000
0 0 1 0 1 0 0 0 0 1 1 1 1 1 0 1 1 1 1 1 0 1 1 0 1 0 0 1 1 1 1 0 1 0 1 1 1 1 1 1 1 0 0 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 0 1 0 0 1 0 1 0 1 0 1 0 1 1 1 0 1 1 0 0 1 0 1 0 1 0 1 0 1 0 1 1 1 0 1 0 1 1 0 1 1 0 1 1 1 0 1 1 0 1 1 1 0 0 0 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 0 1 0 1 0 0 1 0 1 0 1 1 1 1 0 1 1 1 1 0 1 1 0 1 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 1 0 1 0 1 1 0 0 1 1 1 1 0 1 1 0 1 1 1 1 0 1 1 1 1 1 0 1 1 1 0 1 1 1 0 0 0 1 1 1 1 0 1 1 0 1 0 1 0 1 0 1 1 1 0 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 0 0 0 0 1 1 1 1 1 0 0 1 1 0 1 1 0 1 0 1 1 1 1 1 1 0 1 1 0 1 0 1 1 1 1 1 1 0 1 1 1 1 0 0 1 1 0 1 1 1 1 1 1 0 1 1 1 1 1 0 0 0 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 0 1 0 1 1 1 1 0 0 1 0 0 0 1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 0 1 1 1 1 1 0 0 0 1 1 1 1 0 1 1 1 1 1 0 1 0 0 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 0 0 0 1 1 0 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 0 0 1 0 1 1 0 1 1 1 1 1 1 1 1 1 1 0 1 1 1 0 1 0 1 1 1 1 0 1 0 0 1 1 0 0 1 0 1 1 1 1 1 1 1 1 0 1 1 1 1 0 1 1 0 1 1 1 1 0 1 1 1 0 1 0 0 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 0 0 1 1 1 1 1 0 1 1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 0 1 0 1 1 0 1 0 1 1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 0 1 1 0 1 0 1 1 0 1 1 1 0 0 1 0 1 1 1 1 1 1 1 1 1 0 1 1 0 1 1 0 0 1 0 1 1 1 1 1 1 1 1 1 0 1 0 0 1 1 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 1 0 1 1 1 0 1 0 1 1 1 1 1 0 0 0 1 0 1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 0 1 1 0 1 1 1 1 1 1 1 0 1 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 0 1 0 1 1 0 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 0 1 0 0 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 0 0 1 1 1 0 1 1 1 1 0 0 1 0 1 1 1 0 1 1 1 1 1 1 1 1 0 1 1 1 1 1 0 1 0 1 1 1 0 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 0 1 0 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 0 0 1 0 1 1 1 1 0 0 1 1 1 0 1 1 1 1 0 1 1 0 0 1 1 1 0 1 1 1 1 1 1 1 1 1 1 0 1 0 1 1 1 0 0 1 0 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 0 
Test Times 804

实验总结

芯片的测试是两两进行匹配,好的芯片总是可以正确报告测试情况,坏的则是随机报告测试结果,因此测试过程中会出现上述四种情况,考虑到题目要求是找到好的芯片,则芯片好坏个数的前提必须是多于n/2的芯片是好芯片。由于两两测试,可以考虑分奇数和偶数两种情况进行分析,将原本问题分为两个子问题进行解决,最后再合成一个问题的解决,即采用分治法。

对于问题1:证明若多于n/2的芯片是坏的,在这种成对测试方法下,使用任何策略都不能确定哪个芯片是好的。假设坏的芯片可以联合起来欺骗教授。
本题可采用穷举法进行证明,在n个芯片中任选一片与其他所有芯片进行比较,由于多于n/2的芯片是坏的,在两两比较之后,总有多于n/2的芯片测试结果可以联合起来欺骗教授,因此测试结果是不可靠的。

对于问题2:假设有多于n/2的芯片是好的,考虑从n片中找出一片好芯片的问题。证明n/2对测试就足以使问题的规模降至近原来的一半。
本题分两种情况进行讨论,即芯片个数为奇数和芯片个数为偶数。
(1)芯片个数n为奇数
先选取第一个芯片,将其与剩下的n-1个芯片分别进行测试,若测试为两好的次数大于一好一坏的次数,则第一个芯片即为好的芯片;若两种情况的测试次数相等,则将此芯片丢弃,将此芯片丢弃之后,在将剩下的n-1个芯片进行两两分组,就为偶数的考虑情况。

(2)芯片个数n为偶数
芯片两两进行配对,因此分为n/2对测试对,分别对其进行测试,对于测试后的结果采取如下处理:若
一好一坏,则将两个芯片都丢弃;若两好,则随意丢弃一个,留下一个。操作后,留下的好芯片个数一定是大于坏芯片个数,这样问题的规模则可以降至原来的一半。
证明留下的好的芯片个数一定大于坏的芯片个数:
假设测试结果两好的组数为i,测试结果一好一坏的组数为j;测试结果两坏的组数为k。

起初时:2i + 2j + 2k = n
       2i + j > 2k + j
丢弃完之后:i > k

对于问题3:假设多于n/2片芯片是好的,证明好的芯片可用Θ(n)对测试找出。给出并解答表达式测试次数的递归式。
对于芯片测试流程体现如下:

FINDGOOD(n)
k = n;
if k =1 or 2
  任意选取一片
if k = 3
  任意取两片进行测试
     if 测试情况为一好一坏,则取没测试的芯片
     else 任意选取两片中的一片
While(k > 3)
{
     For i = 1 to [k/2] 
        if 两片测试结果为两好,则丢弃一片,取一片
        else 两片芯片都丢弃
}

可以写出问题解答规模公式:
在这里插入图片描述
则当多于n/2片芯片是好的,可以证明好的芯片可用Θ(n)对测试找出。

实验二

实验名称

正则表达式匹配

实验内容

利用递归以及动态规划思想实现正则表达式的匹配。
一个字符串 s 和一个字符规律 p,要求实现一个支持 .和 *
的正则表达式匹配。其中,.匹配任意单个字符;* 匹配零个或多个前面的那一个元素。所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
说明:
(1)s 可能为空,且只包含从 a-z 的小写字母。
(2)p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 * 。

示例 1:
输入:
s = ‘aa’
p =‘a’
输出: false
解释: a 无法匹配 aa整个字符串。
示例 2:
输入:
s = ‘aa’
p = ‘a* ’
输出: true
解释: 因为 * 代表可以匹配零个或多个前面的那一个元素, 
在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。
示例 3:
输入:
s = ‘ab’
p = ‘.*  ’
输出: true
解释: ‘.*  ’表示可匹配零个或多个 ‘*  ’任意字符 ‘. ’。
示例 4:
输入:
s = ‘aab’
p = ‘c * a * b’
输出: true
解释: 因为 ' * ' 表示零个或多个,这里 'c' 为 0 个, 'a' 被重复一次。
因此	可以匹配字符串 ‘aab’。
示例 5:
输入:
s = ’mississippi‘
p = ‘mis * is * p*.’
输出: false

实验代码

  • 递归
//递归
package suanfa;
import java.util.Scanner;

public class Match {
	
	public boolean match(char[] string,char[] pattern)
	{
		if(string == null || pattern == null)
		return false;
		int stringindex = 0;
		int patternindex = 0;
		return match(string,stringindex,pattern,patternindex);
	}
	
	private boolean match(char[] string, int stringindex, char[] pattern, int patternindex)
	{
		if(stringindex == string.length && patternindex == pattern.length)
			return true;
		if(stringindex != string.length && patternindex == pattern.length)
			return false;
		if(patternindex + 1 < pattern.length && pattern[patternindex + 1] == '*')
		{
			if(stringindex != string.length &&( string[stringindex] == pattern[patternindex] || pattern[patternindex] == '.' ))
				return match(string,stringindex,pattern,patternindex + 2)||match(string,stringindex + 1,pattern,patternindex + 2)||
						match(string,stringindex+1,pattern,patternindex);
			else
				return match(string,stringindex,pattern,patternindex + 2);
		}
		if(stringindex != string.length && (string[stringindex] == pattern[patternindex]||pattern[patternindex] == '.'))
			return match(string,stringindex + 1, pattern,patternindex + 1 );
		else 
			return false;
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		System.out.print("请输入字符串" + " " + "s" + " " + "=" + " ");
		Scanner scanner = new Scanner(System.in);
		char string [];
		String s = scanner.nextLine();
		string = s.toCharArray();
		System.out.print("请输入字符串" + " " + "p" + " " + "=" + " ");
		char pattern [];
		String m = scanner.nextLine();
		pattern = m.toCharArray();
//		char string[] = new char[]{'a','a','b'};
//		char pattern[] = new char[]{'c','*','a','*','b'};
	    Match pipei = new Match();
		System.out.println("正则化匹配结果:" + " " + pipei.match(string, pattern));
	}
}
  • 动态规划
//动态规划
package suanfa;
import java.util.Scanner;

public class Matching {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		System.out.print("请输入字符串" + " " + "s" + " " + "=" + " ");
		Scanner scanner = new Scanner(System.in);
		String s = scanner.nextLine();
		System.out.print("请输入字符串" + " " + "p" + " " + "=" + " ");
		String m = scanner.nextLine();
		System.out.print("\n正则化匹配结果:" + " " + ismatch(s,m));

	}
	
	public static boolean ismatch(String s,String p)
	{
		if(s == null || p == null)
			return false;
		boolean[][] match = new boolean[s.length()+1][p.length()+1];
		match[0][0] = true;
		for(int i = 0; i < p.length(); i++)
		{
			if(p.charAt(i) == '*' && match[0][i-1])
			{
				match[0][i+1] = true;
			}	
		}
		
		
		for(int i = 0; i < s.length(); i++)
		{
			for(int j = 0; j < p.length(); j++){
				if(p.charAt(j) == '.' || p.charAt(j) == s.charAt(i))
				{
					match[i+1][j+1] = match[i][j];
				}
				if(p.charAt(j) == '*'){
					if(p.charAt(j-1) != s.charAt(i) && p.charAt(j-1) != '.'){
						match[i+1][j+1] = match[i+1][j-1];
					}
					else{
						match[i+1][j+1] = (match[i+1][j] || match[i][j+1] || match[i+1][j-1]);
					}
				}
			}
		}
		printArray(match);
		return match[s.length()][p.length()];
	}


	private static void printArray(boolean[][] match) {
		// TODO Auto-generated method stub
		System.out.println("\n二维布尔表显示如下");
		int rows = match.length;
		int cols = match[0].length;
		for(int i = 0; i < rows; i++){
		for(int j = 0; j < cols; j++){
				System.out.print(match[i][j]+" ");
			}
			System.out.println(" ");
		}
	}
}

实验结果

1:递归结果
示例1:
请输入字符串 s = aa
请输入字符串 p = a
正则化匹配结果: false

示例2:
请输入字符串 s = aa
请输入字符串 p = a*
正则化匹配结果: true

示例3:
请输入字符串 s = ab
请输入字符串 p = .*
正则化匹配结果: true

示例4:
请输入字符串 s = aab
请输入字符串 p = c*a*b
正则化匹配结果: true

示例5:
请输入字符串 s = mississippi
请输入字符串 p = mis*is*p*.
正则化匹配结果: false

2:动态规划结果
示例1:
请输入字符串 s = aa
请输入字符串 p = a

二维布尔表显示如下
true false  
false true  
false false  

正则化匹配结果: false

示例2:
请输入字符串 s = aa
请输入字符串 p = a*

二维布尔表显示如下
true false true  
false true true  
false false true  

正则化匹配结果: true

示例3:
请输入字符串 s = ab
请输入字符串 p = .*

二维布尔表显示如下
true false true  
false true true  
false false true  

正则化匹配结果: true

示例4:
请输入字符串 s = aab
请输入字符串 p = c*a*b

二维布尔表显示如下
true false true false true false  
false false false true true false  
false false false false true false  
false false false false false true  

正则化匹配结果: true

示例5:
请输入字符串 s = mississippi
请输入字符串 p = mis*is*p*.

二维布尔表显示如下
true false false false false false false false false false false  
false true false false false false false false false false false  
false false true false true false false false false false false  
false false false true true false false false false false false  
false false false false true false false false false false false  
false false false false false true false true false true false  
false false false false false false true true false true true  
false false false false false false false true false true true  
false false false false false false false false false false true  
false false false false false false false false false false false  
false false false false false false false false false false false  
false false false false false false false false false false false  

正则化匹配结果: false

实验总结

这道正则化表达式的匹配问题着重点在于“ * ’”和 ‘“.’”个字符上,题目中说明“.”可以匹配任何字符,而“ * ”匹配零个或多个前面的那一个元素。所以这道题我们以“ * ” 为切入点,首先对这个问题进行大致的分析:
每次从字符串里取出一个字符与模式中的字符匹配,若模式中的字符是“.”,它可以匹配字符串中的任意字符,若不是,那么它与字符串中的字符相等则匹配。当字符串的字符和模式的字符匹配时,接着匹配后面的字符。下面需要重点考虑模式中的第二个字符是不是“ * ”,若不是,则分两种情况:
1:若字符串中的第一个字符和模式中的第一个字符相匹配,那么字符串和模式都后移一个字符,然后匹配剩余的字符串和模式;
2:若字符串中的第一个字符和模式中的第一个字符不匹配,则直接返回false。
然而当模式中的第二个字符是“ * ”时,又分为两种情况:
若模式中的第一个字符和字符串中的第一个字符不匹配,则在模式上后移两个字符,相当于忽略“ * ”和它前面的字符,因“ * ”可匹配字符串中的0个字符;
若模式中的第一个字符和字符串中的第一个字符匹配,则字符串后移一个字符,而在模式上又有两种选择:
(1)模式上后移两个字符(如字符串“ab”和模式“a * ab”匹配到string[0]和pattern[0]时,模式后移两个字符,字符串后移一个字符),这一种状态等效于1的情况,可忽略;
(2)模式保持不变(如字符串“abbba”和模式“ab * a”匹配到str[1]和pattern[1]时,模式保持不变,字符串后移一个字符)。
对于这道题,我用递归和动态规划两种方法解决了这个问题。
第一种方法是递归的解法:每次分别在string 和pattern中取一个字符进行匹配,如果匹配,则匹配下一个字符,否则,返回不匹配。此处匹配递归函数:match(string, pattern)。
若模式匹配字符的下一个字符是“ * ”:
若pattern当前字符和string的当前字符匹配,有三种可能情况:
(1)pattern当前字符能匹配 string 中的0个字符:match(string, pattern+2);
(2)pattern当前字符能匹配 string 中的1个字符:match(string+1, pattern+2);
(3)pattern当前字符能匹配 string 中的多个字符:match(string+1, pattern)。
若pattern当前字符和string的当前字符不匹配,即pattern当前字符能匹配 string 中的 0 个字符则:match(string, pattern+2)。
若模式匹配字符的下一个字符不是“ * ” ,则进行逐字符匹配。
(1)若string第一个字符和pattern中的第一个字符相匹配,那么string和pattern都后移一个字符,然后匹配剩余的。
(2)若string第一个字符和pattern中的第一个字符不匹配,直接返回false。
对于“.” 的情况比较简单,“.” 和一个字符匹配 match(string+1, pattern+1)。
注意点:空字符串“ ”和 “.* ”是匹配的。
每一次递归的时间复杂度都是O(1),最多需要递归m * n次,其中m为字符串s的长度,n为字符串p的长度,则时间复杂度是O(m * n)。由于递归存在对系统栈的调用,因此空间复杂度与递归深度成正比,而递归的最大深度是m * n,因此空间复杂度也是O(m * n)。
我采用的第二种方法是动态规划:与递归的倒序遍历不同,动态规划采用顺序遍历的方式,考虑当前状态是否受上一状态的影响,即建立二维布尔表match[i][j],状态转移方程为match[i][j]=match[i−1][j−1],首先建立动态数组boolean match[string.length+1][pattern.length+1],字符串和模式中的字符都从1开始,并赋初始值match[0][0] == true,表示字符串和模式首端的空字符已匹配。
接着分别遍历字符串和模式,检验模式的上一个字符是否为‘ * ’ ,若是,则:
若模式“ * ”前一字符与字符串的前一字符匹配,则跳过该“ * ”以及前一字符,或者进一步看字符串该字符的前一字符,即match[i][j]=match[i][j−2] || match[i−1][j];
若不匹配,则跳过该“ * ”以及它的前一位字符,即match[i][j]=match[i][j−2];
若模式的前一字符不是“ * ”,且与字符串的前一字符匹配,则match[i][j]=match[i−1][j−1]。
具体体现如下:
如果 p[j] == s[i] || p[j] ==“.”, 此时match[i][j] = match[i-1][j-1];
如果 p[j] ==“ * ”,分两种情况:
(1)如果p[j - 1] != s[i] && p[j - ] !=“.”,
此时match[i][j] = match[i][j - 2] //a* 匹配0次
(2)如果p[j - 1] == s[i] || p[j - 1] == “.”
此时 match[i][j] = match[i][j - 2] // a* 匹配0次
或 match[i][j] = match[i][j - 1] // a* 匹配1次
或 match[i][j] = match[i - 1][j] // a* 匹配多次
此算法的时间复杂度是O(m * n),其中m为字符串s的长度,n为字符串p的长度,但相比递归的方法省略了很多重叠子问题的重复计算。空间复杂度是一个boolean类型的m * n的二维数组,因此空间复杂度是O(m * n)。

实验三

实验名称

黄金矿工

实验内容

要开发一座金矿,地质勘测学家已经探明了这座金矿中的资源分布,并用大小为 m * n 的网格 grid 进行了标注。每个单元格中的整数就表示这一单元格中的黄金数量;如果该单元格是空的,那么就是 0。
为了使收益最大化,矿工需要按以下规则来开采黄金:
(1)每当矿工进入一个单元,就会收集该单元格中的所有黄金。
(2)矿工每次可以从当前位置向上下左右四个方向走。
(3)每个单元格只能被开采(进入)一次。
(4)不得开采(进入)黄金数目为 0 的单元格。
(5)矿工可以从网格中任意一个有黄金的单元格出发或者是停止。

示例 1:
输入:grid = [[0,6,0],[5,8,7],[0,9,0]]
输出:24
解释:
[[0,6,0],
[5,8,7],
[0,9,0]]
一种收集最多黄金的路线是:9 -> 8 -> 7。
示例 2:
输入:grid = [[1,0,7],[2,0,6],[3,4,5],[0,3,0],[9,0,20]]
输出:28
解释:
[[1,0,7],
 [2,0,6],
 [3,4,5],
 [0,3,0],
 [9,0,20]]
一种收集最多黄金的路线是:1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7。
提示:
1 <= grid.length, grid[i].length <= 15
0 <= grid[i][j] <= 100
最多 25 个单元格中有黄金。

实验代码

package suanfa;
import java.util.Scanner;
public class Solution {
	int [][] dir = {{-1,0},{1,0},{0,-1},{0,1}};
	
	public int getMaximumGold(int [][] grid){
		boolean[][] visit = new boolean[grid.length][grid[0].length];
		int ret = 0;//黄金数量
		for(int r = 0; r < grid.length;r++){
			for(int c = 0; c < grid[r].length;c++){
				ret = Math.max(ret, dfs(grid,r,c,0,visit));
			}
		}
		return ret;
	}
	
	public int dfs(int[][] grid, int row, int col, int earn, boolean[][] visit){
		if(row < 0 || row >= grid.length)
		{
			return earn;
		}
		if(col < 0 || col >= grid[row].length)
		{
			return earn;
		}
		if(grid[row][col] == 0){
			return earn;
		}
		if(visit[row][col]){
			return earn;
		}
		visit[row][col] = true;//设置当前位置为已开采
		earn += grid[row][col];//增加黄金价值
		int ret = earn;
		for(int [] d:dir){//遍历周围的四个方向
			int r = row + d[0];
			int c = col + d[1];
			ret = Math.max(ret,dfs(grid,r,c,earn,visit));
		}
		visit[row][col] = false;//恢复当前位置
		return ret;
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub   
		Scanner scanner = new Scanner(System.in);   
		System.out.print("输入黄金grid的行数:");  
		int row =scanner.nextInt();   
		System.out.print("输入黄金grid的列数:"); 
		int col = scanner.nextInt();
		int[][] grid = new int[row][col];    
		System.out.println("输入grid中黄金的情况如下所示");       
		for(int i = 0; i < row; i++){            
			for(int j = 0; j < col; j++){                
				grid[i][j]=scanner.nextInt();//给数组赋值            
				}        
		}        System.out.println("grid中黄金的情况为:");    
		for(int i = 0;i < row;  i++){           
			for(int j = 0;j < col; j++){             
			
				System.out.print(grid[i][j]+"\t");            
				if(j==col-1)                   
					System.out.println();           
				}       
		}    
		
		
		int s = 0;
		boolean flag = true;
		boolean flag1 = true;
		boolean flag2 = true;
		
		if(grid.length > 15 || grid.length < 0 || grid[0].length > 15 || grid[0].length < 0){
			System.out.println("grid网格最大不能超过15");
			flag1 = false;
			
		}
		while(grid.length <= 15 && grid.length >= 0 && grid[0].length <= 15 && grid[0].length >= 0){
			for(int i = 0; i < grid.length; i++)
				for(int j = 0; j < grid[0].length; j++){
					if(grid[i][j] > 0)
						s++;
					if(grid[i][j] > 100){
						System.out.println("单元内黄金价值太大");
						flag = false;
						break;
					}
					if(s > 25){
					   System.out.println("最多 25 个单元格中有黄金");
					   flag2 = false;
					  break;
					}
				}
			break;
		}
	
		if(flag && flag1 && flag2){
		Solution sol = new Solution();
		System.out.println("开采黄金的最大值为:" + " " + sol.getMaximumGold(grid));
		}
	}	
}

实验结果

示例1:
输入黄金grid的行数:3
输入黄金grid的列数:3
输入grid中黄金的情况如下所示
0 6 0
5 8 7
0 9 0
grid中黄金的情况为:
0	6	0	
5	8	7	
0	9	0	
开采黄金的最大值为: 24

示例2:
输入黄金grid的行数:5
输入黄金grid的列数:3
输入grid中黄金的情况如下所示
1 0 7
2 0 6
3 4 5
0 3 0
9 0 20
grid中黄金的情况为:
1	0	7	
2	0	6	
3	4	5	
0	3	0	
9	0	20	
开采黄金的最大值为: 28

示例3:
输入黄金grid的行数:1
输入黄金grid的列数:16
输入grid中黄金的情况如下所示
1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8
grid中黄金的情况为:
1	2	3	4	5	6	7	8	1	2	3	4	5	6	7	8	
grid网格最大不能超过15

示例4:
输入黄金grid的行数:2
输入黄金grid的列数:2
输入grid中黄金的情况如下所示
2 56
4 160
grid中黄金的情况为:
2	56	
4	160	
单元内黄金价值太大

实验总结

这道题我采用了回溯法,对grid网格中的每个有矿的单元进行深度遍历(DFS),首先先设置一个方向数组,包括上下左右,同时设置一个boolean数组visit[ ][ ],其中记录该位置是否被访问过,起初时设置为true,若访问过则设置为false,设一个变量ret,实时地记录采到的黄金的价值,同时实时地进行比较,即与DFS之后的earn值进行比较,取两者中的最大值,多次遍历回溯后得到最终黄金的最大值。
其中存在许多限制条件:grid网格中每个单元内的黄金价值不得超过100;grid网格的行和列都不得超过15;最多只能有25个网格单元中有黄金。只有满足这些所有限制条件,才可以进行开采,对于这些繁琐的嵌套循环,我尝试了很多次组合,但总是因为break与循环等问题没有成功,因此我设置了三个boolean变量,flag,flag1,flag2,起初时都设置为true,每次不满足上述限制条件则设置flag为false,只有当三个flag都保持原始的true时,才进行开采。

实验四

实验名称

单词接龙

实验内容

给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列。转换需遵循如下规则:
1.每次转换只能改变一个字母。
2.转换过程中的中间单词必须是字典中的单词。
说明:
(1)如果不存在这样的转换序列,返回一个空列表。
(2)所有单词具有相同的长度。
(3)所有单词只由小写字母组成。
(4)字典中不存在重复的单词。
(5)你可以假设 beginWord 和 endWord 是非空的,且二者不相同。

示例 1:
输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
输出:
[
["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"]
]
示例 2:
输入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
输出: []
解释: endWord "cog" 不在字典中,所以不存在符合要求的转换序列。

实验代码

  • 广度优先遍历和深度优先遍历
package suanfa;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Scanner;

public class Solution1 {

	public static List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList){
	    
		
	    //新建一个List<List<String>>类型的变量retListList用来记录要返回的值
		List<List<String>> retListList = new ArrayList<>();
		if(!wordList.contains(endWord)){
			return retListList;
		}
		if(!wordList.contains(beginWord)){
			wordList.add(beginWord);
		}
		
		
		//新建一个HashMap<String, List<String>>类型的变量from,用以保存每个结点的前驱结点
		HashMap<String, List<String>> from = new HashMap<>();
		
		//新建一个List<String>类型的变量visited,用以记录该结点是否已经被访问。
		List<String> visited = new ArrayList<>();
		
		//用邻接表的形式来实现图
		//新建一个HashMap<Integer, List<Integer>>类型的变量nextWords,用来记录各个结点是否相连
		HashMap<Integer, List<Integer>> nextWords = new HashMap<>();
		for(int i = 0; i < wordList.size(); i++){
			nextWords.put(i, new ArrayList<>());
		}
		
		//在设置i结点与j结点相连的同时也可以设置j结点与i结点相连
		for(int i = 0; i < wordList.size(); i++){
			for(int j = i + 1; j < wordList.size(); j++){
				if(hasPath(wordList.get(i).toCharArray(),wordList.get(j).toCharArray())){
					nextWords.get(i).add(j);
					nextWords.get(j).add(i);
				}
			}
		}
		
		
		//新建一个Queue<String>类型的队列queue,并把第一个元素beginWord入队,同时在visited中标记beginWord元素已经被访问。
		Queue<String> queue = new LinkedList<>();
		queue.add(beginWord);
		visited.add(beginWord);
		
		//队列不为空,就进行以下操作循环
		while(!queue.isEmpty()){
			//获得队列中的元素个数,记录为levelCount变量
			int levelCount = queue.size();
			
			//新建一个List<String>类型的变量tempVisited
			List<String> tempVisited = new ArrayList<>();
			
			//这层内循环其实就是在循环同一层上的所有元素
			while(levelCount-- > 0){
				//获取队首元素temp
				String temp = queue.poll();
				int n = wordList.indexOf(temp);
				//确定temp的后继结点
				List<Integer> nextWord = nextWords.get(n);
				
				//如果该后继结点还没有被访问过,即在visited中的标记没有被访问过
				for(int i = 0; i < nextWord.size(); i++){
					String string = wordList.get(nextWord.get(i));
					if(!visited.contains(string)){
						if(!from.containsKey(string)){
							tempVisited.add(string);
							queue.add(string);
						}
						if(from.containsKey(string)){
							List<String> tempList = from.get(string);
							//在该后继结点的前驱列表中添加temp元素
							tempList.add(temp);
							from.put(string, tempList);
						}else{
							List<String> tempList = new ArrayList<>();
							tempList.add(temp);
							from.put(string, tempList);
						}
					}
				}
			}
		
			
			//根据tempVisited中的值来设置visited中的值,即在每一层循环结束后才来标记其下一层的所有元素是否被访问过。
			for(String string : tempVisited){
				visited.add(string);
			}
			
			//当endWord被访问过时,break语句跳出循环
			if(visited.contains(endWord)){
				break;
			}
		}
		
		
	
		//将beginWord的前驱置为null
		from.put(beginWord, null);
		
		//进行深度优先遍历
		dfs(beginWord, endWord, new ArrayList<>(),from, retListList);
		return retListList;
	}
	
	private static void dfs(String beginWord, String curWord, List<String> tempList ,HashMap<String, List<String>> from, List<List<String>> templistList){
		if(curWord.equals(beginWord)){
			tempList.add(curWord);
			Collections.reverse(tempList);
			templistList.add(tempList);
			return;
		}
		tempList.add(curWord);
		if(from.get(curWord) != null){
			for(String string: from.get(curWord)){
				dfs(beginWord,string, new ArrayList<>(tempList), from, templistList);
			}
		}
	}
	
	//设置一个函数hasPah()用来判断两个字符串之间是否能相互转换,即图中两个结点是否存在着路径
	private static boolean hasPath(char[] arr1, char[] arr2){
		int diff = 0;
		for(int i =0; i < arr1.length; i++){
			if(arr1[i] != arr2[i]){
				diff++;
			}
		}
		if(diff == 1){
			return true;
		}
		return false;
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner scanner = new Scanner(System.in);
		System.out.print("请输入beginWord" + " " + "=" + " ");
		String beginWord = scanner.nextLine();
		System.out.print("请输入endWord" + " " + "=" + " ");
		String endWord = scanner.nextLine();
		List<String> wordList = new ArrayList<>();
		System.out.print("请输入wordList个数:" + " ");
		int n = scanner.nextInt();
		System.out.print("请输入wordList:" + " ");
        for (int i = 0; i < n; i++) {
            wordList.add(scanner.next());
        }
		System.out.print("wordlist为:"+" " +wordList);
        long startTime = System.currentTimeMillis();    //获取开始时间
		List<List<String>> listList = findLadders(beginWord, endWord, wordList);
		System.out.print("\n" + "单词接龙结果为:" + " " + listList);
		long endTime = System.currentTimeMillis();    //获取结束时间
		System.out.println("程序运行时间:" + (endTime - startTime) + "ms");    //输出程序运行时间
	}
}
  • SPFA算法+深度优先遍历(回溯)
package suanfa;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Scanner;

public class Solution3 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner scanner = new Scanner(System.in);
		System.out.print("请输入beginWord" + " " + "=" + " ");
		String beginWord = scanner.nextLine();
		System.out.print("请输入endWord" + " " + "=" + " ");
		String endWord = scanner.nextLine();
		List<String> wordList = new ArrayList<>();
		wordList.add("hot");
		wordList.add("dot");
		wordList.add("dog");
		wordList.add("lot");
		wordList.add("log");
		wordList.add("cog");
		System.out.print("wordlist为:"+" " +wordList);
		List<List<String>> listList = findLadders(beginWord, endWord, wordList);
		System.out.print("\n" + "单词接龙结果为:" + " " + listList);
		
        
	}
	
	public static List<List<String>> listList = new ArrayList<>();
	public static  List<String> list = new ArrayList<>();
	public static  boolean[][] graph;
	public static  int[] d;
	public  final static int INF = 1000000000;
	public static  int[] countInq;
	public static  boolean[] inq;
	public static  int size;
	public static  ArrayList<HashSet<Integer>> pre;
	public static  List<Integer> tempPath = new ArrayList<>();
	public static  int start = 0;
	public static int end = 0;
	
	public static  List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList){
		HashSet<String> hashSet = new HashSet<>();
		hashSet.addAll(wordList);
		hashSet.add(beginWord);
		if(!hashSet.contains(endWord)){
			return listList;
		}
		int index = 0;
		for(String s : hashSet){
			list.add(s);
			if(s.equals(beginWord)){
				start = index;
			}
			if(s.equals(endWord)){
				end = index;
			}
			index++;
		}
		size = list.size();
		graph = new boolean[size][size];
		for(int i = 0;i < size; i++){
			for(int j = i+1 ;j < size; j++){
				if(hashPath(list.get(i),list.get(j))){
					graph[i][j] = graph[j][i] = true;
				}
			}
		}
		d = new int[size];
		Arrays.fill(d,INF);
		countInq = new int[size];
		Arrays.fill(countInq,0);
		inq = new boolean[size];
		pre = new ArrayList<>();
		for(int i = 0; i < size; i++){
			pre.add(new HashSet<>());
		}
		spfa(start);
		dfs(end);
		return listList;
	}
	
	private static boolean hashPath(String s1, String s2){
		int count = 0;
		for(int i = 0; i < s1.length(); i++){
			if(s1.charAt(i) != s2.charAt(i)){
				count++;
			}
		}
		if(1 == count){
			return true;
		}
		return false;
	}
	
	private static boolean spfa(int s){
		d[s] = 0;
		Queue<Integer> queue = new LinkedList<>();
		queue.add(s);
		countInq[s]++;
		inq[s] = true;
		while(!queue.isEmpty()){
			int u = queue.poll();
			inq[u] = false;
			for(int v = 0; v < size; v++){
				if(graph[u][v]){
					if(d[u] + 1 < d[v]){
						pre.get(v).clear();
						pre.get(v).add(u);
						d[v] = d[u] + 1;
						if(!inq[v]){
							queue.add(v);
							countInq[v]++;
							inq[v] = true;
							if(countInq[v] > size - 1){
								return false;
							}
						}
					}else if(d[u] + 1 == d[v]){
						pre.get(v).add(u);
						if(!inq[v]){
							queue.add(v);
							countInq[v]++;
							inq[v] = true;
							if(countInq[v] > size - 1){
								return false;
							}	
						}
					}
				}
			}	
		}
		return true;
	}
	
	private static void dfs(int nowVisit){
		tempPath.add(nowVisit);
		if(nowVisit == start){
			List<String> path = new ArrayList<>();
			for(int i = tempPath.size() - 1; i >=0; i--){
				path.add(list.get(tempPath.get(i)));
			}
			listList.add(path);
			tempPath.remove(tempPath.size() - 1);
			return;
		}
		for(Integer integer: pre.get(nowVisit)){
			dfs(integer);
		}
		tempPath.remove(tempPath.size() - 1);
	}
}
  • Dijkstra算法
package suanfa;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Scanner;

public class Solution4 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Scanner scanner = new Scanner(System.in);
		System.out.print("请输入beginWord" + " " + "=" + " ");
		String beginWord = scanner.nextLine();
		System.out.print("请输入endWord" + " " + "=" + " ");
		String endWord = scanner.nextLine();
		List<String> wordList = new ArrayList<>();
		System.out.print("请输入wordList个数:" + " ");
		int n = scanner.nextInt();
		System.out.print("请输入wordList:" + " ");
        for (int i = 0; i < n; i++) {
            wordList.add(scanner.next());
        }
		System.out.print("wordlist为:"+" " +wordList);
		long startTime = System.currentTimeMillis();    //获取开始时间
		List<List<String>> listList = findLadders(beginWord, endWord, wordList);
		System.out.print("\n" + "单词接龙结果为:" + " " + listList);
		
		long endTime = System.currentTimeMillis();    //获取结束时间
		System.out.println("程序运行时间:" + (endTime - startTime) + "ms");    //输出程序运行时间
	}
	
	public static List<List<String>> findLadders(String beginWord, String endWord, List<String> wordList){
		//Dijkstra
		//用Map<String,Integer>记录每一个节点到起点的最短长度
		//用Map<String, List<String>> 记录每个节点最短路径的前一节点
		Queue<String> queue = new LinkedList<>();
		Map<String, Integer> dis = new HashMap<>();
		Map<String, List<String>> relation = new HashMap<>();
		
		dis.put(beginWord, 0);
		queue.add(beginWord);
		while(!queue.isEmpty()){
			// 获取未访问节点中,路径最短的节点,所有边的权重为1
			String str = queue.poll();
			int minPath = dis.get(str);
			
			// 更新与未访问节点中路径最短节点相邻节点的最短路径,同时记录节点的最短路径前一节点
			for(String key : wordList){
				 // 判断两个节点是否相邻
				int k = 0;
				for(int i = 0;i < key.length(); i++){
					if(str.charAt(i) != key.charAt(i))
						k++;
					if(k > 1)
						break;
				}
				
				// 相邻节点,更新最短路径,同时记录节点的最短路径前一节点
				if(k == 1){
					if(relation.get(key) == null || dis.get(key) == null || minPath + 1 < dis.get(key))
					{
						List<String> re = new ArrayList<>();
						re.add(str);
						relation.put(key,re);
					}else{
						if(minPath + 1 == dis.get(key))
							relation.get(key).add(str);
					}
					if(!dis.containsKey(key)){
						queue.add(key);
						dis.put(key, Math.min(minPath+1, null == dis.get(key) ? Integer.MAX_VALUE : dis.get(key)));
					}	
				}
			}
				// 剔除已访问过节点
				dis.remove(str);
				wordList.remove(str);
			}
			
			List<List<String>> res = new LinkedList<>();
			LinkedList<String> tmp = new LinkedList<>();
			tmp.addFirst(endWord);;
			dfs(endWord, tmp , relation, res, beginWord);
			
			return res;
		}

		private static void dfs(String cur, LinkedList<String> tmp, Map<String, List<String>> relation, List<List<String>> res, String beginWord){
			if(cur.equals(beginWord)){
				res.add(new LinkedList<>(tmp));
				return;
			}
			if(!relation.containsKey(cur))
				return;	
			
			List<String> pre = relation.get(cur);
			for(int i = 0; i < pre.size(); i++){
				cur = pre.get(i);
				tmp.addFirst(cur);
				dfs(cur, tmp, relation, res, beginWord);
				tmp.removeFirst();
			}
		}
	}

实验结果

示例1:
请输入beginWord = hit
请输入endWord = cog
请输入wordList个数: 6
请输入wordList: hot dot dog lot log cog
wordlist为: [hot, dot, dog, lot, log, cog]
单词接龙结果为: [[hit, hot, dot, dog, cog], [hit, hot, lot, log, cog]]
程序运行时间:1ms

示例2:
请输入beginWord = hit
请输入endWord = cog
请输入wordList个数: 5
请输入wordList: hot dot dog lot log
wordlist为: [hot, dot, dog, lot, log]
单词接龙结果为: []
程序运行时间:8ms

实验总结

对于这道单词接龙题,我用了三种方法去解决,涉及到图的深度和广度优先遍历以及Dijkstra算法。
首先是方法一:图的广度和深度优先遍历
首先要新建一个List<List>类型的变量retListList用来记录要返回的值和一个HashMap<String, List>类型的变量from,用以保存每个结点的前驱结点,同时建立一个List类型的变量visited,用以记录该结点是否已经被访问,此道题我采用邻接表来实现图,记录各个结点是否相连需要建一个HashMap<Integer, List>类型的变量nextWords,同时在设置i结点与j结点相连的同时也可以设置j结点与i结点相连。与此同时,建一个Queue类型的队列queue,并把第一个元素beginWord入队,同时在visited中标记beginWord元素已经被访问。若队列不为空可进行以下操作:用levelcount变量来记录队列中元素的个数,建一个List类型的变量tempVisited,以下循环的操作即循环同一层上的所有元素:获取队首元素temp,确定temp的后继结点,若后继结点没有被访问过,则在visited中标记未访问,在该后继结点的前驱列表中添加temp元素。根据tempVisited中的值来设置visited的值,需要在每一层循环结束后才来标记下一层的所有元素是否被访问过,若endWord被访问过,则break跳出循环。开始时需要将beginWord的前驱设置为null后进行深度优先遍历,其中需要设置一个函数hasPah()用来判断两个字符串之间是否能相互转换,即图中两个结点是否存在着路径。
在这里插入图片描述
对于方法三,则采用的是Dijkstra算法,其空间复杂度是,其中n为wordList中的单词数量。
对于Dijkstra算法:算法运行过程中,维持一个数组(最小优先队列),数组中记录起点到每个节点的最短路径。首先建一个Map:dis用来记录单词字典中每个单词至beginWord的最短路径和一个Map:relation记录单词字典中每个单词的父节点。计算完单词字典中每个单词的父节点后,用深度优先遍历,从endWord开始倒推出最短转换路径。在使用Dijkstra算法过程中,由于需要每次从未访问的结点中求出最短路径最小的结点,以更新周围直接相连的结点,每次都需要最小优先队列来获取当前路径最短的结点,由于这题中没设置权重,则可以自己设置每条边的权值均为1,则可按照访问到的顺序将单词放入队列中,那么按照访问顺序取出的单词,一定是当前未访问结点的最短路径,可进一步提高效率。

PS

以上便是算法设计与分析的四个实验内容,该实验的过程中参考了一些Blog,在此基础上进行了一定修改并加入了自己的理解与总结。

在这里插入图片描述

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值