1、情侣牵手
问题描述:N 对情侣坐在连续排列的 2N 个座位上,想要牵到对方的手。 计算最少交换座位的次数,以便每对情侣可以并肩坐在一起。
一次交换可选择任意两人,让他们站起来交换座位。人和座位用 0 到 2N-1 的整数表示,情侣们按顺序编号,第一对是 (0, 1),第二对是 (2, 3),以此类推,最后一对是 (2N-2, 2N-1)。这些情侣的初始座位 row[i] 是由最初始坐在第 i 个座位上的人决定的。
示例1:输入: row = [0, 2, 1, 3] 输出: 1 解释: 我们只需要交换row[1]和row[2]的位置即可。
示例2:输入: row = [3, 2, 0, 1] 输出: 0 解释: 无需交换座位,所有的情侣都已经可以手牵手了。
2、分发糖果
问题描述:老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。你需要按照以下要求,帮助老师给这些孩子分发糖果:(1)每个孩子至少分配到 1 个糖果;(2)相邻的孩子中,评分高的孩子必须获得更多的糖果。那么这样下来,老师至少需要准备多少颗糖果呢?
示例 1: 输入: [1,0,2] 输出: 5 解释: 你可以分别给这三个孩子分发 2、1、2 颗糖果。
示例 2: 输入: [1,2,2] 输出: 4 解释: 你可以分别给这三个孩子分发 1、2、1 颗糖果。 第三个孩子只得到 1 颗糖果,这已满足上述两个条件。
3、跳跃游戏
问题描述:给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例: 输入: [2,3,1,1,4] 输出: 2 解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
说明: 假设你总是可以到达数组的最后一个位置。
4、编程实现单源最短路径Dijkstra算法,并回答如果将图中所有边的权重+1,所得到的最短路径是否仍为原最短路径,为什么?
5、证明题:请证明利用贪心算法构造的霍夫曼编码的正确性。
三、实验内容及结果
1、情侣牵手
问题描述:N 对情侣坐在连续排列的 2N 个座位上,想要牵到对方的手。 计算最少交换座位的次数,以便每对情侣可以并肩坐在一起。
一次交换可选择任意两人,让他们站起来交换座位。人和座位用 0 到 2N-1 的整数表示,情侣们按顺序编号,第一对是 (0, 1),第二对是 (2, 3),以此类推,最后一对是 (2N-2, 2N-1)。这些情侣的初始座位 row[i] 是由最初始坐在第 i 个座位上的人决定的。
示例1:输入: row = [0, 2, 1, 3] 输出: 1 解释: 我们只需要交换row[1]和row[2]的位置即可。
示例2:输入: row = [3, 2, 0, 1] 输出: 0 解释: 无需交换座位,所有的情侣都已经可以手牵手了。
利用贪心算法,想到当找到一对座位上的人不是一对情侣时,则遍历后面的每个座位直到找到情侣与第二个座位上的人相互调换,而在代码中寻找情侣时使用x^1,x异或1即x为奇数即为x-1,x为偶数即为x+1,能得到相应的情侣,由贪心算法这样就能得到最少交换次数。
def minSwapsCouples(row):
num=0
for i in range(0,len(row),2):#每次遍历2个位置
x=row[i]#x为第一个位置的值
if row[i+1]==x^1:#x^1异或操作当x为奇数即为x-1,x为偶数即为x+1,可以完美表示x的情侣的序号
continue
num+=1#当没有coninue重新循环说明row[i+1]不是情侣,则num+1
for j in range(i+2,len(row)):
#对i+2后的数遍历,找到情侣交换而且由贪心算法可得出该方式为最少的交换方式
if row[j] == x^1:
row[i+1],row[j] = row[j],row[i+1]
break
return num
row1 = [1,2,0,3]
print(minSwapsCouples(row1))
row2 = [3, 2, 0, 1]
print(minSwapsCouples(row2))
2、分发糖果
问题描述:老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。你需要按照以下要求,帮助老师给这些孩子分发糖果:(1)每个孩子至少分配到 1 个糖果;(2)相邻的孩子中,评分高的孩子必须获得更多的糖果。那么这样下来,老师至少需要准备多少颗糖果呢?
示例 1: 输入: [1,0,2] 输出: 5 解释: 你可以分别给这三个孩子分发 2、1、2 颗糖果。
示例 2: 输入: [1,2,2] 输出: 4 解释: 你可以分别给这三个孩子分发 1、2、1 颗糖果。 第三个孩子只得到 1 颗糖果,这已满足上述两个条件。
分发糖果,先从左边遍历,比左边分数高,则糖果为左边的糖果加1,比左边的小即仍为1,再从右边遍历,如果左边分数更高但是右边糖果数多时,说明没有符合分数高的糖果多的原则,则等于糖果数等于右边的加1.
def distributeCandy(scores):
if len(scores)<=1:
return len(scores)
candy=[1 for i in range(len(scores))]#candy[i]记录第i个人该分的糖果
for i in range(1,len(scores)):
#从左向右遍历,当右边的分数高时,candy[i]=candy[i-1]+1
if scores[i]>scores[i-1]:
candy[i]=candy[i-1]+1
for i in range(len(scores)-2,-1,-1):
#从右到左遍历,当左边分数高且右边的糖果多时,重新记录candy[i]=candy[i+1]+1
if scores[i]>scores[i+1] and candy[i]<=candy[i+1]:
candy[i]=candy[i+1]+1
return sum(candy)
scores1=[1,0,2]
print(distributeCandy(scores1))
scores1=[1,2,2]
print(distributeCandy(scores1))
3、跳跃游戏
问题描述:给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例: 输入: [2,3,1,1,4] 输出: 2 解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
说明: 假设你总是可以到达数组的最后一个位置。
3.从1遍历,每次加上该点跳跃的求出所能到达的最远距离reach,n表示跳的次数+1,当最远距离reach大于最远点时,说明可以到达,返回n
def jump(nums):
leng,reach,n=len(nums),0,0#leng为数组的长度,reach为到达的最远距离,n为跳跃的最小次数
for i in range(leng):
#对数组依次遍历,当i<=reach时,重新记录所能跳跃的最远点reach,n+1
if i<=reach:
reach=max(reach,i+nums[i])
n+=1
if reach>=leng-1:#当reach大于等于最远点时,返回n
return n
nums=[2,3,1,1,4]
print(jump(nums))
4、编程实现单源最短路径Dijkstra算法,并回答如果将图中所有边的权重+1,所得到的最短路径是否仍为原最短路径,为什么?
Dijkstra算法,单源最短路径每次寻找路径中最短的点u,用松弛操作一一遍历出发点s到该点u加上到其他点v的距离与出发点s到点v的距离,由于对每个点都遍历了Q中的所有点,就相当于对s到v的每条路径都进行了遍历找到最近的距离,这样就可以找到Q里面剩下的v到s的最短距离。
将图中所有边的权重+1,所得到的最短路径不是原最短路径,因为到最短路径的边数可能很多,只不过每条边都很短,比其他路径长的路径距离更短,但加上1即加上路径的边数,这样原来的最短路径就可能比别的路径长度更长。
def Dijkstra(G,s):
numVertex = len(G)#邻接矩阵的长度即点的个数
Q=[]#记录尚未找到最短路径各个结点
D = {} #记录s到各点的最短路径
precursorNode = {} #表示每个结点最短路径的前驱结点
for i in range(numVertex):#初始化Q,D,precursorNode
Q.append(i)
D[i] = 2**32 - 1
precursorNode[i] = 'null'
D[s] = 0 #s到s的距离为0
while(Q):#当Q未完全遍历完时
u=extract_min_queue(Q,D)#首先找到剩下的点中到s路径最短的点
for v in range(numVertex):#v遍历所有的点,经过多次松弛可以对每条路径都进行遍历从而找到各个点的最短路径
if G[u][v] == 0: # G[u][v] == 0说明点u与点v不可到达
continue
realx(D,G,precursorNode,u,v)#当u与v有路径到达时,对(u,v)进行松弛操作
print ('distance:', D)#输出各个点的最短路径
print ('precursorNode:', precursorNode)#输出各个点最短路径的前驱结点
def extract_min_queue(Q,D):#提取Q中最短路径的点
min_dist = 2**32 - 1#最短路径长度
min_index = None#最短路径的点
for vertex in Q:#在尚未找到最短路径的结点中找到路径最短的点,并记录min_dist,min_index
if D[vertex] < min_dist:
min_dist = D[vertex]
min_index = vertex
Q.remove(min_index)#当找到最短路径的点后从Q中删除这个点
return min_index#返回最短路径的点
def realx(D,G,precursorNode,u,v):#松弛(u,v)可以更新s到v的最短路径与前驱结点
if D[v] > D[u] + G[u][v]:#如果(s,v)的距离大于(s,u)的距离加上(u,v)的距离则更新D[v]与前驱结点
D[v] = D[u] + G[u][v]
precursorNode[v] = u
G = [[0,3,0,0,5],
[0,0,6,0,2],
[0,0,0,2,0],
[3,0,7,0,0],
[0,1,4,6,0]]
Dijkstra(G, 0)
- 证明题:请证明利用贪心算法构造的霍夫曼编码的正确性。
首先知道霍夫曼编码有贪心选择和最优子结构心智,那么用反证法,假设利用贪心算法得到的霍夫曼编码不是最优编码,设利用贪心算法得到的编码树为T1,就存在最优编码树T2满足B(T1)>B(T2),那么将T2与T1其中一个不同的子节点x与x在T1上的位置的结点y对换,有贪心算法知x<y,而对换后的树设为T3,易知B(T2)>B(T3)>=B(T1),所以不存在其他的最优霍夫曼树,即由贪心算法构造的霍夫曼树为最佳树,也证明了利用贪心算法构造的霍夫曼编码的正确性
四、实验总结
(根据实验写出一些心得或分析等)
本次实验让我更加理解了贪心算法以及单源最短路径的算法与霍夫曼树的构造,让我在面对一些问题时有了新的思考方法。