实验四:动态规划算法与回溯法

实验四:动态规划算法与回溯法

用动态规划算法设计实现0-1背包问题,用回溯法设计实现0-1背包问题,并且用不同数据量进行实验对比分析,要求分析算法的时间复杂性并且提交相关代码等文档;

  1. 问题描述

(1)背包问题

给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。也可以将背包问题描述为决定性问题,即在总重量不超过W的前提下,总价值是否能达到V?它是在1978年由Merkle和Hellman提出的。

(2)动态规划算法

动态规划(Dynamic Programming,DP)是运筹学的一个分支,是求解决策过程最优化的过程。20世纪50年代初,美国数学家贝尔曼(R.Bellman)等人在研究多阶段决策过程的优化问题时,提出了著名的最优化原理,从而创立了动态规划。动态规划的应用极其广泛,包括工程技术、经济、工业生产、军事以及自动化控制等领域,并在背包问题、生产经营问题、资金管理问题、资源分配问题、最短路径问题和复杂系统可靠性问题等中取得了显著的效果

(3)回溯法

回溯法(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。

  1. 实验目的

掌握动态规划法和回溯法设计思想、程序设计。

  1. 实验原理

(1)基于动态规划算法的0-1背包问题:

给定n种物品和一个背包,物品i的重量是wi,价值为vi,背包的容量为c。如何选择物品使得装进背包里的物品总价值最大。

解法就是利用动态规划和递归,将大问题分解为若干个小问题。

①最优子结构性质:

0-1背包问题具有最优子结构性质,一个原因是若干个最优解对应的物品总数就是减去一个最优解的重量后剩下的最优解物品总数。

②递归关系:

反复求最优解。下一个最优解是上一个的:

若物品不装入,则最优解直接跳过。

若物品计划装入,则取装入与不装入的价值最大值。

 

我的函数设计和书上的一致,但是用python实现的,原因在前一个实验已经讲明。设计如下:

 

Bag函数的时间复杂度是O(nc)

Show函数的时间复杂度是O(n)

(2)基于回溯法设计的0-1背包问题:

回溯法核心:

能进则进,进不了则换,换不了则退。(按照条件深度优先搜索,搜到某一步时,发现不是最优或者达不到目标,则退一步重新选择)。目前考虑的回溯法不进行算法的二次优化,只进行原始的代码编写。

 

解法采用面向对象的思想设计(其实是因为别的设计方案难以解决问题)

 

解决本问题思路:

使用0/1序列表示物品的放入情况。将搜索看做一棵二叉树,二叉树的第 i 层代表第 i 个物品,若剩余空间允许物品 i 放入背包,扩展左子树。若不可放入背包,判断限界条件,若后续继续扩展有可能取得最优价值,则扩展右子树(即此 i 物品不放入,但是考虑后续的物品)。在层数达到物品的个数时,停止继续扩展,开始回溯。

 

约束条件:

放入背包中物品的总质量小于等于背包容量

 

限界条件:

当前放入背包中物品的总价值(i及之前) + i 之后的物品总价值 < 已知的最优值     这种情况下就没有必要再进行搜索

  1. 实验设计

4.1 0-1背包问题(动态规划)

输入为物品的数量,书包能承受的重量,两个均为整数int

还有重量和价值数组(python 列表)

 

输出为最优价值,加入的物品(以编号列表的形式)

以及程序执行的时间

 

执行多次记录数据,图形化分析。

 

4.2 0-1 背包问题(回溯法解决)

使用面向对象方法解决。

因为vs code运行代码效率比较低,这次使用pycharm。

  1. 实验结果与分析

5.1 0-1背包问题(动态规划)

先尝试执行程序如下:

没有问题,很好,开始进行随机数测试:

首先n和c是有逻辑关系的,c过大会导致大部分的物品都被选中,c过小又会导致n的增加毫无意义。至于物品的价值和重量,为了方便就取1-n范围内的随机数。

Bag与show的时间默认取3次平均值,体现在表里了

忽略掉所有print对运行时间的影响

 

(1)首先不改变n,让c等差增加:

c n c*n bag时间(ns) show时间(ns) bag1 bag2 bag3 show1 show2 show3
1000 50 50000 23246633.33 1364933.333 22898700 22937500 23903700 1102600 996800 1995400
1200 50 60000 28243233.33 984700 29920300 25927500 28881900 999100 957800 997200
1400 50 70000 33128433.33 979533.3333 35489100 30984900 32911300 995900 928300 1014400
1600 50 80000 38128733.33 959900 37474800 39948500 36962900 1002900 942000 934800
1800 50 90000 47240066.67 964266.6667 47907200 45941000 47872000 960000 933800 999000

这是c*n与bag,show时间的图表

可以看到,c*n值的增加使得bag时间线性增长,但show的值几乎不变,这是因为bag的时间复杂度是O(cn),show的时间复杂度是O(n),而n的值被控制不变,只改变c的值。

(2)改变n的值使其等差增长,但不改变c的值:

c n c*n bag时间(ns) show时间(ns) bag1 bag2 bag3 show1 show2 show3
2000 50 100000 49222800 961433.3 47931200 47829100 51908100 936100 998600 949600
2000 55 110000 56543767 955700 50886100 58842000 59903200 937000 997300 932800
2000 60 120000 61846667 985600 64827900 63827400 56884700 999300 996900 960600
2000 65 130000 65841567 979866.7 66872700 64827600 65824400 944300 997400 997900
2000 70 140000 67170133 985833.3 66822400 65791100 68896900 956000 1003600 997900

Show的时间随n的增加基本呈线性关系,由于数据差太小,这种线性关系可能不是很明显。

N=50,c=1400的一次运行截图:

N=70,c=2000的一次运行截图:

 

5.2 0-1背包问题(回溯法)

先试运行:

进行数据随机化测量:

(注:经过理论证明和多次试运行,这个算法的效率要远低于动态规划算法,因此不取太大的数)

取背包重量200,价值和单个重量的值从1到50随机:

商品数量

执行时间(ns)

15

16806966

20

376673867

25

2410045300

30

14938310733

35

38633429100

 

  1. 结论

使用动态规划算法和回溯法解决0-1背包问题,结论得到了较好的验证。在编写程序的过程中查阅了网上和书上的一些资料,在此表示感谢。

 

  1. 程序源码

7.1 动态规划算法:

import random
import time

def bag(n, c, w, v):
    # 置零,表示初始状态
    value = [[0 for j in range(c + 1)] for i in range(n + 1)]
    for i in range(1, n + 1):
        for j in range(1, c + 1):
            value[i][j] = value[i - 1][j]
            # 背包总容量够放当前物体,遍历前一个状态考虑是否置换
            if j >= w[i - 1] and value[i][j] < value[i - 1][j - w[i - 1]] + v[i - 1]:
                value[i][j] = value[i - 1][j - w[i - 1]] + v[i - 1]
    print("weight: ")
    print(w)
    print("value: ")
    print(v)
    # for x in value:
    #     print(x)
    return value

def show(n, c, w, value):
    print('the best value is ', value[n][c])
    x = [False for i in range(n)]
    j = c
    for i in range(n, 0, -1):
        if value[i][j] > value[i - 1][j]:
            x[i - 1] = True
            j -= w[i - 1]
    print('the things in the bag: ')
    for i in range(n):
        if x[i]:
            print('NO. ', i+1, ' thing,', end='')

n = 70  # 物品的数量,
c = 2000 # 书包能承受的重量,
w=[0 for _ in range(n)] # the weight of goods
v=[0 for _ in range(n)] # the values of goods
for k in range(n):
    w[k]=random.randint(1,n)
    v[k]=random.randint(1,n)

# 乱码严重,改为英文输出(utf-8 也会乱码我也是醉了)
t1=time.time_ns()
total = bag(n,c,w,v)
e1=time.time_ns()

t2=time.time_ns()
show(n,c,w,total)
e2=time.time_ns()

print("\n===============================")
print("time of function bag :"+str(e1-t1)+" ns")
print("time of function show :"+str(e2-t2)+" ns")

 

7.2 回溯法

import random
import time
import numpy

class BackSack():  # 定义背包类
    def __init__(self, capacity):  # 类的初始化
        self.capacity = capacity  # 背包最大容量(重量)
        self.currentWeight = 0  # 背包当前重量
        self.bestValue = 0  # 背包可容纳货物的最大价值,最优值
        self.currentValue = 0  # 背包内当前已装货物的价值

    def Backtrack(self, i):  # 遍历解空间寻找最优值,I:当前搜索的深度
        global length, weight, value, goods  # 全局变量
        if (i > length):
            if self.currentValue > self.bestValue:  # 更新最优值
                self.bestValue = self.currentValue
                self.currentCapacity = self.currentWeight  # 当前最优解下的背包重量
                self.bestgoods = goods[0:10]
                print('best:', self.bestgoods)  # 输出当前的最优解,最后一次输出即是最终的最优解
            return
        if self.currentWeight + weight[i] <= self.capacity:  # 进入左子树,即选取goods[i]放入背包
            goods[i] = 1
            self.currentWeight = self.currentWeight + weight[i]
            self.currentValue = self.currentValue + value[i]
            self.Backtrack(i + 1)
            self.currentValue = self.currentValue - value[i]  # 进入右子树,即舍弃goods[i],不放入背包
            self.currentWeight = self.currentWeight - weight[i]
            goods[i] = 0
        self.Backtrack(i + 1)


def main():
    global length, weight, value, goods  # 全局变量,分别表示货物数目,货物的重量数组,价值数组,货物的选取即0-1值
    # currentWeight = 0
    # bestValue = 0
    # currentValue = 0
    capacity = 200
    number_goods = 35
    value_max = 50
    weight_max = 50

    weight = [0 for _ in range(number_goods)]
    for index1 in range(number_goods):
        weight[index1] = random.randint(1, weight_max)
    print(weight)

    # weight = [2, 2, 6, 5, 4]

    value = [0 for _ in range(number_goods)]
    for index2 in range(number_goods):
        value[index2] = random.randint(1, value_max)
    print(value)

    # value = [6, 3, 5, 4, 6]

    goods = [0 for _ in range(number_goods)]

    length = len(weight) - 1
    backsack = BackSack(capacity)

    start_time = time.time_ns()
    backsack.Backtrack(0)
    end_time = time.time_ns()

    # backsack.Backtrack=Backtrack
    print("===============================================")
    print("Bag weight: " + str(capacity))
    print("Number of goods: " + str(capacity // 2))
    print("Best value: " + str(backsack.bestValue))
    print("Current weight: " + str(backsack.currentCapacity))  # 输出最优值和背包内物品的总重量
    print("Total time: " + str(end_time - start_time) + " ns.")
    print(backsack.bestgoods)  # 输出最优解

    return end_time - start_time


times = [0, 0, 0]
for ix in range(3):
    ctime = main()
    times[ix] = ctime
print("################################################################")
print(numpy.mean(times))

项目源代码:github地址

猜你喜欢

转载自blog.csdn.net/qq_37387199/article/details/109722375