例題詳解Java?dfs與記憶化搜索和分治遞歸算法的使用
一、dfs(深度優(yōu)先搜索)
1.圖的dfs
/** * 深度優(yōu)先搜索 * * @param node * @param set */ public void DFS(Node node, Set<Node> set) { if (node == null) { //當(dāng)沒有節(jié)點時,退出此次方法 return; } if (!set.contains(node)) { //沒有搜到過就保存,并且繼續(xù)向下推進(jìn) set.add(node); System.out.print(node.value + " "); for (Node node1 : node.nexts) { DFS(node1, set); } } }
2.樹的dfs
樹(邊數(shù)=頂點數(shù)-1的圖)的dfs
public static void dfs(Node treeNode) { if (treeNode == null) { return; } System.out.print(treeNode.value + " "); // 遍歷左節(jié)點 dfs(treeNode.left); // 遍歷右節(jié)點 dfs(treeNode.right); }
使用dfs代替狀壓枚舉: dfs 優(yōu)于 狀壓枚舉
二、記憶化搜索
記憶化搜索,指通過記錄已經(jīng)搜索到的值來降低重復(fù)操作次數(shù),從而加快運(yùn)行速度。
斐波那契數(shù)列問題: 1,1,2,3,5,8,... 求斐波那契數(shù)列第n項
1.普通遞歸:O(2^n)
public static int f(int n){ if(n<=1)return 1; return f(n-1)+f(n-2); }
2.記憶化搜索: O(n)
static int[] dp = new int[1000] ; public static int f1(int n){ if(n<=1) return dp[n] = 1; if(dp[n]!=0)return dp[n]; return dp[n]=f1(n-1)+f1(n-2); }
三、分治
將問題拆分成子問題進(jìn)行求解。
例如歸并排序: 1,4,7,2,5,8,3,6,9
第一步 拆分: 左邊:1,4,7,2 右邊:5,8,3,6,9
第二步 遞歸排序 左邊:1,2,4,7 右邊:3,5,6,8,9
第三步 合并兩個有序數(shù)組 左邊:1,2,4,7 右邊:3,5,6,8,9
四、算法題
1.dia和威嚴(yán)
難度??
知識點:dfs
從根開始dfs即可,dfs的過程中就能找到每個點需要的時間,維護(hù)一下最大值。
題目描述:
dia是學(xué)生會長。她經(jīng)常有信息要傳達(dá)給別人。
學(xué)生會的階層等級森嚴(yán)。每個階級傳達(dá)信息只能傳達(dá)到自己的下屬。
一個人可以有多個下屬,但一個人最多只能有一個上級。(樹)
dia作為學(xué)生會長,很明顯是沒有上級的(即全學(xué)生會權(quán)利最大者)。
已知每個人傳達(dá)到下屬所消耗的時間。(傳達(dá)給不同的下屬,時間不一定相同) 現(xiàn)在dia有一個信息要傳達(dá)給一個指定者。只有信息接收的指定者才需要理解信息(花費ai的時間)。她不想讓效率太慢,于是她想找出最大時間消耗的那個路線(包括信息接收指定者所需要理解信息的時間),這樣就能方便整改。
輸入描述:
第一行一個正整數(shù)n,代表學(xué)生會的人數(shù)(dia是1號,其他人標(biāo)記為2號到n號)。 (1≤n≤200000)第二行有n-1個正整數(shù)ai,代表從2號到n號,每個人需要理解信息的時間。 (1≤a1≤100)接下來的n-1行,每行有3個正整數(shù)x,y,k,代表x是y的上級,x向y傳播信息需要消耗k的時間。(1≤x,y≤n,1≤k≤100)
輸出描述:
一個正整數(shù),代表dia選定某人作為信息接收指定者后,花費總時間的最大值。
示例
輸入
3
3 4
1 2 4
1 3 2
輸出
7
說明
若dia指定3號為信息接受者,消耗時間為2+4=6。
若dia指定2號為信息接受者,消耗為4+3=7。
故最大值為7。
備注:
注:只有指定者需要理解信息,傳達(dá)者不需要花費時間理解信息。
import java.util.*; import java.io.*; public class Main{ static class Edge{ int to; int weight; //保存子節(jié)點,與線權(quán)(傳播時間)。 Edge(int to,int weight){ this.to = to; this.weight = weight; } } static ArrayList<Edge>[] g; static int maxtime; static int weight[]; public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int n = Integer.parseInt(br.readLine()); //n為總?cè)藬?shù) weight = new int[n+1]; g = new ArrayList[n+1]; // 每個桶中保存一個ArrayList(可達(dá)) //這里的i代表了第幾個數(shù)(從1開始) for(int i = 1;i<=n;i++){ g[i] = new ArrayList(); } //保存所有點權(quán)(理解時間)紀(jì)錄了從2到n的點權(quán),與數(shù)字的位子相對應(yīng)。 String[] s = br.readLine().split(" "); for(int i = 0;i<n-1;i++){ weight[i+2] = Integer.parseInt(s[i]); } for(int i = 0;i<n-1;i++){ String[] s1 = br.readLine().split(" "); int x = Integer.parseInt(s1[0]); int y = Integer.parseInt(s1[1]); int z = Integer.parseInt(s1[2]); g[x].add(new Edge(y,z)); } dfs(1,0); System.out.println(maxtime); } static void dfs(int i ,int time){ if(maxtime<time+weight[i])maxtime = time+weight[i]; for(int k = 0;k<g[i].size();k++){ dfs(g[i].get(k).to,time+g[i].get(k).weight); } } }
非常典型的dfs模板題目:dfs一個樹,尋找根到某點的線權(quán)總合+某點的點權(quán)
思考:???
思考1:假如變成根到某點的線權(quán)總合+根到某點的點權(quán)總合,程序應(yīng)該怎么改?
解:
dfs(g[i].get(k).to,time+g[i].get(k).weight+weight[i]);//那就加個點權(quán)
思考2:假如變成根到葉子點的線權(quán)總合+葉子點的點權(quán),程序應(yīng)該怎么改?
(1)首先思考一個問題:這個遞歸沒有任何判斷結(jié)束的條件。他是怎么結(jié)束的?
因為我們將他遞歸的過程安排的明明白白,他只是在執(zhí)行有序有限的遞歸操作。所以沒有給出判斷條件。
(2)那么我們應(yīng)該怎么判斷他到葉子節(jié)點了呢?
我們單單在遞歸的代碼上修改是不能判斷出是否到達(dá)了葉子節(jié)點,所以還需要想清楚結(jié)構(gòu)體是咋樣的。
解:
if(maxtime<time+weight[i]&&g[i].isEmpty())maxtime = time+weight[i];
思考3:假如變成隨意定點到某點的線權(quán)總合+某點的點權(quán),程序應(yīng)該怎么改?
解:
dfs(點的編號,0); 0代表時間從0開始遞歸
2.小紅點點點
難度??
知識點:dfs
開一個visited數(shù)組用來標(biāo)記是否訪問。對于每個沒被訪問過的紅色節(jié)點,開始dfs并標(biāo)記其相鄰的紅色節(jié)點。只要標(biāo)號是從小到大開始遍歷,最終形成的方案就一定是字典序最小的
題目描述:
小紅拿到了一個圖,其由 n個頂點、m 條邊組成。
這個圖上面的一些點被染成了紅色。
小紅有一個能力:她每次可以點擊一個紅色的頂點,并將和這個頂點相鄰的紅色連通塊的所有紅色頂點全部標(biāo)記。
當(dāng)兩個紅色頂點有一條邊相連時,這兩個紅色頂點被稱為連通的。另外,若a和b連通且b和c連通,那么a和c也是連通的。
小紅想知道自己至少要點擊多少次,可以把所有紅色的頂點全部標(biāo)記。
你能告訴她點擊的次數(shù)嗎?并請你輸出一個點擊的方案,如果有多個方案合法,請你輸出一個字典序最小的方案。
注:字典序的定義:兩個不同方案的字典序比較:對于從左到右數(shù)第一個不同的數(shù),哪個方案最小,那么它的字典序最小。
例如:方案[1,4,5]和方案[1,3,6]相比,后者更小。因為第一個出現(xiàn)的不同的數(shù)是第二個數(shù),4>3。
輸入描述:
第一行輸出兩個正整數(shù) n 和 m ,用空格隔開。分別代表頂點數(shù)和邊數(shù)。
第二行輸入一個長度為 n 的字符串,代表每個頂點的染色情況。第 i 個字符為'R'代表第 i 個點被染成紅色,為'W'代表未被染色。
接下來的 m 行,每行兩個正整數(shù) x 和 y ,代表 x 和 y 有一條無向邊相連。
不保證圖是整體連通的。不保證沒有重邊和自環(huán)。
數(shù)據(jù)范圍:
輸出描述:
第一行輸出一個正整數(shù) cnt ,代表小紅的點擊次數(shù)。
第二行輸出 cnt 個正整數(shù),用空格隔開,代表小紅點擊的順序。
示例1
輸入
5 5
RRRWR
3 4
3 1
2 5
5 5
4 5
輸出
2
1 2
說明
可以發(fā)現(xiàn),共有2個紅色連通塊:{1,3}和{2,5}。只需要點擊2次即可,字典序最小的方案是[1,2]
import java.util.*; import java.io.*; public class Main { static ArrayList<Integer>[] g; static String[] strings; static int[] visited; public static void main(String[] args) throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String[] firstLine = br.readLine().split(" "); int n = Integer.parseInt(firstLine[0]); int m = Integer.parseInt(firstLine[1]); g = new ArrayList[n+1]; visited = new int[n+1]; for (int i=1;i<n+1;i++) { g[i] = new ArrayList<Integer>(); } //一個字符一個字符的讀取 strings = br.readLine().split(""); for (int i=0;i<m;i++) { //描繪雙向圖 String[] temp = br.readLine().split(" "); int x = Integer.parseInt(temp[0]); int y = Integer.parseInt(temp[1]); g[x].add(y); g[y].add(x); } int cnt = 0; StringBuilder sb = new StringBuilder(); for (int i=1;i<n+1;i++) { if (visited[i] ==0 && strings[i-1].equals("R")) { cnt++; sb.append(i + " "); //從糖葫蘆小的開始紀(jì)錄,然后延深。 dfs(i); } } System.out.println(cnt); System.out.println(sb); } static void dfs(int x) { if (visited[x] ==0 && strings[x-1].equals("R")) { //點亮它 visited[x] = 1; for (int i=0;i<g[x].size();i++) { dfs(g[x].get(i)); } } } }
3.kotori和素因子
難度???
知識點:dfs(回溯法)
按照題意進(jìn)行dfs即可。注意素因子的判重,可以使用set或者visited數(shù)組。
題目描述:
kotori拿到了一些正整數(shù)。她決定從每個正整數(shù)取出一個素因子。但是,kotori有強(qiáng)迫癥,她不允許兩個不同的正整數(shù)取出相同的素因子。
她想知道,最終所有取出的數(shù)的和的最小值是多少?
注:若a%k==0,則稱k是a的因子。若一個數(shù)有且僅有兩個因子,則稱其是素數(shù)。顯然1只有一個因子,不是素數(shù)。
輸入描述:
第一行一個正整數(shù)n,代表kotori拿到正整數(shù)的個數(shù)。
第二行共有n個數(shù)ai,表示每個正整數(shù)的值。
保證不存在兩個相等的正整數(shù)。
1<=n<=10
2<=ai<=1000
輸出描述:
一個正整數(shù),代表取出的素因子之和的最小值。若不存在合法的取法,則輸出-1。
示例1
輸入
4
12 15 28 22
輸出
17
說明
分別取3,5,7,2,可保證取出的數(shù)之和最小
示例2
輸入
5
4 5 6 7 8
輸出
-1
備注: 1<=n<=10 2<=ai<=1000
import java.util.*; import java.io.*; public class Main{ static boolean[] check = new boolean[2000]; static int[] ai; static int n; static int min = Integer.MAX_VALUE; //判斷是否為素數(shù) static boolean isPrime(int x){ if(x<2) return false; //這是根據(jù)開根號的演化版本,提高了效率。 for(int i=2;i*i<=x;i++){ if(x%i==0)return false; } return true; } static void dfs(int index,int sum){ if(index==n){ min=Math.min(min,sum); return; } //查找這個數(shù)沒有被占用的素因子。 for(int i=2;i<=ai[index];i++){ if(ai[index]%i==0&&check[i]==false&&isPrime(i)){ check[i]=true; dfs(index+1,sum+i); //回溯的方法。 check[i]=false; } } } public static void main(String[] args)throws IOException{ BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); n = Integer.parseInt(br.readLine()); String[] a = br.readLine().split(" "); //負(fù)責(zé)保存所有輸入的數(shù) ai = new int[n]; for(int i=0;i<n;i++){ ai[i]=Integer.parseInt(a[i]); } dfs(0,0); System.out.println(min==Integer.MAX_VALUE ? -1:min); br.close(); } }
4.kotori和糖果
難度????
知識點:記憶化搜索
正難則反,我們反向推理。
如果共有n個糖果:
- 若n為偶數(shù),最后一次合并一定是兩堆n/2合并最優(yōu)。
- 若n為奇數(shù),最后一次合并一定是一堆n/2和一堆n/2+1合并最優(yōu)(合并的價值為1)。
所以得到遞推式:f(n)=f(n/2)+f(n-n/2)+n%2
題目描述:
kotori共有n塊糖果,每塊糖果的初始狀態(tài)是分散的,她想把這些糖果聚在一堆。但她每次只能把兩堆糖果合并成一堆。
已知把兩堆數(shù)量為a和b的糖果聚在一堆的代價是|a-b|。
kotori想知道,她把這n塊糖果聚在一堆的最小代價是多少?
輸入描述:
第一行是一個正整數(shù)T,代表數(shù)據(jù)組數(shù)。
第二行到第T+1行,每行一個非負(fù)整數(shù)n,代表kotori的糖果總數(shù)量。
輸出描述:
每行一個整數(shù),代表kotori需要花費的最小代價。
示例
輸入
2
5
6
輸出
2
2
說明
n=5時,kotori可以這樣聚集糖果:
1 1 1 1 1
2 1 1 1
2 2 1
2 3
5
每一步的代價分別是0,0,1,1,總代價為2。
備注:
對于50%的數(shù)據(jù),0<T≤100000, 0≤n≤100000
對于另外50%的數(shù)據(jù),T=1 , 0≤n≤1e18(這個數(shù)據(jù)范圍是為了為了讓你動態(tài)規(guī)劃做不了,上面的范圍可以做)
import java.util.*; import java.io.*; public class Main{ static HashMap<Long,Long> map = new HashMap<>(); public static void main (String[] args) throws IOException{ BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); int num = Integer.parseInt(br.readLine()); for(int i = 0; i<num; i++){ System.out.println(recurse(Long.parseLong(br.readLine()))); } } static long recurse(long num){ if(num <= 1) return 0; //用于保存 if(map.containsKey(num)) return map.get(num); long pay = recurse(num/2) + recurse(num-num/2) +num%2; map.put(num,pay); return pay; } }
num%2的作用就是為了計數(shù),且保存。
到此這篇關(guān)于例題詳解Java dfs與記憶化搜索和分治遞歸算法的使用的文章就介紹到這了,更多相關(guān)Java 遞歸算法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringCloud_Eureka服務(wù)注冊與發(fā)現(xiàn)基礎(chǔ)及構(gòu)建步驟
Eureka服務(wù)注冊中心,主要用于提供服務(wù)注冊功能,當(dāng)微服務(wù)啟動時,會將自己的服務(wù)注冊到Eureka Server,這篇文章主要介紹了SpringCloud中Eureka的配置及詳細(xì)使用,需要的朋友可以參考下2023-01-01Win10系統(tǒng)下配置Java環(huán)境變量
今天給大家?guī)淼氖顷P(guān)于Java的相關(guān)知識,文章圍繞著Win10系統(tǒng)下配置Java環(huán)境變量展開,文中有非常詳細(xì)的介紹及圖文示例,需要的朋友可以參考下2021-06-06Springboot2 集成 druid 加密數(shù)據(jù)庫密碼的配置方法
這篇文章給大家介紹Springboot2 集成 druid 加密數(shù)據(jù)庫密碼的配置方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2021-07-07淺析Java中XPath和JsonPath以及SpEL的用法與對比
XPath,即XML路徑語言,是一種用于在XML文檔中查找信息的語言,JsonPath是從XPath中發(fā)展而來的,專門用于JSON數(shù)據(jù)格式,本文主要來講講他們的用法與區(qū)別,需要的可以參考下2023-11-11spring-boot-maven-plugin未指定版本導(dǎo)致的編譯錯誤問題
這篇文章主要介紹了spring-boot-maven-plugin未指定版本導(dǎo)致的編譯錯誤問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04Spring Boot中Bean定義方調(diào)用方式解析
這篇文章主要介紹了Spring Boot中Bean定義方調(diào)用方式解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-07-07