通过练习掌握分治法的基本思想
设要排序的数组时A[0]…A[N-1],首先任意选取一个数据(通常选用数组的第一个数)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序。
一趟快速排序的算法是:
1)设置两个变量i、j,排序开始时:i=0,j=N-1;
2)以第一个数组元素作为关键数据,赋给key,即key=A[0];
3)从j开始向前搜索,即由后开始向前搜索j–,找到第一个小于key的值A[j],将A[j]和A[i]互换;
4)从i开始向后搜索,即由前开始向后搜索i++,找到第一个大于key的A[i],将A[i]和A[j]互换。
重复第3、4步,直到i=j;(3,4步中没有找到符合条件的值,即3中A[j]不小于key,4中A[i]不大于key的时候改变j、i的值,使得j=j-1,i=i+1,直到找到为止。找到符合条件的值,进行交换的时候i,j指针位置不变。另外,i==j这一过程一定正好是i++或j–完成的时候,此时令循环结果。)
注:如果是算法,要有对算法基本过程的归纳。
棋盘覆盖
#include
#include
#include
#include
using namespace std;
//特殊方格的位置为:(2,6)
//8*8 N=2^k;
const int N=8;
int Board[N][N];///二维整形数组N*N的棋盘
int tile=1;///全局变量,表示L型骨牌号
void InitChessBoard(int m,int n)///表示初始化
{
for(int i=0;i<m;i++)
for(int j=0;j<n;j++)
{
Board[i][j]=0;
}
}
/**
tr表示棋盘左上角的行号
tc表示棋盘左上角的列号
dr表示特殊方格左上角的列号
dc表示特殊方格左上角的列号
size=2^k
*/
void ChessBoard(int tr,int tc,int dr,int dc,int size)///棋盘覆盖的分治法如下(结合递归)
{
if(size==1)///递归的出口
return;
int t=tile++;///L型骨牌号
int s=size/2;///将棋盘一分为四(分治法)
///覆盖左上角的棋盘
if(dr<tr+s&&dc<tc+s)///在此棋盘中
{
ChessBoard(tr,tc,dr,dc,s);///此时长度发生变化(递归分治,将s缩到最小去寻找此特殊棋盘)
}
else
{
Board[tr+s-1][tc+s-1]=t;///该棋盘不存在特殊方格则覆盖
ChessBoard(tr,tc,tr+s-1,tc+s-1,s);///覆盖其他方格
}
///覆盖右上角的棋盘
if(dr<tr+s&&dc>=tc+s)///在此棋盘中
{
ChessBoard(tr,tc+s,dr,dc,s);///将刚刚覆盖的棋盘作为特殊方格并且与下一个方格进行比较
}
else
{
Board[tr+s-1][tc+s]=t;///该棋盘不存在特殊方格则覆盖
ChessBoard(tr,tc+s,tr+s-1,tc+s,s);///覆盖其他方格
}
///覆盖左下角的棋盘
if(dr>=tr+s&&dc<tc+s)///在此棋盘中
{
ChessBoard(tr+s,tc,dr,dc,s);///将刚刚覆盖的棋盘作为特殊方格并且与下一个方格进行比较
}
else
{
Board[tr+s][tc+s-1]=t;///该棋盘不存在特殊方格则覆盖
ChessBoard(tr+s,tc,tr+s,tc+s-1,s);///覆盖其他方格
}
///覆盖右下角的棋盘
if(dr>=tr+s&&dc>=tc+s)///在此棋盘中
{
ChessBoard(tr+s,tc+s,dr,dc,s);///将刚刚覆盖的棋盘作为特殊方格并且与下一个方格进行比较
}
else
{
Board[tr+s][tc+s]=t;///该棋盘不存在特殊方格则覆盖
ChessBoard(tr+s,tc+s,tr+s,tc+s,s);///覆盖其他方格
}
}
void OutPut(int n,int m)///输出覆盖后的棋盘矩阵
{
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
cout<<setw(3)<<Board[i][j];
}
cout<<endl<<endl;
}
}
int main()
{
cout<<"\t输出ChessBoard(棋盘覆盖的算法如下——L型骨牌覆盖)"<<endl<<endl;
int k;//表示边长的次方()
int n;///表示特殊方格的行号
int m;///表示特殊方格的列号
cin>>k;
cin>>n;
cin>>m;
int t1=clock();//时间节点1
InitChessBoard(N,N);//初始化棋盘
ChessBoard(0,0,n,m,N);
cout<<endl<<endl;
OutPut(N,N);//输出棋盘
int t2=clock();//时间节点2
cout<<endl<<endl;
printf("%15.10fm",(double)(t2-t1)/CLOCKS_PER_SEC);
return 0;
}
快速排序
#include
using namespace std;
//数组打印
void P(int a[],int n)
{
for(int i=0; i<n; i++)
cout<<a[i]<<" ";
cout<<endl;
}
int Partition(int s[], int l, int r){
int i = l, j = r+1, x = s[l];
int b;//将最左元素记录到x中
while (true) {
while (s[++i] <x && i<r);
while (s[--j] >x);
if (i >= j) break;
b=s[i];
s[i]=s[j];
s[j]=b;
}
s[l] = s[j];
s[j] = x;
P(s,10);
return j;
}
void QuickSort(int s[], int l, int r)
{
if (l<r) {
int q=Partition(s,l,r);
QuickSort (s,l,q-1); //对左半段排序
QuickSort (s,q+1,r); //对右半段排序
}
}
int main()
{
int a[]= {5,2,3,6,1,7,8,9,0,4};
cout<<"快速排序:"<<" \n";
P(a,10);
QuickSort(a,0,9);//注意最后一个参数是n-1
cout<<"最终排序:"<<" \n";
P(a,10);
return 0;
}
n不同时运行时间分析:
理论复杂度:
解此递归方程可得:由于2n*2n棋盘所有的骨牌个数为(4^n-1)/3,所以这个算法是一个渐进意义的最优算法。
用大量数据(10000个随机生成的数据)测试算法与冒泡排序算法的完成时间比较
当n=10000时,冒泡排序的运行时间
由此可见,快速排序的T(n)<<冒泡排序的T(n)
补充:
分治法所能解决的问题一般具有以下几个特征:
1.该问题的规模缩小到一定的程度就可以容易地解决;(因为问题的计算复杂性一般是随着问题规模的增加而增加,因此大部分问题满足这个特征。)
2.该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质(这条特征是应用分治法的前提,它也是大多数问题可以满足的,此特征反映了递归思想的应用)
3.利用该问题分解出的子问题解可以合并为该问题的解;(能否利用分治法完全取决于问题是否具有这条特征,如果具备了前两条特征,而不具备第三条特征,则可以考虑贪心算法或动态规划。)
4.该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。(这条特征涉及到分治法的效率,如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然也可用分治法,但一般用动态规划较好。)