日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

常用十大算法

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

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

目錄

二分查找(非遞歸)

分治算法

動態(tài)規(guī)劃算法?

KMP算法?

貪心算法?

普利姆(Prim)算法

克魯斯卡爾(Kruskal)算法

迪杰斯特拉(Dijkstra)算法

弗洛伊德(Floyd)算法

騎士周游算法

總結(jié)


二分查找(非遞歸)

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

實現(xiàn)思路

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

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

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

    left=mid+1;然后重復(fù)3步驟

    ? ? ? 4.當(dāng)循環(huán)結(jié)束,該查找方法未結(jié)束,說明沒有查找到對應(yīng)的值,直接返回-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);}/*** 非遞歸式實現(xiàn)二分查找** @param arr 要查找的數(shù)組* @param data 要查找的數(shù)據(jù)* @return 如果找到,返回該數(shù)據(jù)的下標(biāo);否則返回-1*/public static int binarySearch(int[] arr, int data) {//創(chuàng)建兩個指針,分別指向第一個數(shù)據(jù)與最后一個數(shù)據(jù)int left = 0;int right = arr.length - 1;while (left<=right){//當(dāng)兩個指針不相對僭越時,說明數(shù)據(jù)仍未被遍歷完int mid = (left+right)/2;if(arr[mid]==data){//如果中間值剛好就是要查找的值//直接返回return mid;}else if(arr[mid]<data){//要查找的數(shù)據(jù)在該中間值的右邊left=mid+1;}else {//要查找的數(shù)據(jù)在該中間值的左邊right=mid-1;}}//如果循環(huán)結(jié)束,該方法沒有終結(jié),說明沒有找到。return -1;} }

    分治算法

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

    分治算法可以解決的一些經(jīng)典問題

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

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

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

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

    漢諾塔思路求解

  • 如果有一個盤,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 盤的個數(shù)* @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);}} }

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


    動態(tài)規(guī)劃算法?

    動態(tài)規(guī)劃算法(Dynamic Programming)

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

    我們借助于一個背包問題來了解動態(tài)規(guī)劃算法:

    韓老師的圖解還是很經(jīng)典的:

    ????????

    思路分析

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

    (1)? v[i][0]=v[0][j]=0;? //表示填入表第一行和第一列是0

    (2) 當(dāng) w[i]> j 時:v[i][j]=v[i-1][j]?? // 當(dāng)準(zhǔn)備加入新增的商品的容量大于當(dāng)前背包的容量時,就直接使用上一個單元格的裝入策略

    (3)?? 當(dāng)j>=w[i]時:v[i][j]=max{v[i-1][j],? v[i]+v[i-1][j-w[i]]}

    //當(dāng)準(zhǔn)備加入的新增的商品的容量小于等于當(dāng)前背包的容量

    //裝入的方式:

    v[i-1][j]:就是上一個單元格的裝入的最大值

    v[i] :表示當(dāng)前商品的價值

    v[i-1][j-w[i]]:裝入i-1商品,到剩余空間j-w[i]的最大值

    當(dāng)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*/ //動態(tài)規(guī)劃算法->背包問題 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);}/*** 通過動態(tài)規(guī)劃算法解決背包問題* 輸出在背包容量范圍內(nèi)的最大價值** @param capacity 背包的容量* @param weight 物品重量的數(shù)組* @param value 物品價值的數(shù)組*/public static void dynamic(int capacity, int[] weight, int[] value) {//創(chuàng)建物品最大價值與背包容量之間的關(guān)系,第一行與第一列置零,所以+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的行進(jìn)行遍歷for (int j = 1; j < arr[i].length; j++) {//對arr的列進(jìn)行遍歷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來實現(xiàn)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中//只要我們不是采取了上一行的數(shù)據(jù),而是采用了新的數(shù)據(jù),則說明此時加入了新商品,所以我們需要對其標(biāo)記path[i][j] = 1;} else {arr[i][j] = arr[i - 1][j];}}}}//遍歷價值數(shù)組for (int[] temp : arr) {for (int data : temp) {System.out.print(data + " ");}System.out.println();}//將物品情況輸出//這里從最后開始輸出的原因是,在最后,價值是最大的,我們只要輸出最大的那個價值對應(yīng)的商品即可int i = path.length - 1;//行的最大下標(biāo)int j = path[0].length - 1;//列的最大下標(biāo)while (i > 0 && j > 0) {//從path的最后開始找if (path[i][j] == 1) {System.out.println("第" + i + "個商品放到背包");j -= weight[i - 1];//此時已經(jīng)找到最大價值的商品,如果不止一件,我們就減少該件的重量,去尋找下一件,所以j要減去當(dāng)前件的重量}i--;//向前尋找}} }

    KMP算法?

    KMP算法

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

    問題引入:

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

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

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

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

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

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

    暴力匹配算法的實現(xiàn)

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

    缺點

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

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

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

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

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

    例如:“ABCDAD”

    當(dāng)只有A時,其前綴為空,后綴也為空,此時共有元素長度為0;

    當(dāng)AB時,前綴為A,后綴為B,此時共有長度為0;

    當(dāng)ABC時,前綴為A,AB,后綴為BC,C,此時共有長度為0;

    當(dāng)ABCD時,前綴為A,AB,ABC,后綴為BCD,CD,D,此時共有長度為0;

    當(dāng)ABCDA時,前綴為A,AB,ABC,ABCD,后綴為BCDA,CDA,DA,A,此時共有長度為1

    當(dāng)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算法的思路:

    關(guān)鍵點:

    需要移動的位數(shù)=已匹配的字符數(shù)-對應(yīng)的部分匹配值。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算法的實現(xiàn)** @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),去調(diào)整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) {//創(chuàng)建一個next數(shù)組保存部分匹配值int[] next = new int[dest.length()];next[0] = 0;//字符串的首個元素的匹配值為0for (int i = 1, j = 0; i < dest.length(); i++) {//當(dāng)dest.charAt(i)!=dest.charAt(j),我們需要從next[j-1]獲取新的j//直到我們發(fā)現(xiàn)有dest.charAt(i)==dest.charAt(j)成立才退出//這是kmp算法的核心點while (j > 0 && dest.charAt(i) != dest.charAt(j)) {j = next[j - 1];}//當(dāng)dest.charAt(i)==dest.charAt(j)滿足時,部分匹配值就是+1if (dest.charAt(i) == dest.charAt(j)) {j++;}next[i] = j;}return next;} }

    貪心算法?

    貪心算法:

  • 貪婪算法(貪心算法)是指在對問題進(jìn)行求解時,在每一步選擇中都采取最好或者最優(yōu)(即最有利)的選擇,從而希望能夠?qū)е陆Y(jié)果是最好或者最有的算法。
  • 貪心算法所得到的結(jié)果不一定是最優(yōu)的結(jié)果(有時會是最優(yōu)解),但是都是相對近似最優(yōu)解的結(jié)果
  • 貪心算法最佳應(yīng)用—集合覆蓋

    思路

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

    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) {//先創(chuàng)建對應(yīng)的廣播臺集合HashMap<String, HashSet<String>> broadcasts = new HashMap();//創(chuàng)建對應(yīng)的廣播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);//創(chuàng)建一個集合,記錄最終的廣播臺ArrayList<String> selects = new ArrayList<>();//創(chuàng)建一個集合,記錄要覆蓋的地區(qū)HashSet<String> allAreas = new HashSet<>();allAreas.add("北京");allAreas.add("上海");allAreas.add("天津");allAreas.add("廣州");allAreas.add("深圳");allAreas.add("成都");allAreas.add("杭州");allAreas.add("大連");//創(chuàng)建一個最大覆蓋的指針String maxSize;HashSet<String> temp = new HashSet<>();//借用一個臨時的hashSet變量,用來存儲交集while (allAreas.size() != 0) {//當(dāng)要覆蓋的區(qū)域還不等于0時,說明還沒有選擇完廣播臺,繼續(xù)循環(huán)maxSize = null;for (String key : broadcasts.keySet()) {//遍歷廣播臺的數(shù)據(jù)temp.clear();//將臨時的變量置空temp.addAll(broadcasts.get(key));//將當(dāng)前廣播臺覆蓋的地區(qū)添加到臨時變量中temp.retainAll(allAreas);//找到當(dāng)前變量與總地區(qū)重合的地區(qū)數(shù)//這里體現(xiàn)貪心算法,每一步都選取最優(yōu)的選擇if (temp.size() > 0 && maxSize == null) {maxSize = key;} else if (temp.size() > 0 && maxSize != null) {HashSet<String> max = broadcasts.get(maxSize);//獲取最大覆蓋指針指向的廣播臺max.retainAll(allAreas);//獲取其與總地區(qū)重合的地區(qū)數(shù)if (temp.size() > max.size()) {//如果當(dāng)前遍歷的廣播臺的包含數(shù)大于指針指向的包含數(shù),則指針指向當(dāng)前keymaxSize = key;}}}if (maxSize != null) {//當(dāng)for循環(huán)結(jié)束,找到覆蓋最多的廣播臺selects.add(maxSize);//并且在allAreas中刪除maxSize所指向的地區(qū)allAreas.removeAll(broadcasts.get(maxSize));}}System.out.println(selects);} }

    普利姆(Prim)算法

    普利姆算法

    問題引入—修路問題:

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

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

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

    普利姆算法的實現(xiàn)如下

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

    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 圖節(jié)點的數(shù)值* @param weight 圖節(jié)點之間的權(quán)值*/public static Graph creatGraph(Graph graph, char[] data, int[][] weight) {for (int i = 0; i < data.length; i++) {graph.data[i] = data[i];//給圖的節(jié)點賦值for (int j = 0; j < weight[i].length; j++) {graph.weight[i][j] = weight[i][j];//給圖的節(jié)點之間的權(quán)值復(fù)制}}return graph;}/*** 普利姆算法的實現(xiàn)** @param graph 要搜尋最短路徑的圖* @param v 最短路徑的起始點*/public static void prim(Graph graph, int v) {//先創(chuàng)建一個isVisited數(shù)組,用以表示是否已訪問的數(shù)組,boolean[] isVisited = new boolean[graph.data.length];int index1 = -1;//記錄第一個節(jié)點的下標(biāo)int index2 = -1;//記錄第二個節(jié)點的下標(biāo)int min = Integer.MAX_VALUE;//借用該輔助值,記錄下最小的權(quán)值//先把當(dāng)前的節(jié)點標(biāo)記為已訪問isVisited[v] = true;for (int i = 1; i < graph.data.length; i++) {//大循環(huán),循環(huán)邊的個數(shù),n個結(jié)點,有n-1條邊f(xié)or (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) {//當(dāng)進(jìn)入if判斷,即說明此時找到了較小的權(quán)值min = graph.weight[j][k];//記錄下該兩個結(jié)點的坐標(biāo)index1 = j;index2 = k;}}}//當(dāng)?shù)诙€for循環(huán)結(jié)束,此時已找到未訪問點的最小權(quán)值,即找到最小路徑System.out.println(graph.data[index1] + "--->" + graph.data[index2] + " 路徑長度為:" + min);//重置min值,并標(biāo)記data[k]值已訪問isVisited[index2] = true;min = Integer.MAX_VALUE;}} }//創(chuàng)建圖對象 class Graph {char[] data;//圖的節(jié)點的值int[][] weight;//表示兩個節(jié)點之間的距離,即權(quán)值int vertexs;//節(jié)點個數(shù)public Graph(int vertexs) {this.vertexs = vertexs;weight = new int[vertexs][vertexs];data = new char[vertexs];} }


    克魯斯卡爾(Kruskal)算法

    克魯斯卡爾(kruskal)算法

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

    Kruskal算法介紹

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

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

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

    Krukal算法的圖解

    Kruskal算法分析

    判斷是否構(gòu)成回路

    代碼:

    package com.liu.algorithm;import java.util.Arrays;/*** @author liuweixin* @create 2021-09-23 19:59*/ //克魯斯卡爾算法—最短路徑公交問題 public class KruskalCaseAlgorithm {int[][] weight;//對應(yīng)的頂點與頂點之間的權(quán)值char[] data;//頂點數(shù)據(jù)int edgeNum;//邊的個數(shù)final static int INF = Integer.MAX_VALUE;//一個常數(shù),用以表示兩個頂點之間不連通//構(gòu)造器初始化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;//記錄最終結(jié)果數(shù)組的索引//創(chuàng)建一個終點數(shù)組int[] ends = new int[edgeNum];//創(chuàng)建一個數(shù)組記錄最終的結(jié)果EData[] result = new EData[edgeNum];EData[] eDatas = getEData();//獲取邊的數(shù)據(jù)的集合//對邊的數(shù)據(jù)的權(quán)重進(jìn)行排序sortEData(0, eDatas.length - 1, eDatas);System.out.println("邊的集合" + Arrays.toString(eDatas) + " 共有" + edgeNum + "條");//遍歷eDatas數(shù)組,將邊添加到最小生成樹中,判斷準(zhǔn)備加入的邊是否形成了回路,如果沒有,就加入result,否則不能加入for (int i = 0; i < edgeNum; i++) {//獲取到第i條邊的第一個頂點與第二個頂點,即起點與終點int index1 = getPosition(eDatas[i].start);int index2 = getPosition(eDatas[i].end);//獲取兩個頂點的終點坐標(biāo)int end1 = getEnd(ends, index1);int end2 = getEnd(ends, index2);//判斷是否構(gòu)成回路if (end1 != end2) {//沒有構(gòu)成回路ends[end1] = end2;//設(shè)置m 在"已有最小生成樹中的終點"result[index++] = eDatas[i];//把邊加入到result數(shù)組}}System.out.println();for (int i = 0; i < result.length; i++) {if(result[i]!=null){System.out.println(result[i]);}}}/*** 獲取對應(yīng)頂點的下標(biāo)** @param data 要獲取的頂點* @return 找到則返回下標(biāo),否則返回-1*/public int getPosition(char data) {for (int i = 0; i < this.data.length; i++) {if (this.data[i] == data) {return i;}}return -1;}/*** 初始化邊的數(shù)組** @return 返回一個記錄了邊的數(shù)組*/public EData[] getEData() {int index = 0;//記錄數(shù)組的下標(biāo)EData[] eDatas = new EData[edgeNum];//創(chuàng)建一個記錄邊的數(shù)組for (int i = 0; i < weight.length; i++) {for (int j = i + 1; j < weight[i].length; j++) {if (weight[i][j] != INF) {//當(dāng)權(quán)值不為INF時,說明此時兩個點連通//給數(shù)組賦值eDatas[index++] = new EData(data[i], data[j], weight[i][j]);}}}return eDatas;}/*** 功能:獲取下標(biāo)為i的頂點的終點(),用于判斷后面兩個頂點的重點是否相同** @param ends 數(shù)組就是記錄了各個頂點對應(yīng)的終點是哪個,ends數(shù)組是在遍歷過程中,逐漸形成* @param i 表示傳入的頂點對應(yīng)的下標(biāo)* @return 返回的就是下標(biāo)為i的這個頂點對應(yīng)的終點的下標(biāo)*/public int getEnd(int[] ends, int i) {while (ends[i] != 0) {//循環(huán)的目的是找到最終的終點i = ends[i];}return i;}/*** 對邊的數(shù)組以權(quán)重大小進(jìn)行排序* 我這里使用快速排序,具體不加注釋** @param eData 要排序的數(shù)組* @param left1 數(shù)組其實位置的下標(biāo)* @param right1 數(shù)組最后一個數(shù)據(jù)的下標(biāo)*/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);}} }//創(chuàng)建一個邊對象 class EData {char start;//邊的起點char end;//邊的終點int weight;//邊的權(quán)值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)算法

    迪杰斯特拉算法

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

    區(qū)別

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

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

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

    Dijkstra算法實現(xiàn)的過程

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

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

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

    4)重復(fù)執(zhí)行兩步驟,直到最短路徑頂點為目標(biāo)頂點即可結(jié)束

    通俗地說

    我們以這個圖為例:

    首先假設(shè)我們設(shè)置的起始點為C:

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

    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};//創(chuàng)建 Graph對象Graph graph = new Graph(vertex, matrix);//測試, 看看圖的鄰接矩陣是否okgraph.showGraph();//測試迪杰斯特拉算法graph.dsj(2);//Cgraph.showDijkstra();}}class Graph {private char[] vertex; // 頂點數(shù)組private int[][] matrix; // 鄰接矩陣private VisitedVertex vv; //已經(jīng)訪問的頂點的集合// 構(gòu)造器public Graph(char[] vertex, int[][] matrix) {this.vertex = vertex;this.matrix = matrix;}//顯示結(jié)果public void showDijkstra() {vv.show();}// 顯示圖public void showGraph() {for (int[] link : matrix) {System.out.println(Arrays.toString(link));}}//迪杰斯特拉算法實現(xiàn)/*** * @param index 表示出發(fā)頂點對應(yīng)的下標(biāo)*/public void dsj(int index) {vv = new VisitedVertex(vertex.length, index);update(index);//更新index頂點到周圍頂點的距離和前驅(qū)頂點for(int j = 1; j <vertex.length; j++) {index = vv.updateArr();// 選擇并返回新的訪問頂點update(index); // 更新index頂點到周圍頂點的距離和前驅(qū)頂點} }//更新index下標(biāo)頂點到周圍頂點的距離和周圍頂點的前驅(qū)頂點,private void update(int index) {int len = 0;//根據(jù)遍歷我們的鄰接矩陣的 matrix[index]行for(int j = 0; j < matrix[index].length; j++) {// len 含義是 : 出發(fā)頂點到index頂點的距離 + 從index頂點到j(luò)頂點的距離的和 len = vv.getDis(index) + matrix[index][j];// 如果j頂點沒有被訪問過,并且 len 小于出發(fā)頂點到j(luò)頂點的距離,就需要更新if(!vv.in(j) && len < vv.getDis(j)) {vv.updatePre(j, index); //更新j頂點的前驅(qū)為index頂點vv.updateDis(j, len); //更新出發(fā)頂點到j(luò)頂點的距離}}} }// 已訪問頂點集合 class VisitedVertex {// 記錄各個頂點是否訪問過 1表示訪問過,0未訪問,會動態(tài)更新public int[] already_arr;// 每個下標(biāo)對應(yīng)的值為前一個頂點下標(biāo), 會動態(tài)更新public int[] pre_visited;// 記錄出發(fā)頂點到其他所有頂點的距離,比如G為出發(fā)頂點,就會記錄G到其它頂點的距離,會動態(tài)更新,求的最短距離就會存放到dispublic int[] dis;//構(gòu)造器/*** * @param length :表示頂點的個數(shù) * @param index: 出發(fā)頂點對應(yīng)的下標(biāo), 比如G頂點,下標(biāo)就是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數(shù)組Arrays.fill(dis, 65535);this.already_arr[index] = 1; //設(shè)置出發(fā)頂點被訪問過this.dis[index] = 0;//設(shè)置出發(fā)頂點的訪問距離為0}/*** 功能: 判斷index頂點是否被訪問過* @param index* @return 如果訪問過,就返回true, 否則訪問false*/public boolean in(int index) {return already_arr[index] == 1;}/*** 功能: 更新出發(fā)頂點到index頂點的距離* @param index* @param len*/public void updateDis(int index, int len) {dis[index] = len;}/*** 功能: 更新pre這個頂點的前驅(qū)頂點為index頂點* @param pre* @param index*/public void updatePre(int pre, int index) {pre_visited[pre] = index;}/*** 功能:返回出發(fā)頂點到index頂點的距離* @param index*/public int getDis(int index) {return dis[index];}/*** 繼續(xù)選擇并返回新的訪問頂點, 比如這里的G 完后,就是 A點作為新的訪問頂點(注意不是出發(fā)頂點)* @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;}//顯示最后的結(jié)果//即將三個數(shù)組的情況輸出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)算法也是一種用于尋找給定的加權(quán)圖中頂點間最短路徑的算法。該算法名稱以創(chuàng)始人之一、1978 年圖靈獎獲得者、斯坦福大學(xué)計算機(jī)科學(xué)系教授羅伯特·弗洛伊德命名

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

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

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

    Floyd算法分析

    1)設(shè)置頂點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)然后再找下一個中間點,重復(fù)上述操作……

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

    代碼:

    package com.liu.algorithm;import java.util.Arrays;/*** @author liuweixin* @create 2021-09-25 10:16*/ //弗洛伊德算法——各個頂點到其他頂點得最短路徑 public class Floyd {int[][]pre_visited;//前驅(qū)關(guān)系圖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];//初始時,每個點的前驅(qū)頂點都是自己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++) {//先把鄰接矩陣賦值給距離數(shù)組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++) {//第一層循環(huán),遍歷中間頂點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];//前驅(qū)結(jié)點也需要變動pre_visited[j][k]=pre_visited[i][k];}}}}}//顯示前驅(qū)結(jié)點數(shù)組和距離數(shù)組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();}} } //創(chuàng)建圖對象 class Graph1{int[][]distant;//鄰接矩陣char[]vertexs;//數(shù)據(jù)元素public Graph1(int[][] distant, char[] vertexs) {this.distant = distant;this.vertexs = vertexs;} }

    騎士周游算法

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

    騎士周游算法介紹

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

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

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

    騎士周游算法思路圖解:

    通俗地講

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

    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;//棋盤的列數(shù)static int Y;//棋盤的行數(shù)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];}/*** 騎士周游算法的實現(xiàn)* @param chess 棋盤* @param x 起始點的坐標(biāo)X,即為列,從0開始* @param y 起始點的坐標(biāo)Y,即為行,從0看i是* @param step 馬走的步數(shù),第幾步,從1開始*/public void HorseChessAlgorithm(int[][]chess,int x,int y,int step){chess[y][x]=step;//先設(shè)置步數(shù)visited[y*X+x]=true;//把當(dāng)前坐標(biāo)設(shè)置為已訪問ArrayList<Point> next = next(new Point(x, y));//獲取該點的下一步的走法sort(next);//體現(xiàn)貪心算法,將其排序while(!next.isEmpty()){Point point = next.remove(0);//獲取第一步走法if(!visited[point.y*X+point.x]){//如果該點未被訪問過//則走該步,進(jìn)行下一次的走法,即進(jìn)入遞歸HorseChessAlgorithm(chess,point.x,point.y,step+1);}}//判斷馬兒是否完成了任務(wù),使用step和應(yīng)該走的步數(shù)比較//如果沒有達(dá)到數(shù)量,則表示沒有完成任務(wù),將整個棋盤置0// 說明:step < X * Y成立的情況有兩種//1.棋盤到目前位置,仍然沒有走完//2.棋盤處于一個回溯過程if(step<X*Y&&!finished){chess[y][x]=0;//將棋盤置零visited[y*X+x]=false;//設(shè)置該點未訪問}else {//否則,已經(jīng)完成了該棋盤的走法finished=true;}}/*** 對傳入進(jìn)來的點,判斷其下一步有多少種走法** @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;}/*** 對該步驟的下一步進(jìn)行排序* 體現(xiàn)貪心算法** @param list*/public void sort(ArrayList<Point> list) {//對Point實現(xiàn)comparator接口并重寫方法list.sort(new Comparator<Point>() {@Overridepublic int compare(Point o1, Point o2) {//獲取o1下一步的所有位置的個數(shù)int count1 = next(o1).size();//獲取o2下一步的所有位置的個數(shù)int count2 = next(o2).size();if (count1 < count2) {return -1;} else if (count1 == count2) {return 0;} else {return 1;}}});} }


    總結(jié)

    這篇文章結(jié)束后,數(shù)據(jù)結(jié)構(gòu)與算法就結(jié)束了,希望大家能有所收獲,還是那句話,需要注重敲代碼與細(xì)細(xì)debug,才能真正地搞懂。后續(xù)我還會復(fù)習(xí)技術(shù)、做項目,覺得重要的點也會發(fā)布文章,希望大家多多關(guān)注。

    總結(jié)

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

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。