For j=2 to A.length
Key = A[j]
i = j – 1
while i > 0 and A[i] < key
A[i+1] = A[i]
i = i – 1
A[i + 1] = key
输入:n个数的一个序列A=
输出:下标i使得v = A[i] 或当v不在A中出现时。V为特殊值NIL。
写出线性查找的伪代码。用循环不变时来证明算法的正确性。
For i=1 to A.length
if A[i] == v
return i
return NIL
循环不变式:子数组(已遍历的数组)中没有与v的值相等的。
初始化:子数组为空,必定没有与v相等的,循环不变式成立。
保持:每次迭代都会对子数组中新的数的值进行判断。如果与v相等,则循环终止。如果不等,则A[1,2,…i] 这个子数组中没有与v相等的,继续循环,循环不变式保持。
终止:当i > A.length时或A[i] == v时循环终止。但此时子数组(下标小于i的数字)中是没有与v相等的数的,算法成立。
C = [0,0,……0] (C.length = A.length + 1)
For i=1 to A.length:
if A[i] + B[i] + C[i] == 0:
C[i] = 0
else if A[i] + B[i] + C[i] == 1:
C[i] = 1
else if A[i] + B[i] + C[i] == 2:
C[i] = 0
C[i+1] = 1
else if A[i] + B[i] + C[i] == 2:
C[i] = 1
C[i+1] = 1
C = C.reverse
Θ ( n 3 ) \Theta(n^3) Θ(n3)
for i=1 to A.length - 1:
min_num = inf
min_index = i
for j=i to A.length:
if A[j] < min:
min = A[j]
min_index = j
temp = A[i]
A[i] = min
A[min_index] = temp
该算法的循环不变式是A[1,2,……i]这个子数组是已排序的。
因为当该算法对前n-1个元素执行完毕时,最后一个元素一定是比前n-1个元素大的,否则它将被替换进前n-1个元素中。故它只需要对前n-1个元素执行该算法。
最好情况为该数组已经排序完成,则只需要遍历整个数组,而不需要进行任何的交换操作。而两次循环的时间为O(n2)。
同理,最坏情况也需要完成两次循环,额外的情况只是几次交换操作,是常数级的,因此最坏情况运行时间也为O(n2)。
平均需要检查输入序列的一半元素。最坏情况需要检查输入序列的全部元素。两者的运行时间均为O(n)。
平均情况需要检查输入序列的一半元素,为 $ \frac{1}{2}n$ 。而最坏情况为 n n n。由于O()方式不在意常数因子,因此两种情况的O()方式的运行时间均为O(n)。
应当把某些可想到的最好情况写在算法的开头,就可以得到良好的最好运行时间。如一些边界条件。
MERGE(A, p, q, r)
n1 = q - p + 1
n2 = r - q
let L[1..n1+1] and R[1..n2+1] be new arrays
for i = 1 to n1
L[i] = A[p + i -1]
for j =1 to n2
R[j] = A[q + j]
i = j = 1
for k = p to r
if L[i] << R[j]
A[k] = L[i]
if i = n1:
A[k + 1 .. n1 + n2] = R[j .. n2] //将R中剩下部分复制回A
else i = i + 1
else
A[k] = R[j]
if j = n2:
A[k + 1 .. n1 + n2] = L[i .. n1] //将L中剩下部分复制回A
else j = j + 1
若 若 T ( n ) = { 2 若 n = 2 2 T ( n / 2 ) + n 若 n = 2 k , k > 1 若若T(n) = \begin{cases}2& {若n=2}\\2T(n/2)+n&{若n=2_k,k>1}\end{cases} 若若T(n)={22T(n/2)+n若n=2若n=2k,k>1
当 n = 2 n=2 n=2时, T ( 2 ) = 2 ∗ l g 2 = 2 T(2)=2*lg2=2 T(2)=2∗lg2=2,得证。
当 n = 4 n=4 n=4时, T ( 4 ) = 4 ∗ l g 4 = 8 T(4)=4*lg4=8 T(4)=4∗lg4=8, T ( 4 ) = 2 T ( 2 ) + 4 = 2 ∗ 2 + 4 = 8 T(4) = 2T(2)+4 = 2*2+4=8 T(4)=2T(2)+4=2∗2+4=8,得证。
当 n = k n=k n=k时, T ( k ) = 2 T ( k / 2 ) + k = … = k / 2 ∗ T ( 2 ) + k ( l g k − 1 ) = k + k ( l g k − 1 ) = k l g k T(k)=2T(k/2)+k=…=k/2*T(2) + k(lgk-1)=k + k(lgk-1)=klgk T(k)=2T(k/2)+k=…=k/2∗T(2)+k(lgk−1)=k+k(lgk−1)=klgk,得证。
最坏运行时间为 T ( n ) = Θ ( n 2 ) T(n)=\Theta(n^2) T(n)=Θ(n2)。
T ( n ) = { Θ ( 1 ) i f n = 1 T ( n − 1 ) + Θ ( n ) i f n > 1 T(n)=\begin{cases}\Theta(1)&if\ n=1\\T(n-1) + \Theta(n) &if\ n>1\end{cases} T(n)={Θ(1)T(n−1)+Θ(n)if n=1if n>1
binary search(A, v, p, q)
if q > p
r = (q - p) // 2
if v > A[r]
binary search(A, v, r, q)
if v < A[r]
binary search(A, v, p, r)
if v == A[r]
return r
if q == p
if A[q] == v
return q
else return NIL
T ( n ) = { 1 i f n = 1 T ( n / 2 ) + 1 i f n > 2 T(n)=\begin{cases}1&if\ n=1\\T(n/2) +1 &if\ n>2\end{cases} T(n)={1T(n/2)+1if n=1if n>2
证明:
T ( n ) = T ( n / 2 ) + 1 = T ( n / 4 ) + 3 = . . . = 1 + l g n = Θ ( l g n ) T(n)=T(n/2) + 1=T(n/4)+3=...=1+lg\ n = \Theta(lg\ n) T(n)=T(n/2)+1=T(n/4)+3=...=1+lg n=Θ(lg n)
是可以的,只需要将插入排序中查找该对应位置的循环改成二分查找即可。伪代码如下。
binary search(A, v, p, q)
if q > p
r = (q - p) // 2
if v > A[r]
binary search(A, v, r, q)
if v < A[r]
binary search(A, v, p, r)
if v == A[r]
return r
if q == p + 1
return p
可以看看leetcode上的第一题两数之和,与这道题其实是一样的。
使用归并排序将集合 S S S 排列好,时间为 Θ ( n l g n ) \Theta(n\ lg\ n) Θ(n lg n)然后遍历一边集合 S S S,将每个元素都记录到一个hash表中。再遍历一次集合,查看hash表中是否有与该值相加为 x x x的元素。因为查找hash表的时间为 Θ ( 1 ) \Theta(1) Θ(1),因此两次遍历的时间也只为 Θ ( n ) \Theta(n) Θ(n),小于 Θ ( n l g n ) \Theta(n\ lg\ n) Θ(n lg n),因此运行时间为 Θ ( n l g n ) \Theta(n\ lg\ n) Θ(n lg n)。
每个子表的时间为 Θ ( n 2 ) \Theta(n^2) Θ(n2),则总时长为 Θ ( k 2 ) ∗ n / k = T h e t a ( n k ) \Theta(k^2)*n/k=Theta(nk) Θ(k2)∗n/k=Theta(nk)。
每层 n n n个元素,共需要复制 n n n个元素。共 n / k n/k n/k个子表,共需要合并 l g ( n / k ) lg(n/k) lg(n/k)次。则为 n ∗ l g ( n / k ) = Θ ( n l g ( n / k ) ) n*lg(n/k)=\Theta(n\ lg(n/k)) n∗lg(n/k)=Θ(n lg(n/k))。
令 n k + n l g ( n / k ) = n l g n nk+n\ lg(n/k)=n\ lg\ n nk+n lg(n/k)=n lg n,有 k + l g ( n / k ) = l g n k+lg(n/k)=lg\ n k+lg(n/k)=lg n,忽略 l g ( n / k ) lg(n/k) lg(n/k),得到结果为 k = l g n k=lg\ n k=lg n。
根据实践中的 n n n来决定,只需要在当前 k k k下,插入排序比归并排序快即可。
还需要证明,最终序列和原序列的构成是相同的。
循环不变式:每次迭代中, A [ j ] = m i n { A [ k ] : j ≤ k ≤ n } A[j]=min\{A[k]:j\le k\le n\} A[j]=min{A[k]:j≤k≤n},并且子数组是原数组的一部分。
初始化: 开始时, j = n j=n j=n, A [ j . . n ] A[j..n] A[j..n]中只有 A [ n ] A[n] A[n]一个元素,显然成立。
保持: A [ j ] A[j] A[j]是A [ j . . n ] [j..n] [j..n]中的最小值,那么,由 i f if if判断语句我们会发现, A [ j − 1 ] A[j−1] A[j−1]大于 A [ j ] A[j] A[j],所以肯定会交换,这样一来 A [ j ] A[j] A[j] 就是 A [ j − 1.. n ] A[j−1..n] A[j−1..n]的最小值,由此下去,我们就会正确的把后一部分的最小值移动到前面已经排好的子序列里面,加上前面排好的 A [ 1.. i + 1 ] A[1..i+1] A[1..i+1] 是原数组的子数组,后面未排序的 A [ i + 2.. n ] A[i+2..n] A[i+2..n]也是原数组的子数组,不变式成立。
终止: 终止时 , i = n , j = n ,i=n,j=n ,i=n,j=n,如果排序完成, A [ j ] = m i n { A [ k ] : j ≤ k ≤ n } A[j]=min\{A[k]:j≤k≤n\} A[j]=min{A[k]:j≤k≤n}显然成立,因为 A [ j . . n ] A[j..n] A[j..n]中只有 A [ n ] A[n] A[n]。
**循环不变式:**子数组 A [ 1.. i − 1 ] A[1..i−1] A[1..i−1]里是原始数组中前 i − 1 i-1 i−1个最小数据,并且已经排好顺序,并且 A [ i . . n ] A[i..n] A[i..n]中是原始数组中除 A [ 1.. i − 1 ] A[1..i−1] A[1..i−1]外的部分。
证明同上。
冒泡排序的最坏情况运行时间为 Θ ( n 2 ) \Theta(n^2) Θ(n2)。与插入排序同为 Θ ( n 2 ) \Theta(n^2) Θ(n2)。
y = 0
for i = n downto 0
y = a[i] + x * y
实现了用于求值多项式
P ( x ) = ∑ k = 0 n a k x k = a 0 + x ( a 1 + x ( a 2 + ⋅ ⋅ ⋅ + x ( a n − 1 + x a n ) ⋅ ⋅ ⋅ ) ) P(x)=\sum^{n}_{k=0}a_kx^k=a_0+x(a_1+x(a_2+···+x(a_n-1+xa_n) ··· )) P(x)=∑k=0nakxk=a0+x(a1+x(a2+⋅⋅⋅+x(an−1+xan)⋅⋅⋅))
的霍纳规则。
单循环。运行时间为 Θ ( n ) \Theta(n) Θ(n)。
y = 0
for i = 0 to n
y = y + a[i] * x^i
双循环(x的i次幂相当于一次循环),运行时间为 Θ ( n 2 ) \Theta(n^2) Θ(n2)。霍纳规则性能更优。
在第2~3行for循环每次迭代的开始有: y = ∑ k = 0 n − ( i + 1 ) a k + i + 1 x k y=\sum^{n-(i+1)}_{k=0}a_{k+i+1}x^k y=∑k=0n−(i+1)ak+i+1xk
把没有项的和式解释为等于0。遵照本章中给出的循环不变式证明的结构,使用该循环不变式来证明终止时有 y = ∑ k = 0 n a k x k y=\sum^{n}_{k=0}a_kx^k y=∑k=0nakxk。
循环不变式证明如下:
**初始化:**循环开始前, y = 0 , i = n , k = 0 , − 1 y=0,i=n,k=0,-1 y=0,i=n,k=0,−1,有 y = a n = a n x 0 y=a_n=a_nx^0 y=an=anx0
**保持:**对任意 ( 0 ≤ i < n ) (0\leq i<n) (0≤i<n),有 y ( i ) = a i + x ∗ y ( i + 1 ) = ∑ k = 0 n − ( i + 1 ) a k + i + 1 x k y(i)=a_i+x*y(i+1)=\sum^{n-(i+1)}_{k=0} a_{k+i+1}x^k y(i)=ai+x∗y(i+1)=∑k=0n−(i+1)ak+i+1xk
终止: i = − 1 i=-1 i=−1时,有 y = ∑ k = 0 n a k x k y=\sum^{n}_{k=0}a_kx^k y=∑k=0nakxk
综上,得证。
同上。
〈 2 , 1 〉 , 〈 3 , 1 〉 , 〈 8 , 1 〉 , 〈 6 , 1 〉 , 〈 8 , 6 〉 〈2,1〉,〈3,1〉,〈8,1〉,〈6,1〉,〈8,6〉 〈2,1〉,〈3,1〉,〈8,1〉,〈6,1〉,〈8,6〉
从大到小排列的数组: { n , n − 1 , ⋅ ⋅ ⋅ , 1 } {n,n-1,···,1} {n,n−1,⋅⋅⋅,1},有 n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n−1)对逆序对。
无逆序对时,为最优情况,运行时间为 Θ ( n ) \Theta(n) Θ(n)。
逆序对最多时,为最差情况,运行时间为 。 Θ ( n 2 ) 。 。\Theta(n^2)。 。Θ(n2)。
逆序对数量与交换次数相等。
修改归并排序使,当左序列指针指向的元素大于右序列的指针指向的元素,计数就自增。
原理也很简单,逆序列就是左边比右边大的数据对,而归并排序正是把这样的数据对调整过来的过程,其实不仅仅是这样,这里可以思考另一个过程,如果我们把归并换成快排,快排的每次排序也是把左大右小的数据对调整过来,实际上基于比较的排序都是这样,但是快排是错误的比如序列{4,3,2,1} {4,3,2,1}就明显不对,其实问题就出在快排每趟排序会破坏原序列的整体关系,比如第一趟排序过后会变成{1,3,2,4} {1,3,2,4} 这里破坏了1和2,3的逆序关系,而归并排序恰恰有从局部开始,不破坏整体关系的优点
所以归并排序的正确性就在于:1 正好是调整逆序数对的过程,2 不破坏数列的整体关系
MERGER(A,p,q,r)
A1 = A[p..q]
A2 = A[q+1..r]
i=1,j=1
count = 0
for k=p to r
if i > A1.length
A[k] = A2[j]
j++
else if j>A2.length
A[k] = A1[i]
i++
else if A1[i] >A2[j]
count = count + A.length - i + 1
A[k] = A2[j]
j++
else
A[k] = A1[I]
i++
return count