Python课程作业中的算法总结

本文探讨了Python中实现全排列的两种方法,包括基于邻位对换的算法和寻找直接后继的方式,详细分析了算法思路和实现过程。同时,介绍了求子串最大和、八皇后问题以及最小编辑距离的解决方案,展示了动态规划和递归回溯的应用。
摘要由CSDN通过智能技术生成

BUAA-Python作业中的算法思考

1、【3-1课下】全排列

​ 全排列的算法网络上确实很多,但是这个题目有一个要求就是需要将时间复杂度控制在O(nlogn)以内。从而需要优化。本次作业主要采取了两种思路进行求解(前一种不太可行)

方法一:利用离散课程中的邻位对换算法

​ 离散数学这门课程中对于如何求出所有排列有一个比较好的算法,通过邻位对换的算法进行分析。当时在考虑全排列算法的时候第一时间想到了这个算法。这个算法的分析如下:

  • 可移动的数:如果一个集合{1,2, ⋯ \cdots ,n}中整数 k k k的箭头指向与其相邻但比它小的整数,则称 k k k是可移动的

    如图所示:

    在这里插入图片描述

  • 算法描述:生成{1,2,...,n}的排列算法

    • 初始: 1 ← 2 ← 3 ← . . . n ← \overleftarrow{1}\overleftarrow{2}\overleftarrow{3}...\overleftarrow{n} 1 2 3 ...n ;
    • while 存在活动整数:
      • 求出最大活动整数m
      • 交换m和其箭头指向的相邻整数的位置
      • 改变所有满足 p > m p\gt m p>m的整数 p p p的箭头方向
    • 不存在活动整数时,算法结束

    有了以上说明之后,接下来便可以按照算法求出全排列。

  • 算法分析:

''' 
定义一个类:
1、包含数字(number)、
2、是否可移动(movables)、
3、当前指向(direction)、
4、当前位置(index)
'''
class obj(object):
    def __init__(self, number, index, direction, movables):
        self.movables = movables
        self.number = number
        self.index = index
        self.direction = direction
'''
判断并且修改每一个对象中的movable
'''

def movable(item, para_lis):
    lt = len(para_lis)
    if item.direction == 'left':								# 前提一:左边
        if item.index == 0:										# 细节一:左边没有数字
            item.movables = False
        else:
            compare_item = para_lis[item.index - 1]
            if compare_item.number < item.number:				# 前提二:大于
                item.movables = True
            else:
                item.movables = False
    elif item.direction == 'right':								# 箭头方向右边同理
        if item.index == lt - 1:
            item.movables = False
        else:
            compare_item = para_lis[item.index + 1]
            if compare_item.number < item.number:
                item.movables = True
            else:
                item.movables = False
    return item.movables
'''
计算最大的可移动数
'''

def getMaxMovableNumber(para_lis):
    max_number = 0
    this_index = 0
    for item in para_lis:
        if movable(item, para_lis):								# 前提一:可移动
            if item.number > max_number:
                max_number = item.number
                this_index = item.index							# 细节一:需要保留数的下标
    if max_number == 0:					               # 细节二:没有可移动数,返回none特判
        return None
    return para_lis[this_index]

'''
交换两个对象的位置
这个地方可能有更好的算法,但是没有查到直接交换对象中一些数的方法,因此就按照学到一个交换技巧进行交换
'''
def swapIndex(item1, item2):
    item1.number, item2.number = item2.number, item1.number
    item1.movables, item2.movables = item2.movables, item1.movables
    item1.direction, item2.direction = item2.direction, item1.direction

'''
改变所有大于数number的方向
'''
def swapDirection(para_lis, number):
    for item in para_lis:
        if item.number > number:
            if item.direction == 'right':
                item.direction = 'left'
            else:
                item.direction = 'right'

​ 另外在书写代码的过程中同样也遇到了一些问题:这种方式得到的排列并不是按照字典序进行的。因此实际上还需要进行排序,通过字典序排序即可(sorted函数),但是程序整体复杂(第一个点TLE)。不适合用于全排列算法的生成。

​ 对于上述问题的处理和主函数的建立如下:

import copy

lis = []
this_number = int(input())
for i in range(1, this_number + 1):
    t = obj(i, i - 1, 'left', True)
    lis.append(t)
  												# 构建好对象
answer = [copy.deepcopy(lis)]
												# 由于每次list都会进行变换,因此使用了copy
    											# 而且使用深拷贝去除深拷贝和浅拷贝的问题
        										# 由于存在对象,因此使用深拷贝
            									# 这也是当时py学得不够精炼
t = getMaxMovableNumber(lis)
while t is not None:
    this_number = t.number
    if t.direction == 'right':
        swapIndex(t, lis[t.index + 1])
    else:
        swapIndex(t, lis[t.index - 1])
    swapDirection(lis, this_number)
    answer.append(copy.deepcopy(lis))
    t = getMaxMovableNumber(lis)
    											# 一直循环,得到list存入answer中
int_array = []
for i in answer:
    temp_array = []
    for key in range(len(i)):
        temp_array.append(str(i[key].number))
    int_array.append(' '.join(temp_array))
												# 将answer中的每一个对象的number取出
        										# 注意提前转化成string类型
int_array = sorted(int_array)					# 通过排序自动按照字典序排列
for i in int_array:
    print(i)
方法二:利用寻找直接后继的方式实现(推荐)

​ 这个地方实际上在后面的作业中才用到。但是当我做完那道题反过头来发现似乎可以用在全排列上,而且非常简洁,因此在这里一并进行分析。

这里直接推广到任意一个数组求出直接后继排列和全排列

  • 题干描述:

    实现如下功能:给定N位组成数字,求出由相同数字构成而是这个数的直接后继。如给定25468,得到25486。

  • 问题分析:对于任意一个数N

    • 从右往左,找到一个位置 i i i:该位置满足 i + 1N- i - 1全部为逆序排列。而ii + 1 为顺序排列。假设数组为 A A A, 即:
      对 任 意 j ∈ { i + 1 , N − i − 1 } , A j > A j + 1 且 A i < A i + 1 对任意 j \in \{i + 1, N- i -1\},A_j > A_{j+1}\\ 且A_{i} < A_{i + 1} j{i+1,Ni1},Aj>Aj+1Ai<Ai+1

    • i + 1N - i - 1的部分进行reverse

    • 找出reversei + 1N - i - 1部分中从左到右第一个大于i处的位置m

    • 交换 im处两位置的值。

  • 代码分析如下:

def get_next(t):
    lis = []
    lis.extend(t)
    index = 0
    
    # 第一步先找到index,这里采用反向的方法进行书写
    for i in range(1, len(lis)):
        if lis[-i] > lis[-(i + 1)]:
            index = len(lis) - i
            break
    if index == 0:							# 细节一:如果此时完全从大到小排列,标志结束
        return []
    
    # 第二步是交换后面的位置(reverse)
    for i in range((len(lis) - index) // 2):
        lis[len(lis) - i - 1], lis[index + i] = \
        	lis[index + i], lis[len(lis) - i - 1]
            
    # 第三步是找到第一个比i-1大的数,并交换
    for i in range(index, len(lis)):
        if lis[i] > lis[index - 1]:
            lis[i], lis[index - 1] = lis[index - 1], lis[i]
            break

    return lis

​ 可以看出相当的简洁,这也是通过网络学习得到的一点小知识。

​ 最后附上主体函数,完成全排列的算法。

number = int(input())
new_list = [i for i in range(1, number + 1)]
print(' '.join(list(map(str, new_list))))
t = get_next(new_list)
while len(t) != 0:
    print(' '.join(list(map(str, t))))
    t = get_next(t)

2、【4-2课上】求子串最大和

  • 题目描述:

    通过给定一个list,每个元素为可正可负,求出子list中最大连续整数值。比如:

11 -3 15 -10 -15 10 -7得到23(11 -3 15 是最大的子串)

  • 方法介绍:(实际上个人感觉可能算不上算法层面)

    • 首先考虑特殊情况:如果所有数都是非正数,那么最大的那个数就是结果
    • 其次考虑一般情况:当存在至少一个正数,
      • 定义nStart = 0nAll = 0分别记录数值。其中nAll记录的是结果中最后需要的数值,而nStart记录过程值。
      • 定义循环体:
        • 首先判断nStart是否小于0,如果小于,重新计数nStart = 0
        • 其次将nStart加上数组的数值
        • 如果当前nStart > nAll更新nAll
        • 重复直到循环结束
      • nAll即为结果
  • 代码实现

    上述表述已经足够清晰,这里通过代码进行一下进一步说明:

def max_number(lis):
    nStart = int(lis[0])
    nAll = int(lis[0])
    # 定义循环体
    for i in range(1, len(lis)):
        if nStart < 0:						# 过程一:先判断
            nStart = 0
        nStart += int(lis[i])				# 过程二:再相加
        if nStart > nAll:
            nAll = nStart					# 过程三:再判断
    return nAll


l = list(input().split(' '))
answer = max_number(l)
if answer <= 0:						      # 全部是非正数,则直接返回最大值
    l = sorted(l, reverse=True)
    print(l[0])
else:
    print(answer)

3、【4-5课上】八皇后问题(递归回溯)

  • 题目综述

​ 在8 * 8格的国际象棋上摆放8个皇后,使其不能相互攻击,即任意两个皇后都不能处于同一行、同一列或者同一斜线上,问总共有多少摆法。

  • 问题分析

    根据网络上对于八皇后的说明,实际上利用递归回溯的方式进行分析。

    • 当选择了第一个皇后的位置,那么有些位置就不能再放皇后,于是只能从第二行选择第二个皇后
    • 当选择第二个皇后之后,又有一些位置不能够被选择。只能从第三行找到下一个皇后。
    • 如此反复,直到找到最后一个皇后。

​ 这是一次寻找。但是中间也可能出现这样的情况:某一行不能放上任何一个皇后。这种情况下,可以视为寻找失败。

​ 那每次循环的依据是什么?这便是递归回溯中的重要一点。实际上上,在每一步中皇后可以选择的位置不仅仅只有一个。因此,假设第二个皇后在第二行的另外一个位置,这样会得出新的结果。

​ 此时,需要将原来是皇后的地方去掉,然后在另外一个地方增加新的皇后。

​ 而每行只有8列,因此每一个皇后需要循环判断8次。

  • 代码展示(代码源于网络,本题实际上和标准的八皇后有些不同,因此没有放上本人代码)
def find_Queen(row):

    if row>7:
        global count						# 总数加1
        count+=1
        print_queen()
        return
    
    for column in range(8):
        if check(row,column):		    # 过程一:判断该位置是否可以放皇后
            Queen[row][column]=1        # 过程二:放上皇后
            find_Queen(row+1)			# 过程三:放上皇后后,找下一排
            Queen[row][column]=0		
            # 过程四:去除这个地方的皇后,通过循环判断是否还有可以选择的位置

​ 而对于检查,实际上非常容易:检查同一行、同一列、对角线上是否有皇后。

def check(row, column):
    for i in range(8):
        if queen_locate[row][i] == 1 or \
        	queen_locate[i][column] == 1:
            return False
    if not (checkDiagonal(row, column, 1, 1) and 
            checkDiagonal(row, column, -1, 1) and
            checkDiagonal(row, column, -1, -1) and 
            checkDiagonal(row, column, 1, -1)):
        return False
    return True


def checkDiagonal(row, column, row_step, column_step):
    index_row = row
    index_column = column
    while 0 <= index_column <= 7 and 0 <= index_row <= 7:
        if queen_locate[index_row][index_column] == 1:
            return False
        index_row += row_step
        index_column += column_step
    return True

​ 于是完整的八皇后就得到了,之后根据实际情况写出主函数即可。

4、【4-1课下】一个简单的思路

  • 题目概述:

    给定一组非负整数 n u m s ( 0 − 1000000 ) nums(0-1000000) nums01000000,重新排列每个数字的顺序(每个数字不可分离)使其成为最大的整数。比如输入:10,2,3,返回3210

  • 简单思路:

    关键在于如何构造比较函数。

    由于每个数在0-1000000之间,因此可以将每个数进行变换,得出结果。

    • 看做字符串进行比较
    • 需要对字符串进行一些变换:重复最后一位数字。这样能够保证32,3得到的是332而不是323;34,3得到的是343而不是334(32<33, 34 > 33)
  • 代码示例

def compare(x):
    ans = []
    ans.extend(list(x))
    while len(ans) < 7:					# 对于每个字符串进行对齐、延伸
        ans.append(x[-1])				# 延伸的方式就是重复最后一位数
    return ''.join(ans)


lis = list(input().split(','))					# 直接利用字符串进行比较
lis = sorted(lis, key=compare, reverse=True)
if ''.join(lis).replace('0', '') == '':
    print(0)
else:
    print(''.join(lis))

5、【4-2课下】直接后继

​ 直接查看【3-1】课下中方法二即可。

6、【4-4课下】最小编辑距离(Levenshtein Distance算法)

  • 题目描述:

    这里有两个词给你 word1 和 word2,请计算 word1 转换为 word2 的最小操作数。

    您可以对一个单词进行以下三种操作:

    1.插入字符

    2.删除一个字符

    3.替换一个字符

  • 方法描述

    由于这个地方需要说明的有点多。因此个人决定暂时先转载一个比较好的博客:

    编辑距离算法详解:Levenshtein Distance算法——动态规划问题_www.helloworld.com-CSDN博客

    这个地方主要分析动态规划的思路:

    令输入的两个字符串长度分别为m、n。

    • 构造行数为m+1n+1的数组,保存从ij所需要的最少步数。也即是将串x[1...m]转到y[1...n]所需要的最少的步数就是数组中[m][n]的值。

    • 将数组第一行和第一列每个元素依次赋值0--m-1或者0--n-1

    • 根据动态规划的表达式,分别计算出每一个数组中的元素,然后返回得到[m][n]

    • 动态规划表达式如下:
      l [ i ] [ j ] = m i n ( l [ i − 1 ] [ j ] + 1 , l [ i ] [ j − 1 ] + 1 , l [ i − 1 ] [ j − 1 ] + e q ) l[i][j] = min(l[i -1][j] + 1, l[i][j-1]+1, l[i-1][j-1]+eq)\\ l[i][j]=min(l[i1][j]+1,l[i][j1]+1,l[i1][j1]+eq)

      i f w o r d 1 [ i − 1 ] = = w o r d 2 [ j − 1 ] : e q = 0 e l s e : e q = 1 if \quad word1[i-1] == word2[j-1]:\quad eq=0\\ else:eq = 1 ifword1[i1]==word2[j1]:eq=0else:eq=1

    通过上述方程和计算,直接得到结果即可。

  • 代码实现

word1 = list(input())
word2 = list(input())
len1 = len(word1)
len2 = len(word2)
matrix = []

'''
	构造matrix(实际上可以用numpy包或者直接利用for,但是当时没了解这些haha)
'''
for i in range(len1 + 1):
    row = []
    for j in range(len2 + 1):
        row.append(0)
    matrix.append(row)
'''
	将第一行依次赋值
'''
first_row = 0
for i in range(len(matrix[0])):
    matrix[0][i] = first_row
    first_row += 1
    
'''
	将第一列依次赋值
'''
first_col = 0
for i in range(len(matrix)):
    matrix[i][0] = first_col
    first_col += 1

'''
	判断和计算
'''
for i in range(1, len(matrix)):
    row = matrix[i]
    for j in range(1, len(row)):				# 判断eq
        eq = 1
        if i <= len(word1) and j <= len(word2):
            if word1[i - 1] == word2[j - 1]:
                eq = 0
        row[j] = min(matrix[i - 1][j - 1] + eq, 1 + row[j - 1], 1 + matrix[i - 1][j])

print(matrix[len(word1)][len(word2)])
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值