Skip to content

Commit 9a54b46

Browse files
📝 更新文档,新增贪心算法与动态规划示例
- 在文档中新增了贪心集合覆盖问题的描述及示例,展示了如何使用贪心算法解决覆盖问题。 - 增加了背包问题的详细说明与动态规划解法,阐述了如何在给定容量下选择物品以最大化总价值。 - 提升了文档的实用性与参考价值,增强了对算法应用场景的理解。
1 parent 77576de commit 9a54b46

File tree

1 file changed

+332
-0
lines changed

1 file changed

+332
-0
lines changed

docs/docs/选择编程语言/Python/99练习.mdx

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1498,3 +1498,335 @@ B+树是B树的一种变种和改进,在数据库系统中应用更加广泛
14981498
14991499
由于B+树这些优势,大多数现代数据库系统(如MySQL的InnoDB引擎、PostgreSQL等)都采用B+树作为索引结构,特别适合处理大量的范围查询和排序操作。
15001500
1501+
1502+
### 贪心集合覆盖
1503+
1504+
每个广播站都覆盖一个或者多个州市,广播站的覆盖范围可能会有重复。如何设计一个算法,找到最少广播站,覆盖所有州市?
1505+
1506+
这是一个典型的**集合覆盖**问题,可以使用贪心算法来解决。
1507+
1508+
贪婪算法寻找局部最优解,可能不是全局最优解(例如背包问题),但通常情况下,贪婪算法的结果已经足够接近最优解。
1509+
1510+
数据示例:
1511+
1512+
```python showLineNumbers
1513+
stations = {
1514+
'kone': {'id', 'nv', 'ut'},
1515+
'ktwo': {'wa', 'id', 'mt'},
1516+
'kthree': {'or', 'nv', 'ca'},
1517+
'kfour': {'nv', 'ut'},
1518+
'kfive': {'ca', 'az'},
1519+
}
1520+
```
1521+
1522+
#### 思路
1523+
1524+
正确的解可能有多个,你需要遍历所有未选择的广播站,从中选择一个最多未覆盖的广播站。
1525+
1526+
#### 题解
1527+
1528+
```python showLineNumbers
1529+
stations = {
1530+
'kone': {'id', 'nv', 'ut'},
1531+
'ktwo': {'wa', 'id', 'mt'},
1532+
'kthree': {'or', 'nv', 'ca'},
1533+
'kfour': {'nv', 'ut'},
1534+
'kfive': {'ca', 'az'},
1535+
}
1536+
1537+
# 最终选择的广播站
1538+
final_stations = set()
1539+
1540+
# 获取所有州
1541+
states_needed = set.union(*stations.values())
1542+
print(states_needed)
1543+
# {'mt', 'az', 'nv', 'wa', 'id', 'ut', 'ca', 'or'}
1544+
1545+
# 如果还有未覆盖的州,则继续选择最好的广播站
1546+
while states_needed :
1547+
# 当前最好的选择
1548+
best_station = None
1549+
# 当前最好的选择覆盖的州
1550+
states_covered = set()
1551+
for station, states in stations.items():
1552+
# 计算当前广播站与还需覆盖的州的交集
1553+
covered = states & states_needed
1554+
# 如果此交集比 states_covered 大,
1555+
if len(covered) > len(states_covered):
1556+
# 则更新当前最好的选择
1557+
best_station = station
1558+
# 则更新覆盖的州为当前交集
1559+
states_covered = covered
1560+
# 需要覆盖的州减去当前最好的选择的覆盖的州
1561+
states_needed -= states_covered
1562+
# 将当前最好的选择添加到最终选择的广播站中
1563+
print(best_station)
1564+
final_stations.add(best_station)
1565+
"""
1566+
kone
1567+
ktwo
1568+
kthree
1569+
kfive
1570+
"""
1571+
```
1572+
1573+
### 背包问题
1574+
1575+
假设你有一个4磅的背包,需要在容量范围内,选择总价值最高的物品放入背包。
1576+
1577+
```python showLineNumbers
1578+
items = {
1579+
"音响":{'重量':4, '价值':3000},
1580+
"笔记本电脑":{'重量':3, '价值':2000},
1581+
"吉他":{'重量':1, '价值':1500},
1582+
"iphone":{'重量':1, '价值':2000},
1583+
}
1584+
```
1585+
1586+
暴力求解的复杂度为$$2^n$$,其中n是物品数量。
1587+
1588+
#### 思路
1589+
1590+
这题属于动态规划问题,首先定义一个二维表格。
1591+
1592+
|物品名称|1|2|3|4|
1593+
|---|---|---|---|---|
1594+
|音响| | | | |
1595+
|笔记本电脑| | | | |
1596+
|吉他| | | | |
1597+
|iphone| | | | |
1598+
1599+
这个表格的右上角表示:当只有音响可以选择时,且背包容量为1时,可选的最大价值。
1600+
1601+
横向增加说明在当前可选择的物品中,背包容量增加。
1602+
纵向增加说明在当前背包容量下,可选择的物品增加。
1603+
1604+
所以我们可以从左上角开始,逐步填充表格。
1605+
1606+
由于音响的重量为4,所以当背包容量为1 ~ 3时,无法选择音响。最大价值为0。当背包容量为4时,可以选择音响,最大价值为3000。
1607+
1608+
|物品名称|1|2|3|4|
1609+
|---|---|---|---|---|
1610+
|音响| 0 | 0 | 0 | 3000|
1611+
|笔记本电脑| | | | |
1612+
|吉他| | | | |
1613+
|iphone| | | | |
1614+
1615+
当来到第二行时,可以选择笔记本电脑或音响。其中笔记本电脑重量为3,所以当背包容量为1 ~ 2时,无法选择笔记本电脑。最大价值为0。
1616+
1617+
当背包容量为3时,可以选择笔记本电脑,最大价值为2000。当背包容量为4时,可以选择笔记本电脑或音响,由于音响的价值更高,所以选择音响,最大价值为3000。
1618+
1619+
|物品名称|1|2|3|4|
1620+
|---|---|---|---|---|
1621+
|音响| 0 | 0 | 0 | 3000|
1622+
|笔记本电脑| 0 | 0 | 2000 | 3000|
1623+
|吉他| | | | |
1624+
|iphone| | | | |
1625+
1626+
当来到第三行时,可以选择吉他、笔记本电脑或音响。
1627+
1628+
当背包重量为1~2时,选吉他,最大价值为1500。不选则为0。所以应该选择,此时最大价值为1500。
1629+
1630+
你会发现从上往下,每行都比上一行多一个物品。且对应的最大价值不会变小(只会变大或者与上一行相等)
1631+
1632+
目前已经更新的表的最后一行,就表示当前容量下,可选物品的最大价值。
1633+
1634+
且从左向右,每列都比前一格容量更多,所以对应的最大价值也不会变小(只会变大或者与上一行相等)
1635+
1636+
最终右下角的值,就是整个表最大的值之一(因为可能会有多个相等的最大值)
1637+
1638+
|物品名称|1|2|3|4|
1639+
|---|---|---|---|---|
1640+
|音响| 0 | 0 | 0 | 3000|
1641+
|笔记本电脑| 0 | 0 | 2000 | 3000|
1642+
|吉他| 1500 | 1500 | X | Y |
1643+
|iphone| | | | |
1644+
1645+
所以当填写 "X" 时,我们<Highlight>向上数一格</Highlight>,发现至少有一个价值2000的选择。
1646+
1647+
接下来比较其他可选项:吉他(1500)、或者不选(0)。取最大值,所以填写2000。
1648+
1649+
当我们填写 "Y" 时,我们<Highlight>向左看</Highlight>,发现"Y"只比左侧的"X"增加<Highlight>一个</Highlight>空间。<Highlight>向上看</Highlight>,发现之前的最佳选择是3000。
1650+
1651+
所以我们计算 容量3的最大值 + 容量1的最大值 = 2000 + 1500 = 3500
1652+
1653+
和历史最佳3000取最大值,所以应该填 3500
1654+
1655+
|物品名称|1|2|3|4|
1656+
|---|---|---|---|---|
1657+
|音响| 0 | 0 | 0 | 3000|
1658+
|笔记本电脑| 0 | 0 | 2000 | 3000|
1659+
|吉他| 1500 | 1500 | 2000 | 3500 |
1660+
|iphone| | | | |
1661+
1662+
最后一层,我们尝试编写伪代码
1663+
1664+
<Highlight>每下移一行,我们就会多了一个物品可以选择。所以这一行的每个判断都是围绕着是否选择这个新增物品展开的。</Highlight>
1665+
1666+
首先判断当前背包空间是否大于等于这个物品的空间。如果是否则直接沿用上一列对应格子的值。
1667+
1668+
其次判断如果比当前物品大,是否选择:
1669+
1670+
如果选择这个物品,`最大价值 = 当前物品价值 + 表格[前一行][(当前背包空间-物品空间)的值]`
1671+
1672+
如果不选择这个物品,`最大价值 = 表格[前一行][当前背包空间的值]`
1673+
1674+
我们来试一下:
1675+
1676+
```bash showLineNumbers
1677+
1678+
当前容量 = 1
1679+
1680+
当前背包空间是否大于等于这个物品的空间? 1 >=1 ——>
1681+
1682+
如果选择这个物品:
1683+
1684+
最大价值 = 当前物品价值(2000) + 表格[前一行][(当前背包空间-物品空间)的值](0) = 2000
1685+
1686+
如果不选择这个物品:
1687+
1688+
最大价值 = 表格[前一行][当前背包空间的值](1500) = 1500
1689+
1690+
所以应该选择这个物品,最大价值为2000。
1691+
```
1692+
1693+
|物品名称|1|2|3|4|
1694+
|---|---|---|---|---|
1695+
|音响| 0 | 0 | 0 | 3000|
1696+
|笔记本电脑| 0 | 0 | 2000 | 3000|
1697+
|吉他| 1500 | 1500 | 2000 | 3500 |
1698+
|iphone| 2000 | | | |
1699+
1700+
```bash showLineNumbers
1701+
当前容量 = 2
1702+
1703+
当前背包空间是否大于等于这个物品的空间? 2 >=1 ——>
1704+
1705+
如果选择这个物品:
1706+
1707+
最大价值 = 当前物品价值(2000) + 表格[前一行][(当前背包空间-物品空间)的值](1500) = 3500
1708+
1709+
如果不选择这个物品:
1710+
1711+
最大价值 = 表格[前一行][当前背包空间的值](1500) = 1500
1712+
1713+
所以应该选择这个物品,最大价值为3500。
1714+
```
1715+
1716+
|物品名称|1|2|3|4|
1717+
|---|---|---|---|---|
1718+
|音响| 0 | 0 | 0 | 3000|
1719+
|笔记本电脑| 0 | 0 | 2000 | 3000|
1720+
|吉他| 1500 | 1500 | 2000 | 3500 |
1721+
|iphone| 2000 | 3500 | | |
1722+
1723+
```bash showLineNumbers
1724+
当前容量 = 3
1725+
1726+
当前背包空间是否大于等于这个物品的空间? 3 >=1 ——>
1727+
1728+
如果选择这个物品:
1729+
1730+
最大价值 = 当前物品价值(2000) + 表格[前一行][(当前背包空间-物品空间)的值](1500) = 3500
1731+
1732+
如果不选择这个物品:
1733+
1734+
最大价值 = 表格[前一行][当前背包空间的值](2000) = 2000
1735+
1736+
所以应该选择这个物品,最大价值为3500。
1737+
```
1738+
|物品名称|1|2|3|4|
1739+
|---|---|---|---|---|
1740+
|音响| 0 | 0 | 0 | 3000|
1741+
|笔记本电脑| 0 | 0 | 2000 | 3000|
1742+
|吉他| 1500 | 1500 | 2000 | 3500 |
1743+
|iphone| 2000 | 3500 | 3500 | |
1744+
1745+
1746+
```bash showLineNumbers
1747+
当前容量 = 4
1748+
1749+
当前背包空间是否大于等于这个物品的空间? 4 >=1 ——>
1750+
1751+
如果选择这个物品:
1752+
1753+
最大价值 = 当前物品价值(2000) + 表格[前一行][(当前背包空间-物品空间)的值](2000) = 4000
1754+
1755+
如果不选择这个物品:
1756+
1757+
最大价值 = 表格[前一行][当前背包空间的值](3500) = 3500
1758+
1759+
所以应该选择这个物品,最大价值为4000。
1760+
```
1761+
1762+
|物品名称|1|2|3|4|
1763+
|---|---|---|---|---|
1764+
|音响| 0 | 0 | 0 | 3000|
1765+
|笔记本电脑| 0 | 0 | 2000 | 3000|
1766+
|吉他| 1500 | 1500 | 2000 | 3500 |
1767+
|iphone| 2000 | 3500 | 3500 | 4000 |
1768+
1769+
最后的最大值一定在左下角。(可能出现多个相等的最大值)
1770+
1771+
#### 题解
1772+
1773+
```python showLineNumbers
1774+
items = {
1775+
"音响": {'重量': 4, '价值': 3000},
1776+
"笔记本电脑": {'重量': 3, '价值': 2000},
1777+
"吉他": {'重量': 1, '价值': 1500},
1778+
"iphone": {'重量': 1, '价值': 2000},
1779+
}
1780+
# 背包最大容量
1781+
max_weight = 4
1782+
# 物品名称
1783+
names = list(items.keys())
1784+
1785+
# 行:物品个数,列:当前背包容量(1~max_weight)
1786+
rows = len(names)
1787+
cols = max_weight
1788+
1789+
# dp[i][w] 表示:在前 i 个物品中选择,容量为 w 时的最大价值
1790+
dp = [[0] * (cols + 1) for _ in range(rows + 1)]
1791+
1792+
# 为了让我们使用统一的状态转移方程,我们多生成了一列0和一行0,便于我们使用统一的状态转移方程。
1793+
# 表示当没有物品或没有背包空间时,最大价值为0。
1794+
# 下图中我用0.0 将其特殊标注出来
1795+
# 你从A开始逐行填写,填到Z
1796+
"""
1797+
[[0.0, 0.0, 0.0, 0.0, 0.0],
1798+
[0.0, A, 0, 0, 0],
1799+
[0.0, 0, 0, 0, 0],
1800+
[0.0, 0, 0, 0, Z]]
1801+
"""
1802+
for i in range(1, rows + 1):
1803+
name = names[i - 1]
1804+
weight = items[name]['重量']
1805+
value = items[name]['价值']
1806+
# 判断当前行的每一个背包空间是否应该选择这个物品
1807+
for w in range(1, cols + 1):
1808+
# 首先判断当前背包空间是否大于等于这个物品的空间
1809+
if w < weight:
1810+
# 如果否,则直接沿用上一行对应格子的值
1811+
dp[i][w] = dp[i - 1][w]
1812+
else:
1813+
# 如果是,则围绕「是否选择这个新增物品」展开判断
1814+
# 选择这个物品:当前物品价值 + 表格[前一行][当前背包空间-物品空间]
1815+
choose = value + dp[i - 1][w - weight]
1816+
# 不选择这个物品:表格[前一行][当前背包空间]
1817+
not_choose = dp[i - 1][w]
1818+
# 取两者中的最大值
1819+
dp[i][w] = max(choose, not_choose)
1820+
1821+
# 打印表格,便于和上面的推导对照
1822+
header = ["物品名称"] + [str(w) for w in range(1, cols + 1)]
1823+
print("\t".join(header))
1824+
for i in range(1, rows + 1):
1825+
row_name = names[i - 1]
1826+
values = [str(dp[i][w]) for w in range(1, cols + 1)]
1827+
print(row_name + "\t" + "\t".join(values))
1828+
1829+
# 最右下角就是最大价值
1830+
max_value = dp[rows][cols]
1831+
print("最大总价值:", max_value)
1832+
```

0 commit comments

Comments
 (0)