严蔚敏数据结构(C语言版) 红皮书清华大学p290 算法10.18 是一个非常有趣的算法,该算法利用地址排序的结果对线性表中的记录进行重新排列,即设待排序的序列数组 a a a为 a 1 , a 2 , − − − , a n a_{1},a_{2},---,a_{n} a1,a2,−−−,an 地址排序的结果数组 b b b为 b 1 , b 2 , − − − , b n b_{1},b_{2},---,b_{n} b1,b2,−−−,bn 使得 a b 1 , a b 2 , − − − , a b n a_{b_{1}},a_{b_{2}},---,a_{b_{n}} ab1,ab2,−−−,abn 是有序序列,现在要重排数组 a a a中的记录令其变为 a 1 ′ , a 2 ′ , − − − , a n ′ a_{1}',a_{2}',---,a_{n}' a1′,a2′,−−−,an′ 使得 a b i = a i ′ a_{b_{i}}=a_{i}' abi=ai′ 其中 1 < = i < = n 1<=i<=n 1<=i<=n
书上对该算法的说明看上去处处合理,其正确性有一种“显然”的错觉,但是算法的正确性有必要依据背后的数学原理进行必要证明,事实上 1 < = b i < = n 1<=b_{i}<=n 1<=bi<=n 且 b i ≠ b j i ≠ j b 1 , b 2 , − − − , b n b_{i} \ne b_{j} i\ne j b_{1},b_{2},---,b_{n} bi=bji=jb1,b2,−−−,bn是 1 , 2 , − − − , n 1,2,---,n 1,2,−−−,n 的一个排列,即 ( b 1 , b 2 , − − − , b n ) (b_{1},b_{2},---,b_{n}) (b1,b2,−−−,bn) 就是 1 , 2 , − − − , n 1,2,---,n 1,2,−−−,n 上的一个置换,由于任意置换都可以唯一分解成轮换的乘积,我们把该分解结果中的轮换按第一分量从小到大排列就可以把 ( b 1 , b 2 , − − − , b n ) (b_{1},b_{2},---,b_{n}) (b1,b2,−−−,bn) 写为
( c 11 , c 12 , − − − , c 1 i 1 ) ( c 21 , c 22 , − − − , c 2 i 2 ) − − − ( c s 1 , c s 2 , − − − , c s i s ) (c_{11},c_{12},---,c_{1i_{1}}) (c_{21},c_{22},---,c_{2i_{2}}) --- (c_{s1},c_{s2},---,c_{si_{s}}) (c11,c12,−−−,c1i1)(c21,c22,−−−,c2i2)−−−(cs1,cs2,−−−,csis)
其中 c m k c_{mk} cmk( 1 < = m < = s , 1 < = k < = i m 1<=m<=s,1<=k<=i_{m} 1<=m<=s,1<=k<=im) 满足 1 < = c m k < = n 1<=c_{mk}<=n 1<=cmk<=n 且不同的 c m k c_{mk} cmk 互不相等
1 < = s < = n i 1 + i 2 + − − − + i s = n c 11 < c 21 < − − − < c s 1 1<=s<=n i_{1}+i_{2}+---+i_{s} = n c_{11}<c_{21}<---<c_{s1} 1<=s<=ni1+i2+−−−+is=nc11<c21<−−−<cs1
下面按照通常的思维模式说明一下证明思路,而不寻求严格化的描述和证明
从位置 j = c 11 j = c_{11} j=c11 开始往后搜索,找到第一个满足 b j ≠ j b_{j} \ne j bj=j 的 j j j 设它为 c s 1 1 c_{s_{1}1} cs11
考察以 c s 1 1 c_{s_{1}1} cs11 为首元素的未处理轮换 ( c s 1 1 , c s 1 2 , − − − , c s 1 i s 1 ) (c_{s_{1}1},c_{s_{1}2},---,c_{s_{1}i_{s_{1}}}) (cs11,cs12,−−−,cs1is1) 由于 b c s 1 1 = c s 1 2 b_{c_{s_{1}1}} = c_{s_{1}2} bcs11=cs12 故应当在 c s 1 1 c_{s_{1}1} cs11 处放置 a c s 1 2 a_{c_{s_{1}2}} acs12 我们把 a c s 1 1 a_{c_{s_{1}1}} acs11 保存在 L 0 L_{0} L0 中并将 a c s 1 2 a_{c_{s_{1}2}} acs12 赋给 a c s 1 1 a_{c_{s_{1}1}} acs11 这样 a c s 1 1 = a c s 1 1 ′ a_{c_{s_{1}1}} = a_{c_{s_{1}1}}' acs11=acs11′并令 b c s 1 1 = c s 1 1 b_{c_{s_{1}1}} = c_{s_{1}1} bcs11=cs11 ,由于 b c s 1 2 = c s 1 3 b_{c_{s_{1}2}} = c_{s_{1}3} bcs12=cs13 故应当在 c s 1 2 c_{s_{1}2} cs12 处放置 a c s 1 3 a_{c_{s_{1}3}} acs13 我们把将 a c s 1 3 a_{c_{s_{1}3}} acs13 赋给
a c s 1 2 a_{c_{s_{1}2}} acs12 这样 a c s 1 2 = a c s 1 2 ′ a_{c_{s_{1}2}} = a_{c_{s_{1}2}}' acs12=acs12′ 并令 b c s 1 2 = c s 1 2 b_{c_{s_{1}2}} = c_{s_{1}2} bcs12=cs12 以此类推—由于 b c s 1 i s 1 = c s 1 1 b_{c_{s_{1}i_{s_{1}}}} = c_{s_{1}1} bcs1is1=cs11 故应当在 c s 1 i s 1 c_{s_{1}i_{s_{1}}} cs1is1 处放置 L 0 L_{0} L0 我们把 L 0 L_{0} L0 赋给 a c s 1 i s 1 a_{c_{s_{1}i_{s_{1}}}} acs1is1 这样
a c s 1 i s 1 = a c s 1 i s 1 ′ 并令 b c s 1 i s 1 = c s 1 i s 1 a_{c_{s_{1}i_{s_{1}}}} = a_{c_{s_{1}i_{s_{1}}}}' 并令 b_{c_{s_{1}i_{s_{1}}}} = c_{s_{1}i_{s_{1}}} acs1is1=acs1is1′并令bcs1is1=cs1is1 此时就 ( c s 1 1 , c s 1 2 , − − − , c s 1 i s 1 ) (c_{s_{1}1},c_{s_{1}2},---,c_{s_{1}i_{s_{1}}}) (cs11,cs12,−−−,cs1is1) 的操作结束,位置 c s 1 1 , c s 1 2 , − − − , c s 1 i s 1 c_{s_{1}1},c_{s_{1}2},---,c_{s_{1}i_{s_{1}}} cs11,cs12,−−−,cs1is1 上的记录均正确到位,且 b c s 1 h = c s 1 h 1 < = h < = i s 1 b_{c_{s_{1}h}} = c_{s_{1}h} 1<=h<=i_{s_{1}} bcs1h=cs1h1<=h<=is1
接下来我们从位置 j = c s 1 1 + 1 j = c_{s_{1}1} + 1 j=cs11+1 开始往后搜索,找到第一个满足 b j ≠ j b_{j} \ne j bj=j 的 j j j 设它为 c s 2 1 c_{s_{2}1} cs21 ,然后我们重复类似以上说明的步骤,对轮换 ( c s 2 1 , c s 2 2 , − − − , c s 2 i s 2 ) (c_{s_{2}1},c_{s_{2}2},---,c_{s_{2}i_{s_{2}}}) (cs21,cs22,−−−,cs2is2) 进行操作(注意该轮换一定没有处理过且分量数目大于1),操作完毕后位置 c s 2 1 , c s 2 2 , − − − , c s 2 i s 2 c_{s_{2}1},c_{s_{2}2},---,c_{s_{2}i_{s_{2}}} cs21,cs22,−−−,cs2is2 上的记录均正确到位,且 b c s 2 h = c s 2 h b_{c_{s_{2}h}} = c_{s_{2}h} bcs2h=cs2h 1 < = h < = i s 2 1<=h<=i_{s_{2}} 1<=h<=is2
然后从位置 j = c s 2 1 + 1 j = c_{s_{2}1} + 1 j=cs21+1
开始往后搜索,找到第一个满足 b j ≠ j b_{j} \ne j bj=j 的 j j j 设它为 c s 3 1 c_{s_{3}1} cs31
然后再对下一个轮换(它没有被处理过且分量数目大于1)进行类似操作,这样不断进行下去直到所有s个轮换全部处理完毕(再也找不到分量数目大于2的未处理轮换首元素,注意只有单个分量的轮换实际上并未处理但可以认为处理完毕)此时位置 c 11 , c 12 , − − − , c 1 i 1 , c 21 , c 22 , − − − , c 2 i 2 , − − − , c s 1 , c s 2 , − − − , c s i s c_{11},c_{12},---,c_{1i_{1}},c_{21},c_{22},---,c_{2i_{2}}, --- ,c_{s1},c_{s2},---,c_{si_{s}} c11,c12,−−−,c1i1,c21,c22,−−−,c2i2,−−−,cs1,cs2,−−−,csis
上的记录全部正确到位,即 1 , 2 , − − − , n 1,2,---,n 1,2,−−−,n 上的记录全部正确到位,即 a 1 , a 2 , − − − , a n a_{1},a_{2},---,a_{n} a1,a2,−−−,an 被变换为 a 1 ′ , a 2 ′ , − − − , a n ′ a_{1}',a_{2}',---,a_{n}' a1′,a2′,−−−,an′ 算法的正确性就得到了证明
算法中的一个巧妙之处是每当一个位置 i i i上的记录通过赋值正确到位就令 b i b_{i} bi 为 i i i 这样当当前轮换处理完毕后搜索下一个轮换的首元素时能够跳过已处理完毕的分量数目大于1的轮换各分量对应的所有位置,事实上这些记录上的位置均已正确到位故跳过是合理的(未处理但只有一个分量的轮换的唯一位置同样也会跳过,跳过同样也是合理的)