算法设计与分析实验报告python实现(串匹配问题、采用分治法求解最大连续子序列和问题、用分治策略求众数问题、最近点对问题)

一、 实验目的

1.加深学生对算法设计方法的基本思想、基本步骤、基本方法的理解与掌握;
2.提高学生利用课堂所学知识解决实际问题的能力;
3.提高学生综合应用所学知识解决实际问题的能力。

二、实验任务

1、串匹配问题
给定一段文本,在该文本中查找并定位任意给定字符串。
要求:
(1)实现BF算法;
(2) 实现BF算法的改进算法:KMP算法
2、采用分治法求解最大连续子序列和问题
给定一个有n(n≥1)个整数的序列,要求求出其中最大连续子序列的和。
例如: 序列(-2,11,-4,13,-5,-2)的最大子序列和为20序列(-6,2,4,-7,5,3,2,-1,6,-9,10,-2)的最大子序列和为16。
3、用分治策略求众数问题
问题描述:给定含有n个元素的多重集合S,每个元素在S中出现的次数称为该元素的重数。多重集S中重数最大的元素称为众数。要求对给定的又n个自然数的组成的多重集S,计算S的众数及其重数。
4、最近点对问题
设p1=(x1, y1), p2=(x2, y2), …, pn=(xn, yn)是平面上n个点构成的集合S,设计算法找出集合S中距离最近的点对。
(1)分别用蛮力法和分治法求解最近对问题;
(2)分析算法的时间性能,设计实验程序验证分析结论。

三、实验设备及编程开发工具

实验设备:Win10电脑
开发工具:Python 3.7,Pycharm

四、实验过程设计(算法思路及描述,代码设计)

1、串匹配问题

给定一段文本,在该文本中查找并定位任意给定字符串。
要求:
(1)实现BF算法;
(2)实现BF算法的改进算法:KMP算法

(1)解题思路:
BF算法就是暴力求解,用所求字符串s2与主串s1做匹配,当不匹配时,从本次主串循环的下一个字符开始重新匹配。

代码:

def BF_algorithm(s1,s2): 
    n1 = len(s1) 
    n2 = len(s2)
    i,j = 0,0
    while i < n1 and j < n2:
        if (s1[i] == s2[j]):
            i += 1
            j += 1
        else:
            i = i - j + 1
            j = 0
    if j >= n2:
        return i - n2
    else:
        return -1

(2)解题思路:

KMP算法需要一个next数组,next数组的含义就是一个固定字符串的最长前缀和最长后缀相同的长度。当匹配失败时,数组下标会移动到最长前缀的后一个位置,从而减少了重复匹配。

代码:

def get_next(p):
    i, k, m = 0, -1, len(p)
    p_next = [-1] * m
    while i < m-1:
        if k == -1 or p[i] == p[k]:
            i, k = i+1, k+1
            if p[i] == p[k]:
                p_next[i] = p_next[k]
            else:
                p_next[i] = k
        else:
            k = p_next[k]
    return p_next

def KMP_algorithm(t,p):
    j, i = 0, 0
    n, m = len(t), len(p)
    p_next = get_next(p)
    while j < n and i < m:
        if i == -1 or t[j] == p[i]:
            j, i = j+1, i+1
        else:
            i = p_next[i]
    if i == m:
        return j-i
    return -1

# 测试
t = "abbcabcaabbcaa"  # 目标串
p = "abbcaa"  # 模式串
print(KMP_algorithm(t, p))

2、采用分治法求解最大连续子序列和问题

给定一个有n(n≥1)个整数的序列,要求求出其中最大连续子序列的和。
例如: 序列(-2,11,-4,13,-5,-2)的最大子序列和为20序列(-6,2,4,-7,5,3,2,-1,6,-9,10,-2)的最大子序列和为16。

解题思路:
分治法求最大连续子序列问题,可以先从序列的中间分开,分别求左序列和右序列的最大连续子序列,但是还需要考虑如果最大连续子序列正好越过序列的划分线的情况。如果最大连续子序列刚好越过划分线,那么它必然是左边最大连续子序列和右边连续最大子序列的和。所以我们只需要比较左边最大连续子序列,右边最大连续子序列,左边最大连续子序列加上右边最大连续子序列的和,从这三者中取最大值就可以求出整个序列的最大连续子序列。

代码:

def Max_zixulie(nums):
    n = len(nums)
    if n == 1:
        return nums[0]
    else:
        max_left = Max_zixulie(nums[0:n//2])
        max_right = Max_zixulie(nums[n//2:n])

    temp,max_leftsum = 0,0
    for i in range(n//2 - 1,-1,-1):
        temp = temp + nums[i]
        max_leftsum = max(temp,max_leftsum)
    temp,max_rightsum = 0,0
    for i in range(n//2,n):
        temp = temp + nums[i]
        max_rightsum = max(temp,max_rightsum)

    return max(max_left,max_right,max_leftsum + max_rightsum)

print(Max_zixulie([-2,11,-4,13,-5,-2]))

3、用分治策略求众数问题

问题描述:给定含有n个元素的多重集合S,每个元素在S中出现的次数称为该元素的重数。多重集S中重数最大的元素称为众数。要求对给定的又n个自然数的组成的多重集S,计算S的众数及其重数。

解题思路:
首先需要用分治策略解题,所以需要对元素进行排序,然后选取序列中间的元素,由它出发向前和向后寻找和它一样的元素,从而记录众数和重数。接下来从左侧和右侧分别继续递归比较,并在比较过程中判断是否大于已知的重数。在递归比较规程中,我们需要用众数比较剩下的左右剩余序列长度,如果众数大于等于左/右剩余序列长度,那么就不需要进行比较,直接输出就可以。

代码:

p, q = 0, 0
def ZhongShu(nums):
    n = len(nums)
    global p,q
    z_max,k_max,left,right = split(nums)
    if k_max > q:
        q = k_max
        p = z_max
    if left > q:
        ZhongShu(nums[0:left])
    if n - right > q:
        ZhongShu(nums[right:n])
    return p,q

def split(nums):
    n = len(nums)
    low, high = 0, n//2 + 1
    while low < n:
        if nums[low] == nums[n//2]:
            break
        else:
            low += 1
    while high < n:
        if nums[high] == nums[n//2]:
            high += 1
        else:
            break
    z = nums[low]
    k = high - low
    return z,k,low,high

print(ZhongShu([1,1,1,1,1,1,2,3,3,4,4,5,5,5,6,6,6,8,8,8,8,8,8,8]))

4、最近点对问题

设p1=(x1, y1), p2=(x2, y2), …, pn=(xn, yn)是平面上n个点构成的集合S,设计算法找出集合S中距离最近的点对。
(1)分别用蛮力法和分治法求解最近对问题;
(2) 分析算法的时间性能,设计实验程序验证分析结论。

(1)解题思路:
蛮力法直接用for循环遍历,对每两个点都做运算,并且比较记录最小值,最后输出最短距离和最近的点对。
分治方法在考虑了左、右两边各自的最近点对之后,还需要考虑是否会有最近的点对穿过了我们的划分线。这其实和寻找最长连续子序列有异曲同工之处,但是在考虑这个问题上,我们只需要从划分线向两侧分别延展已求的最近点对长度就可以,然后寻找在这个区域内的最近点对。

代码:(蛮力法)

def ManLi(nums):
    n = len(nums)
    x = float('inf')
    if n <= 1:
        return False
    for i in range(1,n):
        j = 1
        while j <= i:
            y = pow((nums[i][0] - nums[i - j][0]) ** 2 + (nums[i][1] - nums[i - j][1]) ** 2,0.5)
            x = min(x,y)
            if x == y:
                l = []
                l.append(nums[i])
                l.append(nums[i - j])
            j += 1
    return x,l[0],l[1]

print(ManLi([[0,4],[1,1],[1,5],[2,4],[3,2],[3,5],[4,0],[4,3],[5,2],[5,5]]))

代码:(分治法)

import sys
import math

def distance(x, y):
    return math.sqrt((x[0] - y[0]) ** 2 + (x[1] - y[1])** 2)

def FenZhi(points):
    n = len(points)
    if n < 2:
        return sys.maxsize, None, None
    elif n == 2:
        return distance(points[0], points[1]), points[0], points[1]

    points = sorted(points)
    half = (n - 1) // 2
    d1, a1, b1 = FenZhi(points[:half])
    d2, a2, b2 = FenZhi(points[half:])
    d, a, b = (d1, a1, b1) if d1 < d2 else (d2, a2, b2)
    calibration = points[half][0]
    left, right = [], []
    for u in points:
        if calibration - d < u[0] < calibration:
            left.append(u)
        elif calibration <= u[0] < calibration + d:
            right.append(u)

    right = sorted(right, key=lambda x: (x[1], x[0]))
    res = d
    for u in left:
        l, r = -1, len(right) - 1
        while r - l > 1:
            m = (l + r) >> 1
            if right[m][1] <= u[1] - d:
                l = m
            else:
                r = m
        idx = r
        for j in range(7):
            if j + idx >= len(right):
                break
            if distance(u, right[idx + j]) < res:
                res = distance(u, right[idx + j])
                a, b = u, right[idx + j]

    return res, a, b

print(FenZhi([[0,4],[1,1],[1,5],[2,4],[3,2],[3,5],[4,0],[4,3],[5,2],[5,5]]))

(2)蛮力法用了两个循环,因而其时间复杂度是 O ( n 2 ) O(n^2) On2,分治法由公式有 T ( n ) = T ( n / 2 ) + O ( n ) T(n) = T(n/2) + O(n) T(n)=T(n/2)+O(n),由主定理可以得出其时间复杂度是 O ( n l o g n ) O(nlogn) Onlogn

通过设置100,1000,10000个随机点对,测试蛮力策略和分治策略的用时,从而比较各自的时间复杂度。

image-20240403173629934

实验结果如下:

image-20240403173541725

由此可见蛮力法的时间大致沿着 n 2 n^2 n2的速率增长,而分治法大致沿着 n l o g n nlogn nlogn增长,符合我们的计算结果。

六、实验小结(包括问题和解决方法、心得体会等)

本次作业是分治算法的一个应用,分治和递归相辅相成,通过编写分治算法,让我深刻了解了递归的含义,并且熟悉应用了分治算法解决问题。
串匹配问题是我们在数据结构中就遇到的老朋友,其中的BF算法思想很简单,就是蛮力策略,而KMP算法主要思想是减少重复比较的时间,所以定义了一个next的方法,用来寻找最长前后缀,从而达到不移动主串,只移动子串的快速匹配。
寻找最长连续子序列问题,难点在于想到如果所求序列正好被划分的情况,而后续在三个值中寻找最大值不难分析。
分治策略求众数也是很典型的分治策略的应用,但是和上一题不一样,我们需要先行找到当前最大的众数,然后再应用递归到左、右两个序列中。
最近点对问题应该是最有难度的一道题,蛮力策略很简单,但是分治方法在考虑了左、右两边各自的最近点对之后,还需要考虑是否会有最近的点对穿过了我们的划分线。这其实和寻找最长连续子序列有异曲同工之处,但是在考虑这个问题上,我们只需要从划分线向两侧分别延展已求的最近点对长度就可以,然后寻找在这个区域内的最近点对。
通过本次作业,我对算法有了更进一步的认知,也锻炼了我的编程能力。

  • 8
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

温柔说给风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值