Python实现求两个字符串的最长公共子序列的算法

        前几天用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()


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值