前面写过一篇关于轮廓算法的文章,是把合并建筑和合并轮廓是分开对待的,并且为了使轮廓合并的时候算法简单,对x坐标使用了double类型,然后对整形的x坐标数据进行合并。这样做是为了使得需找拐点的算法容易理解,遇到拐点的时候方便辨认。但是缺点也很明显:它对所有建筑的x坐标区间(MaxX-MinX)非常敏感,区间变大的话,效率就会下降(它用double类型来遍历所有x的整形坐标,double可以使用0.5的步长)。但是如果输入的建筑横坐标数据是浮点型的话这个算法就需要再改动下才能使用,所以它的限制很多,通用性不好。原文在这里:http://www.cnblogs.com/andyzeng/p/3670432.html

有没有一个算法能解决上面提到的限制呢?

后来有时间仔细想了一下这个算法,并且重新审视了一下数据输入和数据输出的要求,发现其中的一些联系:

-----------------------

输入的建筑数据:

10 110 50
20 60 70
30 130 90
120 70 160
140 30 250
190 180 220
230 130 290
240 40 280

输出(粗体格式为轮廓高度):

10 110 30 130 90 0 120 70 160 30 190 180 220 30 230 130 290 0

-------------------------

如果单独拿出一个建筑的输入来看,10 110 50,数据代表的意思是建筑的左边边界x坐标10,建筑的高度110,建筑的右边边界50。

再看输出的格式10 110 30 130 90 0 120 70 160 30 190 180 220 30 230 130 290 最后一个0表示这个轮廓线的最后高度变为0,如果拿掉最后一个0,那么输出就变成10 110 30 130 90 0 120 70 160 30 190 180 220 30 230 130 290 ,这样它和单独一个建筑的数据,比如10 110 50就非常相似了,轮廓数据的意义是一个x坐标,一个高度坐标,一个x坐标,一个高度坐标....最后是一个x坐标。两个x坐标之间的高度坐标是这两个x之间的建筑高度。这样轮廓也可以看成一个封闭的建筑,它是由多个单独的建筑组成的,分解成单独的建筑就是

10 110 30, 130 90, 0 120, 70 160, 30 190, 180 220, 30 230, 130 290

绿色的x坐标是我添加的,现在就明了了,两个轮廓线的合并就是可以看成两组建筑的合并,两组建筑的合并就又可以拆解成其中每两个建筑的合并。

尝试设计一个算法把合并建筑和合并轮廓统一解决。

设计一个类来表示输入建筑和输出轮廓:

其中用一个列表Data来存储建筑/轮廓的数据

索引器只是为了后面编写算法时简化一下访问,在这里不是必须的。

public class NewBuilding
{
public NewBuilding()
{
}
public NewBuilding(int x1, int h, int x2)
{
Data.Add(x1);
Data.Add(h);
Data.Add(x2);
}
public int this[int i]
{
get
{
return Data[i];
}
set
{
Data[i] = value;
}
}
public List<int> Data = new List<int>();
}

//分治法合并建筑/轮廓,这个就是一个递归过程,每次都将待合并的建筑数组分成两段,然后分别递归调用合并,直到最后合并成一个建筑输出。

 public static NewBuilding MergeBuildings(NewBuilding[] blds, int leftIndex, int rightIndex)
{
       if (rightIndex == leftIndex)//one same building
{
return blds[leftIndex];
}
     else if (rightIndex - leftIndex == )//two buildings
{
return newMergeTwoBuildingsImpl(blds[leftIndex], blds[rightIndex]);
}
else
{
int middle = (rightIndex + leftIndex) / ;
NewBuilding firstOutlines = MergeBuildings3(blds, leftIndex, middle);
NewBuilding secondOutlines = MergeBuildings3(blds, middle + , rightIndex);
return newMergeTwoBuildingsImpl(firstOutlines, secondOutlines);
}
}

//下面为核心代码,方法 newMergeTwoBuildingsImpl是合并两个建筑/轮廓的方法,getKneePoint是寻找下一个拐点的方法

拐点定义:当轮廓高度发生变化时,在图形上就是轮廓线开始向上或者向下转向时,这个转向的x坐标点为拐点。当输出时,拐点就是把高度夹在中间的各个x轴坐标。

拐点图例:绿色标志出来的横坐标就是kneePoint,会发现所有的kneePoint横坐标都是可能产生轮廓弯曲的点的横坐标,都是需要检查的点,有些点在合并的时候被舍弃,因为它被更高的轮廓包含。如果把舍弃的点去掉,就可以尝试写出最后合并的轮廓输出了:)

接下来逐行讲解代码,row 3表示第三行代码,下同。

row1: 参数为两个建筑(可能为只包含一个建筑对象,也可能是一个复杂的多个建筑组成的轮廓建筑对象)

row 3:nb为存储合并结果的对象

row4,row5:分别定义建筑A和建筑B的游标,存储当前处理到的各自的x坐标所在的索引值,如下图所示。

row6:分别定义两个建筑的和坐标和纵坐标,分别记录当前待合并的两个简单建筑(从轮廓线里面取出来的,后面讲到取法)

row7:定义两个长度变量,简化后面的代码

row8:取得两个复杂建筑的x起点,在坐标上表现为取得最左边建筑轮廓线的边界x坐标

row9:只要至少一个轮廓建筑没有处理完就继续处理

row12-row16:当两个轮廓都处理到最后一个点时,把最后的最大的x坐标添加到nb里面,退出while循环,全部处理完了

row18-row21:对A轮廓处理到了最右边的边界点时,总是取A[lenthA - 1],0,0为待合并简单建筑,其实就是放弃了合并,等待B把剩下的轮廓加进来

row22-row25:对A正常取得待合并的简单建筑数据

row26-row33:同row18-row25

row35:取得当前拐点的高度数值

row36-row41:当为第一个加入结果的x边界和高度值时,或者与前面已经合并的轮廓线不等高时才加入新合并的结果

row43:关键,取得下一个拐点,即下一个x坐标值,这个值为两个正在合并的简单建筑的所有x坐标中第一个大于当前拐点的值,也可以理解为下一个可能出现轮廓线上下变向的x坐标。

row45,row46:判断新的拐点是否已经超出各自的轮廓线,并且如果轮廓线还没处理完,那么把游标指向下一个x边界。

row50-row61:解决row43的问题,取得第一个大于当前拐点的值

     private static NewBuilding newMergeTwoBuildingsImpl(NewBuilding A, NewBuilding B)
{
NewBuilding nb = new NewBuilding();
int cursorA = ;//the cursor for the first build
int cursorB = ;////the cursor for the second build
int ax1, ah, ax2, bx1, bh, bx2;
int lenthA = A.Data.Count, lenthB = B.Data.Count;//长度都必为奇数,迭代步长为2,最后一个点为迭代终止点
int kneePointX = Math.Min(A[cursorA], B[cursorB]);//拐点
while (cursorA < lenthA || cursorB < lenthB)
{
//边界情况
if (cursorA == lenthA - && cursorB == lenthB - )
{
nb.Data.Add(Math.Max(A[cursorA], B[cursorB]));
break;
} if (cursorA >= lenthA - )
{
ax1 = A[lenthA - ]; ah = ; ax2 = ax1;
}
else
{
ax1 = A[cursorA]; ah = A[cursorA + ]; ax2 = A[cursorA + ];
}
if (cursorB >= lenthB - )
{
bx1 = B[lenthB - ]; bh = ; bx2 = bx1;
}
else
{
bx1 = B[cursorB]; bh = B[cursorB + ]; bx2 = B[cursorB + ];
}
//高度取当前x<=kneePointX<=x2的建筑高度的最大值
int h = (Math.Max(ax1 <= kneePointX && kneePointX <= ax2 ? ah : , bx1 <= kneePointX && kneePointX <= bx2 ? bh : ));
if ((nb.Data.Count == ) || (nb.Data.Count > && nb.Data.Last() != h))//第一个点或者与前面的轮廓不等高点才添加新的轮廓
{
//x点取kneePointX,封闭前面的轮廓,加入新的高度
nb.Data.Add(kneePointX);
nb.Data.Add(h);
}
//关键,下一个拐点的取法:取大于kneePointX的最小点
kneePointX = getKneePoint(new int[] { ax1, ax2, bx1, bx2 }, kneePointX);
//关键:检查两个轮廓的游标是否需要向前步进
if (ax2 <= kneePointX && cursorA < lenthA - ) cursorA = cursorA + ;
if (bx2 <= kneePointX && cursorB < lenthB - ) cursorB = cursorB + ;
}
return nb;
}
private static int getKneePoint(int[] arr, int kneePointX)
{
int minMax = int.MaxValue;
for (int i = ; i < arr.Length; i++)
{
if (arr[i] > kneePointX && arr[i] < minMax)
{
minMax = arr[i];
}
}
return minMax;
}

调用方法MergeBuildings并取得结果后,在所得结果上面再添加一个0,就是题目所要求的输出了。

此算法要比之前的算法快几十倍,10万个随机建筑合并需要大约80ms(不同机器上可能略有不同)。

 算法复杂度分析:

每次将n个建筑平均分成两组建筑,每组数量为n/2,合并两个简单建筑(长方体型的简单建筑)的时候,检查一次kneePoint大约需要20次比较,大约12次数据赋值操作。最少2个kneePoint,最多7个kneePoint

最好情况,每次两个kneePoint,总的比较和数据移动次数为 32*2,即T(2)=T(1)=32*2=64,P(n)表示n个建筑合并,P(n)的时间最好是64n

T(n)=2T(n/2)+P(n)=2[2T[n/4]+P(n/2)]+P(n)=2[2T[n/4]+((64n)/2)n]+(64n)=2[2T[n/4]]+(64n)+(64n)=(64n)+(64n)+.(logn个)..+(64n)=64n*Log

所以此时时间复杂度O(nLogn

最坏情况,每个建筑都不相交或者每两个建筑相交(建筑相包含但高度高的范围被包含在高度低的里面)都产生3个高度。此时P(n)的时间是32*7n

T(n)=2T(n/2)+P(n)=32*7*n*Logn

所以此时时间复杂度O(nLogn

所以此算法的平均时间复杂度为O(nLogn

代码下载

作者:Andy Zeng

欢迎任何形式的转载,但请务必注明出处。

http://www.cnblogs.com/andyzeng/p/3683498.html

轮廓问题/Outline Problem-->改进的算法及时间复杂度分析的更多相关文章

  1. 轮廓问题/Outline Problem

    --------------------------------------------------- //已发布改进后的轮廓问题算法:http://www.cnblogs.com/andyzeng/ ...

  2. W3School-CSS 轮廓(Outline)实例

    CSS 轮廓(Outline)实例 CSS 实例 CSS 背景实例 CSS 文本实例 CSS 字体(font)实例 CSS 边框(border)实例 CSS 外边距 (margin) 实例 CSS 内 ...

  3. ML(6)——改进机器学习算法

    现在我们要预测的是未来的房价,假设选择了回归模型,使用的损失函数是: 通过梯度下降或其它方法训练出了模型函数hθ(x),当使用hθ(x)预测新数据时,发现准确率非常低,此时如何处理? 在前面的章节中我 ...

  4. CSS:CSS 轮廓(outline)

    ylbtech-CSS:CSS 轮廓(outline) 1.返回顶部 1. CSS 轮廓(outline) 轮廓(outline)是绘制于元素周围的一条线,位于边框边缘的外围,可起到突出元素的作用. ...

  5. AVL树的插入删除查找算法实现和分析-1

    至于什么是AVL树和AVL树的一些概念问题在这里就不多说了,下面是我写的代码,里面的注释非常详细地说明了实现的思想和方法. 因为在操作时真正需要的是子树高度的差,所以这里采用-1,0,1来表示左子树和 ...

  6. KMP算法的时间复杂度与next数组分析

    一.什么是 KMP 算法 KMP 算法是一种改进的字符串匹配算法,用于判断一个字符串是否是另一个字符串的子串 二.KMP 算法的时间复杂度 O(m+n) 三.Next 数组 - KMP 算法的核心 K ...

  7. 算法设计与分析 - 李春葆 - 第二版 - html v2

    1 .1 第 1 章─概论   1.1.1 练习题   1 . 下列关于算法的说法中正确的有( ).   Ⅰ Ⅱ Ⅲ Ⅳ .求解某一类问题的算法是唯一的   .算法必须在有限步操作之后停止   .算法 ...

  8. 算法设计与分析 - AC 题目 - 第 5 弹(重复第 2 弹)

    PTA-算法设计与分析-AC原题 - 最大子列和问题 (20分) 给定K个整数组成的序列{ N1, N2, ..., NK },“连续子列”被定义为{ Ni, Ni+, ..., Nj },其中 ≤i ...

  9. 算法设计与分析 - AC 题目 - 第 2 弹

    PTA-算法设计与分析-AC原题7-1 最大子列和问题 (20分)给定K个整数组成的序列{ N1, N2, ..., NK },“连续子列”被定义为{ Ni, Ni+1, ..., Nj },其中 1 ...

随机推荐

  1. sqli-labs学习笔记 DAY1

    DAY 1 准备工作 安装phpstudy 安装配置sqli-labs 学习笔记 SQL语句的注释:–, # +在URL经过编码后会编码为空格 SQL语句的查询语句:SELECT column_nam ...

  2. B. Counting-out Rhyme(约瑟夫环)

    Description n children are standing in a circle and playing the counting-out game. Children are numb ...

  3. Scrum立会报告+燃尽图(Beta阶段第二周第七次)

    此作业要求参见:https://edu.cnblogs.com/campus/nenu/2018fall/homework/2415 项目地址:https://coding.net/u/wuyy694 ...

  4. HDU 5655 CA Loves Stick 水题

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5656 CA Loves Stick Accepts: 381   Submissions: 3204 ...

  5. web登录密码加密

    文章:如何实现登录页面密码加密 文章:用RSA加密实现Web登录密码加密传输 文章:web登录用户名密码加密 知乎文章:Web前端密码加密是否有意义? 文章:记录一次黑客模拟攻击 成功拿到淘宝账号和密 ...

  6. lintcode-491-回文数

    491-回文数 判断一个正整数是不是回文数. 回文数的定义是,将这个数反转之后,得到的数仍然是同一个数. 注意事项 给的数一定保证是32位正整数,但是反转之后的数就未必了. 样例 11, 121, 1 ...

  7. C++对象模型 多重继承与虚函数表

    一 多重继承 1) 代码: Code#include <iostream>using namespace std; class B1{public:    int x;    virtua ...

  8. EF 联合查询

    EF 文章表和标签表联合查询标签id在dis中的文章,还不知道性能如何 var query = tagRepo.Entities.Include("Tags").Where(t = ...

  9. (九) 使用Jmeter 做分布式压测 ;

    在使用Jmeter进行性能测试时,如果并发数比较大(比如最近项目需要支持1000并发),单台电脑的配置(CPU和内存)可能无法支持,这时可以使用Jmeter提供的分布式测试的功能. 一.Jmeter分 ...

  10. 编码转换,基础,copy

    阅读目录 编码转换 基础补充 深浅拷贝 文件操作 一,编码转换 1. ASCII : 最早的编码. ⾥⾯有英⽂⼤写字⺟, ⼩写字⺟, 数字, ⼀些特殊字符. 没有中⽂, 8个01代码, 8个bit, ...