日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【尚硅谷_数据结构与算法】十二、算法

發布時間:2023/12/31 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【尚硅谷_数据结构与算法】十二、算法 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 參考文獻
  • 1. 分治算法
    • 1.1 基本介紹
    • 1.2 基本算法步驟
    • 1.3 算法實踐——漢諾塔
  • 2. 動態規劃算法
    • 2.1 核心思想
    • 2.2 算法實踐——背包問題
  • 3. KMP算法
    • 3.1 應用場景-字符串匹配問題
    • 3.2 KMP算法介紹
      • 3.2.1 思路分析
      • 3.2.2 部分匹配表的產生
  • 4. 貪心算法
    • 4.1 算法介紹
    • 4.2 算法應用——集合覆蓋
      • 4.2.1 思路分析
    • 4.3 算法應用——錢幣找零
  • 5. 普利姆算法
    • 5.1 問題提出——修路問題
    • 5.2 最小生成樹
    • 5.3 普利姆算法介紹
      • 5.3.1 普利姆算法步驟
      • 5.3.2 算法實踐——修路問題
  • 6. 克魯斯卡爾算法
    • 6.1 問題提出——公交站問題
    • 6.2 算法介紹
      • 6.2.1 算法圖解說明
      • 6.2.2 算法分析
      • 6.2.3 代碼實現
  • 7. 迪杰斯特拉算法
    • 7.1 問題提出——最短路徑問題
    • 7.2 算法介紹
      • 7.2.1 算法過程
      • 7.2.2 算法實現
  • 8. 弗洛伊德算法
    • 8.1 算法介紹
    • 8.2 算法分析
    • 8.3 算法實踐——最短路徑問題
  • 9. 馬踏棋盤游戲
    • 9.1 游戲介紹
    • 9.2 思路分析

參考文獻

  • 數據結構與算法
  • https://www.bilibili.com/video/BV1E4411H73v?p=153
  • https://cloud.tencent.com/developer/article/1805386
  • 圖解迪杰斯特拉算法(最短路徑問題)
  • 1. 分治算法

    1.1 基本介紹

    • 分治法是一種很重要的算法。字面上的解釋是“分而治之”,就是把一個復雜的問題分成兩個或更多的相同或相似的子問題,再把子問題分成更小的子問題……直到最后子問題可以簡單的直接求解,原問題的解即子問題的解的合并。這個技巧是很多高效算法的基礎,如排序算法(快速排序,歸并排序),傅立葉變換(快速傅立葉變換)……
    • 分治算法可以求解的一些經典問題
      • 二分搜索
      • 大整數乘法
      • 棋盤覆蓋
      • 合并排序
      • 快速排序
      • 線性時間選擇
      • 最接近點對問題
      • 循環賽日程表
      • 漢諾塔

    1.2 基本算法步驟

    • 分治法在每一層遞歸上都有三個步驟:
      • 分解:將原問題分解為若干個規模較小,相互獨立,與原問題形式相同的子問題
      • 解決:若子問題規模較小而容易被解決則直接解,否則遞歸地解各個子問題
      • 合并:將各個子問題的解合并為原問題的解

    1.3 算法實踐——漢諾塔

    • A、B、C三個塔

      • 如果只有一個盤,直接A->C
      • 如果大于等于兩個盤,就分成兩部分。1. 最下面的一個盤為一部分;2. 上面的所有盤為一部分
        • 將上面的所有盤:A->B
        • 最下面的一個盤:A->C
        • 再將B中的盤:B->C
    • 代碼實現

      package pers.chh3213.divide_and_conquer.hanoi;/*** Created with IntelliJ IDEA.** @author : chh3213* @version : 1.0* @Project : DataStructures_Algorithms* @Package : pers.chh3213.divide_and_conquer.hanoi* @ClassName : Hanoi.java* @createTime : 2022/2/19 11:28* @Email :* @Description :*/ public class Hanoi {public static void main(String[] args) {hanoiTower(2,'A','B','C');}/*** 漢諾塔* @param num 盤子總數* @param a 塔A* @param b 塔B* @param c 塔C*/public static void hanoiTower(int num,char a,char b, char c){if(num==1) {System.out.printf("第%d個盤子從%s到%s",num,a,c);System.out.println();}else{//如果我們有n>=2情況,我們總是可以看做是兩個盤1.最下邊的一個盤2.上面的所有盤,//1.先把最上面的所有盤A->B, 移動過程會使用到chanoiTower(num-1,a,c,b);//2.把最下面的盤從A移動到CSystem.out.printf("第%d個盤子從%s到%s",num,a,c);System.out.println();//3.再將B中的盤:B->ChanoiTower(num-1,b,a,c);}} }
    • 遞歸更多講解請參見這篇博客好文

    2. 動態規劃算法

    2.1 核心思想

    • 動態規劃(Dynamic Programming)算法的核心思想是:將大問題劃分為小問題進行解決,從而一步步獲取最優解的處理算法
    • 動態規劃算法與分治算法類似,其基本思想也是將待求解問題分解成若干個子問題,先求解子問題,然后從這些子問題的解得到原問題的解
    • 與分治法不同的是,適合于用動態規劃求解的問題,經分解得到子問題往往不是互相獨立的。( 即下一個子階段的求解是建立在上一個子階段的解的基礎上,進行進一步的求解 )
    • 動態規劃可以通過填表的方式來逐步推進,得到最優解

    2.2 算法實踐——背包問題

    • 有一個背包,容量為4磅,現有如下物品

    • 要求達到的目標為裝入的背包的總價值最大,并且重量不超出
    • 要求裝入的物品不能重復
    • 思路分析

  • 背包問題主要是指一個給定容量的背包、若干具有一定價值和重量的物品,如何選擇物品放入背包使物品的價值最大。其中又分01背包和完全背包(完全背包指的是:每種物品都有無限件可用)

  • 這里的問題屬于01背包,即每個物品最多放一個。而無限背包可以轉化為01背包。

  • 算法的主要思想,利用動態規劃來解決。每次遍歷到的第i個物品,根據w[i]和v[i]來確定是否需要將該物品放入背包中。即對于給定的n個物品,設v[i]、w[i]分別為第i個物品的價值和重量,C為背包的容量。再令v[i]j]表示在前i個物品中能夠裝入容量為j的背包中的最大價值。則我們有下面的結果:

    //表示填入表的第一行和第一列是 0,主要是為了方便表示物品和容量 (1) v[i][0]=v[0][j]=0; // 當準備加入新增的商品的重量大于當前背包的容量時,就直接使用上一個單元格的裝入策略(裝入物品的價值) (2) 當 w[i]>j 時:v[i][j]=v[i-1][j] // 當準備加入的新增的商品的容量小于等于當前背包的容量, // 裝入的方式: (3) 當 j>=w[i]時:v[i][j]=max{v[i-1][j], v[i]+v[i-1][j-w[i]]}v[i-1][j]:上一個裝法的總價值v[i] : 表示當前商品的價值v[i-1][j-w[i]] : 裝入i-1商品,到剩余空間j-w[i]的總價值
    • 簡單來說:

    • 裝入物品的容量大于背包容量時,直接使用之前裝入背包物品的最大價值
    • 裝入物品容量小于等于背包容量時,比較:裝入該物品之前,背包物品的最大價值與裝入該物品后,該物品的價值+剩余容量能放入物品的最大價值。而后選取其中較大者。
    • 代碼實現

      package pers.chh3213.dynamic_program;import java.util.Arrays;/*** Created with IntelliJ IDEA.** @author : chh3213* @version : 1.0* @Project : DataStructures_Algorithms* @Package : pers.chh3213.dynamic_program* @ClassName : KnapsackProblem.java* @createTime : 2022/2/19 14:47* @Email :* @Description :*/ public class KnapsackProblem {//存儲最大價值private int[][] value;//用于表示物品放入背包的方式private int[][] method;//背包容量private int m;//物品個數private int n;public static void main(String[] args) {//各個物品的重量int[] w = {1,4,3};//物品價值int[] v = {1500,3000,2000};//背包容量int m = 4;//物品個數int n = v.length;KnapsackProblem knapsackProblem = new KnapsackProblem(m, n);knapsackProblem.DP(w,v);}public KnapsackProblem(int m, int n) {this.m = m;this.n = n;value = new int[n+1][m+1];method = new int[n+1][m+1];}/*** 動態規劃* @param w 物品重量數組* @param v 物品價值數組*/public void DP(int[] w, int[]v){///初始化第一行和第一列,這里在本程序中,可以不去處理,因為默認就是0for (int i = 0; i < value.length; i++) {value[i][0]=0;}for (int i = 0; i < value[0].length;i++) {value[0][i]=0;}//不處理第一行,i是從1開始的for (int i = 1; i < value.length; i++) {//不處理第一列,j是從1開始的for(int j = 1; j < value[0].length; j++){//因為我們程序i是從1開始的,因此原來公式中的w[i]修改成w[i-1]if(w[i-1]>j){value[i][j]= value[i-1][j];}else{//背包剩余的容量int remain = j-w[i-1];//如果放入該物品前的最大價值大于放入該物品后的最大價值,就不放入該物品if(value[i-1][j]>v[i-1]+value[i-1][remain]){value[i][j]=value[i-1][j];}else{value[i][j]=v[i-1]+value[i-1][remain];//存入放入方法method[i][j]=1;}}}}//打印放入背包的最大價值for (int[] val:value) {System.out.println(Arrays.toString(val));}//打印價值最大的放法//存放方法的二維數組的最大下標,從最后開始搜索存放方法int i = method.length - 1;int j = method[0].length - 1;while(i > 0 && j > 0) {if(method[i][j] == 1) {System.out.println("將第" + i + "個物品放入背包");//背包剩余容量j -= w[i-1];}i--;}}}

    3. KMP算法

    3.1 應用場景-字符串匹配問題

    • 字符串匹配問題:

    有一個字符串 str1= BBC ABCDAB ABCDABCDABDE,和一個子串 str2=ABCDABD。現在要判斷 str1 是否含有 str2, 如果存在,就返回第一次出現的位置, 如果沒有,則返回-1

    • 暴力匹配package pers.chh3213.KMP;/*** Created with IntelliJ IDEA.** @author : chh3213* @version : 1.0* @Project : DataStructures_Algorithms* @Package : pers.chh3213.KMP* @ClassName : bruteForce.java* @createTime : 2022/2/19 15:31* @Email :* @Description :暴力解法解決字符串匹配問題*/ public class bruteForce {public static void main(String[] args) {String str1="BBC ABCDAB ABCDABCDABDE";String str2="ABCDABD";int included = isIncluded(str1, str2);if(included!=-1){System.out.println("匹配成功");System.out.println("輸出位置為"+included);}else{System.out.println("匹配失敗");}}public static int isIncluded(String str1,String str2){char[] charArray1 = str1.toCharArray();char[] charArray2 = str2.toCharArray();for (int i = 0; i < charArray1.length; i++) {int index = i;int j;for (j = 0; j < charArray2.length&&index<charArray1.length;) {if(charArray1[index]==charArray2[j]){index++;j++;}else{break;}}if(j==charArray2.length){return i;}}return -1;} }
    • 用暴力方法解決的話就會有大量的回潮,每次只移動一位,若是不匹配,移動到下一位接著判斷,浪費了大量,的時間。

    3.2 KMP算法介紹

    • KMP 算法(Knuth-Morris-Pratt 算法)是一個著名的字符串匹配算法,效率很高

    • KMP方法算法就利用之前判斷過信息,通過一個next數組,保存模式串中前后最長公共子序列的長度,每次回溯時,通過next數組找到,前面匹配過的位置,省去了大量的計算時間

    • 延續章節3.1的問題,要求使用KMP算法完成。

    3.2.1 思路分析

  • 首先,用 str1的第一個字符和 str2的第一個字符去比較,不符合,關鍵詞向后移動一位

  • 重復第一步,還是不符合,再后移

  • 一直重復,直到 Str1有一個字符與 Str2的第一個字符符合為止

  • 接著比較字符串和搜索詞的下一個字符,還是符合

  • 遇到 Str1有一個字符與 Str2對應的字符不符合

  • 這時候,想到的是繼續遍歷 str1的下一個字符,重復第 1步。(其實是很不明智的,因為此時 BCD已經比較過了,沒有必要再做重復的工作,一個基本事實是,當空格與D不匹配時,你其實知道前面六個字符是” ABCDAB”。KMP 算法的想法是:設法利用這個已知信息,不要把”搜索位置”移回已經比較過的位置,繼續把它向后移,這樣就提高了效率。

  • 怎么做到把剛剛重復的步驟省略掉?可以對 str2計算出一張部分匹配表,這張表的產生在后面介紹。
    str2的部分匹配表如下

  • 已知空格與 D不匹配時,前面六個字符” ABCDAB”是匹配的。查表可知,最后一個匹配字符B對應的部分匹配值為 2,因此按照下面的公式算出向后移動的位數:
    移動位數 = 已匹配的字符數 - 對應的部分匹配值。因為 6 - 2 等于 4,所以將搜索詞向后移動 4 位

  • 因為空格與C不匹配,搜索詞還要繼續往后移。這時,已匹配的字符數為2(”AB”),對應的部分匹配值為0。所以,移動位數=2-0,結果為2,于是將搜索詞向后移2位。

  • 因為空格與A不匹配,繼續后移一位。

  • 逐位比較,直到發現 C與 D不匹配。于是,移動位數 = 6 - 2,繼續將搜索詞向后移動 4 位

  • 逐位比較,直到搜索詞的最后一位,發現完全匹配,于是搜索完成。如果還要繼續搜索(即找出全部匹配),移動位數 = 7 - 0,再將搜索詞向后移動 7 位,這里就不再重復了

  • 3.2.2 部分匹配表的產生

    • 前綴與后綴

    • 部分匹配值就是”前綴”和”后綴”的最長的共有元素的長度。
      以”ABCDABD”為例:

      • ”A”的前綴和后綴都為空集,共有元素的長度為 0;
      • ”AB”的前綴為[A],后綴為[B],共有元素的長度為 0;
      • ”ABC”的前綴為[A, AB],后綴為[BC, C],共有元素的長度 0;
      • ”ABCD”的前綴為[A, AB, ABC],后綴為[BCD, CD, D],共有元素的長度為 0;
      • ”ABCDA”的前綴為[A, AB, ABC, ABCD],后綴為[BCDA, CDA, DA, A],共有元素為”A”,長度為 1;
      • ”ABCDAB”的前綴為[A, AB, ABC, ABCD, ABCDA],后綴為[BCDAB, CDAB, DAB, AB, B],共有元素為”AB”,長度為 2;
      • ”ABCDABD”的前綴為[A, AB, ABC, ABCD, ABCDA, ABCDAB],后綴為[BCDABD, CDABD, DABD, ABD, BD,D],共有元素的長度為 0。
    • ”部分匹配”的實質是,有時候,字符串頭部和尾部會有重復。比如,”ABCDAB”之中有兩個”AB”,那么它的”部分匹配值”就是2(” AB”的長度)。搜索詞移動的時候,第一個”AB”向后移動4位(字符串長度-部分匹配值),就可以來到第二個”AB”的位置。

    • 代碼實現

      package pers.chh3213.KMP;import java.util.Arrays;/*** Created with IntelliJ IDEA.** @author : chh3213* @version : 1.0* @Project : DataStructures_Algorithms* @Package : pers.chh3213.KMP* @ClassName : KMPSearch.java* @createTime : 2022/2/19 16:37* @Email :* @Description :*/ public class KMPSearch {public static void main(String[] args) {String str1="BBC ABCDAB ABCDABCDABDE";String str2="ABCDABD";int[] table = getTable(str2);System.out.println("部分匹配表:"+Arrays.toString(table));//[0, 0, 0, 0, 1, 2, 0]int included = kmp(str1, str2,table);if(included!=-1){System.out.println("匹配成功");System.out.println("輸出位置為"+included);//15}else{System.out.println("匹配失敗");}}/*** 得到字符串的部分匹配表* @param matchStr 用于匹配的字符串* @return 部分匹配表*/public static int[] getTable(String matchStr){//部分匹配值的數組int[] partTable = new int[matchStr.length()];//字符串的第一個元素沒有前綴與后綴,部分匹配值為0partTable[0]=0;//i用來指向部分匹配字符串末尾的字符,j用來指向開始的字符for (int i = 1,j=0; i < matchStr.length(); i++) {//當j>0且前綴后綴不匹配時while (j>0&&matchStr.charAt(i)!=matchStr.charAt(j)){//使用部分匹配表中前一個表項的值j = partTable[j-1];}//如果前綴后綴匹配,j向后移,繼續比較if(matchStr.charAt(i)==matchStr.charAt(j)){j++;}//存入匹配值partTable[i]=j;}return partTable;}/*** kmp搜索算法* @param str1 原字符串* @param str2 字串* @param partTable 字串對應的部分匹配表* @return 如果是-1就是沒有匹配到,否則返回第一個匹配的位置,*/public static int kmp(String str1,String str2,int[] partTable){for (int i = 0,j=0; i < str1.length(); i++) {while (j>0&&str1.charAt(i)!=str2.charAt(j)){j=partTable[j-1];}if(str1.charAt(i)==str2.charAt(j)){j++;}if(j==str2.length()){//如果匹配完成,返回第一個字符出現位置return i-j+1;}}return -1;} }

    4. 貪心算法

    4.1 算法介紹

    • 貪心算法(貪心算法)是指在對問題進行求解時,在每一步選擇中都采取最好或者最優(即最有利)的選擇,從而希望能夠導致結果是最好或者最優的算法
    • 貪心算法所得到的結果不一定是最優的結果(有時候會是最優解),但是都是相對近似(接近)最優解的結果

    4.2 算法應用——集合覆蓋

    • 假設存在下面需要付費的廣播臺,以及廣播臺信號可以覆蓋的地區。如何選擇最少的廣播臺,讓所有的地區都可以接收到信號

    4.2.1 思路分析

    • 遍歷所有的廣播電臺, 找到一個覆蓋了最多未覆蓋的地區的電臺(此電臺可能包含一些已覆蓋的地區,但沒有關系
    • 將這個電臺加入到一個集合中(比如 ArrayList), 想辦法把該電臺覆蓋的地區在下次比較時去掉。
    • 重復第 1步直到覆蓋了全部的地區

    圖解

    • 遍歷電臺的覆蓋地區,發現K1覆蓋的地區最多,將K1覆蓋的地區從地區集合中移除。然后將K1放入電臺集合中,并更新覆蓋地區個數

    • 遍歷,發現K2覆蓋的地區最多,將K2覆蓋的地區從地區集合中移除。然后將K2放入電臺集合中,并更新覆蓋地區個數

    • 遍歷,發現K3覆蓋的地區最多,將K3覆蓋的地區從地區集合中移除。然后將K3放入電臺集合中,并更新覆蓋地區個數

    • 遍歷,發現K5覆蓋的地區最多,將K5覆蓋的地區從地區集合中移除。然后將K5放入電臺集合中,并更新覆蓋地區個數。所有區域都被覆蓋,算法結束

    • 關鍵代碼示例

      /**** @param allAreas 存放的所有地區* @param broadcasts 所有廣播電臺* @return*/public ArrayList<String> greedyMethod(HashSet<String>allAreas, HashMap<String,HashSet<String>>broadcasts){//創建ArrayList,存放選擇的電臺集合ArrayList<String> selects = new ArrayList<>();//定義一個臨時的集合,在遍歷的過程中,//存放遍歷過程中的電臺覆蓋的地區和當前還沒有覆蓋的地區的HashSet<String> tempSet = new HashSet<>();//定義給maxKey,保存在一次遍歷過程中,能夠覆蓋最大未覆蓋的地區對應的電臺的key//如果maxKey不為null,則會加入到selectsString maxKey=null;//如果allAreas不為0,則表示還沒有覆蓋到所有的地區while (allAreas.size()!=0){maxKey=null;//遍歷broadcasts,取出對應 keyfor (String key:broadcasts.keySet()) {//每進行一次fortempSet.clear();//當前這個key能夠覆蓋的地區HashSet<String> areas = broadcasts.get(key);tempSet.addAll(areas);//求出tempSet和allAreas集合的交集,交集會賦給tempSettempSet.retainAll(allAreas);//如果當前這個集合包含的未覆蓋地區的數量,比maxKey指向的集合地區還多//就需要重置maxKeyif(tempSet.size()>0&&(maxKey==null||tempSet.size()>broadcasts.get(maxKey).size())){maxKey = key;}}//maxKey != null, 應該將maxKey加入 selectsif(maxKey!=null){selects.add(maxKey);///將maxKey指向的廣播電臺覆蓋的地區,從allAreas去掉allAreas.removeAll(broadcasts.get(maxKey));}}return selects;}
    • 完整代碼見于gitee倉庫

    4.3 算法應用——錢幣找零

    • 假設紙幣金額為1元、5元、10元、20元、50元、100元,用盡可能少的紙幣數量湊成123元。

    • 盡可能從大面值一直往下減即可

    • 代碼實現

      package pers.chh3213.greedy;import java.util.Arrays;/*** Created with IntelliJ IDEA.** @author : chh3213* @version : 1.0* @Project : DataStructures_Algorithms* @Package : pers.chh3213.greedy* @ClassName : MoneyChange.java* @createTime : 2022/2/19 20:04* @Email :* @Description :*/ public class MoneyChange {public static void main(String[] args) {int[] cases = {100,50, 20, 10, 5, 1};//需要從大到小排列int[] arrCases = change(123,cases);System.out.println(Arrays.toString(arrCases));//打印結果for(int i = 0; i<cases.length; i++) {if(arrCases[i] != 0) {System.out.println("需要" + cases[i] + "元的紙幣" + arrCases[i] + "張");}}}/*** 湊錢* @param money 總金額* @param cases 紙幣數組* @return 返回每張錢幣要拿出的數量組成的數組*/public static int[] change(int money,int[]cases){int leftMoney = money;int[] counter = new int[cases.length];if(money<0){System.out.println("輸入金額為負數!!");return null;}while (leftMoney>0){for (int i = 0; i < cases.length; i++) {int count=0;while(leftMoney-cases[i]>=0){count++;leftMoney = leftMoney-cases[i];}counter[i]=count;}}return counter;} }

    5. 普利姆算法

    5.1 問題提出——修路問題

    • 勝利鄉有7個村莊(A, B,C,D, E, F,G) ,現在需要修路把7個村莊連通,各個村莊的距離用邊線表示(權),比如A-B距離5公里。問:如何修路保證各個村莊都能連通,并且總的修建公路總里程最短?

    5.2 最小生成樹

    • 修路問題本質就是就是最小生成樹問題
    • 最小生成樹(Minimum Cost Spanning Tree),簡稱 MST。給定一個帶權的無向連通圖,如何選取一棵生成樹,使樹上所有邊上權的總和為最小,這叫最小生成樹。
    • N個頂點,一定有N-1條邊
    • 包含全部頂點
    • N-1條邊都在圖中
    • 求最小生成樹的算法主要是普里姆算法克魯斯卡爾算法

    5.3 普利姆算法介紹

    • 普利姆(Prim)算法求最小生成樹,也就是在包含n個頂點的連通圖中,找出只有(n-1)條邊包含所有n個頂點的連通子圖,也就是所謂的極小連通子圖。

    5.3.1 普利姆算法步驟

    • G=(V,E)G=(V, E)G=(V,E)是具有n個頂點的連通網, T=(U,TE)T=(U, TE)T=(U,TE)是G的最小生成樹, T的初始狀態為U=u0(u0∈V)U={u0} (u0\in V)U=u0(u0V), TE={}。重復執行下述操作:在所有u∈Uu\in UuU, v∈V?Uv\in V-UvV?U的邊中找一條代價最小的邊(u,v)(u,v)(u,v)并入集合TE,同時v并入U,直至U=V。
    • Prim算法的基本思想用偽代碼描述如下;

    5.3.2 算法實踐——修路問題

    • MGraph類代碼

      package pers.chh3213.prim;/*** Created with IntelliJ IDEA.** @author : chh3213* @version : 1.0* @Project : DataStructures_Algorithms* @Package : pers.chh3213.prim* @ClassName : MGraph.java* @createTime : 2022/2/20 9:55* @Email :* @Description :圖類的創建*/ public class MGraph {int vertex;char[] data;int[][] weight;public MGraph(int vertex) {//表示圖的頂點個數this.vertex = vertex;//存放頂點數據data = new char[vertex];//存放邊,就是我們的鄰接矩陣weight = new int[vertex][vertex];} }
    • MinTree類代碼

      package pers.chh3213.prim;import java.util.Arrays;/*** Created with IntelliJ IDEA.** @author : chh3213* @version : 1.0* @Project : DataStructures_Algorithms* @Package : pers.chh3213.prim* @ClassName : MinTree.java* @createTime : 2022/2/20 9:58* @Email :* @Description :根據村莊的圖創建最小生成樹*/ public class MinTree {//圖對象MGraph graph;//圖的各個頂點的值char[] data;//圖對應的頂點int vertex;//圖的鄰接矩陣int[][]weight;public MinTree(char[] data, int[][] weight) {this.data = data;this.weight = weight;this.vertex = data.length;this.graph = new MGraph(this.vertex);}/*** 創建圖的鄰接矩陣*/public void createGraph(){int i,j;for (i = 0; i < vertex; i++) {graph.data[i]=data[i];for (j=0;j<vertex;j++){graph.weight[i][j]=weight[i][j];}}}/*** 顯示圖的鄰接矩陣*/public void showGraph(){for (int[] link: graph.weight) {System.out.println(Arrays.toString(link));}}/*** 編寫prim算法,得到最小生成樹* @param v 表示從圖的第幾個頂點開始生成*/public void prim(int v){///visited[]標記結點(頂點)是否被訪問過int[] visited = new int[graph.vertex];//把當前這個結點標記為已訪問visited[v]=1;//h1和h2記錄兩個頂點的下標int h1 = -1;int h2 = -1;//將minWeight初始成一個大數,后面在遍歷過程中,會被替換,int minWeight = 10000;//因為有graph.verxs頂點,普利姆算法結束后,有graph.verxs-1邊for (int i = 1; i < graph.vertex; i++) {//確定每一次生成的子圖,和哪個結點的距離最近for (int j = 0; j < graph.vertex ; j++) {//j結點表示被訪問過的結點for (int k = 0; k < graph.vertex; k++) {//k結點表示未被訪問過的結點if(visited[j]==1&&visited[k]==0&&graph.weight[j][k]<minWeight){minWeight = graph.weight[j][k];h1=j;h2=k;}}}//找到一條邊是最小System.out.println("邊<" + graph.data[h1] +"," + graph.data[h2] + ">權值 :" + minWeight);//將當前這個結點標記為已經訪問visited[h2]=1;//minweight 重新設置為最大值10000minWeight=10000;}}}
    • PrimDemo類代碼

      package pers.chh3213.prim;/*** Created with IntelliJ IDEA.** @author : chh3213* @version : 1.0* @Project : DataStructures_Algorithms* @Package : pers.chh3213.prim* @ClassName : PrimDemo.java* @createTime : 2022/2/20 9:54* @Email :* @Description :*/ public class PrimDemo {public static void main(String[] args) {char[] data = new char[]{'A','B','C','D','E','F','G'};//鄰接矩陣的關系使用二維數組表示,10000這個大數,表示兩個點不聯通int[][] weight = new int[][]{{10000,5,7,10000,10000,10000,2},{5,10000,10000,9,10000,10000,3},{7,10000,10000,10000,8,10000,10000},{10000,9,10000,10000,10000,4,10000},{10000,10000,8,10000,10000,5,4},{10000,10000, 10000,4,5,10000,6},{2,3,10000, 10000,4,6,10000}};MinTree minTree = new MinTree(data, weight);minTree.createGraph();minTree.showGraph();//普利姆算法生成最小生成樹minTree.prim(0);} }
    • PrimDemo代碼運行結果為:

      /* 邊<A,G>權值 :2 邊<G,B>權值 :3 邊<G,E>權值 :4 邊<E,F>權值 :5 邊<F,D>權值 :4 邊<A,C>權值 :7*/
    • 代碼倉庫見于gitee

    6. 克魯斯卡爾算法

    6.1 問題提出——公交站問題

  • 某城市新增7個站點(A, B, C, D, E, F, G),現在需要修路把7個站點連通
  • 各個站點的距離用邊線表示(權),比如A-B距離12公里,
  • 問:如何修路保證各個站點都能連通,并且總的修建公路總里程最短?
  • 6.2 算法介紹

  • 克魯斯卡爾(Kruskal)算法,也是用來求加權連通圖的最小生成樹的算法;
  • 基本思想:按照權值從小到大的順序選擇n-1條邊,并保證這n-1條邊不構成回路;
  • 具體做法:首先構造一個只含n個頂點的森林,然后依權值從小到大從連通網中選擇邊加入到森林中,并使森林中不產生回路,直至森林變成一棵樹為止。
  • 6.2.1 算法圖解說明

    • 以城市公交站問題來圖解說明克魯斯卡爾算法的原理和步驟:

    • 對于如上圖G4所示的連通網可以有多棵權值總和不相同的生成樹。

    • 假設用數組R保存最小生成樹結果

    • 第1步:將邊<E,F>加入R中。邊<E,F>的權值最小,因此將它加入到最小生成樹結果R中。

    • 第2步:將邊<C,D>加入R中。上一步操作之后,邊<C,D>的權值最小,因此將它加入到最小生成樹結果R中。

    • 第3步:將邊<D,E>加入R中。上一步操作之后,邊<D,E>的權值最小,因此將它加入到最小生成樹結果R中。

    • 第4步:將邊<B,F>加入R中。上一步操作之后,邊<C,E>的權值最小,但<C,E>會和已有的邊構成回路:因此,跳過邊<C,E>。同理,跳過邊<C,F>。將邊<B,F>加入到最小生成樹結果R中。

    • 第5步:將邊<E,G>加入R中。
      上一步操作之后,邊<E,G>的權值最小,因此將它加入到最小生成樹結果R中。

    • 第6步:將邊<A,B>加入R中。上一步操作之后,邊<F,G>的權值最小,但<F,G>會和已有的邊構成回路:因此,跳過邊<F,G>。同理,跳過邊<B,C>。將邊<A,B>加入到最小生成樹結果R中。

    • 此時,最小生成樹構造完成!它包括的邊依次是: <E,F> <C,D> <D,E> <B,F> <E,G><A,B>。

    6.2.2 算法分析

    克魯斯卡爾算法重點需要解決的以下兩個問題:

    • 問題一:對圖的所有邊按照權值大小進行排序。采用排序算法進行排序即可。

    • 問題二:將邊添加到最小生成樹中時,怎么樣判斷是否形成了回路。處理方式是:記錄頂點在"最小生成樹"中的終點,頂點的終點是"在最小生成樹中與它連通的最大頂點"。
      然后每次需要將一條邊添加到最小生存樹時,判斷該邊的兩個頂點的終點是否重合,重合的話則會構成回路。

    • 舉例

      • 在將<E,F> <C,D> <D,E>加入到最小生成樹R中之后,這幾條邊的頂點就都有了終點:
        (01) C的終點是F。
        (02) D的終點是F。
        (03) E的終點是F。
        (04) F的終點是F
      • 關于終點的說明:
      • 就是將所有頂點按照從小到大的順序排列好之后:某個頂點的終點就是"與它連通的最大頂點"。
      • 因此,接下來,雖然<C,E>是權值最小的邊。但是C和E的終點都是F,即它們的終點相同,因此,將<C,E>加入最小生成樹的話,會形成回路。這就是判斷回路的方式。也就是說,我們加入的邊的兩個頂點不能都指向同一個終點,否則將構成回路。

    6.2.3 代碼實現

    參考gitee倉庫

    7. 迪杰斯特拉算法

    7.1 問題提出——最短路徑問題

    • 戰爭時期,勝利鄉有7個村莊(A, B, C, D, E, F,G),現在有六個郵差,從G點出發,需要分別把郵件分別送到A, B, C,D,E,F六個村莊, 各個村莊的距離用邊線表示(權),比如A-B距離5公里, 問:如何計算出G村莊到其它各個村莊的最短距離?如果從其它點出發到各個點的最短距離又是多少?

    7.2 算法介紹

    • 迪杰斯特拉(Dijkstra)算法是典型最短路徑算法,用于計算一個結點到其他結點的最短路徑。它的主要特點是以起始點為中心向外層層擴展(廣度優先搜索思想),直到擴展到終點為止。

    7.2.1 算法過程

  • 設置出發頂點為v,頂點集合V(vl,v2,vi…), v到V中各頂點的距離構成距離集合Dis, Dis (d1,d2,di…), Dis集合記錄著v到圖中各頂點的距離(到自身可以看作0, v到vi距離對應為di)
  • 從Dis中選擇值最小的di并移出Dis集合,同時移出V集合中對應的頂點vi,此時的v到vi即為最短路徑
  • 更新Dis集合,更新規則為:比較v到V集合中頂點的距離值,與v通過vi到V集合中頂點的距離值,保留值較小的一個(同時也應該更新頂點的前驅節點為vi,表明是通過vi到達的)
  • 重復執行兩步驟,直到最短路徑頂點為目標頂點即可結束
  • 7.2.2 算法實現

    代碼實現來源于 參考資料4.

    package pers.chh3213.dijkstra;import java.util.ArrayList;public class Dijkstra {// 約定 10000 代表距離無窮大public static void main(String[] args) {// 頂點char[] vertexes = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };int[][] weight = { // 圖的鄰接矩陣/*A*//*B*//*C*//*D*//*E*//*F*//*G*//*A*/{0, 5, 7, INF, INF, INF, 2},/*B*/{5, 0, INF, 9, INF, INF, 3},/*C*/{7, INF, 0, INF, 8, INF, INF},/*D*/{INF, 9, INF, 0, INF, 4, INF},/*E*/{INF, INF, 8, INF, 0, 5, 4},/*F*/{INF, INF, INF, 4, 5, 0, 6},/*G*/{2, 3, INF, INF, 4, 6, 0}};// 起點下標int source = 6;// 使用迪杰斯特拉查找最短路徑int[] dis = dijkstra(source, vertexes, weight);// 輸出最短路徑長度for (int i=0; i<dis.length; i++){System.out.println(vertexes[source] + "->" + vertexes[i] + " = " + dis[i]);}}private final static int INF = 10000;/*** 迪杰斯特拉算法求解最短路徑問題* @param source 起點下標* @param vertexes 頂點集合* @param weight 鄰接矩陣* @return int[] 起點到各頂點最短路徑距離*/public static int[] dijkstra(int source, char[] vertexes, int[][] weight){// 記錄起點到各頂點的最短路徑長度,如 dis[2] 表示起點到下標為 2 的頂點的最短路徑長度int[] dis;// 存儲已經求出到起點最短路徑的頂點。ArrayList<Character> S = new ArrayList<>();/* 初始化起點 */dis = weight[source];S.add(vertexes[source]);/* 當 S 集合元素個數等于頂點個數時,說明最短路徑查找完畢 */while(S.size() != vertexes.length){int min = INF;// 記錄已經求出最短路徑的頂點下標int index = -1; /* 從 V-S 的集合中找距離起點最近的頂點 */for (int j=0; j<weight.length; j++){if (!S.contains(vertexes[j]) && dis[j] < min){min = weight[source][j];index = j;}}// 更新起點到該頂點的最短路徑長度dis[index] = min;// 將頂點加入到 S 集合中,即表明該頂點已經求出到起點的最小路徑S.add(vertexes[index]);/* 更新起點經過下標為 index 的頂點到其它各頂點的最短路徑 */for (int m=0; m<weight.length; m++){if (!S.contains(vertexes[m]) && dis[index] + weight[index][m] < dis[m]){dis[m] = dis[index] + weight[index][m];}}}return dis;} }

    8. 弗洛伊德算法

    8.1 算法介紹

  • 和Dijkstra算法一樣,弗洛伊德(Floyd)算法也是一種用于尋找給定的加權圖中頂點間最短路徑的算法。該算法名稱以創始人之一、1978年圖靈獎獲得者、斯坦福大學計算機科學系教授羅伯特,弗洛伊德命名
  • 弗洛伊德算法(Floyd)計算圖中各個頂點之間的最短路徑
  • 迪杰斯特拉算法用于計算圖中某一個頂點到其他頂點的最短路徑。
  • 弗洛伊德算法VS迪杰斯特拉算法:迪杰斯特拉算法通過選定的被訪問頂點,求出從出發訪問頂點到其他頂點的最短路徑;弗洛伊德算法中每一個頂點都是出發訪問點,所以需要將每一個頂點看做被訪問頂點,求出從每一個頂點到其他頂點的最短路徑。
  • 8.2 算法分析

  • 假設頂點viv_ivi?到頂點vkv_kvk?的最短路徑己知為LikL_{ik}Lik?,頂點vkv_kvk?vjv_jvj?的最短路徑己知為LkjL_{kj}Lkj?,頂點viv_ivi?vjv_jvj?的路徑為LijL_{ij}Lij?,則viv_ivi?vjv_jvj?的最短路徑為: min((Lik+Lkj),Lij)min((L_{ik}+L_{kj}),L_{ij})min((Lik?+Lkj?),Lij?), vkv_kvk?的取值為圖中所有頂點,則可獲得viv_ivi?vjv_jvj?的最短路徑
  • 至于viv_ivi?vkv_kvk?的最短路徑LikL_{ik}Lik?或者vkv_kvk?vjv_jvj?的最短路徑LkjL_{kj}Lkj?,是以同樣的方式獲得。
  • 8.3 算法實踐——最短路徑問題

    • 戰爭時期,勝利鄉有7個村莊(A, B, C, D, E, F,G),現在有六個郵差,從G點出發,需要分別把郵件分別送到A, B, C,D,E,F六個村莊, 各個村莊的距離用邊線表示(權),比如A-B距離5公里, 問:如何計算出G村莊到其它各個村莊的最短距離?如果從其它點出發到各個點的最短距離又是多少?

    • 代碼實現

      package pers.chh3213.floyd;/*** Created with IntelliJ IDEA.** @author : chh3213* @version : 1.0* @Project : DataStructures_Algorithms* @Package : pers.chh3213.floyd* @ClassName : FloydAlgo.java* @createTime : 2022/2/20 15:14* @Email :* @Description :*/ public class FloydAlgo {public static void main(String[] args) {int INF = 10000;// 頂點char[] vertexes = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };int[][] dis = { // 圖的鄰接矩陣/*A*//*B*//*C*//*D*//*E*//*F*//*G*//*A*/{0, 5, 7, INF, INF, INF, 2},/*B*/{5, 0, INF, 9, INF, INF, 3},/*C*/{7, INF, 0, INF, 8, INF, INF},/*D*/{INF, 9, INF, 0, INF, 4, INF},/*E*/{INF, INF, 8, INF, 0, 5, 4},/*F*/{INF, INF, INF, 4, 5, 0, 6},/*G*/{2, 3, INF, INF, 4, 6, 0}};FloydAlgo floydAlgo = new FloydAlgo();int[][] newDis = floydAlgo.floyd(dis);floydAlgo.show(vertexes,newDis);}/*** 弗洛伊德算法* @param dis 從各個頂點出發到其它頂點的距離,最后的結果,也是保留在該數組*/public int[][] floyd(int[][] dis){//遍歷中轉頂點for (int k = 0; k < dis.length; k++) {//從i頂點出發for (int i = 0; i < dis.length; i++) {//到達j頂點for (int j = 0; j < dis.length; j++) {if(dis[i][k]+dis[k][j]<dis[i][j]){dis[i][j]=dis[i][k]+dis[k][j];}}}}return dis;}/*** 輸出每一對頂點之間的最短距離* @param vertex 頂點數組* @param dis 距離數組*/public void show(char[] vertex,int[][] dis){for (int i = 0; i < dis.length; i++) {for (int j = 0; j < dis[i].length; j++) {System.out.print(vertex[i]+"->"+vertex[j]+"="+dis[i][j]+"\t");}System.out.println();}} }
    • 運行結果為

    9. 馬踏棋盤游戲

    9.1 游戲介紹

  • 馬踏棋盤算法也被稱為騎士周游問題;
  • 將馬隨機放在國際象棋的8×8棋盤的某個方格中,馬按走棋
    規則(馬走日字)進行移動。要求每個方格只進入一次,走遍棋盤上全部64個方格
  • 9.2 思路分析

    馬踏棋盤問題實際上是圖的深度優先搜索(DFS)的應用。

    • 假設以 V 為起點,首先找出在指定規則下 V 點下一步可能的落點。
    • 在下一步的可能的落點中選擇一個點(假設是 U 點),然后走到 U 點。
    • 再以 U 點為起點,找出指定規則下 U 點下一步可能的落點。
    • 在下一步可能的落點中選擇一個點(假設是 W 點),然后走到 W 點。
    • 如此循環下去,直至走完了所有的格子。

    需要注意的是,在下一步可能的落點中選擇一個點這個操作具體怎么進行呢?是隨機選擇一個點的嗎?

    并不是隨機的,這里面用到了貪心算法:為了讓之后的選擇盡可能少一點,一般會在下一步可能的落點選項中優先選擇這樣的一個點,這個點的特點是它的下一步可能的落點的選擇最少。

    • 代碼實現(代碼中的Point類為java庫,在包package java.awt下)

      package pers.chh3213.horse_chessboard;import java.awt.*; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator;/*** Created with IntelliJ IDEA.** @author : chh3213* @version : 1.0* @Project : DataStructures_Algorithms* @Package : pers.chh3213.horse_chessboard* @ClassName : HorseChessboard.java* @createTime : 2022/2/20 15:48* @Email :* @Description :*/ public class HorseChessboard {//棋盤的行數private static int X=8;//棋盤的列數private static int Y=8;//創建一個數組,標記棋盤的各個位置是否被訪問過private static boolean[][] isVisited;//1使用一個屬性,標記是否棋盤的所有位置都被訪問// 如果為 true,表示成功private static boolean isFinished;public static void main(String[] args) {//馬兒初始位置的行,從1開始編號,int row = 1;//馬兒初始位置的列,從1開始編號,int column=1;//創建棋盤int[][] chessboard = new int[X][Y];//初始值都是falseisVisited = new boolean[X][Y];///測試一下耗時long start = System.currentTimeMillis();traversalChessboard(chessboard, row - 1, column - 1, 1);long end = System.currentTimeMillis();System.out.println("共耗:"+ (end - start) +"毫秒");for (int[] item:chessboard) {System.out.println(Arrays.toString(item));}}/*** 完成騎士周游問題的算法* @param chessboard 棋盤* @param row 當前位置的行,從0開始* @param col 當前位置的列,從0開始* @param step 第幾步,初始位置是第一步*/public static void traversalChessboard(int[][] chessboard,int row,int col, int step){chessboard[row][col]=step;isVisited[row][col]=true;ArrayList<Point> nextPoints = next(new Point(row, col));sort(nextPoints);while (!nextPoints.isEmpty()){// 排序后,移動到的下一個點的下一步的可選項個數時最小的Point point = nextPoints.remove(0);//若沒有訪問過//System.out.println(point.x);//System.out.println(point.y);if(!isVisited[point.x][point.y]){traversalChessboard(chessboard,point.x, point.y, step+1);}}//判斷馬兒是否完成了任務,使用 step和應該走的步數比較,//如果沒有達到數量,則表示沒有完成任務,將整個棋盤置0)//說明: step<X*Y 成立的情況有兩種// 1.棋盤到目前位置,仍然沒有走完// 2.棋盤處于一個回溯過程if(step<X*Y&&!isFinished){chessboard[row][col]=0;isVisited[row][col]=false;}else{isFinished=true;}}/*** *功能:根據當前位置(Point對象),計算馬兒還能走哪些位置(Point),* 并放入到一個集合中(ArrayList),最多有8個位置* @param curPoint 當前位置* @return*/public static ArrayList<Point>next(Point curPoint){ArrayList<Point> points = new ArrayList<>();Point p1 = new Point();//馬可以走5這個位置if((p1.x= curPoint.x-2)>=0&&(p1.y= curPoint.y-1)>=0)points.add(new Point(p1));//馬可以走6這個位置if((p1.x= curPoint.x-1)>=0&&(p1.y= curPoint.y-2)>=0)points.add(new Point(p1));//馬可以走7這個位置if((p1.x= curPoint.x+1)<Y&&(p1.y= curPoint.y-2)>=0)points.add(new Point(p1));//馬可以走0這個位置if((p1.x= curPoint.x+2)<Y&&(p1.y= curPoint.y-1)>=0)points.add(new Point(p1));//馬可以走1這個位置if((p1.x= curPoint.x+2)<Y&&(p1.y= curPoint.y+1)<X)points.add(new Point(p1));//馬可以走2這個位置if((p1.x= curPoint.x+1)<Y&&(p1.y= curPoint.y+2)<X)points.add(new Point(p1));//馬可以走3這個位置if((p1.x= curPoint.x-1)>=0&&(p1.y= curPoint.y+2)<X)points.add(new Point(p1));//馬可以走4這個位置if((p1.x= curPoint.x-2)>=0&&(p1.y= curPoint.y+1)<X)points.add(p1);return points;}/*** 將集合中的 Point 對象根據其下一步可移動選項的個數升序排序* @param ps*/public static void sort(ArrayList<Point>ps){ps.sort(new Comparator<Point>() {@Overridepublic int compare(Point o1, Point o2) {return next(o1).size() - next(o2).size();}});}}

    總結

    以上是生活随笔為你收集整理的【尚硅谷_数据结构与算法】十二、算法的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。