1. 程式人生 > >swift--求源點到各頂點的最短距離

swift--求源點到各頂點的最短距離

// 給定一個有向鄰接圖,求從原點出發到任意一點的最短距離 注:

  1. 採用Dijkstra貪心演算法優化版,為了減少建立二維鄰接矩陣的空間開銷,直接使用頂點的屬性。具體關於此演算法的解釋說明可百度.
  2. 優化功能:由於正在學習設計模式,所以增加了工廠類,可以在工廠類中指定任一個頂點為原點,求從原點到其它任意一點的最短距離。
import Foundation

/// 定義頂點的結構
struct Point {
    var id:Int  //編號
    var isYuanDian : Bool //是否原點
    var isV:Bool=false //是否屬於V陣營。true:屬於V陣營; false:屬於V-S陣營
    var juZhen:[(nextId:Int,juli:Int)] = [] //臨接矩陣的陣列,規則為[(指向的頂點編號,距離)]
    init(id:Int,isYuanDian:Bool=false,juZhen:[(nextId:Int,juli:Int)]) {
        self.id=id
        self.isYuanDian = isYuanDian
        self.juZhen=juZhen
        
        //如果原點,則自動加入到V陣營。其它頂點在V-S陣營
        if isYuanDian == true {
            isV = true
        }
    }
}


/// 頂點工廠類,在此生成所有頂點
class PointFactory {
    func createPoints() -> [Point] {
        var points:[Point]=[]  //最終返回的點陣列
        var tmpJuZhen:[(nextId:Int,juli:Int)] = []  //臨時的矩陣陣列,用於生成某個點
        
        //1號頂點,預設為原點
        tmpJuZhen.append((nextId: 2,juli:2))
        tmpJuZhen.append((nextId: 3, juli: 5))
        points.append( Point(id: 1, isYuanDian: true, juZhen: tmpJuZhen ))
        
        //2號頂點
        tmpJuZhen.removeAll()
        tmpJuZhen.append((nextId: 4,juli:6))
        tmpJuZhen.append((nextId: 3, juli: 2))
        points.append( Point(id: 2, isYuanDian: false, juZhen: tmpJuZhen ))
        
        //3號頂點
        tmpJuZhen.removeAll()
        tmpJuZhen.append((nextId: 4,juli:7))
        tmpJuZhen.append((nextId: 5, juli: 1))
        points.append( Point(id: 3, isYuanDian: false, juZhen: tmpJuZhen ))
        
        //4號頂點
        tmpJuZhen.removeAll()
        tmpJuZhen.append((nextId: 3,juli:2))
        tmpJuZhen.append((nextId: 5, juli: 4))
        points.append( Point(id: 4, isYuanDian: false, juZhen: tmpJuZhen ))
        
        //5號頂點
        tmpJuZhen.removeAll()
        points.append( Point(id: 5, isYuanDian: false, juZhen: tmpJuZhen ))
        
        
        //判斷有幾個原點,只允許有一個,如果有多個,則保留第一個原點
        var i=0
        for point in points {
            if point.isYuanDian {
                i += 1
            }
        }
        //沒有原點則預設第一個為原點
        if i==0 {
            points[0].isYuanDian = true
            print("沒有原點,預設第一個頂點為原點")
        }
        //如果有多個原點,則保留第一個
        else if i>1{
            var j=0  //記錄第一個為原點的下標
            for i in 0..<points.count{
                if points[i].isYuanDian {
                    j = i
                    break
                }
            }
            for i in (j+1)..<points.count {
                if points[i].isYuanDian {
                    points[i].isYuanDian = false
                }
            }   
           print("有多個原點,預設設定第一個為原點")
        }
        return points
    }    
}

/// 計算最短路徑的類
class ZuiDuan {
    private   var points : [Point]  //頂點陣列
    private   let number :Int    //頂點個數,包含原點
    private   let wuQiongDa = 10000  //定義無窮大
    private   var yuanDian = 0//原點的下標編號
     
    private  var zuiDuan:[Int] = [] //各個頂點的最短路徑陣列
    private var p:[Int] = []  //頂點的前驅陣列
  
    init() {
        points = PointFactory().createPoints() //生成頂點集合
        number = points.count     //頂點個數
        
        //先求哪個是頂點
        for i in 0..<number {
            if points[i].isYuanDian {
                self.yuanDian = i
                break
            }
        }
     
        //初始化各個頂點的最短路徑陣列zuiduan[]
        for _ in 0..<number {
            zuiDuan.append(wuQiongDa)  //對陣列全部用無窮大填充
        }
        //再用原點的下級鄰接矩陣填充各頂點的最短路徑陣列zuiduan[]
        zuiDuan[yuanDian]=0
        for tmpJuZhen in points[yuanDian].juZhen{
            //讀取原點的鄰接矩陣
            zuiDuan[tmpJuZhen.nextId-1] = tmpJuZhen.juli
        }
        
        //初始化各頂點的前驅陣列
        for i in 0..<number{
            //如果【0】的值為無窮大,則為-1,表示沒有前驅點
            if zuiDuan[i] == 0 || zuiDuan[i] == wuQiongDa {
                p.append(-1)
            }
                //如果值不為無窮大,則為1,表示和頂點有邊相連,前驅點為頂點
            else{
                p.append(yuanDian+1)
            }
        }
    }
    
    /// 列印鄰接矩陣,預設最短路徑陣列,前驅陣列
    func prinfJuZhen(){
        print("各頂點到原點的最短路徑陣列:")
        for juli in zuiDuan {
            print("\(juli)  ")
        }
        
        print("各頂的前驅陣列為:")
        for x in p{
            print("\(x)")
        }
    }
    
    /// 求V-S中到頂點距離最短的點和距離
    ///
    /// - Returns: 返回最短的ID和距離,其中iD為下標,當ID為-1時說明已經沒有下級節點
    func getVS_ZuiDuan() ->(id:Int,juli:Int) {
        //再確定離頂點最短的距離和頂點
        var min=wuQiongDa //最小的距離,預設為無窮大
        var t = -1   //最小的距離對應的頂點下標,如果=-1,說明沒有下級節點
        
        //統計V-S中的頂點 --新建VS陣列,存放還在V-S中的頂點
        var vs:[Point] = []
        
        for i in 0..<points.count{
            if points[i].isV == false {
                vs.append(points[i])
            }
        }
        //如果V-S為空,代表所有頂點都在V陣營,返回t=-1
        if vs.isEmpty {
            return (t,min)
        }
        
        //當還有頂點在V-S陣營的時侯,求V-S陣營中離原點的最短距離
        for i in 0..<vs.count  {
            if min >= zuiDuan[vs[i].id-1] {
                min = zuiDuan[vs[i].id-1]
            }
        }
        
        // 求對短距離對應的點
        for i in 0..<number  {
            //必須為VS陣營中的頂點
            if min == zuiDuan[i] && points[i].isV == false{
                t=i
                break
            }
        }
        return (t,min)
    }
    
    
    /// 列印結果
    func echoResult() {
        //顯示第i+1個頂點的資訊
        //print("原點為頂點\(yuanDian+1)。。。")
        for i in 0..<number {
            var str = ""  //最短的路徑
            if points[i].isYuanDian != true {
                var x = i //定義最開始的起始點
                str = String(i+1)   //最後顯示的字串
                while p[x] >= 0  {
                    str = str + "->" +  String(p[x])
                    x=p[x]-1
                }
            }
            if zuiDuan[i] == wuQiongDa {
                str = "頂點\(i+1)無法到達"
            }
            else if zuiDuan[i] == 0{
                str = "頂點\(i+1)為原點"
            }
            else{
               str = "頂點\(i+1)的最短距離為:\(zuiDuan[i]),最短路徑為:"+str
            } 
            print(str)
        }
    }
    
    /// 計算各個點到頂點的最短路徑
    ///
    /// - Returns:
    func zuiDuanLuJin() {
        
        //開始按貪心演算法求解。
        //1.令V={1},V-S={2,3,4,5},找V-S中離頂點最近的點
        let zuiJin_VS=getVS_ZuiDuan()
        
        //如果VS陣營中已經沒有頂點了,則顯示資料
        if zuiJin_VS.id == -1 {
            //顯示結果
            //  prinfJuZhen()
            
            echoResult()
            return
        }
        
        //更新最短距離陣列
        let i = zuiJin_VS.id
        zuiDuan[i] = zuiJin_VS.juli
        
        //2.走捷徑。與i鄰接的點,分別判斷最短
        //a.通過上一級節點的最短距離。
        //b.最短距離陣列p[]中已有的值。
        //a和b對比,小的就是新的最短距離,重新更新到陣列zuiduan[]中,同時更新前驅陣列p[]
        var minJuli=0 //定義一個最短距離的變數
        
        //  print("頂點=\(i+1)")
        
        for juZhen_item in points[i].juZhen {
            
            //    print("頂點矩陣為:\(juZhen_item.nextId):\(juZhen_item.juli)")
            
            //a.通過上級節點的最短距離
            minJuli = zuiDuan[i] + juZhen_item.juli
            
            //b.和P【】中的值對比,將比較小的更新到P[]中
            if minJuli <= zuiDuan[juZhen_item.nextId-1] {
                zuiDuan[juZhen_item.nextId-1] = minJuli
                p[juZhen_item.nextId-1] = i+1
            }
        }
        
        //3.將i劃入V陣營,重新在V-S陣容中找到頂點最短的
        points[i].isV = true
        
        //使用遞迴進行迴圈
        zuiDuanLuJin()
    }
}

let zuiduan = ZuiDuan()
zuiduan.zuiDuanLuJin()
//zuiduan.prinfJuZhen()