《数据结构与算法分析》贪婪算法与分治算法--二维最近点问题详解

前言:

      这段时间晚上都在做实习的工作,所以学习的进度有点慢了下来。不过基础是非常重要的,所以一定不能把基础给拉下,平时挤时间出来也得好好看书学习。

这本书现在到了第十章,还有两章。本来打算9月底一定要弄完这本书的,不过发生了太多的事情,但是还是要尽全力往前赶。

我的github:

我实现的代码全部贴在我的github中,欢迎大家去参观。

https://github.com/YinWenAtBIT

介绍:

一、贪婪算法:

一、定义:

贪婪算法分阶段的工作,每一个阶段,都选择当前能选择的最好的选择,不考虑将来的后果。每次的选择都是局部最优,期望最后的结果能达到全局最优,它的策略就是它名字的来由。

二、贪婪算法的应用:

1. Dijkstra算法,在图论中使用的最短路径优先算法就是贪婪算法,每次选择当前可见的路径中具有最低加权的边。即是使用了贪婪策略。

2.Prim算法,图论中最小生成树。每次选择与原有树相连的具有最小加权的边,同样是贪婪策略。

3. Kruskal算法,同样是求最小生成树,使用贪婪策略。

这几个算法我已经在之前的学习中学习过了,并且全部实现出来了。接下来是这一章中提及的新的算法

4.调度问题;

这个问题是要得到最短的平均完成时间,在单处理器情况下,优先调用耗时最短的任务即可。

在多处理器中,采用同样的策略可以得到最短的平均完成时间,但是如果要是的最后完成时间最小,这就会变成一个NP完全问题,这个问题就不再是可以简单处理的问题了。这个问题,难的太难,简单的太简单,没有什么编码实现的必要,直接略过了。

5.Huffman编码

这个Huffman编码说起来可就真的有历史了,本科的数据结构课程就学习过了,不过当时我只会背诵这个算法,对于如何真正去实现它一筹莫展,直到上个学期,我学习图像处理课程,其中也要求使用matlab来完成Huffman编码,当时我也是不明白怎么做,看了一篇别人的博客,发现用链表实现特别好,并且突然发现链表原来是这么用的。。最后那个作业还是抄的别人的,因为我不知道怎么在matlab里面写一个链表。

今天再来看这个算法,只不过是生成一颗树而已,先把所有的节点都加入优先队列,每次取出两个来组成一个新的节点再加入队列,时间复杂度O(N*logN),N代表需要编码的元素。或者直接使用链表来写,O(N^2)的复杂度。书上没有给出伪代码,我也觉得没有必要。通过这么一段时间的训练,我感觉这样的算法再写出来也基本是浪费时间而已了。直接手写,使用以前编写好的优先队列或者是链表,估计20分钟就完成了。不过现在连20分钟都不愿意花在这上面了。感觉自己这半年来进步已经非常的大了。

6.装箱问题

这个问题主要的难点在于联机算法与脱机算上。

联机算法代表一次只能知道一个物品大小的的信息,处理完一个物品的装箱,才知道另外一个物品的信息。所以在这种情况下,最优解是很难达到的,此时能到到的最优解为4/3倍与最优解的箱子数。不过达到这个限度的算法并没有。

使用贪婪策略的两个算法:首次适合法和最佳适合法,都可以达到17/10最优箱子的解。实现这个算法可以使用链表,或者是优先队列。

脱机时,就是知道所有的物品大小信息,然后进行装箱,能达到11/9最优。这里只进行了证明,并没有给出可行的算法。

三、编码实现:

这一块需要进行新的编码的地方基本没有,huffman编码现在对我来说已经太简单,不用再浪费时间了。

二、分治算法:

一、定义:

分治算法由两部分组成:

分:递归解决较小的问题

治:从子问题的解构建原问题的解

传统上,在正文至少含有两个递归调用的例程叫做分治算法,我们一般坚持子问题是不相交的(即基本不重叠)。

二、分治算法的应用:

1.最大子序列,这个问题可以把序列分成两部分,最大的序列出现在左边,或者右边,或者中间。时间复杂度O(N*logN)

2.归并排序与快速排序,都是使用分治思想,算法复杂度O(N*logN)

3.最近点问题,同样是使用分治算法,可以根据x坐标将排好序的点分成左右两部分,然后最近点位于左边,右边,或者中间。

三、分治算法的运行时间:

对于方程的:

T(N) =aT(N/b)+O(N^k)

的解为:

T(N) = O(N^(lga/lgb))  a>b^k时

        = O(N^(N^k*logN))  a=b^k时

        = O(N^(N^k))  a<b^k时

这里原方程代表处理算法的复杂度方程,其中a为划分之后的子问题个数,b是子问题相对于原问题的大小的倒数,最后一项是每次递归需要做的额外工作。

接下来将详细讲解如何求二维最近点问题:

三、最近点问题:

1问题模型:

给定平面上n个点,找其中的一对点,使得在n个点的所有点对中,该点对的距离最小。严格地说,最接近点对可能多于1对。为了简单起见,这里只限于找其中的一对。

2.一维最近点问题:

对于一维的最近点问题,可以采用分治法,做法是排序之后,从中间分开,最近点在左边,右边,或者横跨左右两部分。一维的分治法很好实现,算反复杂度O(N*logN)。实际上,在头脑中调用这个递归过程,实际上很容易发现,实际上就是对相邻的两个点求距离,然后找到最近的那个。我们这里就可以利用动态规划的思想,直接对排好序的点,求相邻点的距离,排序的复杂度O(N*logN)。复杂度相同。

3.二维最近点问题:

当维度升高之后,就不能像之前那样直接求取相邻点的距离了,因为此时如果按照x坐标排序,那么y此时便不是按顺序排列的了。那么就没有相邻点的意义在这了。

那么此时,如何化简这个问题呢,做法还是利用分治法,先按照x排序,然后分开,左边,右边,中间。这样先把问题给化简。然后对于每一个最简的x区域,对这个区域内的所有点的距离进行计算,所以调用到最简时,计算的复杂度为O(N)。但是这时有一个缺陷。如果说所有的点都是均匀分布的,那么这个算法复杂度为O(N*logN),但是如果所有的点都落在了同一带内,那么这时候这个算法就失效了。如果此时能够把y也按照顺序排列,比较的时候就只用比较y“相邻”的点了。实际上是比较y之差小于epsilon的点。

算法过程:

预处理:把所有点先对x坐标进行归并排序,放入数组X中,再对X数组中的数据按照y排序,放入数组Y中

1.对输入的X数组的数据从中间分开,以中间点为界限,将Y数组按照中间的x分成左右两部分,放入数组Z中,左边部分x小于中间点的x坐标,右边相反。

2.递归求左右数组的最短距离;

3. 取左右数组最小距离,合并Z中的数组,然后对在左右最小距离里的点求取距离。

更加具体的解释,大家可以参考:http://blog.csdn.net/liufeng_king/article/details/8484284

4.编码实现二维最近点问题:

首先是类定义:

class PointX
{
public:
	PointX(){}
	bool operator <(PointX &P2){return x<P2.x;}
	bool operator <=(PointX &P2){return x<=P2.x;}
	int ID;
	float x,y;
	
};

class PointY
{
public:
	PointY(){}
	bool operator <(PointY &P2){return y<P2.y;}
	bool operator <=(PointY &P2){return x<=P2.y;}
	int xID;
	float x,y;
	
};

这里PointX与PointY都重载了<与<=符号,两个的目的就是在排序的时候分别比较x与y。

预处理:

预处理之后直接调用了真正的处理函数,直接返回了处理的结果

void getNearDis(PointX X[], int N, PointX &a, PointX &b, float &d)
{
	MergeSort(X, N);

	PointY *Y = new PointY[N];

	for(int i=0; i<N; i++)
	{
		Y[i].xID = i;
		Y[i].x = X[i].x;
		Y[i].x = X[i].y;
	}

	MergeSort(Y, N);

	PointY *Z = new PointY[N];
	getClosest(X, Y, Z, 0, N-1, a, b, d);

	delete [] Y;
	delete [] Z;
}
分治处理核心:

这一部分处理代码比较长,也不太好懂,需要反复思考问题的模型之后才能比较明白。

void getClosest(PointX X[], PointY Y[], PointY Z[], int left, int right, PointX &a, PointX &b, float &d)
{
	/*剩下两个点*/
	if(right -left ==1)
	{
		d = getDis(X[left], X[right]);
		a = X[left];
		b = X[right];
		return;
	}
	/*剩下三个点*/
	if(right - left ==2)
	{
		float d1, d2, d3;
		d1 = getDis(X[left], X[right-1]);
		d2 = getDis(X[left], X[right]);
		d3 = getDis(X[left+1], X[right]);

        if(d1<=d2 && d1<=d3)   
        {   
            a=X[left];  
            b=X[right -1];  
            d=d1;  
            return;  
        }   
  
        if(d2<=d3)  
        {   
            a=X[left];  
            b=X[right];  
            d=d2;  
        }   
        else {   
            a=X[left+1];   
            b=X[right];   
            d=d3;   
        } 
			
		return;
	}

	int center = (left + right)/2;

	int k = left;
	int g= center +1;
	/*将已经按Y排序的点,根据中线,分别放入中线左边和右边*/
	for(int i = left; i <=right; i++)
	{
		if(Y[i].xID <= center) 
			Z[k++] = Y[i];
		else
			Z[g++] = Y[i];
	}

	PointX atmp, btmp;
	float dtmp;

	getClosest(X, Z, Y, left, center, a, b, d);
	getClosest(X, Z, Y, center+1, right, atmp, btmp, dtmp);

	if(dtmp< d)
	{
		a = atmp;
		b = btmp;
		d = dtmp;
	}

	Merge(Z, Y, left, center+1, right);

	int f = left;
	/*距离中心线距离在d内的点放入Z中,并且这些点是按照y坐标从小到大排序的 */
	for(int i =left; i<=right; i++)
	{
		if(ABS(X[center].x - Y[i].x) <d)
			Z[f++] = Y[i];
	}

	/*搜索刚刚加入strip中的点, 如果y之间的距离大于d,就开始搜索*/
	for(int i=left; i<f; i++)
		for(int j = i+1; j< f && (Y[j].y-Y[i].y) < d; j++)
		{
			dtmp =getDis(Y[i], Y[j]);
			if(dtmp <d)
			{
				d = dtmp;
				a = X[Y[i].xID];
				b = X[Y[j].xID];
			}
		}
}

测试结果

这里只对二维最近点问题进行了编码,测试也只测试这个代码:

生成1000个随机的x,y坐标,算法很快就得到了答案。


总结:

这一次对于二维最近点问题的学习与编码,基本上让我对于分治算法有了更深入的理解了。想要想出一个好的分治算法,一定要先想清楚整个分与治的流程。想明白之后,再进行编码就容易多了。







  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值