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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

常用十大算法

發布時間:2023/12/31 编程问答 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 常用十大算法 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

這里的講解圖主要使用的是尚硅谷韓順平老師的圖,請周知。

目錄

二分查找(非遞歸)

分治算法

動態規劃算法?

KMP算法?

貪心算法?

普利姆(Prim)算法

克魯斯卡爾(Kruskal)算法

迪杰斯特拉(Dijkstra)算法

弗洛伊德(Floyd)算法

騎士周游算法

總結


二分查找(非遞歸)

二分查找(非遞歸)算法:前面我們涉及的是遞歸實現二分查找算法。如今實現的是非遞歸的方式。同樣,二分查找只適用于有序數列。二分查找的運行時間為對數時間O(log2n),即查找到需要的目標位置最多只需要log2n步。

實現思路

  • 我們需要借助兩個指針left和right,left指向第一個數據;right指向最后一個數據。
  • 通過一個循環while(left<=right),含義為當兩個指針不相對僭越時,說明數據仍未被遍歷完。
  • 將中間值arr[mid],mid=(left+right)/2與要查找的數據進行比較
  • ????????3.1如果相等,直接返回,結束查找

    ??????3.2如果arr[mid]>data,則說明要查找的數據在此中間值的左邊,將right指針向左移動,right=mid-1;然后重復3步驟

    ????????3.3如果arr[mid]<data,則說明要查找的數據在此中間值的右邊,將left指針向右移動,

    left=mid+1;然后重復3步驟

    ? ? ? 4.當循環結束,該查找方法未結束,說明沒有查找到對應的值,直接返回-1。

    代碼:

    package com.liu.algorithm;/*** @author liuweixin* @create 2021-09-20 16:13*/ //二分查找(非遞歸) public class BinarySearch {public static void main(String[] args) {int [] arr = new int[]{1,3,8,10,11,67,100};int search = binarySearch(arr, 101);System.out.println(search);}/*** 非遞歸式實現二分查找** @param arr 要查找的數組* @param data 要查找的數據* @return 如果找到,返回該數據的下標;否則返回-1*/public static int binarySearch(int[] arr, int data) {//創建兩個指針,分別指向第一個數據與最后一個數據int left = 0;int right = arr.length - 1;while (left<=right){//當兩個指針不相對僭越時,說明數據仍未被遍歷完int mid = (left+right)/2;if(arr[mid]==data){//如果中間值剛好就是要查找的值//直接返回return mid;}else if(arr[mid]<data){//要查找的數據在該中間值的右邊left=mid+1;}else {//要查找的數據在該中間值的左邊right=mid-1;}}//如果循環結束,該方法沒有終結,說明沒有找到。return -1;} }

    分治算法

    分治算法:分治算法的主要思想是將一個復雜而龐大的問題分解成若干個小的容易解決的子問題,進而進行治,而將治后的結果進行匯總合并,就得到了該復雜龐大問題的結果。這個思想在之前的歸并排序中就曾出現過。

    分治算法可以解決的一些經典問題

  • 二分搜索
  • 大整數乘法
  • 棋盤覆蓋
  • 歸并排序
  • 快速排序
  • 線性時間選擇
  • 最接近點對問題
  • 循環賽日程表
  • 漢諾塔
  • 分治算法的基本步驟

    分治法在每一層遞歸上都有三個步驟:

  • 分解:將原問題分解為若干個規模較小,相互獨立,與原問題形式相同的子問題
  • 解決:若子問題規模較小而容易被解決則直接解,否則遞歸地解每個子問題
  • 合并:將各個子問題的解合并為原問題的解。
  • 這里我們以漢諾塔的實際求解來了解分治算法

    漢諾塔:漢諾塔(又稱河內塔)問題是源于印度一個古老傳說的益智玩具。大梵天創造世界的時候做了三根金 剛石柱子,在一根柱子上從下往上按照大小順序摞著 64 片黃金圓盤。大梵天命令婆羅門把圓盤從下面開始按大小 順序重新擺放在另一根柱子上。并且規定,在小圓盤上不能放大圓盤,在三根柱子之間一次只能移動一個圓盤。

    漢諾塔思路求解

  • 如果有一個盤,A->C
  • 如果我們有n>=2情況,我們總是可以看作是兩個盤 1.最下邊的盤 2.上面的盤

  • 先把最上面的盤 A->B
  • 把最下邊的盤A->C
  • 把B塔的所有盤 從B->C
  • ?代碼附上:

    package com.liu.algorithm;/*** @author liuweixin* @create 2021-09-20 21:38*/ //分治算法--漢諾塔問題 public class Hanluotower {public static void main(String[] args) {hanluoTower(4,'A','B','C');}/*** 漢諾塔的移動的方法* 使用分治算法* @param num 盤的個數* @param a 第一個塔a* @param b 需要借助的塔b* @param c 第三個塔c*/public static void hanluoTower(int num,char a,char b,char c){//如果只有一個盤if(num==1){System.out.println("第1個盤從"+a+"->"+c);}else {//如果我們有n>=2情況,我們總可以看作是兩個盤//1.先把最上面的所有盤A->B,移動過程中會使用到chanluoTower(num-1,a,c,b);//2.把最下邊的盤A->CSystem.out.println("第"+num+"個盤從"+a+"->"+c);//3.把B盤的所有盤從B->C,移動過程中使用到a塔hanluoTower(num-1,b,a,c);}} }

    ?漢諾塔問題,雖然代碼少,但是里面變化還是很多的,在我細細debug時,發現其中傳參的變化讓我感嘆萬分,tql!大家也可以仔細debug看看


    動態規劃算法?

    動態規劃算法(Dynamic Programming)

    動態規劃算法的核心是:將大問題劃分為小問題進行解決,從而一步步獲取最優解的處理算法,與分治算法類似。但區別是適用于用動態規劃求解的問題,經分解得到子問題往往不是相互獨立的(即下一個子階段的求解是建立在上一個子結點的解的基礎上,進行進一步的求解)。動態規劃可以通過填表的方式來逐步推進,得到最優解。

    我們借助于一個背包問題來了解動態規劃算法:

    韓老師的圖解還是很經典的:

    ????????

    思路分析

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

    (1)? v[i][0]=v[0][j]=0;? //表示填入表第一行和第一列是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]的最大值

    當j>=w[i]時:v[i][j]=max{v[i-1][j], v[i]+v[i-1][j-w[i]]}

    代碼:

    package com.liu.algorithm;/*** @author liuweixin* @create 2021-09-21 20:46*/ //動態規劃算法->背包問題 public class KnapsackProblem {public static void main(String[] args) {int[] weight = new int[]{1, 4, 3};int[] value = new int[]{1500, 3000, 2000};int capacity = 4;//背包容量dynamic(capacity, weight, value);}/*** 通過動態規劃算法解決背包問題* 輸出在背包容量范圍內的最大價值** @param capacity 背包的容量* @param weight 物品重量的數組* @param value 物品價值的數組*/public static void dynamic(int capacity, int[] weight, int[] value) {//創建物品最大價值與背包容量之間的關系,第一行與第一列置零,所以+1int[][] arr = new int[weight.length + 1][capacity + 1];//先將第一行與第一列置零for (int i = 0; i < arr.length; i++) {arr[i][0] = 0;}for (int i = 0; i < arr[0].length; i++) {arr[0][i] = 0;}int[][] path = new int[weight.length + 1][capacity + 1];//為了記錄放入商品的情況for (int i = 1; i < arr.length; i++) {//對arr的行進行遍歷for (int j = 1; j < arr[i].length; j++) {//對arr的列進行遍歷if (weight[i - 1] > j) {//如果該商品的容量大于背包容量arr[i][j] = arr[i - 1][j];//直接使用上一個單元格的裝入策略} else {//如果該商品的容量小于背包容量//arr[i][j] = Math.max(arr[i - 1][j], value[i - 1] + arr[i - 1][j - weight[i - 1]]);//為了記錄商品放到背包的情況,我們不能直接使用上面的公式,需要借助if-else來實現if (arr[i - 1][j] < (value[i - 1] + arr[i - 1][j - weight[i - 1]])) {arr[i][j] = value[i - 1] + arr[i - 1][j - weight[i - 1]];//并把情況記錄到path中//只要我們不是采取了上一行的數據,而是采用了新的數據,則說明此時加入了新商品,所以我們需要對其標記path[i][j] = 1;} else {arr[i][j] = arr[i - 1][j];}}}}//遍歷價值數組for (int[] temp : arr) {for (int data : temp) {System.out.print(data + " ");}System.out.println();}//將物品情況輸出//這里從最后開始輸出的原因是,在最后,價值是最大的,我們只要輸出最大的那個價值對應的商品即可int i = path.length - 1;//行的最大下標int j = path[0].length - 1;//列的最大下標while (i > 0 && j > 0) {//從path的最后開始找if (path[i][j] == 1) {System.out.println("第" + i + "個商品放到背包");j -= weight[i - 1];//此時已經找到最大價值的商品,如果不止一件,我們就減少該件的重量,去尋找下一件,所以j要減去當前件的重量}i--;//向前尋找}} }

    KMP算法?

    KMP算法

    這里我就借助尚硅谷韓老師的例子,順便幫尚硅谷打一波小廣告。

    問題引入:

    1)有一個字符串str1="硅硅谷 尚硅谷你尚硅 尚硅谷你尚硅谷你尚硅你好",和一個子串??? str2="尚硅谷你尚硅 你"

    2)現在要判斷str1是否含有str2,如果存在,就返回第一次出現的位置,如果沒有,則返回-1

    對該問題,我們第一想法是采取暴力匹配的方式去解決;

    如果用暴力匹配的思路,并假設現在 str1 匹配到 i 位置,子串 str2 匹配到 j 位置,則有:

    1) 如果當前字符匹配成功(即str1[i] == str2[j]),則 i++,j++,繼續匹配下一個字符

    2) 如果失配(即 str1[i]! = str2[j]),令? i = i - (j - 1),j = 0。相當于每次匹配失敗時,i回溯,j被置為0。

    暴力匹配算法的實現

  • 我們借助兩個循環,一個循環則是循環str1字符串,另一個循環則循環str2字符串。
  • i為循環str1字符串的指針,j為循環str2字符串的指針。
  • 當str1[i]==str2[j]時,兩個指針同時邁進。
  • 當str1[i]!=str2[j]時,說明此時已不匹配,需要將指針回溯。此時j需要回溯到0,而i則
  • 需要回溯到剛開始遍歷的i的后一位,即與j同步的長度的后一位,即i=i-j+1。然后繼續進行下一位的匹配。

    缺點

    用暴力方法解決的話就會有大量的回溯,每次只移動一位,若是不匹配,移動到下一位接著判斷,浪費了大量 的時間。(不可行!)

    所以我們這里就采取KMP算法來解決。

    KMP是一個解決模式串在文本串是否出現過,如果出現過,返回最早出現位置的經典算法。KMP算法,即Knuth-Morris-Pratt字符串查找算法。KMP算法就是利用之前判斷過的信息,通過一個nect數組,保存模式串中前后最長公共子序列的長度,每次回溯時,通過next數組找到,前面匹配過的位置,省去了大量的計算時間。

    在講解KMP算法的具體實施前,我們先介紹《部分匹配表》是如何實現的,該表在KMP算法的實施中占據關鍵性作用。

    《部分匹配表》是通過一個字符串的前綴與后綴集合中一致的字串的個數,對應而列出來的一個表。

    例如:“ABCDAD”

    當只有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,此時共有長度為1

    當ABCDAD時,前綴為A,AB,ABC,ABCD,ABCDA,后綴為BCDAD,CDAD,DAD,AD,D,此時共有長度為0;

    所以可以列出表:

    搜索詞

    A

    B

    C

    D

    A

    D

    部分匹配值

    0

    0

    0

    0

    1

    0

    有了部分匹配表的知識,我們就可以講解KMP算法的思路:

    關鍵點:

    需要移動的位數=已匹配的字符數-對應的部分匹配值。J=next[j-1]

    代碼:

    package com.liu.algorithm;import com.sun.org.apache.bcel.internal.generic.NEW;/*** @author liuweixin* @create 2021-09-22 20:13*/ //KMP算法,即Knuth-Morris-Pratt字符串查找算法 public class KMP {public static void main(String[] args) {String str1 = "BBC ABCDAB ABCDABCDABDE";String str2 = "ABCDABD";KMP kmp = new KMP();int[] next = kmp.kmpNext(str2);int index = kmp.kmpSearch(str1, str2, next);System.out.println(index);}/*** kmp算法的實現** @param str1 文本串* @param str2 模式串* @param next 部分匹配表* @return 如果找到,返回第一個匹配的位置;如果沒有找到,則返回-1。*/public static int kmpSearch(String str1, String str2, int[] next) {//遍歷for (int i = 0, j = 0; i < str1.length(); i++) {//需要處理str1.charAt()!=str2.charAt(j),去調整j的大小while (j > 0 && str1.charAt(i) != str2.charAt(j)) {//即將模式串向后挪動j = next[j - 1];}if (str1.charAt(i) == str2.charAt(j)) {j++;}if (j == str2.length()) {//找到了return i - j + 1;}}return -1;}/*** 獲取傳入字符串的部分匹配值表** @param dest 傳入字符串* @return 返回一個部分匹配值表*/public static int[] kmpNext(String dest) {//創建一個next數組保存部分匹配值int[] next = new int[dest.length()];next[0] = 0;//字符串的首個元素的匹配值為0for (int i = 1, j = 0; i < dest.length(); i++) {//當dest.charAt(i)!=dest.charAt(j),我們需要從next[j-1]獲取新的j//直到我們發現有dest.charAt(i)==dest.charAt(j)成立才退出//這是kmp算法的核心點while (j > 0 && dest.charAt(i) != dest.charAt(j)) {j = next[j - 1];}//當dest.charAt(i)==dest.charAt(j)滿足時,部分匹配值就是+1if (dest.charAt(i) == dest.charAt(j)) {j++;}next[i] = j;}return next;} }

    貪心算法?

    貪心算法:

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

    思路

  • 我們通過選擇每次覆蓋最多地區的廣播臺,然后將其添加到我們記錄廣播臺的ArrayList集合中即selects。
  • 每次添加完廣播臺,我們需要在記錄所有需要覆蓋地區的集合allAreas中刪除掉該廣播臺覆蓋的地區,然后繼續循環,執行1操作
  • allAreas中的地區全部刪除掉,循環結束,此時所有地區已覆蓋完畢。
  • 代碼:

    package com.liu.algorithm;import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet;/*** @author liuweixin* @create 2021-09-23 15:37*/ //貪心算法—集合覆蓋問題 public class GreedyAlgorithm {public static void main(String[] args) {//先創建對應的廣播臺集合HashMap<String, HashSet<String>> broadcasts = new HashMap();//創建對應的廣播HashSet<String> hashSet1 = new HashSet<>();hashSet1.add("北京");hashSet1.add("上海");hashSet1.add("天津");HashSet<String> hashSet2 = new HashSet<>();hashSet2.add("廣州");hashSet2.add("北京");hashSet2.add("深圳");HashSet<String> hashSet3 = new HashSet<>();hashSet3.add("成都");hashSet3.add("上海");hashSet3.add("杭州");HashSet<String> hashSet4 = new HashSet<>();hashSet4.add("上海");hashSet4.add("天津");HashSet<String> hashSet5 = new HashSet<>();hashSet5.add("杭州");hashSet5.add("大連");broadcasts.put("k1", hashSet1);broadcasts.put("k2", hashSet2);broadcasts.put("k3", hashSet3);broadcasts.put("k4", hashSet4);broadcasts.put("k5", hashSet5);//創建一個集合,記錄最終的廣播臺ArrayList<String> selects = new ArrayList<>();//創建一個集合,記錄要覆蓋的地區HashSet<String> allAreas = new HashSet<>();allAreas.add("北京");allAreas.add("上海");allAreas.add("天津");allAreas.add("廣州");allAreas.add("深圳");allAreas.add("成都");allAreas.add("杭州");allAreas.add("大連");//創建一個最大覆蓋的指針String maxSize;HashSet<String> temp = new HashSet<>();//借用一個臨時的hashSet變量,用來存儲交集while (allAreas.size() != 0) {//當要覆蓋的區域還不等于0時,說明還沒有選擇完廣播臺,繼續循環maxSize = null;for (String key : broadcasts.keySet()) {//遍歷廣播臺的數據temp.clear();//將臨時的變量置空temp.addAll(broadcasts.get(key));//將當前廣播臺覆蓋的地區添加到臨時變量中temp.retainAll(allAreas);//找到當前變量與總地區重合的地區數//這里體現貪心算法,每一步都選取最優的選擇if (temp.size() > 0 && maxSize == null) {maxSize = key;} else if (temp.size() > 0 && maxSize != null) {HashSet<String> max = broadcasts.get(maxSize);//獲取最大覆蓋指針指向的廣播臺max.retainAll(allAreas);//獲取其與總地區重合的地區數if (temp.size() > max.size()) {//如果當前遍歷的廣播臺的包含數大于指針指向的包含數,則指針指向當前keymaxSize = key;}}}if (maxSize != null) {//當for循環結束,找到覆蓋最多的廣播臺selects.add(maxSize);//并且在allAreas中刪除maxSize所指向的地區allAreas.removeAll(broadcasts.get(maxSize));}}System.out.println(selects);} }

    普利姆(Prim)算法

    普利姆算法

    問題引入—修路問題:

    修路問題的本質就是最小生成樹(MST)的問題。給定一個帶權的無向連通圖,如何選取一顆生成樹,使得樹上所有邊上的權的總和為最小,這叫做最小生成樹。

  • N個頂點,一定有N-1條邊
  • 包含全部頂點
  • N-1條邊都在圖中
  • 求最小生成樹一般是采用普利姆算法和克魯斯卡爾算法

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

    普利姆算法的實現如下

  • 設G=(V,E)是連通圖,T=(U,D)是最小生成樹,V,U是頂點集合,E,D是邊的集合
  • 若從頂點u開始構建最小生成樹,則從集合V中取出頂點u放入集合U中,標記頂點v的visited[u]=1
  • 若集合U中頂點ui與集合V-U中搞得頂點vj之間存在邊,則尋找這些邊中權值最小的邊,但不能構成回路,將頂點vj加入集合U中,將邊(ui,uj)加入集合D中,標記visited[vj]=1
  • 重復步驟2,直到U與V相等,即所有頂點都被標記為訪問過,此時D中有n-1條邊
  • 實現代碼:

    package com.liu.algorithm;/*** @author liuweixin* @create 2021-09-23 18:59*/ //普利姆算法 public class PrimAlgorithm {public static void main(String[] args) {char[] data = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G'};int[][] weight = new int[][]{{Integer.MAX_VALUE, 5, 7, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, 2},{5, Integer.MAX_VALUE, Integer.MAX_VALUE, 9, Integer.MAX_VALUE, Integer.MAX_VALUE, 3},{7, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, 8, Integer.MAX_VALUE, Integer.MAX_VALUE},{Integer.MAX_VALUE, 9, Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, 4, Integer.MAX_VALUE},{Integer.MAX_VALUE, Integer.MAX_VALUE, 8, Integer.MAX_VALUE, Integer.MAX_VALUE, 5, 4},{Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE, 4, 5, Integer.MAX_VALUE, 6},{2, 3, Integer.MAX_VALUE, Integer.MAX_VALUE, 4, 6, Integer.MAX_VALUE}};Graph graph = new Graph(data.length);Graph graph1 = creatGraph(graph, data, weight);prim(graph1,0);}/*** 對一個圖對象賦值** @param graph 要賦值的圖對象* @param data 圖節點的數值* @param weight 圖節點之間的權值*/public static Graph creatGraph(Graph graph, char[] data, int[][] weight) {for (int i = 0; i < data.length; i++) {graph.data[i] = data[i];//給圖的節點賦值for (int j = 0; j < weight[i].length; j++) {graph.weight[i][j] = weight[i][j];//給圖的節點之間的權值復制}}return graph;}/*** 普利姆算法的實現** @param graph 要搜尋最短路徑的圖* @param v 最短路徑的起始點*/public static void prim(Graph graph, int v) {//先創建一個isVisited數組,用以表示是否已訪問的數組,boolean[] isVisited = new boolean[graph.data.length];int index1 = -1;//記錄第一個節點的下標int index2 = -1;//記錄第二個節點的下標int min = Integer.MAX_VALUE;//借用該輔助值,記錄下最小的權值//先把當前的節點標記為已訪問isVisited[v] = true;for (int i = 1; i < graph.data.length; i++) {//大循環,循環邊的個數,n個結點,有n-1條邊for (int j = 0; j < graph.data.length; j++) {//尋找已訪問的點for (int k = 0; k < graph.data.length; k++) {//尋找未訪問的點if (isVisited[j] && !isVisited[k] && graph.weight[j][k] < min) {//當進入if判斷,即說明此時找到了較小的權值min = graph.weight[j][k];//記錄下該兩個結點的坐標index1 = j;index2 = k;}}}//當第二個for循環結束,此時已找到未訪問點的最小權值,即找到最小路徑System.out.println(graph.data[index1] + "--->" + graph.data[index2] + " 路徑長度為:" + min);//重置min值,并標記data[k]值已訪問isVisited[index2] = true;min = Integer.MAX_VALUE;}} }//創建圖對象 class Graph {char[] data;//圖的節點的值int[][] weight;//表示兩個節點之間的距離,即權值int vertexs;//節點個數public Graph(int vertexs) {this.vertexs = vertexs;weight = new int[vertexs][vertexs];data = new char[vertexs];} }


    克魯斯卡爾(Kruskal)算法

    克魯斯卡爾(kruskal)算法

    之前提到,求最小生成樹我們一般可以采用兩種算法,一種是普利姆算法,一種是克魯斯卡爾算法。因此我們同樣引入求最短路徑的的問題來了解克魯斯卡爾算法。

    Kruskal算法介紹

    1)克魯斯卡爾(Kruskal)算法,是用來求加權連通圖的最小生成樹的算法。

    2)基本思想:按照權值從小到大的順序選擇 n-1 條邊,并保證這 n-1 條邊不構成回路

    3)具體做法:首先構造一個只含 n 個頂點的森林,然后依權值從小到大從連通網中選擇邊加入到森林中,并使森林中不產生回路,直至森林變成一棵樹為止

    Krukal算法的圖解

    Kruskal算法分析

    判斷是否構成回路

    代碼:

    package com.liu.algorithm;import java.util.Arrays;/*** @author liuweixin* @create 2021-09-23 19:59*/ //克魯斯卡爾算法—最短路徑公交問題 public class KruskalCaseAlgorithm {int[][] weight;//對應的頂點與頂點之間的權值char[] data;//頂點數據int edgeNum;//邊的個數final static int INF = Integer.MAX_VALUE;//一個常數,用以表示兩個頂點之間不連通//構造器初始化public KruskalCaseAlgorithm(int[][] weight, char[] data) {this.weight = weight;this.data = data;for (int i = 0; i < weight.length; i++) {for (int j = i + 1; j < weight[i].length; j++) {if (weight[i][j] != INF) {this.edgeNum++;}}}}public static void main(String[] args) {char[] vertexs = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};int[][] weight = {{0, 12, INF, INF, INF, 16, 14},{12, 0, 10, INF, INF, 7, INF},{INF, 10, 0, 3, 5, 6, INF},{INF, INF, 3, 0, 4, INF, INF},{INF, INF, 5, 4, 0, 2, 8},{16, 7, 6, INF, 2, 0,9},{14, INF, INF, INF, 8, 9,0}};KruskalCaseAlgorithm kruskalCaseAlgorithm = new KruskalCaseAlgorithm(weight, vertexs);kruskalCaseAlgorithm.kruskal();}public void kruskal() {int index = 0;//記錄最終結果數組的索引//創建一個終點數組int[] ends = new int[edgeNum];//創建一個數組記錄最終的結果EData[] result = new EData[edgeNum];EData[] eDatas = getEData();//獲取邊的數據的集合//對邊的數據的權重進行排序sortEData(0, eDatas.length - 1, eDatas);System.out.println("邊的集合" + Arrays.toString(eDatas) + " 共有" + edgeNum + "條");//遍歷eDatas數組,將邊添加到最小生成樹中,判斷準備加入的邊是否形成了回路,如果沒有,就加入result,否則不能加入for (int i = 0; i < edgeNum; i++) {//獲取到第i條邊的第一個頂點與第二個頂點,即起點與終點int index1 = getPosition(eDatas[i].start);int index2 = getPosition(eDatas[i].end);//獲取兩個頂點的終點坐標int end1 = getEnd(ends, index1);int end2 = getEnd(ends, index2);//判斷是否構成回路if (end1 != end2) {//沒有構成回路ends[end1] = end2;//設置m 在"已有最小生成樹中的終點"result[index++] = eDatas[i];//把邊加入到result數組}}System.out.println();for (int i = 0; i < result.length; i++) {if(result[i]!=null){System.out.println(result[i]);}}}/*** 獲取對應頂點的下標** @param data 要獲取的頂點* @return 找到則返回下標,否則返回-1*/public int getPosition(char data) {for (int i = 0; i < this.data.length; i++) {if (this.data[i] == data) {return i;}}return -1;}/*** 初始化邊的數組** @return 返回一個記錄了邊的數組*/public EData[] getEData() {int index = 0;//記錄數組的下標EData[] eDatas = new EData[edgeNum];//創建一個記錄邊的數組for (int i = 0; i < weight.length; i++) {for (int j = i + 1; j < weight[i].length; j++) {if (weight[i][j] != INF) {//當權值不為INF時,說明此時兩個點連通//給數組賦值eDatas[index++] = new EData(data[i], data[j], weight[i][j]);}}}return eDatas;}/*** 功能:獲取下標為i的頂點的終點(),用于判斷后面兩個頂點的重點是否相同** @param ends 數組就是記錄了各個頂點對應的終點是哪個,ends數組是在遍歷過程中,逐漸形成* @param i 表示傳入的頂點對應的下標* @return 返回的就是下標為i的這個頂點對應的終點的下標*/public int getEnd(int[] ends, int i) {while (ends[i] != 0) {//循環的目的是找到最終的終點i = ends[i];}return i;}/*** 對邊的數組以權重大小進行排序* 我這里使用快速排序,具體不加注釋** @param eData 要排序的數組* @param left1 數組其實位置的下標* @param right1 數組最后一個數據的下標*/public static void sortEData(int left1, int right1, EData[] eData) {int left = left1;int right = right1;int mid = eData[(left + right) / 2].weight;EData temp;while (left < right) {while (eData[left].weight < mid) {left++;}while (eData[right].weight > mid) {right--;}if (left >= right) {break;}temp = eData[left];eData[left] = eData[right];eData[right] = temp;if (eData[left].weight == mid) {right--;}if (eData[right].weight == mid) {left++;}}if (left == right) {left++;right--;}if (left1 < right) {sortEData(left1, right, eData);}if (right1 > left) {sortEData(left, right1, eData);}} }//創建一個邊對象 class EData {char start;//邊的起點char end;//邊的終點int weight;//邊的權值public EData(char start, char end, int weight) {this.start = start;this.end = end;this.weight = weight;}@Overridepublic String toString() {return "EData{" +"start=" + start +", end=" + end +", weight=" + weight +'}';} }

    迪杰斯特拉(Dijkstra)算法

    迪杰斯特拉算法

    問題場景引入—最短路徑問題

    區別

    這個與普利姆算法(prim)和克魯斯卡爾算法(kruskal)求解的問題不同的是,該問題是求某個村莊到其他各個村莊之間的最短距離。而prim與kruskal求解的是邊的權值和最小的問題。Dijkstra注重的是局部1對1,而prim和kruskal求的最小生成樹注重的是整體。

    迪杰斯特拉(Dijkstra)算法的介紹

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

    Dijkstra算法實現的過程

    1)設置出發頂點為v,頂點集合V{v1,v2,vi...},v 到V中各頂點的距離構成距離集合Dis,Dis{d1,d2,di...},Dis 集合記錄著v到圖中各頂點的距離(到自身可以看作0,v到vi距離對應為di)

    2)從Dis中選擇值最小的di并移出Dis集合,同時移出 V 集合中對應的頂點 vi,此時的v到vi即為最短路徑

    3)更新Dis集合,更新規則為:比較v到V集合中頂點的距離值,與v通過vi到V集合中頂點的距離值,保留值較小的一個(同時也應該更新頂點的前驅節點為vi,表明是通過vi到達的)

    4)重復執行兩步驟,直到最短路徑頂點為目標頂點即可結束

    通俗地說

    我們以這個圖為例:

    首先假設我們設置的起始點為C:

  • Dijkstra算法的特點是廣度優先遍歷思想。我們以C為中心,我們先標記C為已訪問,然后找到與其直連的點A、E,因為是直連的,所以兩者都是最短的,此時把C到A、E的距離記錄下來。
  • 找到直連后,我們再找權值最短的邊,即A,這里有一點貪心算法的意味,然后我們再以A作為搜索點,設置A為已訪問,廣度優先遍歷,找到B、G,然后先把CG、CB的距離記錄下來。
  • 我們以A作為搜索點遍歷完后,再找下一條權值小的邊,即CE,找到E點;把E設置為已訪問。
  • 我們以E點作為搜索點,廣度優先遍歷,找到G、F,計算出CG、CF的長度,此時發現CEG>CAG,所以我們就以CAG的長度作為最短距離,同樣把CF的長度記錄下來。
  • 然后再找下一個權值最短邊,找到G點,重復上述操作…..
  • 最終就能得到一個以C為起點的到各個頂點最短路徑的結果。
  • 代碼:

    package com.liu.algorithm;import java.util.Arrays;public class DijkstraAlgorithm {public static void main(String[] args) {char[] vertex = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };//鄰接矩陣int[][] matrix = new int[vertex.length][vertex.length];final int N = 65535;// 表示不可以連接matrix[0]=new int[]{N,5,7,N,N,N,2}; matrix[1]=new int[]{5,N,N,9,N,N,3}; matrix[2]=new int[]{7,N,N,N,8,N,N}; matrix[3]=new int[]{N,9,N,N,N,4,N}; matrix[4]=new int[]{N,N,8,N,N,5,4}; matrix[5]=new int[]{N,N,N,4,5,N,6}; matrix[6]=new int[]{2,3,N,N,4,6,N};//創建 Graph對象Graph graph = new Graph(vertex, matrix);//測試, 看看圖的鄰接矩陣是否okgraph.showGraph();//測試迪杰斯特拉算法graph.dsj(2);//Cgraph.showDijkstra();}}class Graph {private char[] vertex; // 頂點數組private int[][] matrix; // 鄰接矩陣private VisitedVertex vv; //已經訪問的頂點的集合// 構造器public Graph(char[] vertex, int[][] matrix) {this.vertex = vertex;this.matrix = matrix;}//顯示結果public void showDijkstra() {vv.show();}// 顯示圖public void showGraph() {for (int[] link : matrix) {System.out.println(Arrays.toString(link));}}//迪杰斯特拉算法實現/*** * @param index 表示出發頂點對應的下標*/public void dsj(int index) {vv = new VisitedVertex(vertex.length, index);update(index);//更新index頂點到周圍頂點的距離和前驅頂點for(int j = 1; j <vertex.length; j++) {index = vv.updateArr();// 選擇并返回新的訪問頂點update(index); // 更新index頂點到周圍頂點的距離和前驅頂點} }//更新index下標頂點到周圍頂點的距離和周圍頂點的前驅頂點,private void update(int index) {int len = 0;//根據遍歷我們的鄰接矩陣的 matrix[index]行for(int j = 0; j < matrix[index].length; j++) {// len 含義是 : 出發頂點到index頂點的距離 + 從index頂點到j頂點的距離的和 len = vv.getDis(index) + matrix[index][j];// 如果j頂點沒有被訪問過,并且 len 小于出發頂點到j頂點的距離,就需要更新if(!vv.in(j) && len < vv.getDis(j)) {vv.updatePre(j, index); //更新j頂點的前驅為index頂點vv.updateDis(j, len); //更新出發頂點到j頂點的距離}}} }// 已訪問頂點集合 class VisitedVertex {// 記錄各個頂點是否訪問過 1表示訪問過,0未訪問,會動態更新public int[] already_arr;// 每個下標對應的值為前一個頂點下標, 會動態更新public int[] pre_visited;// 記錄出發頂點到其他所有頂點的距離,比如G為出發頂點,就會記錄G到其它頂點的距離,會動態更新,求的最短距離就會存放到dispublic int[] dis;//構造器/*** * @param length :表示頂點的個數 * @param index: 出發頂點對應的下標, 比如G頂點,下標就是6*/public VisitedVertex(int length, int index) {this.already_arr = new int[length];this.pre_visited = new int[length];this.dis = new int[length];//初始化 dis數組Arrays.fill(dis, 65535);this.already_arr[index] = 1; //設置出發頂點被訪問過this.dis[index] = 0;//設置出發頂點的訪問距離為0}/*** 功能: 判斷index頂點是否被訪問過* @param index* @return 如果訪問過,就返回true, 否則訪問false*/public boolean in(int index) {return already_arr[index] == 1;}/*** 功能: 更新出發頂點到index頂點的距離* @param index* @param len*/public void updateDis(int index, int len) {dis[index] = len;}/*** 功能: 更新pre這個頂點的前驅頂點為index頂點* @param pre* @param index*/public void updatePre(int pre, int index) {pre_visited[pre] = index;}/*** 功能:返回出發頂點到index頂點的距離* @param index*/public int getDis(int index) {return dis[index];}/*** 繼續選擇并返回新的訪問頂點, 比如這里的G 完后,就是 A點作為新的訪問頂點(注意不是出發頂點)* @return*/public int updateArr() {int min = 65535, index = 0;for(int i = 0; i < already_arr.length; i++) {if(already_arr[i] == 0 && dis[i] < min ) {min = dis[i];index = i;}}//更新 index 頂點被訪問過already_arr[index] = 1;return index;}//顯示最后的結果//即將三個數組的情況輸出public void show() {System.out.println("==========================");//輸出already_arrfor(int i : already_arr) {System.out.print(i + " ");}System.out.println();//輸出pre_visitedfor(int i : pre_visited) {System.out.print(i + " ");}System.out.println();//輸出disfor(int i : dis) {System.out.print(i + " ");}System.out.println();//為了好看最后的最短距離,我們處理char[] vertex = { 'A', 'B', 'C', 'D', 'E', 'F', 'G' };int count = 0;for (int i : dis) {if (i != 65535) {System.out.print(vertex[count] + "("+i+") ");} else {System.out.println("N ");}count++;}System.out.println();}}

    弗洛伊德(Floyd)算法

    弗洛伊德(Floyd)算法

    Floyd算法介紹

    1)和 Dijkstra? 算法一樣,弗洛伊德(Floyd)算法也是一種用于尋找給定的加權圖中頂點間最短路徑的算法。該算法名稱以創始人之一、1978 年圖靈獎獲得者、斯坦福大學計算機科學系教授羅伯特·弗洛伊德命名

    2)弗洛伊德算法(Floyd)計算圖中各個頂點之間的最短路徑

    3)迪杰斯特拉算法用于計算圖中某一個頂點到其他頂點的最短路徑。

    4) 弗洛伊德算法VS迪杰斯特拉算法:迪杰斯特拉算法通過選定的被訪問頂點,求出從出發訪問頂點到其他頂點的最短路徑;弗洛伊德算法中每一個頂點都是出發訪問點,所以需要將每一個頂點看做被訪問頂點,求出從每 一個頂點到其他頂點的最短路徑。

    Floyd算法分析

    1)設置頂點vi 到頂點vk的最短路徑已知為Lik,頂點vk到vj的最短路徑已知為Lkj,頂點vi到vj的路徑為Lij,則vi 到vj的最短路徑為:min((Lik+Lkj),Lij),vk 的取值為圖中所有頂點,則可獲得vi 到vj的最短路徑

    2)至于vi到vk的最短路徑Lik或者vk到vj的最短路徑Lkj,是以同樣的方式獲得

    Floyd算法圖解

    通俗地講

    就是我們找尋一個中間點(遍歷所有的點作為中間點)

    1)如上圖,我們首先找到A作為中間點。

    2)然后找到以A點為中間點的起始點和終點,我們可以找到C-A-G、G-A-B、C-A-B三條邊,然后記錄下CG、GB、BC之間的距離。

    3)我們通過遍歷找到下一個中間點B,同樣以B為中間點找到起始點和終點,這里我們可以找到G-B-A,G-B-D,A-B-D,然后記錄下GA、GD、AD之間的距離。

    4)然后再找下一個中間點,重復上述操作……

    注意:當我們以E作為中間點時,同樣也會有C-E-G即表示CG兩點之間距離的關系,這時候我們需要跟記錄距離的數組中的數據相比,如果小于數組中的元素,就以C-E-G這條邊的權值和替換數組中的數據,反之不操作。

    代碼:

    package com.liu.algorithm;import java.util.Arrays;/*** @author liuweixin* @create 2021-09-25 10:16*/ //弗洛伊德算法——各個頂點到其他頂點得最短路徑 public class Floyd {int[][]pre_visited;//前驅關系圖Graph1 graph1;int[][]dis;static final int N = 65553;public Floyd(Graph1 graph1) {this.graph1=graph1;pre_visited=new int[graph1.vertexs.length][graph1.vertexs.length];//初始時,每個點的前驅頂點都是自己for (int i = 0; i < pre_visited.length; i++) {Arrays.fill(pre_visited[i],i);}dis=new int[graph1.vertexs.length][graph1.vertexs.length];for (int i = 0; i < dis.length; i++) {//先把鄰接矩陣賦值給距離數組for (int j = 0; j < dis[i].length; j++) {dis[i][j]=graph1.distant[i][j];}}}public static void main(String[] args) {int[][] distant = {{0,5,7,N,N,N,2},{5,0,N,9,N,N,3},{7,N,0,N,8,N,N},{N,9,N,0,N,4,N},{N,N,8,N,0,5,4},{N,N,N,4,5,0,6},{2,3,N,N,4,6,0}};char[]vertexs={'A','B','C','D','E','F','G'};Graph1 graph1 = new Graph1(distant, vertexs);Floyd floyd = new Floyd(graph1);floyd.floyd();floyd.show();}public void floyd(){for (int i = 0; i < dis.length; i++) {//第一層循環,遍歷中間頂點for (int j = 0; j < dis.length; j++) {//遍歷起始頂點for (int k = 0; k < dis.length; k++) {//遍歷終點if((dis[j][i]+dis[i][k])<dis[j][k]){//如果小于兩者間的距離,則替換//更新距離dis[j][k]=dis[j][i]+dis[i][k];//前驅結點也需要變動pre_visited[j][k]=pre_visited[i][k];}}}}}//顯示前驅結點數組和距離數組public void show(){char[]vertex = {'A','B','C','D','E','F','G'};for (int i = 0; i < dis.length; i++) {for (int j = 0; j < dis.length; j++) {System.out.print(vertex[pre_visited[i][j]]+" ");}System.out.println();for (int j = 0; j < dis.length; j++) {System.out.print("("+vertex[i]+"到"+vertex[j]+"的最短路徑是"+dis[i][j]+")");}System.out.println();System.out.println();}} } //創建圖對象 class Graph1{int[][]distant;//鄰接矩陣char[]vertexs;//數據元素public Graph1(int[][] distant, char[] vertexs) {this.distant = distant;this.vertexs = vertexs;} }

    騎士周游算法

    騎士周游算法(馬踏棋盤算法)

    騎士周游算法介紹

    1)馬踏棋盤算法也被稱為騎士周游問題

    2) 將馬隨機放在國際象棋的8×8棋盤Board[0~7][0~7]的某個方格中,馬按走棋規則(馬走日字)進行移動。要求每個方格只進入一次,走遍棋盤上全部64個方格

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

    騎士周游算法思路圖解:

    通俗地講

  • 我們先定義一個起始點的坐標x,y
  • 我們標記該點為已訪問,棋盤上該點的值,即chess[y][x]的值賦為步數step,這樣有便利我們在后面遍歷棋盤,就能得到馬走的方式。
  • 然后我們求出該起始點坐標的能走的下一步的所有坐標,記錄在ArrayList中,下面以list代替
  • 然后我們對list中的所有點的坐標進行排序(排序的原則是該點下一步能走的數量),實現非遞減排序,為什么要這樣排序呢?因為這里我們采用貪心算法對其進行優化,我們先走那個下一步具有最多走法的那一步,這樣的話對我們能更快走完整個棋盤有一定的效率提升。
  • 然后我們對排完序的list中,取出第一位,其下一步能走的更多次數的那個坐標,然后判斷該點是否未被訪問過,如果未被訪問過,就進入遞歸。
  • 當馬走無可走時,就需要回溯。
  • 當step=棋盤方格數時,即此時馬已經走完
  • 代碼:

    package com.liu.algorithm;import java.awt.*; import java.util.ArrayList; import java.util.Comparator;/*** @author liuweixin* @create 2021-09-25 14:58*/ //騎士周游算法——馬踏棋盤 public class HorseChess {static int X;//棋盤的列數static int Y;//棋盤的行數boolean[] visited;//判斷該點是否已訪問int [][]chess;//棋盤static boolean finished;//如果為true,則訪問成功public static void main(String[] args) {HorseChess horseChess = new HorseChess(8, 8);int x=1;//起始列int y=1;//起始行horseChess.HorseChessAlgorithm(horseChess.chess,x-1,y-1,1);for(int[]rows:horseChess.chess){for(int step:rows){System.out.print(step+"\t");}System.out.println();}}public HorseChess(int x,int y ){X=x;Y=y;chess=new int[X][Y];visited = new boolean[X*Y];}/*** 騎士周游算法的實現* @param chess 棋盤* @param x 起始點的坐標X,即為列,從0開始* @param y 起始點的坐標Y,即為行,從0看i是* @param step 馬走的步數,第幾步,從1開始*/public void HorseChessAlgorithm(int[][]chess,int x,int y,int step){chess[y][x]=step;//先設置步數visited[y*X+x]=true;//把當前坐標設置為已訪問ArrayList<Point> next = next(new Point(x, y));//獲取該點的下一步的走法sort(next);//體現貪心算法,將其排序while(!next.isEmpty()){Point point = next.remove(0);//獲取第一步走法if(!visited[point.y*X+point.x]){//如果該點未被訪問過//則走該步,進行下一次的走法,即進入遞歸HorseChessAlgorithm(chess,point.x,point.y,step+1);}}//判斷馬兒是否完成了任務,使用step和應該走的步數比較//如果沒有達到數量,則表示沒有完成任務,將整個棋盤置0// 說明:step < X * Y成立的情況有兩種//1.棋盤到目前位置,仍然沒有走完//2.棋盤處于一個回溯過程if(step<X*Y&&!finished){chess[y][x]=0;//將棋盤置零visited[y*X+x]=false;//設置該點未訪問}else {//否則,已經完成了該棋盤的走法finished=true;}}/*** 對傳入進來的點,判斷其下一步有多少種走法** @param curPoint* @return*/public ArrayList<Point> next(Point curPoint) {ArrayList<Point> list = new ArrayList<Point>();Point point = new Point();//圖上的5這個點可以走if ((point.x = curPoint.x - 2) >= 0 && (point.y = curPoint.y - 1) >= 0) {list.add(new Point(point));}//6if ((point.x = curPoint.x - 1) >= 0 && (point.y = curPoint.y - 2) >= 0) {list.add(new Point(point));}//7if ((point.x = curPoint.x + 1) < X && (point.y = curPoint.y - 2) >= 0) {list.add(new Point(point));}//0if ((point.x = curPoint.x + 2) < X && (point.y = curPoint.y - 1) >= 0) {list.add(new Point(point));}//1if ((point.x = curPoint.x + 2) < X && (point.y = curPoint.y + 1) < Y) {list.add(new Point(point));}//2if ((point.x = curPoint.x + 1) < X && (point.y = curPoint.y + 2) < Y) {list.add(new Point(point));}//3if ((point.x = curPoint.x - 1) >= 0 && (point.y = curPoint.y + 2) < Y) {list.add(new Point(point));}//4if ((point.x = curPoint.x - 2) >= 0 && (point.y = curPoint.y + 1) < Y) {list.add(new Point(point));}return list;}/*** 對該步驟的下一步進行排序* 體現貪心算法** @param list*/public void sort(ArrayList<Point> list) {//對Point實現comparator接口并重寫方法list.sort(new Comparator<Point>() {@Overridepublic int compare(Point o1, Point o2) {//獲取o1下一步的所有位置的個數int count1 = next(o1).size();//獲取o2下一步的所有位置的個數int count2 = next(o2).size();if (count1 < count2) {return -1;} else if (count1 == count2) {return 0;} else {return 1;}}});} }


    總結

    這篇文章結束后,數據結構與算法就結束了,希望大家能有所收獲,還是那句話,需要注重敲代碼與細細debug,才能真正地搞懂。后續我還會復習技術、做項目,覺得重要的點也會發布文章,希望大家多多關注。

    總結

    以上是生活随笔為你收集整理的常用十大算法的全部內容,希望文章能夠幫你解決所遇到的問題。

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