通过代码实现随机隐去个人身份证号码中的 4 位数字(号码信息在输入变量运行后可用*字符或其它字符表示掩去个人信息),并用 1~9 之间的随机数取代,将此序列称为 ID_Number,分别用插入排序法、选择排序法、二分归并排序法、快速排序算法实现对 ID_Number 的排序。(40 分)
要求:
(1)随机隐去和取代过程需用代码实现,替代后的结果需要显示;
(2)几种方法需要分别写出伪代码,选择自己熟悉的平台和语言编写并运行代码;
(3)关键行代码需要对算法进行注释;
随机隐藏四位数字,并用随机数取代
伪代码:
FUNCTION hide(id_num)
num_list = 将 id_num 转为 list 类型
hide_id = 在 range(len(num_list)) 中生成 4 个不重复的随机数下标
# 隐藏身份证号码
FOR i IN hide_id
num_list[i] = '*'
hid_num = 将 num_list 转为字符串类型
输出 隐藏四位数后的字符串 hid_num
# 用1-9的随机数替代隐藏的四位数
nums_list = []
FOR num IN num_list
IF num == '*'
new_num = 随机生成 1-9 之间的随机数
nums_list.append(new_num)
ELSE
nums_list.append(num)
nums = 将 nums_list 转为字符串类型
输出 替代隐藏四位数后的字符串 nums
返回 nums_list 作为函数的输出
END_FUNCTION
1.1插入排序法
伪代码:
FUNCTION insertion_sort(nums_list)
FOR i IN range(1, len(nums_list))
# 暂存当前要插入的元素
temp = nums_list[i]
j = i - 1
# 从已排序序列末尾开始向前遍历,寻找插入位置
WHILE j >= 0 AND nums_list[j] > temp
# 将已排序序列中比当前元素大的元素向后移动
nums_list[j+1] = nums_list[j]
j -= 1
END WHILE
# 找到插入位置,将暂存的元素插入到列表中
nums_list[j+1] = temp
END FOR
RETURN nums_list
END_FUNCTION
1.2选择排序法
伪代码:
FUNCTION selection_sort(nums_list)
# 对 nums_list 进行选择排序
FOR i IN range(len(nums_list)-1)
# 找到未排序部分中最小的元素
min = i
FOR j IN range(i+1, len(nums_list))
IF nums_list[j] < nums_list[min]
# 如果当前元素比已知最小元素还小,则更新最小元素索引
min = j
END IF
END FOR
# 将找到的最小元素与未排序部分的起始元素交换
nums_list[i], nums_list[min] = nums_list[min], nums_list[i]
END FOR
RETURN nums_list
END_FUNCTION
1.3二分归并排序
伪代码:
FUNCTION merge_sort(nums_list)
n = len(nums_list)
IF n <= 1
RETURN nums_list
END IF
mid = n // 2
# 对左右两个部分进行递归排序
left = merge_sort(nums_list[:mid])
right = merge_sort(nums_list[mid:])
# 将两个排好序的子序列合并成一个有序的整体
result = []
l, r = 0, 0 # 左右指针, 用来遍历 left 和 right 子序列
WHILE l < len(left) AND r < len(right)
IF left[l] < right[r] # 比较左右子序列的当前元素大小
result.append(left[l]) # 将 left 子序列的当前元素添加到结果序列中
l += 1 # 左指针向后移动,遍历下一个元素
ELSE
result.append(right[r])
r += 1 # 右指针向后移动,遍历下一个元素
END IF
END WHILE
result.extend(left[l:]) # 将 left 子序列中剩余元素添加到结果序列
result.extend(right[r:]) # 将 right 子序列中剩余元素添加到结果序列
RETURN result
END_FUNCTION
1.4快速排序
伪代码:
FUNCTION quick_sort(nums_list)
IF len(nums_list) <= 1 # 如果序列长度小于等于1,直接返回该序列(递归停止条件)
RETURN nums_list
END IF
pivot = nums_list[0] # 以序列的第一个元素作为基准数
left, right, mid = [], [], [pivot] # 左、右、中间三个临时数组
FOR i IN nums_list[1:]
IF i < pivot
left.append(i) #将比pivot小的元素放入left列表
ELSE IF i > pivot
right.append(i) #将比pivot大的元素放入right列表
ELSE
mid.append(i) #将相等的元素放入mid列表
END IF
END FOR
# 对left和right分别进行递归排序,并将mid列表与left和right拼接成结果列表
result = quick_sort(left) + mid + quick_sort(right)
RETURN result
END_FUNCTION
import random
from datetime import datetime
def hide(id_num):
# 随机隐藏四位身份证号码
num_list = list(id_num) #创建一个空列表,将 id_num 字符串中的每一个字符都转为一个元素,并放入到 num_list 列表中
hide_id = random.sample(range(len(num_list)), 4) #在 num_list 的下标范围内随机生成 4 个不重复的下标
#遍历这四个下标将这四位数字用*代替
for i in hide_id:
num_list[i] = '*'
hid_num = ''.join(num_list) #将处理后的数字连接起来,变成字符串
print(f"随机隐藏四位身份证号码后为: {hid_num}")
# 用1-9的随机数替代隐藏的四位号码
nums_list = [str(random.randint(1, 9)) if num == "*" else num for num in num_list]
nums = ''.join(nums_list)
print(f"随机数替代四位隐藏号码后为: {nums}")
return nums_list
# 使用插入排序对身份证号码进行排序
def insertion_sort(nums_list):
for i in range(1, len(nums_list)):
# 从第 2 个元素开始遍历整个列表
temp = nums_list[i] ## 暂存当前要插入的元素
j = i - 1
# 扫描已排序序列中的元素,寻找插入位置的前一位
while j >= 0 and nums_list[j] > temp:
# 如果已排序序列比当前元素大,则依次将它后移
nums_list[j+1] = nums_list[j]
# 继续向前扫描已排序序列中的元素
j -= 1
# 找到了合适的插入位置,将当前元素插入该位置
nums_list[j+1] = temp
return nums_list
# 使用选择排序对身份证号码进行排序
def selection_sort(nums_list):
for i in range(len(nums_list)-1):
# 找到未排序序列中最小的元素
min = i
for j in range(i+1, len(nums_list)):
if nums_list[j] < nums_list[min]:
# 如果当前元素比已知最小元素还小,则更新最小元素下标
min = j
# 将找到的最小元素与未排序序列的第一个元素交换
nums_list[i], nums_list[min] = nums_list[min],nums_list[i]
return nums_list
def merge_sort(nums_list):
n = len(nums_list)
if n <= 1:
return nums_list
mid = n // 2
# left,right 采用归并排序后形成的有序的新的列表
left = merge_sort(nums_list[:mid])
right = merge_sort(nums_list[mid:])
# 将两个有序的子序列合并为一个新的整体
# merge(left,right)
result = []
l, r = 0, 0 # 左右指针,用来遍历 left 和 right 子序列
while l < len(left) and r < len(right):
if left[l] < right[r]: # 比较左右子序列的当前元素大小
result.append(left[l])# 将 left 子序列的当前元素添加到结果序列中
l += 1 # 左指针向后移动,遍历下一个元素
else:
result.append(right[r])
r += 1 # 右指针向后移动,遍历下一个元素
result.extend(left[l:]) # 将 left 子序列中剩余元素添加到结果序列
result.extend(right[r:]) # 将 right 子序列中剩余元素添加到结果序列
return result
def quick_sort(nums_list):
if len(nums_list) <= 1: # 如果序列长度小于等于1,直接返回该序列(递归停止条件)
return nums_list
pivot = nums_list[0] # 以序列的第一个元素作为基准数
left, right, mid = [], [], [pivot] # 左、右、中间三个临时数组
for i in nums_list[1:]:
if i < pivot:
left.append(i) #对left列表排序
elif i > pivot:
right.append(i) #对right列表排序
else:
mid.append(i)
result = quick_sort(left) + mid + quick_sort(right)
return result
id_number = "420114199605512516" # 待处理的身份证号码
nums_list=hide(id_number)
insertion_sort(nums_list) #插入排序
print(f"使用插入排序对身份证号码进行排序后为: {''.join(insertion_sort(nums_list))}")
selection_sort(nums_list) #选择排序
print(f"使用选择排序对身份证号码进行排序后为: {''.join(selection_sort(nums_list))}")
merge_sort(nums_list) #二分归并排序
print(f"使用二分归并排序对身份证号码进行排序后为: {''.join(merge_sort(nums_list))}")
quick_sort(nums_list) #快速排序
print(f"使用快速排序归并排序对身份证号码进行排序后为: {''.join(quick_sort(nums_list))}")
二、题二
利用第一题中的序列 ID_Number,分别用分治法和动态规划法求出最大非空子数组(子数组和最大,且为连续序列,子序列限制长度为6位数),确定首末位置,输出为最大子数组序列,及子序列的和。(30 分)
要求:
(1)两种方法需要分别写出伪代码,选择自己熟悉的平台和语言编写并运行代码;
(2)关键行代码需要对算法进行注释;
(3)输出包括首末位置、最大子数组以及子数组的和;
2.1分治法
伪代码:
FUNCTION max_subarray(nums: [int], left: int, right: int) -> [int, int, int]:
IF left == right
# 如果只有一个元素,那么它即为最大的子数组
RETURN left, right, nums[left]
# 如果有多个元素,那么可以把数组分为左半部分、右半部分和中间部分三个子数组
mid = (left + right) // 2
# 求解左半部分、右半部分和跨过中间部分三个子数组的解
left_start, left_end, left_sum = max_subarray(nums, left, mid)
right_start, right_end, right_sum = max_subarray(nums, mid + 1, right)
cross_start, cross_end, cross_sum = max_cross_subarray(nums, left, mid, right)
IF left_sum >= right_sum AND left_sum >= cross_sum
# 左半部分子数组的和最大
RETURN left_start, left_end, left_sum
ELIF right_sum >= left_sum AND right_sum >= cross_sum
# 右半部分子数组的和最大
RETURN right_start, right_end, right_sum
ELSE
# 跨越中间部分子数组的和最大
RETURN cross_start, cross_end, cross_sum
FUNCTION max_cross_subarray(nums: [int], left: int, mid: int, right: int) -> [int, int, int]:
# 求解包含 mid 的最大左子数组
left_sum = float('-inf')
sum = 0
FOR i from mid DOWNTO left
sum += nums[i]
IF sum > left_sum
left_sum = sum
max_left = i
# 求解包含 mid 的最大右子数组
right_sum = float('-inf')
sum = 0
FOR j from mid + 1 TO right
sum += nums[j]
IF sum > right_sum
right_sum = sum
max_right = j
# 返回跨越中间部分的子数组
RETURN max_left, max_right, left_sum + right_sum
FUNCTION sliding_window(nums: [int]) -> [int, int, int]:
max_sum = float('-inf')
max_start, max_end = None, None
window_start = 0
window_sum = SUM(nums[:6]) # 窗口大小为 6,求出第一个窗口中元素的和
# 滑动窗口从左往右滑动,计算最大子数组的和、起始位置和结束位置
FOR window_end FROM 6 TO LENGTH(nums) - 1
IF window_sum > max_sum
# 更新最大子数组的和、起始位置和结束位置
max_sum = window_sum
max_start = window_start
max_end = window_end - 1
# 滑动窗口向右滑动,更新窗口元素的和以及窗口的起始位置
window_sum = window_sum - nums[window_start] + nums[window_end]
window_start = window_start + 1
RETURN max_start, max_end, max_sum
def max_subarray(nums, left, right):
if left == right:
# 如果只有一个元素,那么它即为最大的子数组
return left, right, nums[left]
# 如果有多个元素,那么可以把数组分为左半部分、右半部分和中间部分三个子数组
mid = (left + right) // 2
# 求解左半部分、右半部分和中间部分三个子数组的解
# 注意此处需要分别加上 mid 和 mid + 1,因为 mid 既属于左半部分也属于右半部分,相邻两个元素才是子数组
left_start, left_end, left_sum = max_subarray(nums, left, mid)
right_start, right_end, right_sum = max_subarray(nums, mid + 1, right)
cross_start, cross_end, cross_sum = max_cross_subarray(nums, left, mid, right)
# 取左半部分、右半部分和中间部分三个子数组的解中的最大值
if left_sum >= right_sum and left_sum >= cross_sum:
return left_start, left_end, left_sum
elif right_sum >= left_sum and right_sum >= cross_sum:
return right_start, right_end, right_sum
else:
return cross_start, cross_end, cross_sum
def max_cross_subarray(nums, left, mid, right):
# 求解中间子数组的最大和
# 求解包含 mid 的最大左子数组
left_sum = float('-inf')
sum = 0
for i in range(mid, left - 1, -1):
sum += nums[i]
if sum > left_sum:
left_sum = sum
max_left = i
# 求解包含 mid + 1 的最大右子数组
right_sum = float('-inf')
sum = 0
for j in range(mid + 1, right + 1):
sum += nums[j]
if sum > right_sum:
right_sum = sum
max_right = j
return max_left, max_right, left_sum + right_sum
def sliding_window(nums):
# 用滑动窗口求出最大的六位数的子数组
max_sum = float('-inf')
max_start = None
max_end = None
window_start = 0
window_sum = sum(nums[:6])
for window_end in range(6, len(nums)):
if window_sum > max_sum:
max_sum = window_sum
max_start = window_start
max_end = window_end - 1
window_sum = window_sum - nums[window_start] + nums[window_end]
window_start += 1
return max_start, max_end, max_sum
def solve(num):
id_number = "420119199605962536"
num_list = list(id_number)
nums = list(map(int, num_list))
subarray_start, subarray_end, subarray_sum = sliding_window(nums)
start, end, sum = max_subarray(nums, subarray_start, subarray_end)
subseq = nums[start: end + 1]
print(f"最大子串的首尾位置:{start}, {end}")
print(f"最大子序列:{subseq}")
print(f"最大子数组和: {sum}")
2.2动态规划
伪代码
FUNCTION max_subarray(nums: [int]) -> [int, int, int]:
# 动态规划求解最大子数组
n = LENGTH(nums)
# 用 dp[i] 存储以 nums[i] 结尾的最大子数组的和
dp = ARRAY(0, n)
dp[0] = nums[0]
start, end = 0, 0
max_sum = nums[0]
# 动态规划求解以 nums[i] 结尾的最大子数组的和
FOR i FROM 1 TO n - 1
IF dp[i - 1] > 0
dp[i] = dp[i - 1] + nums[i]
ELSE
dp[i] = nums[i]
# 如果 dp[i] 小于等于 0,则以 nums[i] 结尾的子数组不是最大子数组的一部分,需要重新搜索
IF dp[i] <= 0
start = i
# 记录最大子数组的和及其结束位置
IF dp[i] > max_sum
max_sum = dp[i]
end = i
RETURN start, end, max_sum
FUNCTION sliding_window(nums: [int]) -> [int, int, int]:
max_sum = float('-inf')
max_start, max_end = None, None
window_start = 0
window_sum = SUM(nums[:6]) # 窗口大小为 6,求出第一个窗口中元素的和
# 滑动窗口从左往右滑动,计算最大子数组的和、起始位置和结束位置
FOR window_end FROM 6 TO LENGTH(nums) - 1
IF window_sum > max_sum
# 更新最大子数组的和、起始位置和结束位置
max_sum = window_sum
max_start = window_start
max_end = window_end - 1
# 滑动窗口向右滑动,更新窗口元素的和以及窗口的起始位置
window_sum = window_sum - nums[window_start] + nums[window_end]
window_start = window_start + 1
RETURN max_start, max_end, max_sum
def max_subarray(nums):
# 动态规划求解最大子数组
n = len(nums)
# 用 dp[i] 存储以 nums[i] 结尾的最大子数组的和
dp = [0] * n
dp[0] = nums[0]
start = end = 0
max_sum = nums[0]
for i in range(1, n):
# 判断以 nums[i] 结尾的子数组是否包含 nums[i],如果大于 nums[i],则包含
if dp[i - 1] > 0:
dp[i] = dp[i - 1] + nums[i]
else:
dp[i] = nums[i]
# 如果 dp[i] 小于等于 0,则以 nums[i] 结尾的子数组不是最大子数组的一部分,需要重新搜索
if dp[i] <= 0:
start = i
if dp[i] > max_sum:
max_sum = dp[i]
end = i
return start, end, max_sum
def sliding_window(nums):
max_sum = float('-inf')
max_start,max_end = None,None
window_start = 0
window_sum = sum(nums[:6])
for window_end in range(6, len(nums)):
if window_sum > max_sum:
max_sum ,max_start,max_end= window_sum,window_start,window_end - 1
window_sum = window_sum - nums[window_start] + nums[window_end]
window_start += 1
return max_start, max_end, max_sum
id_number = "420119199605962536"
num_list = list(id_number)
nums = list(map(int, num_list))
subarray_start, subarray_end, subarray_sum = sliding_window(nums)
start, end, sum = max_subarray(nums[subarray_start: subarray_end + 1])
start += subarray_start
end += subarray_start
subseq = nums[start: end + 1]
print(f"最大子串的首尾位置:{start}, {end}")
print(f"最大子序列:{subseq}")
print(f"最大子数组和: {sum}")
三、题三
霍夫曼编码(Huffman Coding)实现。(30 分)
要求:
(1)将个人名字的大写英文字符作为字符数组,以“周杰伦”为例,名字英文字符为 ZHOUJIELUN,字符重复出现时以第一次出现的字符为准,后面重复字符不再入列。依据此规则,字符数组为labels = ['Z','H','O','U','J', 'I', 'E', 'L', 'N'];
(2)依次取 ID_Number 的每两位数作为字符出现的频率,若所有数字都已占用,则进行第二轮赋值,每次只取一个非零数字作为剩余字符字频;如一组 ID_Number 为:421088195202042287,'Z'字频为 42,'H'字频为 10,'O'为 88 等;
# 定义节点类
class Node:
def __init__(self, char, freq):
self.char = char # 节点代表的字符
self.freq = freq # 节点代表的字符的频率
self.left = None # 左子节点
self.right = None # 右子节点
def __lt__(self, other):
return self.freq < other.freq
# 构建霍夫曼树
def build_huffman_tree(freq_dict):
heap = []
# 将每个字符作为一个节点插入小根堆,根据频率排序(实现了Node类中的__lt__方法)
for char, freq in freq_dict.items():
node = Node(char, freq)
heapq.heappush(heap, node)
while len(heap) > 1:
# 从小根堆中取出频率最小的两个节点
node1 = heapq.heappop(heap)
node2 = heapq.heappop(heap)
# 合并这两个节点,生成一个新节点作为它们的父节点
merged_freq = node1.freq + node2.freq
merged_node = Node(None, merged_freq)
merged_node.left = node1
merged_node.right = node2
# 将合并后的节点插入小根堆
heapq.heappush(heap, merged_node)
# 返回哈夫曼树的根节点
return heap[0]
# 生成霍夫曼编码
def generate_huffman_codes(root):
codes = {}
# 递归函数,生成每个字符的哈夫曼编码并存储在codes字典中
def traverse(node, code):
if node.char:
# 如果当前节点代表一个字符,则将编码存储到codes字典中
codes[node.char] = code
else:
# 否则,遍历左子树和右子树
traverse(node.left, code + '0') # 左子树编码添加'0'
traverse(node.right, code + '1') # 右子树编码添加'1'
# 从二叉树的根节点开始遍历
traverse(root, '')
# 返回每个字符的哈夫曼编码
return codes
# 主函数
def huffman_coding(name, id_number):
# 生成字符频率字典
freq_dict = {}
for char in name:
if char not in freq_dict:
# 取id_number的前两位作为字符出现频率
freq_dict[char] = int(id_number[:2])
id_number = id_number[2:]
if len(id_number) == 0:
break
# 所有数字已占用,则进行第二轮赋值
if len(freq_dict) < len(name):
for char in name:
if char not in freq_dict:
# 取id_number的前两位作为字符出现频率
freq_dict[char] = int(id_number[:2])
id_number = id_number[2:]
if len(id_number) == 0:
break
# 构建霍夫曼树
huffman_tree = build_huffman_tree(freq_dict)
# 生成霍夫曼编码
huffman_codes = generate_huffman_codes(huffman_tree)
# 输出结果
print("Character\tFrequency\tHuffman Code")
for char in freq_dict:
print(f"{char}\t\t{freq_dict[char]}\t\t{huffman_codes[char]}")