2019阿里校招測評題,光明小學完全圖最短路徑問題(python實現)


題目:光明小學的小朋友們要舉行一年一度的接力跑大賽了,但是小朋友們卻遇到了一個難題:設計接力跑大賽的線路,你能幫助他們完成這項工作么?
光明小學可以抽象成一張有N個節點的圖,每兩點間都有一條道路相連。光明小學的每個班都有M個學生,所以你要為他們設計出一條恰好經過M條邊的路徑。
光明小學的小朋友們希望全盤考慮所有的因素,所以你需要把任意兩點間經過M條邊的最短路徑的距離輸出出來以供參考。

你需要設計這樣一個函數:
res[][] Solve( N, M, map[][]);
注意:map必然是N * N的二維數組,且map[i][j] == map[j][i],map[i][i] == 0,-1e8 <= map[i][j] <= 1e8。(道路全部是無向邊,無自環)2 <= N <= 100, 2 <= M <= 1e6。要求時間復雜度控制在O(N^3*log(M))。

map數組表示了一張稠密圖,其中任意兩個不同節點i,j間都有一條邊,邊的長度為map[i][j]。N表示其中的節點數。
你要返回的數組也必然是一個N * N的二維數組,表示從i出發走到j,經過M條邊的最短路徑
你的路徑中應考慮包含重復邊的情況。

一 、求解

先來說一下求解這一題的思路,這個問題的本質就是M步無向圖的最短路徑遍歷,一般求解最短路徑問題,我們首先想到的就是采用遞歸實現。

已知有N個節點,求解從節點i到節點j之間的M步的最短路徑問題,我們可以把M步分解成1步和M-1步:

1、假設存在節點k,且k節點不同於節點i,節點j,第一步求解節點i到節點k之間的1步最短路徑

2、剩下M-1步,可以看做求解節點k到節點j之間的M-1步最短路徑問題

3、當k取不同值時,我們會得到多個距離值,選取最小的一個距離值

# -*- coding: utf-8 -*-
"""
Created on Fri Aug  3 08:54:35 2018

@author: zy
"""

import numpy  as np

def Solve(maps,M):
    '''
    由N個節點兩兩連接組成路徑,選取從節點i->節點j之間的最短M條路徑
    
    param:
        maps:二維數組,由兩兩節點之間的路徑長度組成
        M:表示所經過的路徑個數
    '''    
    '''
    輸入校驗
    '''    
    if not isinstance(maps,(list,np.ndarray)): 
        raise ValueError('輸入參數maps數據類型必須是list或者numpy.array')
    
    if len(maps.shape) != 2:
        raise ValueError('輸入參數maps為二維數組')
        
    if maps.shape[0] != maps.shape[1]:
        raise ValueError('輸入二維數組maps行數和列數要求一致')
        
    #計算節點的個數
    N = maps.shape[0]    
    
    if N<2 or N>100:
        raise ValueError('輸入二維數組maps行數必須在2~100之間')
    
    if M<2 or M>1E6:
        raise ValueError('輸入參數N的值必須在2~1e6之間')
    
    #輸入二維數組數值校驗
    for i in range(N):
        for j in range(i,N):
            if maps[i][j] != maps[j][i]:
                raise ValueError('輸入二維數組maps必須是對稱的')
            if maps[i][j] < -1e8 or maps[i][j] > 1e8:
                raise ValueError('二維數組maps的元素值必須在-1e8~1e8之間')
            if i==j:
                if maps[i][j] != 0:
                    raise ValueError('二維數組maps的對角元素值必須是0')
    
    
    #用於保存i->j的路徑值
    res = np.zeros_like(maps)
    
    #計算節點i->j的最短路徑
    for i in range(N):
        for j in range(i,N):
            res[i][j] = MinPath(maps,M,i,j)
            res[j][i] = res[i][j]
    return res

def  MinPath(maps,M,i,j):
    '''
    計算i->j的最短路徑
    '''
    #遞歸終止條件
    if M == 1:
        return maps[i][j]
    '''計算i->j的最短路徑'''
    N = maps.shape[0]   
    #用於保存i->j的可能路徑長度
    length = np.zeros(N)    
    #遍歷從k->j的最短路徑
    for k in range(N):
        if k != i and k != j:
            #k->j的M-1條最短路徑 + i->k的一條路徑
            length[k] = MinPath(maps,M-1,k,j) + maps[i][k]
        
    #進行排序,過濾掉為0的值
    length = np.sort(length)
    for  i in length:
        if i != 0:
            return i
                
                
if __name__ == '__main__':
    #maps = np.array([[0,2,3],[2,0,1],[3,1,0]])
    maps = np.array([[0,2,3,4],
                     [2,0,1,3],
                     [3,1,0,2],
                     [4,3,2,0]
            ])
    M = 3
    result = Solve(maps,M)
    print(result)
        

 二、時間復雜度分析

 我們來分析一下該算法的時間復雜度。

先來分析一下遞歸函數MinPath(maps,M,i,j):

1、函數的規模為M

2、當規模是1時,函數結束

3、假設T(M)表示規模為M的問題所需要的步驟數

算法的遞歸方程為:T(M) = (N-2)T(M - 1) +3N+6

注釋:3N+6表示規模毎減少一次,所做的步驟數

  1. if M==1:                      1次
  2. N=maps.shape[0]       1次
  3. length = np.zeros(N)   1次
  4. for k in range(N):        N次

  5. if k != i and k != j:        N次
  6. + maps[i][k]                 當i==j時 N-1次,否則N-2次  我們取N-2次
  7. length = np.sort(length)  1次
  8. 最后的for循環             2次或者4次  我們取4次  

迭代展開:T(M) = (N-2)T(M - 1) + 3N+6

=(N-2)[(N-2)T(M-2) + 3N + 6] + 3N+6

=(N-2)2T(M-2) + (N-2)(3N+6) + 3N+6

=(N-2)3T(M-3) + (N-2)2(3N+6) + (N-2)(3N+6) + 3N+6

=....

=(N-2)(M-1) + (N-2)(M-2)(3N+6)+...+3N+6

=(N-2)(M-1) + [(N-2)(M-1)(3N+6) - 3N-6]/(N-3)

= O((N-2)(M-1))

Solve函數中遞歸函數循環的次數為N(1+N)/2,所以算法的復雜度為O(N2(N-2)(M-1))。並沒有滿足題目的要求,更好的求解方法我還沒有想到,有了解的大佬可以告訴小弟。

參考文章:

[1]遞歸函數時間復雜度分析(轉)

[2]2019阿里校招測評題,光明小學完全圖最短路徑問題(c版本)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM