前几天用C++实现了求两个字符串的最长公共子序列的算法,并对算法进行了优化,现在将该算法用Python重新实现,基本思路C++版本是一致的。参见:求两字符串的最长公共子序列——动态规划
1.其中需要注意几个细节:
(1)Python的赋值语句没有返回值,因此递归算法中不能在return语句中直接递归调用函数将函数的结果赋值给tmp_1[i][j],需要分开写。
(2)产生随机的字符串,Python中提供了一个random.choice()函数,可以产生指定字符串中的一个随机字符,比C++方便。
(3)Python中参数都是以引用的形式传递的,函数发生调用会改变实参的值。而在C++中多数情况不会如此。
(4)Python对于递归的层数做了限制,默认大概950~1000之间,超过这些层数的递归会报错。不过可以在代码中临时修改此限制,例如下面的代码,递归层数限制在204800:
import sys
sys.setrecursionlimit(204800)
2.时间复杂度分析
Python的实现方式同C++实现时间复杂度是一样的,运用动态规划方法都是O(m*n)。从测试结果来看,不同的测试耗费的时间基本通问题的规模呈线性增长,非递归比递归过程快得多。空间优化后比优化前时间耗费增加,因为计算过程中多了必要的取模运算。从C++和Python的对比数据来看,对于非递归过程,C++效率明显的高于Python,这也是在意料之中的。但是对于递归过程,C++发挥的不是很稳定,规模变大之后效率反而不如Python,不知是什么原因。
下表为Python测试情况:
字符串X长度 | 字符串Y长度 | LCS长度 | 递归时间(ms) | 非递归时间(ms) | 非递归(空间优化)时间(ms) |
---|---|---|---|---|---|
100 | 75 | 27 | 29 | 10 | 9 |
100 | 150 | 41 | 55 | 17 | 20 |
200 | 150 | 54 | 104 | 31 | 37 |
200 | 300 | 75 | 188 | 67 | 70 |
400 | 300 | 113 | 402 | 123 | 144 |
400 | 600 | 159 | 898 | 242 | 286 |
800 | 600 | 224 | 1588 | 494 | 586 |
800 | 1200 | 325 | 3320 | 977 | 1180 |
1600 | 1200 | 446 | 6674 | 1960 | 2489 |
1600 | 2400 | 644 | 13669 | 3965 | 4780 |
下表为C++测试情况:
字符串X长度 | 字符串Y长度 | LCS长度 | 递归时间(ms) | 非递归时间(ms)
| ||||
---|---|---|---|---|---|---|---|---|
|
|
| 1.1 | 1.2 | 1.3 | 1.4 | 2.1 | 2.2 |
100 | 75 | 27 | 14 | 7 | 0 | 1 | 0 | 0 |
100 | 150 | 33 | 12 | 11 | 1 | 1 | 0 | 1 |
200 | 150 | 53 | 27 | 25 | 1 | 2 | 1 | 1 |
200 | 300 | 72 | 71 | 63 | 3 | 4 | 1 | 2 |
400 | 300 | 107 | 183 | 171 | 6 | 8 | 4 | 4 |
400 | 600 | 152 | 448 | 430 | 13 | 14 | 10 | 7 |
800 | 600 | 217 | 1256 | 1247 | 25 | 26 | 17 | 15 |
800 | 1200 | 310 | 3529 | 3523 | 54 | 55 | 32 | 30 |
1600 | 1200 | 438 | 10606 | 10911 | 108 | 112 | 66 | 62 |
1600 | 2400 | 626 | 31719 | 32024 | 216 | 224 | 127 | 121 |
3.C++和Python实现效率的分析
上面的两张图看着有点乱,把相关的数据摘录到下面的对比图中(其中对于C++,由于运行时间较短,程序进行了四舍五入,故数据的可参照性较差,以后面几组数据为准)。从下面的对比图中可以看出如下规律:
(1)对于C++实现来说,递归实现的时间大概是非递归实现的1.5倍多一点。
(2)对于Python实现来说,递归实现的时间大概是非递归实现的3倍多一点。
(3)对于递归,C++实现的速度是Python实现的60多倍。
(4)对于非递归,C++实现的速度是Python实现的30多倍。
字符串 X长度 | 字符串 Y长度 | LCS长度 | C++实现(ms) | Python实现(ms) | ||
---|---|---|---|---|---|---|
|
|
| 递归时间 | 非递归时间 | 递归时间 | 非递归时间 |
100 | 75 | 27 | 0 | 0 | 29 | 10 |
100 | 150 | 41 | 1 | 0 | 55 | 17 |
200 | 150 | 54 | 1 | 1 | 104 | 31 |
200 | 300 | 75 | 3 | 1 | 188 | 67 |
400 | 300 | 113 | 6 | 4 | 402 | 123 |
400 | 600 | 159 | 13 | 10 | 898 | 242 |
800 | 600 | 224 | 25 | 17 | 1588 | 494 |
800 | 1200 | 325 | 54 | 32 | 3320 | 977 |
1600 | 1200 | 446 | 108 | 66 | 6674 | 1960 |
1600 | 2400 | 644 | 216 | 127 | 13669 | 3965 |
4.源代码
算法的实现用动态规划的方法。函数LCS_length_1用递归法实现;函数LCS_length_2用递推法实现;函数LCS_length_3也是用递推法实现的,只不过在辅助数组的空间复杂度上做了一个优化,tmp_1数组只需要用两行就可以了。
import random
import datetime
import sys
sys.setrecursionlimit(204800)
def LCS_length_1(x, y, tmp_1, tmp_2, i, j):
xlen = len(x)
ylen = len(y)
if i >= 0 and j >= 0 and i <= xlen and j <= ylen:
if i == 0 or j == 0 or tmp_1[i][j] > 0:
return tmp_1[i][j]
else:
if x[i-1] == y[j-1]:
tmp_2[i][j] = 0;
tmp_1[i][j] = 1 + LCS_length_1(x, y, tmp_1, tmp_2, i-1, j-1)
return tmp_1[i][j]
else:
tmp_1[i][j-1] = LCS_length_1(x, y, tmp_1, tmp_2, i, j-1)
tmp_1[i-1][j] = LCS_length_1(x, y, tmp_1, tmp_2, i-1, j)
if tmp_1[i][j-1] >= tmp_1[i-1][j]:
tmp_2[i][j] = 1
tmp_1[i][j] = tmp_1[i][j-1]
return tmp_1[i][j]
else:
tmp_2[i][j] = -1
tmp_1[i][j] = tmp_1[i-1][j]
return tmp_1[i][j]
def LCS_length_2(x, y):
xlen = len(x);
ylen = len(y);
tmp_1 = [[0 for i in range(ylen + 1)] for j in range(xlen + 1)]
tmp_2 = [[0 for i in range(ylen + 1)] for j in range(xlen + 1)]
for i in range(1, xlen+1):
for j in range(1, ylen+1):
if(x[i-1] == y[j-1]):
tmp_1[i][j] = tmp_1[i-1][j-1] + 1;
tmp_2[i][j] = 0
else:
if(tmp_1[i][j-1] >= tmp_1[i-1][j]):
tmp_1[i][j] = tmp_1[i][j-1]
tmp_2[i][j] = 1
else:
tmp_1[i][j] = tmp_1[i-1][j]
tmp_2[i][j] = -1
return tmp_1, tmp_2
def LCS_length_3(x, y):
xlen = len(x);
ylen = len(y);
tmp_1 = [[0 for i in range(ylen + 1)] for j in range(2)]
tmp_2 = [[0 for i in range(ylen + 1)] for j in range(xlen + 1)]
for i in range(1, xlen+1):
for j in range(1, ylen+1):
if(x[i-1] == y[j-1]):
tmp_1[i%2][j] = tmp_1[(i-1)%2][j-1] + 1;
tmp_2[i][j] = 0
else:
if(tmp_1[i%2][j-1] >= tmp_1[(i-1)%2][j]):
tmp_1[i%2][j] = tmp_1[i%2][j-1]
tmp_2[i][j] = 1
else:
tmp_1[i%2][j] = tmp_1[(i-1)%2][j]
tmp_2[i][j] = -1
return tmp_1, tmp_2
def LCS_print(x, y, tmp_2):
result = []
i = len(x)
j = len(y)
k = 0
while(i > 0 and j > 0):
if(tmp_2[i][j] == 0):
result.append(x[i-1])
k = k + 1
i = i - 1
j = j - 1
elif tmp_2[i][j] == 1:
j = j - 1
elif tmp_2[i][j] == -1:
i = i - 1
return result
#-----------------------------------------------------
def test_1(x, y):
xlen = len(x);
ylen = len(y);
tmp_1 = [[-1 for i in range(ylen + 1)] for j in range(xlen + 1)]
tmp_2 = [[-1 for i in range(ylen + 1)] for j in range(xlen + 1)]
for i in range(xlen + 1):
tmp_1[i][0] = 0
for j in range(ylen + 1):
tmp_1[0][j] = 0
max_len = LCS_length_1(x, y, tmp_1, tmp_2, xlen, ylen)
result = LCS_print(x, y, tmp_2)
print "LCS_1 length : ", len(result)," it is : ",
for i in range(len(result)-1, -1, -1):
print result[i],
print ""
def test_2(x, y):
max_len, tmp_2 = LCS_length_2(x, y)
result = LCS_print(x, y, tmp_2)
print "LCS_2 length : ", len(result)," it is : ",
for i in range(len(result)-1, -1, -1):
print result[i],
print ""
def test_3(x, y):
max_len, tmp_2 = LCS_length_3(x, y)
result = LCS_print(x, y, tmp_2)
print "LCS_3 length : ", len(result)," it is : ",
for i in range(len(result)-1, -1, -1):
print result[i],
print ""
def rand_str(length):
str_0 = []
for i in range(length):
str_0.append(random.choice("abcdefghigklmnopqrstuvwxyz"))
return str_0
def main():
x = rand_str(2000)
y = rand_str(3000)
print "The String X Length is : ",len(x)," String is : ",
for i in range(len(x)):
print x[i],
print ""
print "The String Y Length is : ",len(y)," String is : ",
for i in range(len(y)):
print y[i],
print ""
time_1 = datetime.datetime.now()
test_1(x, y)
time_2 = datetime.datetime.now()
time_3 = datetime.datetime.now()
test_2(x, y)
time_4 = datetime.datetime.now()
time_5 = datetime.datetime.now()
test_3(x, y)
time_6 = datetime.datetime.now()
print "Function 1 spend ",(time_2 - time_1)
print "Function 2 spend ",(time_4 - time_3)
print "Function 3 spend ",(time_6 - time_5)
main()