欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

DFS(深度优先搜索算法)——Java实现(含例题)

程序员文章站 2022-05-22 21:06:23
...

基本概念

深度优先搜索算法(Depth First Search,简称DFS):一种用于遍历或搜索树或图的算法。
沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过或者在搜寻时结点不满足条件,搜索将回溯到发现节点v的那条边的起始节点。整个进程反复进行直到所有节点都被访问为止。属于盲目搜索,最糟糕的情况算法时间复杂度为O(!n)。

回溯法

回溯法(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。

基本模版

int check(参数)
{
    if(满足条件)
        return 1;
    return 0;
}
 
void dfs(int step)
{
        判断边界
        {
            相应操作
        }
        尝试每一种可能
        {
               满足check条件
               标记
               继续下一步dfs(step+1)
               恢复初始状态(回溯的时候要用到)
        }
}   

  1. 水域大小

    leetcode

你有一个用于表示一片土地的整数矩阵land,该矩阵中每个点的值代表对应地点的海拔高度。若值为0则表示水域。由垂直、水平或对角连接的水域为池塘。池塘的大小是指相连接的水域的个数。编写一个方法来计算矩阵中所有池塘的大小,返回值需要从小到大排序。

示例:

输入:
[
[0,2,1,0],
[0,1,0,1],
[1,1,0,1],
[0,1,0,1]
]
输出: [1,2,4]
提示:

0 < len(land) <= 1000
0 < len(land[i]) <= 1000

class Solution {
  public int[] pondSizes(int[][] land) {
		List<Integer> list = new ArrayList<>();
		for (int i = 0; i < land.length; i++) {
			for (int j = 0; j < land[i].length; j++) {
				if (land[i][j] == 0) {
					list.add(dfs(land, i, j));
				}
			}
		}
		int[] arr = new int[list.size()];
		for (int i = 0; i < arr.length; i++) {
			arr[i] = list.get(i);
		}
		Arrays.sort(arr);
		return arr;
	}

	public int dfs(int[][] land, int i, int j) {
		if (i < 0 || j<0 || i>=land.length || j>=land[i].length || land[i][j] != 0) {
			return 0;
		}else {
			land[i][j] = 1;
			int count = 1;
			count += dfs(land, i+1, j);
			count += dfs(land, i-1, j);
			count += dfs(land, i, j+1);
			count += dfs(land, i, j-1);
			count += dfs(land, i-1, j-1);
			count += dfs(land, i-1, j+1);
			count += dfs(land, i+1, j-1);
			count += dfs(land, i+1, j+1);
			return count;
		}		
	}
}
  1. 字符串的全排列
public static void permutation(char[] chars , int index , List<String> res) {
		if (index == chars.length - 1) {
			String string = new String(chars);
			res.add(string);
		}else {
			for (int i = index; i < chars.length; i++) {
				//去重
				if (isSwap(chars, index, i)) {
					swap(chars, index, i);
					
					permutation(chars, index+1, res);
					//回溯,恢复到初始状态
					swap(chars, index, i);
				}
			}
		}
	}
	
	public static boolean isSwap(char[] chars, int begin, int end) {
		for (int i = begin; i < end; i++) {
			if (chars[i] == chars[end]) {
				return false;
			}
		}
		return true;
	}
	
	public static void swap(char[] chars, int i, int j) {
		char temp = chars[j];
		chars[j] = chars[i];
		chars[i] = temp;
	}
  1. 带分数

题目
100 可以表示为带分数的形式:100 = 3 + 69258 / 714。

还可以表示为:100 = 82 + 3546 / 197。

注意特征:带分数中,数字1~9分别出现且只出现一次(不包含0)。

类似这样的带分数,100 有 11 种表示法。

输入格式
从标准输入读入一个正整数N (N<1000*1000)

输出格式
程序输出该数字用数码1~9不重复不遗漏地组成带分数表示的全部种数。

注意:不要求输出每个表示,只统计有多少表示法!

样例输入1
11
样例输出1
14
样例输入2
105
样例输出2
6

static int n, res;
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		n = sc.nextInt();
		sc.close();
		int[] data = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
		getQuanpailie(data, 0);
		System.out.println(res);
	}

	/**
	 * 得到三部分的十进制数
	 * @param data
	 * @param begin
	 * @param end
	 * @return
	 */
	public static int getNum(int[] data, int begin, int end) {
		int num = 0;
		for (int i = begin; i < end; i++) {
			num = num * 10 + data[i];
		}
		return num;
	}
	/**
	 * 实现全排列后,分成三部分进行计算
	 * @param data
	 * @param k
	 */
	public static void getQuanpailie(int[] data, int k) {
		if (data.length - 1 == k) {
			//把全排列好的数,分成三部分,进行计算
			System.out.println(Arrays.toString(data));
			for (int i = 1; i < data.length; i++) {
				for (int j = i+1; j < data.length; j++) {
					int pre = getNum(data, 0, i);
					int mid = getNum(data, i, j);
					int fon = getNum(data, j, 9);
					if (mid % fon != 0) {
						continue;
					}
					if (pre + mid/fon == n) {
						res ++;
					}
				}
			}
		}else {
			//全排列
			for (int i = k; i < data.length; i++) {
				swap(data, k, i);
				getQuanpailie(data, k+1);
				swap(data, k, i);
			}
		}
	}
	
	public static void swap(int[] data, int i, int j) {
		int t = data[i];
		data[i] = data[j];
		data[j] = t;
	}
  1. 素环圈
    环由N个圈组成,如图所示。将自然数1, 2、…、n分别放在每个圆中,两个相邻圆中的数字之和应该是素数。

注意:第一个圈的数字应该总是1。

输入:

n (0<n<20)

输出:

输出格式如下所示。每行代表环中的一系列圆数,从1顺时针和逆时针开始。数字的顺序必须满足上述要求。按词典顺序打印解决方案。

Sample Input:
6

Sample Output:
1 4 3 2 5 6

1 6 5 2 3 4

Sample Input:
8
Sample Output:
1 2 3 8 5 6 7 4

1 2 5 8 3 4 7 6

1 4 7 6 5 8 3 2

1 6 7 4 3 8 5 2

public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		 int n = scanner.nextInt();
		 sushuquan(n);
	}
public static void sushuquan(int n) {
		int[] data = new int[n];
		 for (int i = 0; i < data.length; i++) {
			data[i] = i+1;
		}
		 dfs(data, 1);
	}
	public static void dfs(int[] data, int index) {
		if (index == data.length -1) {
			boolean b = true;
			for (int i = 0; i < data.length-1; i++) {
				if (!isPrime(data[i]+data[i+1])) {
					b = false;
				}
			}
			if (b) {
				b = isPrime(data[0] +data[data.length -1]);
			}
			if (b) {
				for (int i = 0; i < data.length; i++) {
					System.out.print(data[i]);
					System.out.print(" ");
				}
				System.out.println();
			}
		}else {
			for (int i = index; i < data.length; i++) {
				swap(data, index, i);
				dfs(data, index+1);
				swap(data, index, i);
			}
		}
	}
	
	public static void swap(int[] data, int i, int j) {
		int t = data[i];
		data[i] = data[j];
		data[j] = t;
	}
	
	public static boolean isPrime(int num) {
	    if (num <= 3) {
	        return num > 1;
	    }
	    // 不在6的倍数两侧的一定不是质数, 在6的倍数两侧的不一定是质数, 比如25.
	    if (num % 6 != 1 && num % 6 != 5) {
	        return false;
	    }
	    int sqrt = (int) Math.sqrt(num);
	    for (int i = 5; i <= sqrt; i += 6) {
	        if (num % i == 0 || num % (i + 2) == 0) {
	            return false;
	        }
	    }
	    return true;
	}