前言
本文主讲分治法的分析以及应用,有三段程序,基于二分查找方法的插入排序,合并排序以及改变合并排序来计算序列中逆序对的个数。
插入排序
大家都不陌生插入排序,其原版如下(最坏情况Θ(n2) ):
void Insertion_Sort(int a[], int p, int r) { int i,j,t; for (i = p+1; i < r; i++) { j = i - 1; t = a[i]; while (a[j] > t && j >= p) { a[j+1] = a[j]; j--; } a[j+1] = t; } }// end of Insertion
基于二分查找方法的插入排序(最坏情况Θ(nlogn) )
/*
将值A[r+1]插入A[p....r]的非降序序列中 利用的是二分查找方法来插入
*/
void Insertion(int A[], int p, int r)
{
int i;
int value = A[r+1];
int loc = BinaryLocation(A, p, r, value); //自loc(包括loc)之后的元素全部向后移一位
for (i = r; i >= loc; i--)
{
A[i+1] = A[i];
}
A[loc] = value;
}
/*
插入排序 两种实现方式
*/
void InsertionSort(int A[], int p, int r)
{
/*********迭代实现*********/
//int i;
//for (i = p; i < r; i++)
//{
// Insertion(A, p, i);
//}
/*******递归实现*********/
if (r >= p)
{
InsertionSort(A, p, r-1);
Insertion(A, p, r-1);
}
}
合并排序
分治思想:
/* 将R[p....q]和S[q+1....r]合并为A[p.....r] */ void Merge(int a[], int p, int q, int r) { int R[100], S[100]; int i,j,k; for ( i = 0; i < q-p+1; i++) // 将a[p.....q]复制到R[0.....q-p] { R[i] = a[p+i]; } for ( j = 0; j < r-q; j++) //将a[q+1......r]复制到S[0....r-q-1] { S[j] = a[j+q+1]; } R[i] = BIG; S[j] = BIG; i = 0; j = 0; for ( k = p; k < r+1; k++) //R,S中的元素全部是非降序排列,将R,S中的较小元素一次复制到数组a中 { if ( R[i] <= S[j]) { a[k] = R[i]; i++; } else { a[k] = S[j]; j++; //count += q-p-i+1; //计算原数组中的逆序对的个数 } } } /* 合并算法 */ void MergeSort( int a[], int p, int r) { if ( r > p) { int q = (p + r) / 2; MergeSort( a, p, q); MergeSort( a, q+1, r); Merge( a, p, q, r); } }
求逆序对的个数
逆序:当m<n时A[m]>A[n]则表示(m,n)为一个逆序对。
有两个非降序序列R = {5,6,7,8} , S = {3,6}。则序列T = {5,6,7,8,3,4}的逆序对等于 4+2 = 6个。
当s = 3时,3<5=R[0],所以(r,3)的逆序对个数为length(R)个。
当s = 6时,6<7=R[2],所以(r,3)的逆序对个数为length(R) – 2 =2。
当s < R[i]时,(r,s)的逆序对个数为(length(R) - i)个。
只需修改合并排序算法即可。如代码片段3中的37行,q-p[+1是length(R)的长度。count是全局变量,存储逆序对的个数
总结
分治法的运行时间分析,从代码片段3中的第46行克制
T(n) = 2T(n/2) + cn ,其中cn 是合并函数Merge的运行时间。
T(1) = c
T(n) = 2T(n/2) + cn = 4T(n/4) + 2cn = 8T(n/8) + 3cn = ……. = 2kT(n/2k) + kcn
当k = logn时, T(n) = nT(1) + cnlogn = nc+cnlogn = Θ(nlogn)。
所以看到运行时间包含logn的字眼,应该想到可能需要用到分治法来解决问题。