【在校整理-06 最常公共子序列与子串+2048小游戏+KNN简单实例】(注:仅供参考学习使用)
一、课题内容和要求
本篇中 ,主要是本人程序设计实验周的课程试验,包括最常公共子序列与子串、2048小游戏和K-NN算法简单示例共三个不同部分的实验内容。
1、问题描述
1.1最长公共子序列与子串
问题描述:
(1)公共子序列
给定两个字符串,求解这两个字符串的最长公共子序列。
一个特定序列的子序列就是将给定序列中零个或多个元素去掉后得到的结果(不改变元素间相对次序)。一个序列,如果是两个或多个已知序列的子序列,且是所有子序列中最长的,则为最长公共子序列。
算法设计:给定两个字符串,求解这两个字符串的最长公共子序列。数据输入:输入两个字符串;输出:最长公共子序列。
样例:
输入:“aabcd”
“12abcabcd”
输出:“abcd”
(2)公共子串
在计算机科学中,最长公共子串问题是寻找两个或多个已知字符串最长的子串。此问题与最长公共子序列问题的区别在于子序列不必是连续的,而子串却必须是。
有两个字符串,这两个字符串可能会存在公共的部分,如字符串"abcdef" 和字符串"defg",这两个字符串之间有共同的字符串,“d”,“e”,“f”,“de”,“ef”,“def” 等。最长的公共子串就是"def"
1.2 2048小游戏
2.1问题描述与介绍:
给定一个2048的局面,和下一步的指令,计算出变化后的局面,游戏规则如下:(1)游戏是一个4 x 4的格子。
(2)玩家通过上,下,左,右控制数字方格滑动
(3)每滑动一次,所有数字方块都会往滑动的方向靠拢,相同数字的方块在靠拢相撞时会相加:单个数字方块滑动过程中遇到非0的不同数值其他方块,则停止滑动。
(4)不断的叠加最终拼出2048这个数字就算成功。
(5)每次滑动后,会在某个空白格子中随机出现2或者4,如果不存在空白格子,则游戏结束,算作失败。
输入:
样例的前4行是整数行,每行4个整数,如果整数为0表示空白格子,其他为数字。每个样例的第5行,是指令,指令为"LEFT",“DOWN”",“RIGHT”",“UP”,依次表示滑动的方向。
样例输入:
2 2 0 0 2 0 2 0
2 0 2 0 2 2 2 2
2 0 0 2 0 2 0 2
0 0 2 2 4 2 2 0
LEFT LEFT
样例输出:
4 0 0 0 4 0 0 0
4 0 0 0 4 4 0 0
4 0 0 0 4 0 0 0
4 4 0 0 4 4 0 0
1.3 K-NN算法实例
3.1问题描述与介绍:
kNN: k-nearest neighbor classification
K最近邻(k-Nearest Neighbor,KNN)分类算法,是一个理论上比较成熟的方法,也是最简单的机器学习算法之一。该方法的思路是:如果一个样本在特征空间中的k个最相似( 即 特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。
二、源程序代码
最长公共子序列与子串代码:
// 代码很烂,轻喷。注:仅供参考学习使用。
# coding:utf-8
"""
一、
2.最长公共子序列(动态规划)问题描述:
给定两个字符串,求解这两个字符串的最长公共子序列。
一个特定序列的子序列就是将给定序列中零个或多个元素去掉后得到的结果(不改变元素间相对次序)。
一个序列,如果是两个或多个已知序列的子序列,且是所有子序列中最长的,则为最长公共子序列。
算法设计:给定两个字符串,求解这两个字符串的最长公共子序列。数据输入:输入两个字符串
结果输出:最长公共子序列例:
输入:’aabcd‘ ‘12abcabcd’
输出:‘abcd’
"""
import copy
# 求最常公共子序列
def lcs(str1, str2):
len_str1 = len(str1)
len_str2 = len(str2)
# 用字典来解决需要新开列表来存储或指向子序列的问题
init_unit = {
"最长公共子序列长度LSC_len": 0,
"最长公共子序列LSC": []
} # 字典中建立子序列的长度和子序列两个key
# 列表推导式生成多个列表,用于记录后续数据使用
dp = [[init_unit] * (len_str2 + 1) for i in range(len_str1 + 1)]
# m+1行, n+1列
for i in range(0, len_str1 + 1):
for j in range(0, len_str2 + 1):
if i == 0 or j == 0:
dp[i][j] = init_unit # 存在字符串为空的情况直接置零
elif str1[i - 1] == str2[j - 1]:
tmp_str = copy.copy((dp[i - 1][j - 1])["最长公共子序列LSC"])
tmp_str.append(str1[i - 1])
unit = {
"最长公共子序列长度LSC_len": (dp[i - 1][j - 1])["最长公共子序列长度LSC_len"] + 1,
"最长公共子序列LSC": tmp_str}
dp[i][j] = unit
elif str1[i - 1] != str2[j - 1]:
if (dp[i - 1][j])["最长公共子序列长度LSC_len"] > (dp[i][j - 1])["最长公共子序列长度LSC_len"]: # 存储最长的信息
dp[i][j] = dp[i - 1][j]
else:
dp[i][j] = dp[i][j - 1]
else:
pass
pass # 跳出内层循环
pass # 结束循环
for k, v in dp[len_str1][len_str2].items():
if type(v) == list:
v = ''.join(v)
print(k, ':', v) # 循环打印字典中的结果
# 求解最长公共子串
def lsc_of_str(str1, str2):
# 利用二维数组实现两个字符串的遍历后保存连续位相同与否的状态
len_str1, len_str2 = len(str1), len(str2) # 存储字符串长度
# 列表推导式嵌套生成类似数组。多一位确保不会溢出
record = [[0 for i in range(len_str2 + 1)] for j in range(len_str1 + 1)]
len_of_lsc = 0 # 记录最长匹配长度
step = 0 # 匹配的起始位
for i in range(len_str1):
for j in range(len_str2):
if str1[i] == str2[j]:
record[i + 1][j + 1] = record[i][j] + 1 # 相同则累加并标记长度
if record[i + 1][j + 1] > len_of_lsc:
len_of_lsc = record[i + 1][j + 1] # 获取最大匹配长度
step = i + 1 # 记录最大匹配长度的终止位置
if not record:
print('最长公共子串LSC_string为:无' + '\n' + '最长公共子串长度LSC_string_len :', len_of_lsc)
else:
print('最长公共子串长度LSC_str_len :', len_of_lsc, '\n' + '最长公共子串LSC_string :', str1[step - len_of_lsc:step])
# 运行函数
def run():
str1, str2 = input('请输入第一个字符串:'), input('请输入第二个字符串:') # 输入字符串
# str1 = 'aabcd'
# str2 = '12abcabcd'
lcs(str1, str2) # 测试最长公共子序列
lsc_of_str(str1, str2) # 测试最长公共子串
if __name__ == '__main__':
run()
2048小游戏实现代码:
// 代码很烂,轻喷。注:仅供参考学习使用。
# coding ='utf-8'
"""
1.为了测试方便,先把胜利标准记为16(可改)。
2.指令"LEFT","DOWN","RIGHT"","UP"依次记为'a','s','d','w'。
"""
import os, sys
import random
import itertools
def trim(seqs, direction=0): # 去零补零取相反
return ([0, 0, 0, 0] + [n for n in seqs if n])[-4:] if direction else ([n for n in seqs if n] + [0, 0, 0, 0])[:4]
def sum_seqs(seqs, direction=0, turn=0): # turn 用来判断方向是左上:0 还是右下:1
if turn == 0: # (向左向上)
if seqs[0] and seqs[1] and seqs[0] == seqs[1]:
if seqs[3] and seqs[2] and seqs[3] == seqs[2]: # 对应: 2 2 2 2 类型(横向)
return trim([seqs[0] * 2, 0, seqs[2] * 2, 0], direction=direction) # 合并成:4 0 4 0
else: # 对应:2 2 4 8 合并成:4 4 8 0
return trim([seqs[0] * 2, 0, seqs[2], seqs[3]], direction=direction)
if seqs[1] and seqs[2] and seqs[1] == seqs[2]: # 对应:0 2 2 0 合并成:0 4 0 0
return trim([seqs[0], seqs[1] * 2, 0, seqs[3]], direction=direction)
if seqs[2] and seqs[3] and seqs[2] == seqs[3]:
seqs[2], seqs[3] = seqs[2] * 2, 0 # 对应:0 4 2 2 合并成:0 4 4 0
return trim(seqs, direction=direction)
if turn == 1: # 向右向下 与上述相似,想法倒转即可
if seqs[3] and seqs[2] and seqs[3] == seqs[2]:
if seqs[0] and seqs[1] and seqs[0] == seqs[1]:
return trim([0, 0, seqs[2] * 2, seqs[3] * 2], direction=direction)
else:
return trim([seqs[0], seqs[1], 0, seqs[3] * 2], direction=direction)
if seqs[2] and seqs[1] and seqs[2] == seqs[1]:
return trim([seqs[0], 0, seqs[2] * 2, seqs[3]], direction=direction)
if seqs[1] and seqs[0] and seqs[1] == seqs[0]:
seqs[1], seqs[0] = seqs[1] * 2, 0
return trim(seqs, direction=direction)
# 向上控制
def up(grid):
for col in [0, 1, 2, 3]:
for idx, n in enumerate(sum_seqs(trim([row[col] for row in grid], direction=0), direction=0)):
grid[idx][col] = n
return grid
# 向下控制
def down(grid):
for col in [0, 1, 2, 3]:
for idx, n in enumerate(sum_seqs(trim([row[col] for row in grid], direction=1), direction=1, turn=1)):
grid[idx][col] = n
return grid
# 向左控制
def left(grid):
return [sum_seqs(trim(row, direction=0), direction=0) for row in grid]
# 向右控制
def right(grid):
return [sum_seqs(trim(row, direction=1), direction=1, turn=1) for row in grid]
class Game2048:
grid = [] # 表示棋盘
controls = ['w', 'a', 's', 'd']
def rnd_field(self):
# 随机挑选空位置生成2或4 多写几个2,4是因为random是伪随机函数,多一些可以平衡一下概率
number = random.choice([2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4, 2, 4])
x, y = random.choice([(x, y) for x, y in itertools.product([0, 1, 2, 3], [0, 1, 2, 3]) if self.grid[x][y] == 0])
self.grid[x][y] = number
def print_screen(self): # 用来向用户输出界面
os.system("cls") # 清屏
print('-' * 21)
for row in self.grid:
# f-string格式化操作,用or把col的0替换为空格
print(f'''|{'|'.join([str(col or ' ').center(4) for col in row])}|''')
print('-' * 21)
def logic(self, control): # 对输入的控制信号的判断
# 根据参数从字典中获取变量名,直接把变量名当做函数名进行调用
grid = {'w': up, 'a': left, 's': down, 'd': right}[control]([[c for c in r] for r in self.grid])
if grid != self.grid:
del self.grid[:]
self.grid.extend(grid)
if [n for n in itertools.chain(*grid) if n >= 16]: # 改数据
return 1, 'you win!'
self.rnd_field()
else:
if not [1 for g in [f(grid) for f in [up, down, left, right]] if g != self.grid]:
return -1, 'sorry,you lose!'
return 0, '' # 返回两个参数,0代表没有什么事儿,继续运行。1:胜利 -1:失败
def main_loop(self): # 控制游戏循环
del self.grid[:] # 清空棋盘,下一步初始化置0
# self.grid.extend([[0, 0, 0, 0, ], [0, 0, 0, 0, ], [0, 0, 0, 0], [0, 0, 0, 0, ]])
self.grid.extend([[0 for i in range(4)] for j in range(4)])
self.rnd_field() # 调用随机生成方法
# self.rnd_field()
while True: # 进入游戏
self.print_screen() # 向用户输出界面
control = input('input w/a/s/d :')
if control in self.controls:
status, info = self.logic(control)
if status:
self.print_screen()
print(info)
if input('Start another game?[Y/N]').upper() == 'Y':
self.main_loop()
else:
sys.exit(0)
self.main_loop()
if __name__ == '__main__':
Game2048().main_loop()
K-NN实例实现代码:
// 代码很烂,轻喷。注:仅供参考学习使用。
# -*- coding: UTF-8 -*-
"""
K-NN算法实例:
K最近邻(k-Nearest Neighbor,KNN)分类算法,是一个理论上比较成熟的方法,也是最简单的机器学习算法之一。
该方法的思路是:
如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。
K-NN算法的一般流程:
本实验使用的数据格式(datingTestSet): 1.每年飞行客里程数 2.视频游戏消耗时间比例 3.每周消耗冰淇淋公升数 4.标签-喜欢程度
"""
from matplotlib.font_manager import FontProperties
import matplotlib.lines as mlines
import matplotlib.pyplot as plt
import numpy as np
import operator
"""
函数说明:kNN算法,分类器
Parameters:
inX - 用于分类的数据(测试集)
dataSet - 用于训练的数据(训练集)
labes - 分类标签
k - kNN算法参数,选择距离最小的k个点
Returns:
sortedClassCount[0][0] - 分类结果
"""
def classify0(inX, dataSet, labels, k):
# numpy函数shape[0]返回dataSet的行数
dataSetSize = dataSet.shape[0]
# 在列向量方向上重复inX共1次(横向),行向量方向上重复inX共dataSetSize次(纵向)
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
# 二维特征相减后平方
sqDiffMat = diffMat ** 2
# sum()所有元素相加,sum(0)列相加,sum(1)行相加
sqDistances = sqDiffMat.sum(axis=1)
# 开方,计算出距离
distances = sqDistances ** 0.5
# 返回distances中元素从小到大排序后的索引值
sortedDistIndices = distances.argsort()
# 定一个记录类别次数的字典
classCount = {}
for i in range(k):
# 取出前k个元素的类别
voteIlabel = labels[sortedDistIndices[i]]
# dict.get(key,default=None),字典的get()方法,返回指定键的值,如果值不在字典中返回默认值。
# 计算类别次数
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
# python3中用items()替换python2中的iteritems()
# key=operator.itemgetter(1)根据字典的值进行排序
# key=operator.itemgetter(0)根据字典的键进行排序
# reverse降序排序字典
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
print(sortedClassCount)
# 返回次数最多的类别,即所要分类的类别
return sortedClassCount[0][0]
"""
函数说明:打开并解析文件,对数据进行分类:1代表不喜欢,2代表魅力一般,3代表极具魅力
Parameters:
filename - 文件名
Returns:
returnMat - 特征矩阵
classLabelVector - 分类Label向量
"""
def file2matrix(filename):
# 打开文件,此次应指定编码,
fp = open(filename, 'r', encoding='utf-8')
# 读取文件所有内容
arrayOLines = fp.readlines()
# 针对有BOM的UTF-8文本,应该去掉BOM,否则后面会引发错误。
arrayOLines[0] = arrayOLines[0].lstrip('\ufeff')
# 得到文件行数
numberOfLines = len(arrayOLines)
# 返回的NumPy矩阵,解析完成的数据:numberOfLines行,3列
returnMat = np.zeros((numberOfLines, 3))
# 返回的分类标签向量
classLabelVector = []
# 行的索引值
index = 0
for line in arrayOLines:
# s.strip(rm),当rm空时,默认删除空白符(包括'\n','\r','\t',' ')
line = line.strip()
# 使用s.split(str="",num=string,cout(str))将字符串根据'\t'分隔符进行切片。
listFromLine = line.split('\t')
# 将数据前三列提取出来,存放到returnMat的NumPy矩阵中,也就是特征矩阵
returnMat[index, :] = listFromLine[0:3]
# 根据文本中标记的喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力
# 对于datingTestSet2.txt 最后的标签是已经经过处理的 标签已经改为了1, 2, 3
if listFromLine[-1] == 'didntLike':
classLabelVector.append(1)
elif listFromLine[-1] == 'smallDoses':
classLabelVector.append(2)
elif listFromLine[-1] == 'largeDoses':
classLabelVector.append(3)
index += 1
return returnMat, classLabelVector
"""
函数说明:可视化数据
Parameters:
datingDataMat - 特征矩阵
datingLabels - 分类Label
Returns:
无
"""
def showdatas(datingDataMat, datingLabels):
# 设置汉字格式
font = FontProperties(fname=r"c:\windows\fonts\simsunb.ttf", size=14) ##需要查看自己的电脑是否会包含该字体
# 将fig画布分隔成1行1列,不共享x轴和y轴,fig画布的大小为(13,8)
# 当nrow=2,nclos=2时,代表fig画布被分为四个区域,axs[0][0]表示第一行第一个区域
fig, axs = plt.subplots(nrows=2, ncols=2, sharex=False, sharey=False, figsize=(13, 8))
numberOfLabels = len(datingLabels)
LabelsColors = []
for i in datingLabels:
if i == 1:
LabelsColors.append('black')
if i == 2:
LabelsColors.append('orange')
if i == 3:
LabelsColors.append('red')
# 画出散点图,以datingDataMat矩阵的第一(飞行常客例程)、第二列(玩游戏)数据画散点数据,散点大小为15,透明度为0.5
axs[0][0].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 1], color=LabelsColors, s=15, alpha=.5)
# 设置标题,x轴label,y轴label
axs0_title_text = axs[0][0].set_title(u'每年获得的飞行常客里程数与玩视频游戏所消耗时间占比', FontProperties=font)
axs0_xlabel_text = axs[0][0].set_xlabel(u'每年获得的飞行常客里程数', FontProperties=font)
axs0_ylabel_text = axs[0][0].set_ylabel(u'玩视频游戏所消耗时间占比', FontProperties=font)
plt.setp(axs0_title_text, size=9, weight='bold', color='red')
plt.setp(axs0_xlabel_text, size=7, weight='bold', color='black')
plt.setp(axs0_ylabel_text, size=7, weight='bold', color='black')
# 画出散点图,以datingDataMat矩阵的第一(飞行常客例程)、第三列(冰激凌)数据画散点数据,散点大小为15,透明度为0.5
axs[0][1].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)
# 设置标题,x轴label,y轴label
axs1_title_text = axs[0][1].set_title(u'每年获得的飞行常客里程数与每周消费的冰激淋公升数', FontProperties=font)
axs1_xlabel_text = axs[0][1].set_xlabel(u'每年获得的飞行常客里程数', FontProperties=font)
axs1_ylabel_text = axs[0][1].set_ylabel(u'每周消费的冰激淋公升数', FontProperties=font)
plt.setp(axs1_title_text, size=9, weight='bold', color='red')
plt.setp(axs1_xlabel_text, size=7, weight='bold', color='black')
plt.setp(axs1_ylabel_text, size=7, weight='bold', color='black')
# 画出散点图,以datingDataMat矩阵的第二(玩游戏)、第三列(冰激凌)数据画散点数据,散点大小为15,透明度为0.5
axs[1][0].scatter(x=datingDataMat[:, 1], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)
# 设置标题,x轴label,y轴label
axs2_title_text = axs[1][0].set_title(u'玩视频游戏所消耗时间占比与每周消费的冰激淋公升数', FontProperties=font)
axs2_xlabel_text = axs[1][0].set_xlabel(u'玩视频游戏所消耗时间占比', FontProperties=font)
axs2_ylabel_text = axs[1][0].set_ylabel(u'每周消费的冰激淋公升数', FontProperties=font)
plt.setp(axs2_title_text, size=9, weight='bold', color='red')
plt.setp(axs2_xlabel_text, size=7, weight='bold', color='black')
plt.setp(axs2_ylabel_text, size=7, weight='bold', color='black')
# 设置图例
didntLike = mlines.Line2D([], [], color='black', marker='.',
markersize=6, label='didntLike')
smallDoses = mlines.Line2D([], [], color='orange', marker='.',
markersize=6, label='smallDoses')
largeDoses = mlines.Line2D([], [], color='red', marker='.',
markersize=6, label='largeDoses')
# 添加图例
axs[0][0].legend(handles=[didntLike, smallDoses, largeDoses])
axs[0][1].legend(handles=[didntLike, smallDoses, largeDoses])
axs[1][0].legend(handles=[didntLike, smallDoses, largeDoses])
# 显示图片
plt.show()
"""
函数说明:对数据进行归一化
Parameters:
dataSet - 特征矩阵
Returns:
normDataSet - 归一化后的特征矩阵
ranges - 数据范围
minVals - 数据最小值
"""
def autoNorm(dataSet):
# 获得数据的最小值
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
# 最大值和最小值的范围
ranges = maxVals - minVals
# shape(dataSet)返回dataSet的矩阵行列数
normDataSet = np.zeros(np.shape(dataSet))
# 返回dataSet的行数
m = dataSet.shape[0]
# 原始值减去最小值
normDataSet = dataSet - np.tile(minVals, (m, 1))
# 除以最大和最小值的差,得到归一化数据
normDataSet = normDataSet / np.tile(ranges, (m, 1))
# 返回归一化数据结果,数据范围,最小值
return normDataSet, ranges, minVals
"""
函数说明:分类器测试函数
取百分之十的数据作为测试数据,检测分类器的正确性
Parameters:
无
Returns:
无
"""
def datingClassTest():
# 打开的文件名
filename = "datingTestSet.txt"
# 将返回的特征矩阵和分类向量分别存储到datingDataMat和datingLabels中
datingDataMat, datingLabels = file2matrix(filename)
# 取所有数据的百分之十
hoRatio = 0.10
# 数据归一化,返回归一化后的矩阵,数据范围,数据最小值
normMat, ranges, minVals = autoNorm(datingDataMat)
# 获得normMat的行数
m = normMat.shape[0]
# 百分之十的测试数据的个数
numTestVecs = int(m * hoRatio)
# 分类错误计数
errorCount = 0.0
for i in range(numTestVecs):
# 前numTestVecs个数据作为测试集,后m-numTestVecs个数据作为训练集
classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :],
datingLabels[numTestVecs:m], 4)
print("分类结果:%s\t真实类别:%d" % (classifierResult, datingLabels[i]))
if classifierResult != datingLabels[i]:
errorCount += 1.0
print("错误率:%f%%" % (errorCount / float(numTestVecs) * 100))
"""
函数说明:通过输入一个人的三维特征,进行分类输出
Parameters:
无
Returns:
无
"""
def classifyPerson():
# 输出结果
resultList = ['讨厌', '有些喜欢', '非常喜欢']
# 三维特征用户输入
precentTats = float(input("玩视频游戏所耗时间百分比:"))
ffMiles = float(input("每年获得的飞行常客里程数:"))
iceCream = float(input("每周消费的冰激淋公升数:"))
# 打开的文件名
filename = "datingTestSet.txt"
# 打开并处理数据
datingDataMat, datingLabels = file2matrix(filename)
# 训练集归一化
normMat, ranges, minVals = autoNorm(datingDataMat)
# 生成NumPy数组,测试集
inArr = np.array([ffMiles, precentTats, iceCream])
# 测试集归一化
norminArr = (inArr - minVals) / ranges
# 返回分类结果
classifierResult = classify0(norminArr, normMat, datingLabels, 3)
# 打印结果
print("你可能%s这个人" % (resultList[classifierResult - 1]))
"""
函数说明:main函数
Parameters:
无
Returns:
无
"""
def main():
dic={
1:datingClassTest,
2:classifyPerson
}
print('功能1:数据测试'+'\n'+'功能2:应用测试')
num = int(input('请输入功能选项:').strip())
dic[num]()
"""
file2matrix()用于数据解析生成特征矩阵和标签向量,并格式化为分类器所需的数据
showdatas()用于数据可视化
"""
if __name__ == '__main__':
main()
三、测试总结
程序运行的部分就不贴图啦。有兴趣的话,大家自己运行一下吧~
四、其他
以上以及博客中含有在校整理字样的博客内容,均是本人本科在校学习期间所写的代码,源文件以及数据文件等在个人主页资源区可以获取。本人水平很低,大佬轻喷。整理出来是为了帮助初学者学习的过程中,在有需要的情况下能够有所参考。再次声明:仅供学习交流使用。大家有什么问题,欢迎留言~