题目1
Design a Θ(n lg n)-time algorithm that, given a set S of nintegers and another integer x, determines whether or not there exist twoelements in S whose sum is exactly x.
分析
题目要求设计一个复杂度为Θ(n lg n)的算法。当给定一个数组S和一个整数x时,可以通过这个算法求出S中是否恰好有两个数的和是x。
首先依次遍历S = [d0, d1, d2, …, dn]中的各个元素,对S中任意的元素di,若dj = x –di 也在数组S中,那么di和dj的和就是x。易得,遍历数组的复杂度是Θ(n),那么查找dj的时间要控制在Θ(lg n)以内才能满足题目的条件。
采用复杂度为Θ( lgn)折半查找的方法在S中寻找是否存在这样的dj(递归树推演复杂度如下图)。而在折半查找前需要对数组进行排序,可以采用复杂度为Θ(n lg n)的归并排序。
算法源码
'''
Design a Θ(n lg n)-time algorithm that,
given a set S of n integers and another integer x,
determines whether or not there exist two elements in S whose sum is exactly x.
'''
import math
def MERGE(A, p, q, r):
'''
A是数组,
p是第一个部分的起始下标,
q是第一个部分的结尾下标,
r是第二个部分的结尾下标
'''
n1 = q - p + 1
n2 = r - q
L = []
R = []
#拷贝第一部分的内容到新数组L
for i in range(0, n1):
L.append(A[p + i])
#拷贝第二部分的内容到新数组R
for j in range(0, n2):
R.append(A[q + 1 + j])
i, j = 0, 0
for k in range(p, r+1):
if i < n1 and j < n2:
if L[i] <= R[j]:
A[k] = L[i]
i += 1
else:
A[k] = R[j]
j += 1
else:
break
#把L或R中的剩余部分加入
if i < n1:
while k <= r:
A[k] = L[i]
k += 1
i += 1
if j < n2:
while k <= r:
A[k] = R[j]
k += 1
j += 1
def MERGE_SORT(A, p, r):
'''
把过程MERGE作为归并排序算法中的子程序实现排序
A是数组,
p是第一个部分的起始下标,
r是第二个部分的结尾下标
'''
if p < r:
q = int(math.floor(p+r)/2)
MERGE_SORT(A, p, q)
MERGE_SORT(A, q+1, r)
MERGE(A, p, q, r)
def BINARY_SEARCH(A, value, p, r):
'''
A是数组,
value查找的值,
p起始位置
r结束位置
若在A中找不到value返回-1, 否则返回其下标
'''
if p > r:
return -1
else:
q = int(math.floor(p+r)/2)
if A[q] == value:
return q
elif A[q] > value:
return BINARY_SEARCH(A, value, p, q-1)
else:
return BINARY_SEARCH(A, value, q+1, r)
def IS_EXIST(A, value):
'''
A是数字
value为X的值
若A中有两个元素和为X则返回1, 否则返回0
'''
#对数组进行排序
MERGE_SORT(A, 0, len(A)-1)
#查找元素
for i in range(0, len(A)):
rest = value - A[i]
j = BINARY_SEARCH(A, rest, i+1, len(A)-1)
if j != -1:
return 'x={}时, 存在'.format(value)
return 'x={}时, 不存在'.format(value)
#test
A = [2, 4, 5, 7, 1, 2, 3, 6]
print(IS_EXIST(A, 6))
print(IS_EXIST(A, 13))
print(IS_EXIST(A, 100))
运行结果截图
测试数据:
数组S = [2, 4, 5, 7, 1, 2, 3, 6]
总结
做本题时,看完题目的第一反应就是用两个for循环遍历数组找出值,但这种方法的复杂度是Θ(n^2),而采用上面的算法,同样功能的程序复杂度降低到了Θ(n lg n)。可见,使用适当的算法,往往可以降低程序的复杂度,更高效的实现目的。
上面的算法仍存在缺陷。比如只能找出一对符合条件的数。当数组S中有多对数的和等于x时,无法将它们全部找出。
题目2
Let A[1 ‥ n] be anarray of n distinct numbers. If i < j and A[i] > A[j], then the pair (i,j) is called an inversion of A. Give an algorithm that determines the number ofinversions in any permutation on n elements in Θ(n lg n) worst-case time.(Hint: Modify merge sort.)
分析
题目要求设计一个最坏情况下复杂度为Θ(n lg n)的算法。当给定一个数组S时,可以求出其中inversion的个数。该题涉及前后元素的大小比较,很显然是一个排序问题。
采用归并排序对数组进行排序。在特殊情况时,即当数组被分解为多组两两比较时,若比较的两个数需要对换,则这两个数必是inversion。当情况更加普遍一些时, 设L = [q0, q1 ,… , qn]和R = [p0,p1, …, pn](L中的元素序号小于R中的元素序号)是被分解得到的数组中的任意两个,当L和R比较排序时,若存在qi, pj, 使得qi > pj成立,则L中qi到qn的所(n-i+1)个元素都与pj构成inversion。
import math
count = 0
def MERGE(A, p, q, r):
'''
A是数组,
p是第一个部分的起始下标,
q是第一个部分的结尾下标,
r是第二个部分的结尾下标
'''
n1 = q - p + 1
n2 = r - q
L = []
R = []
#拷贝第一部分的内容到新数组L
for i in range(0, n1):
L.append(A[p + i])
#拷贝第二部分的内容到新数组R
for j in range(0, n2):
R.append(A[q + 1 + j])
i, j = 0, 0
for k in range(p, r+1):
if i < n1 and j < n2:
if L[i] <= R[j]:
A[k] = L[i]
i += 1
else:
A[k] = R[j]
j += 1
global count
count += n1 - i
else:
break
#把L或R中的剩余部分加入
if i < n1:
while k <= r:
A[k] = L[i]
k += 1
i += 1
if j < n2:
while k <= r:
A[k] = R[j]
k += 1
j += 1
def MERGE_SORT(A, p, r):
'''
把过程MERGE作为归并排序算法中的子程序实现排序
A是数组,
p是第一个部分的起始下标,
r是第二个部分的结尾下标
'''
if p < r:
q = int(math.floor(p+r)/2)
MERGE_SORT(A, p, q)
MERGE_SORT(A, q+1, r)
MERGE(A, p, q, r)
#test
A = [2, 1, 3, 8, 5, 7, 4, 6, 10]
print('数组:{}'.format(A))
MERGE_SORT(A, 0, len(A)-1)
print('个数:{}'.format(count))
count=0
A = [2, 1, 0, -1]
print('数组:{}'.format(A))
MERGE_SORT(A, 0, len(A)-1)
print('个数:{}'.format(count))
count=0
A = [1, 2, 3]
print('数组:{}'.format(A))
MERGE_SORT(A, 0, len(A)-1)
print('个数:{}'.format(count))
count=0
运行结果截图
总结
采用分治的方法,有时可以非常有效的提高解决问题的效率。