優先連接算法的多種實現(Python)

優先連接算法介紹

優先連接算法是複雜網絡中的一種經典算法。所謂優先連接,就是新的頂點在加入網絡的過程中,以正比於網絡中頂點的度的概率去連接頂點。在複雜網絡演化中運用優先連接,得到的網絡的度分佈具有冪律分佈的特性。

算法1:以正比於頂點度的概率連接頂點

以正比於頂點度的概率連接頂點的算法是最普通的算法。由於頂點的度肯定是整數,所以有一種簡單的方法可以在O(1)的時間內完成一次優先連接選擇。
設有一組頂點,序號爲1,2,3,….,n,頂點的度存儲在數組degrees裏,頂點k的度就是degrees[k].

算法描述:

  1. 生成一個數組choose_array,初始化不含任何元素。
  2. 遍歷數組degrees,對每個遍歷到的頂點索引值k,它度數是degrees[k],那麼就在choose_array中加入degrees[k]個k。
  3. 生成一個在0-len(choose_array)-1之間的隨機整數random_number,choose_array[random_number]就是所選擇的頂點。

算法的原理就是頂點索引在choose_array中出現的次數等於頂點的度。這樣,頂點度多大,在choose_array中出現次數越多,被選中概率越大。

from random import randint


def construct_choose_array(degrees):
    choose_array = []
    length = len(degrees)
    for index in range(1, length):
        degree = degrees[index]
        if degree != 0:
            repeat_index_array = [index] * degree
            choose_array += repeat_index_array
    return choose_array


def prefer_attach_proportional_to_degree(choose_array):
    length = len(choose_array)
    random_number = randint(0, length - 1)
    return choose_array[random_number]

當某個頂點的度增加時,只需要在choose_array數組最後增加這個頂點的索引值即可。頂點在choose_array數組中出現的位置與被選擇的概率無關。

算法2:以正比於頂點吸引力的概率連接頂點

算法1雖然能在O(1)時間內完成一次優先連接選擇,但是是以空間換時間的方式實現的,空間消耗很大。並且,通常情況下我們並不是以完全正比於頂點度的概率去選擇的,而是每個頂點計算一個吸引力(power),以正比於頂點power的概率去選擇。頂點的power不一定是整數,如果要採用算法1,就必須把所有頂點的power轉化成整數。但是這會導致數據的失真,或者所佔空間巨大。
我們需要一種新的算法來解決這個問題。其實以正比於頂點吸引力的概率連接頂點就相當於遺傳算法中的以正比於個體適應度去選擇個體。而遺傳算法採用轉輪盤賭法去解決這個問題。下面介紹轉輪盤賭法。

設有一組頂點,序號爲1,2,3,….,n,頂點的power存儲在數組powers裏,頂點k的power就是powers[k],且powers[k]大於0
算法描述:

  1. 生成一個數組choose_array,初始化爲[0]。
  2. 遍歷數組powers,對每個遍歷到的頂點索引值k,choose_array[k]=powers[k]+
    choose_array[k-1]
  3. 生成一個在0-choose_array[-1]之間的隨機數random_number,用二分搜索找到random_number落在區間(choose_array[x-1],choose_array[x]]中,那麼就是選中了頂點x。

算法的原理就是轉輪盤賭法。

from random import uniform


def construct_choose_array(powers):
    choose_array = [0]
    length = len(powers)
    for index in range(1, length):
        choose_array.append(choose_array[index - 1] + powers[index])

    return choose_array


def binary_search(choose_array, value):
    if value < choose_array[0] or value > choose_array[-1]:
        print('not in this array')
        return
    start = 1
    end = len(choose_array) - 1
    while True:
        middle = int((start + end) / 2)
        if value <= choose_array[middle]:
            if value > choose_array[middle - 1]:
                return middle
            else:
                end = middle - 1
        else:
            start = middle + 1


def prefer_attach_proportional_to_power(choose_array):
    random_number = uniform(0, choose_array[-1])
    return binary_search(choose_array, random_number)

記頂點個數爲n,算法佔用空間複雜度是O(n),算法時間複雜度是O(logn)。

算法3:有附加情況下的輪盤賭法

在使用優先連接時,通常情況下,我們是構造一次choose_array,choose_array保持不變,然後進行多次優先連接選擇。也就是頂點的吸引力與做優先連接選擇的頂點無關。但是在有些情況下,頂點的吸引力與做優先連接的頂點有關,比例在合作網絡演化中,一個作者在選擇他的合作者的時候,會優先考慮之前與他有過合作關係的作者。對於不同的作者,同一個作者對他們的吸引力不同。但只有一小部分頂點的吸引力會改變,其他的不會改變,如果還採用算法2,對於每一次優先連接選擇,就必須構造一次choose_array,效率很低。下面將採用一種比較簡單的等價技巧來解決這個問題。

設有一組頂點,序號爲1,2,3,….,n,頂點的power存儲在數組powers裏。頂點k的power就是powers[k],且powers[k]大於0。做優先連接的頂點爲i。對於頂點i,附加的吸引力在字典additional_powers裏,鍵是頂點索引,值是附加的吸引力。例如頂點k出現在additional_powers裏,那麼頂點k對頂點i的吸引力就是powers[k]+additional_powers[k]。additional_powers的大小遠小於powers的大小。

算法描述:

  1. 生成一個數組choose_array,初始化爲[0]。
  2. 遍歷數組powers,對每個遍歷到的頂點索引值k,在choose_array末尾加上元素 powers[k]+choose_array[k-1] 。
  3. 對於做優先連接的頂點i,生成additional_powers。
  4. 記錄現有choose_array的長度爲length。遍歷additional_powers,對每個遍歷到的頂點索引值k,choose_array末尾加上choose_array[-1]+additional_powers[k],並用字典temp_dict記錄附加頂點在choose_array中的位置信息。
  5. 生成一個在0-choose_array[-1]之間的隨機數random_number,用二分搜索找到random_number落在區間(choose_array[x-1],choose_array[x]]中,如果x<=length-1,那麼就是選中了頂點x。否則,再根據temp_dict去找對應的頂點。
  6. 刪除choose中索引大於length的元素,恢復choose_array。

    算法就是利用了頂點被選中的概率只與頂點的總吸引力有關,而與吸引力被分成多少部分,分佈在數組choose_array的哪些地方無關的性質,達到等價選擇的效果。

from random import uniform


def construct_choose_array(powers): #與算法二一樣
    choose_array = [0]
    length = len(powers)
    for index in range(1, length):
        choose_array.append(choose_array[index - 1] + powers[index])

    return choose_array


def binary_search(choose_array, value): #與算法2一樣
    if value < choose_array[0] or value > choose_array[-1]:
        print('not in this array')
        return
    start = 1
    end = len(choose_array) - 1
    while True:
        middle = int((start + end) / 2)
        if value <= choose_array[middle]:
            if value > choose_array[middle - 1]:
                return middle
            else:
                end = middle - 1
        else:
            start = middle + 1


def prefer_attach_proportional_to_power_additional(choose_array, additional_powers):
    random_number = uniform(0, choose_array[-1])
    return additional_binary_search(choose_array, additional_powers, random_number)


def additional_binary_search(choose_array, additional_powers, random_number):
    temp_dict = {}
    index = len(choose_array)
    short_end = index
    temp_total = choose_array[-1]
    for key in additional_powers:
        temp_total += additional_powers[key]
        choose_array.append(temp_total)
        temp_dict[index] = key
        index += 1

    print(choose_array)
    position = binary_search(choose_array, random_number)
    if position >= short_end:
        position = temp_dict[position]

    end = len(choose_array)
    for i in range(short_end, end):
        del choose_array[-1]
    return position

這樣,也只需要生成一次choose_array,對於不同的做優先連接選擇的頂點,只需要變動一小部分,大大降低了時間複雜度。

第一次寫博客,寫得不好還請見諒~有任何問題或者討論,請留言回覆~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章