问题
想要像C++里面那样创建一个m*n的矩阵,当我使用[[0] * n] * m的时候遇到如下问题:
In[2]: l1 = [[0] * 3] * 4
In[3]: l2 = [[0] * 3 for _ in range(4)]
In[4]: l1[0][0] = 1
In[5]: l2[0][0] = 1
In[6]: l1
Out[6]: [[1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0]]
In[7]: l2
Out[7]: [[1, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]
用id()查看一下内存地址就知道L1中每一个元素是同一个内存地址,而L2每一个元素的内存地址是不一样的:
In[8]: id(l1[0][0])
Out[8]: 140726462423440
In[9]: id(l1[1][0])
Out[9]: 140726462423440
In[10]: id(l2[0][0])
Out[10]: 140726462423440
In[11]: id(l2[1][0])
Out[11]: 140726462423408
解决方法
涉及到python中浅拷贝和深拷贝的概念。
在涉及到对矩阵中的值进行重新赋值(大多用在动态规划的算法题里面)时,最好时用以下两种方式进行拷贝:
# 采用不可变元素
l1 = [[1,2,3],[4,5,6]]
id(l1[0])
Out[13]: 2417349020360
id(l1[1])
Out[14]: 2417346673288
# 列表推导式
l2 = [[0] * 3 for _ in range(4)]
id(l2[0])
Out[16]: 2417346784456
id(l2[1])
Out[17]: 2417348988360
下面补充一下python的浅拷贝和深拷贝的知识
在Python中复制对象
在Python中,我们使用=
运算符来创建对象的副本。您可能会认为这会创建一个新对象。没有。它仅创建一个共享原始对象引用的新变量。
举个例子,我们创建一个名为 old_list 并将对象引用传递给 new_list使用=
运算符。
示例1:使用=运算符进行复制
#Copy using = operator
old_list = [[1, 2, 3], [4, 5, 6], [7, 8, 'a']]
new_list = old_list
new_list[2][2] = 9
print('Old List:', old_list)
print('ID of Old List:', id(old_list))
print('New List:', new_list)
print('ID of New List:', id(new_list))
从输出中可以看到两个变量 old_list 和 new_list共享相同的ID,即140673303268168
。
因此,如果需要修改 new_list 要么 old_list,更改在两者中均可见。
本质上,有时可能希望保持原始值不变,而仅修改新值,反之亦然。在Python中,有两种创建副本的方法:
- 浅拷贝
- 深拷贝
为了使这些复制生效,我们使用了copy
模块。
复制模块(copy module)
将copy
Python模块用于浅层和深层复制操作。假设需要复制化合物列表说X。例如:
import copy
copy.copy(x)
copy.deepcopy(x)
在这里,copy()
返回浅拷贝副本x。同样,deepcopy()
返回的深拷贝副本x。
浅拷贝
浅拷贝副本会创建一个新对象,该对象存储原始元素的引用。
因此,浅拷贝副本不会创建嵌套对象的副本,而是仅复制嵌套对象的引用。这意味着复制过程本身不会递归或创建嵌套对象的副本。
示例2:使用浅拷贝副本创建副本
import copy
old_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
new_list = copy.copy(old_list)
print("Old list:", old_list)
print("New list:", new_list)
在上面的程序中,我们创建了一个嵌套列表,然后使用copy()
方法对其进行浅拷贝复制。
这意味着它将创建具有相同内容的新的独立对象。为了验证这一点,我们同时打印两个old_list 和 new_list。
确认 new_list 与...不同 old_list,我们尝试将新的嵌套对象添加到原始对象并进行检查。
示例3:使用浅拷贝复制将[4,4,4]添加到old_list
import copy
old_list = [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
new_list = copy.copy(old_list)
old_list.append([4, 4, 4])
print("Old list:", old_list)
print("New list:", new_list)
在上面的程序中,我们创建了的浅拷贝副本 old_list。的new_list 包含对存储在其中的原始嵌套对象的引用 old_list。然后,我们添加新的列表即[4, 4, 4]
到old_list。此新子列表未复制到new_list。
但是,当您在其中更改任何嵌套对象时 old_list,更改出现在 new_list。
示例4:使用浅拷贝添加新的嵌套对象
import copy
old_list = [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
new_list = copy.copy(old_list)
old_list[1][1] = 'AA'
print("Old list:", old_list)
print("New list:", new_list)
在以上程序中,我们对 old_list即old_list[1][1] = 'AA'
。的两个子列表old_list 和 new_list索引处[1][1]
已修改。这是因为两个列表共享相同嵌套对象的引用。
深拷贝
深层副本将创建一个新对象,并以递归方式添加原始元素中存在的嵌套对象的副本。
让我们继续示例2。但是,我们将使用模块中提供的deepcopy()
功能来创建深层副本copy
。深层副本创建原始对象及其所有嵌套对象的独立副本。
示例5:使用deepcopy()复制列表
import copy
old_list = [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
new_list = copy.deepcopy(old_list)
print("Old list:", old_list)
print("New list:", new_list)
在上面的程序中,我们使用deepcopy()
函数来创建外观相似的副本。
但是,如果您更改原始对象中的任何嵌套对象 old_list,您将看不到副本的任何更改 new_list。
示例6:使用深拷贝在列表中添加新的嵌套对象
import copy
old_list = [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
new_list = copy.deepcopy(old_list)
old_list[1][0] = 'BB'
print("Old list:", old_list)
print("New list:", new_list)
在上面的程序中,当我们将新值分配给 old_list,我们只能看到 old_list被修改。这意味着old_list和new_list是独立的。这是因为old_list 是递归复制的,这对于它的所有嵌套对象都是如此。