遺傳算法解決揹包問題

遺傳算法解決揹包問題

遺傳算法可以認爲是一種啓發式算法,根據達爾文的進化學說中“優勝劣汰”、“適者生存”的觀點來解決一些實際生活中難以解決的問題。其實簡單來說,遺傳算法所做的事情就是“隨機”生成一些可行解(不是最優解),然後隨機一段時間之後找到局部最優解。但是這個“隨機”的過程模擬了自然界中的進化規律,也是“更優解”更容易生存,“更優解”與“更優解”的結合能夠生成“更更優解”。不斷重複這個結合的過程,最終可以得到局部最優解,甚至可能是全局最優解。

算法具體描述可見:
一文讀懂遺傳算法工作原理
如何理解遺傳算法

在此我給出有遺傳算法求解揹包問題的思路。

問題描述

揹包問題

爲了簡單起見,我此處只介紹01揹包問題。當然其實01揹包問題用動態規劃很容易就能實現。但遺傳算法的意義卻絕不是動態規劃可以代替的。動態規劃只能解決一些一定有明確解的問題,但事實上現在主流問題很少是有明確解的,大多數都是優化問題,也就是隻能尋找局部最優解,並認爲局部最優解已經足夠好了。

import random
import copy
import codecs


class Genetic_alg:
    def __init__(self, weights, support_points, threshold):
        self.weights = weights
        self.support_points = support_points
        self.threshold = threshold
        self.size = len(weights)
        self.populations = []

    def construct_populations(self, times):
        local_populations = []
        local_populations.append([0 for i in range(self.size)])
        for k in range(times):
            chromosome = []
            for i in range(self.size):
                chromosome.append(random.randint(0, 1))
            local_populations.append(chromosome)
        self.selection(local_populations)

    def Mating(self, times=10):
        local_chromosomes = []
        self.populations.sort(key=lambda each: self.compute_points(each), reverse=True)
        total_points = 0
        for each in self.populations:
            total_points += self.compute_points(each)
        for i in range(times):
            father = random.randint(0, total_points)
            mother = random.randint(0, total_points)
            father_chromosome, mother_chromosome = self.Crossover(father, mother)
            local_chromosomes.append(father_chromosome)
            local_chromosomes.append(mother_chromosome)
        self.selection(local_chromosomes)

    def Crossover(self, father, mother):
        father_chromosome = copy.deepcopy(self.populations[-1])
        mother_chromosome = copy.deepcopy(self.populations[-1])
        # bug
        for each in self.populations:
            if self.compute_points(each) > father:
                father_chromosome = each
            else:
                father -= self.compute_points(each)
        for each in self.populations:
            if self.compute_points(each) > mother:
                mother_chromosome = each
            else:
                mother -= self.compute_points(each)
        mating_points = random.randint(0, self.size - 1)
        while mating_points < self.size:
            father_chromosome[mating_points], mother_chromosome[mating_points] \
                = mother_chromosome[mating_points], father_chromosome[mating_points]
            mating_points += 1
        return self.Mutation(father_chromosome, 20), self.Mutation(mother_chromosome, 20)

    def Mutation(self, chromosome, times=1):
        tmp = copy.deepcopy(chromosome)
        for i in range(times):
            mutation_point = random.randint(0, self.size - 1)
            tmp[mutation_point] = 1 - tmp[mutation_point]
        return tmp

    def selection(self, chromosomes):
        local_chromosomes = set()
        for each in chromosomes:
            tmp = self.compute_weights(each)
            if tmp <= self.threshold:
                local_chromosomes.add(' '.join([str(value) for value in each]))
        for each in local_chromosomes:
            self.populations.append([int(value) for value in each.split(' ')])

    def compute_weights(self, chromosome):
        tmp = 0
        for i in range(len(chromosome)):
            if chromosome[i] == 1:
                tmp += self.weights[i]
        return tmp

    def compute_points(self, chromosome):
        tmp = 0
        for i in range(len(chromosome)):
            if chromosome[i] == 1:
                tmp += self.support_points[i]
        return tmp

    def final_result(self):
        self.populations.sort(key=lambda each: self.compute_points(each), reverse=True)
        return self.populations[0]


def evaluation(weights, points, threshold):
    result = [0 for i in range(threshold + 1)]
    size = len(weights)
    for i in range(size):
        if i == 0:
            continue
        j = threshold
        while j > 0:
            if j >= weights[i]:
                result[j] = max(result[j], result[j - weights[i]] + points[i])
            j -= 1
    return max(result)

if __name__ == '__main__':
    with codecs.open('./test.txt', 'r') as f:
        first = 1
        weights = []
        points = []
        weights.append(0)
        points.append(0)
        threshold = 0
        for each in f.readlines():
            each = each.strip('\n')
            array = each.split(' ')
            if first:
                threshold = int(array[0])
                first = 0
            else:
                weights.append(int(array[0]))
                points.append(int(array[1]))
    alg = Genetic_alg(weights, points, threshold)
    alg.construct_populations(1000)
    alg.Mating(10000)
    print(alg.final_result())
    print('Points:' + str(alg.compute_points(alg.final_result())))
    print('Weights:' + str(alg.compute_weights(alg.final_result())))
    print(points)
    print(evaluation(weights, points, threshold))

測試案例:

600 30
95 89
75 59
23 19
73 43
50 100
22 72
6 44
57 16
89 7
98 64
95 89
75 59
23 19
73 43
50 100
22 72
6 44
57 16
89 7
98 64
95 89
75 59
23 19
73 43
50 100
22 72
6 44
57 16
89 7
98 64

總結

在測試過程中,我發現事實上遺傳算法並沒有那麼容易實現。就算是對於有唯一解的基礎揹包問題,也很難保證有正確解。仔細回顧算法的設計其實很容易發現問題的。

在最開始構造基礎population的時候已經不簡單了,特別是對於有很多數據的情況,你甚至很難可以隨機構造一種情況使得可以滿足給定揹包重量大小的條件。於是就變成了在一堆不怎麼好的sample中,期望通過crossover構造一個較好的sample。這其實是非常不容易的。

但我其實覺得,這個算法是可行的,但可能需要比較長的時間。我通過增加mutation的概率來使得一些特別的sample能夠保留下來,並且能夠繁衍。而且我覺得只要進過一段時間後,就可以得到相當不錯的結果。

但是正如前文所說的,遺傳算法不是用於求解全局最優解的(雖然如果有全局最優解的話,一般都能夠達到),而且用於求解局部最優解的,所以我們可以認爲遺傳算法得出的結果是“足夠好的”,事實上,也確實是如此。

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