分治算法学习报告
1.分治策略的基本思想
分治策略
1.将原始问题划分或者归结为规模较小的子问题
2.递归或迭代求解每个子问题
3.将子问题的解综合得到原问题的解
注意事项
1.子问题与原始问题性质完全一样
2.子问题之间可彼此独立地求解
3.递归停止时子问题可以直接求解
2.分治算法的一般性描述
设P是待求解的问题,|P|代表该问题的输入规模,一般的分治算法的伪码描述如下:
Divide-and-Conquer(P)
if |P|<=c then S(P)
divide P into P1,P2,...Pn //将P分解成k个子问题
for i=1 to k do
Divide-and-Conquer(Pi)→Yi//递归求解每个子问题
return Merge(Y1,Y2,...Yk)//把子问题的解进行综合
上述伪码的说明:
如果问题规模不超过c,算法停止递归,直接求解P,S(P)就代表直接求解的过程;否则,将P归约成k个彼此独立的子问题P1,P2,…Pk,然后递归地依次求解这些子问题,得到解Y1,Y2,…Yk.最后将这k个解归并得到原问题的解,Merge代表归并子问题的解的过程。
分治算法的时间复杂度
分治算法通常都是递归算法,这种算法的时间复杂度分析通常需要求解递推方程。如果原问题的输入规模是n,根据上面的伪码,分治算法时间复杂度的递推方程的一般形式是:
W (n) =W (|P1|) +W (|P2|) +···+W (|P|) +f (n)
W (c) =C
上面的C代表直接求解规模为c的子问题的工作量,而f(n)代表将原问题归约为若干子问题以及将子问题的解综合为原问题的解所需要的总工作量.
3,算法举例——芯片测试
测试方法:将两片芯片(a,b)置于测试台上,互相进行测试,测试报告为“好”或“坏”,只取其一。
假设:好芯片的报告一定是正确的,坏芯片的报告是不确定的(可能会出错)
测试结果分析:
A报告 | B报告 | 结论 |
---|---|---|
B是好的 | A是好的 | AB都好或AB都坏 |
B是好的 | A是坏的 | 至少一片是坏的 |
B是坏的 | A是好的 | 至少一片是坏的 |
B是坏的 | A是坏的 | 至少一片是坏的 |
输入:n片芯片,其中好芯片至少比坏芯片多一片
问题:设计一种测试方法,通过测试从n片芯片中挑出一片好芯片。
要求:使用最少的测试次数
结论:至少一半的芯片报“好”,则该芯片为好芯片。
超过一半芯片报“坏”,则该芯片是坏芯片。
分治算法设计思想:
假设n为偶数,将n片芯片两两一组做测试淘汰,剩下芯片构成子问题,进入下一轮。
淘汰规则:
如果两个都是好的,仍留一片进入下一轮。
其他情况下全部抛弃。
判定芯片A
n是偶数:好芯片数>=n/2+1
A好,至少有n/2个报告是“好”。
A坏,至少有n/2+1个报告“坏”。
因此,在n-1份报告中,至少一半报“好”,则A为好芯片
超过一半报“坏”,则A为坏芯片。
n为奇数时的处理
当数量为奇数时需要对单个芯片进行轮空操作:处理办法时对该单个轮空芯片进行单独测试,用其余所有芯片对其进行测试,如果至少一般芯片报好,则该芯片为好芯片,直接返回该芯片即可;如果该芯片是坏芯片,则舍弃该芯片,这样对单独的芯片进行处理了以后,就可以进行两两测试了。
代码实现
//芯片测试
struct Chip{
int num; //芯片编号
int state; //好坏
};
//两个芯片进行测试,2全好,0全坏,1一好一坏
int judge(Chip *a,Chip *b){
int count = 0;
if(a->state==1){
count=+b->state; //如果a是好的,判断b的结果与b一致
}else if(a->state==0){
count +=(rand()%2); //如果a是坏的,判断b可能好可能坏,随机产生
}
if(b->state==1){
count += a->state;
}else if(b->state==0){
count +=(rand()%2);
}
return count;
}
//如果为奇数个
vector<Chip*> oddTest(vector<Chip*> a){
int size = a.size();
vector<Chip*> result;
int count = 0; //轮空操作
for(int i=1;i<size;i++){
if(a[i]->state ==1){
count = count + a[0]->state;
}else{
count = count + (rand()%2);
}
}
if(count >= size/2){ //如果轮空的是好的
result.push_back(a[0]); //只返回这个 否则舍弃
}else{
for(int i =1;i<size;i+=2){
int r = judge(a[i],a[i+1]);
if(r == 2){
result.push_back(a[i]); //判断后面的
}
}
}
return result;
}
//需测芯片个数为偶数
vector<Chip*> evenTest(vector<Chip*> a){
int size = a.size();
vector<Chip*> result;
//两个为一组判断芯片好坏
for(int i =0;i<size;i+=2){
int r = judge(a[i],a[i+1]);
if(r == 2){
result.push_back(a[i]);
} //两个都是好的进入下一轮
}
return result;
}
int test(vector<Chip*> a){
int n = a.size();//有几个芯片
if(n==3){
int r = judge(a[0],a[1]); //选前两个进行测试
if(r==1){ //如果一好一坏 取第三个
return a[2]->num;
}else{ //其他 任取一片
return a[0]->num;
}
}
else if(n==2||n==1){
return a[0]->num; //任取一片 取第一片
}
vector<Chip*> next;
while(n>3){
if(n%2==0){
next = evenTest(a);
}else{
next = oddTest(a);
}
return test(next);
}
}
int main(){
//芯片测试
vector<Chip*> chip ;
for(int i=0;i<10;i++){
Chip* a = new Chip();
a->num = i;
cin>>a->state;
chip.push_back(a);
}
int t = test(chip);
cout<<"芯片为:"<<t;
return 0;
}
4,改进分治算法的途径
因为考虑到分治算法的效率以及时间复杂度等方面,改进分治算法是很有必要的,我们可以用以下两种方法来改进分治算法:
1.通过代数变换减少子问题个数
减少子问题是降低时间复杂度的有效方法。一种可行的办法是寻找子问题之间的依赖关系,如果一个子问题的解可以用其他子问题的解通过简单的运算得到,那么在用到这个子问题的解时,不必重新递归计算,而是通过组合其他子问题的解来得到。这样就可以有效减少子问题的个数,从而提高算法效率。
2.利用预处理减少递归内部的计算量
如果在算法设计时,尽量把某些工作提到递归过程之外,作为预处理,从而有效减少递归内部的调用工作量,也是提高算法效率的一个有效途径。
5,总结
分治法和蛮力法比起来给我们提供了一个更方便的编程思想,但是它也不是万能的在以下情况下使用分治法更加适合:
1.该问题的规模缩小到一定的程度就可以容易地解决;
2.该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质;
3.利用该问题分解出的子问题的解可以合并为该问题的解;
4.该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。