当前位置: 首页 > 编程日记 > 正文

技术图文:如何改进算法的运行效率?

背景

前段时间,一位好友发给我如下的文件:

每个CSV文件中的数据由三个属性组成,第一个属性为ID,第二个属性为X坐标,第三个属性为Y坐标。由于是二维数据,可以绘制出每个文件的散点图,把这些散点图连接起来,构成如下的GIF图像。类似于视频中的帧,每一帧对应一个CSV文件。

要做的事情是什么呢?

在每一帧数据中,快速找到以每个数据点为圆心,r = 0.004为半径的范围内包含的其它数据点。


技术分析

刚拿到这个问题的第一个反应就是利用暴力的方法,循环计算每个点与其它点的距离,把小于r的记录下来就可以了。可是这样做会耗费大量的计算时间。

如何改进呢,首先想到的是把计算欧氏距离的根号去掉,只计算欧氏距离的平方,这样可以避免开根号,从而提升运算效率。于是写了“暴力求解算法”的代码,发现执行效率很低,执行 10次 的平均时间约为 2754毫秒。

static float Distance(PointF p1, PointF p2)
{float d = (p1.X - p2.X)*(p1.X - p2.X) + (p1.Y - p2.Y)*(p1.Y - p2.Y);return d;
}for (int i = 0; i < lst.Count; i++)
{PointF p1 = lst[i].Original;for (int j = i + 1; j < lst.Count; j++){PointF p2 = lst[j].Original;if (Distance(p1, p2) <= r * r){lst[i].LstWithin.Add(p2);lst[j].LstWithin.Add(p1);}}
}

怎么办呢?

我们知道平面上的点属于R2空间,是基于e1=(1,0),e2=(0,1)这组基的,为了方便比较我们可以做一个坐标变换,让这些数据基于a1=(r,0),a2=(0,r)这组基。这样给定一个点,我们就能很快判断出是否超出单位圆的范围。由于做基变换的过渡矩阵是可逆的,新的坐标和初始坐标可以相互线性表示,这样就把原问题简化了。于是写了“利用坐标变换进行计算”的代码,发现效率的提升并不明显,仅仅缩短了一半左右的执行时间,执行 10次 的平均时间约为 1296毫秒。

for (int i = 0; i < lst.Count; i++)
{PointF p1 = lst[i].AfterConversion;for (int j = i + 1; j < lst.Count; j++){PointF p2 = lst[j].AfterConversion;if (p1.X + 1 < p2.X || p1.X - 1 > p2.X || p1.Y + 1 < p2.Y || p1.Y - 1 > p2.Y)continue;if (Distance(p1, p2) <= 1){lst[i].LstWithin.Add(p2);lst[j].LstWithin.Add(p1);}}
}

怎么办呢?

我们发现如果对数据的某一个维度进行排序,这样可以减少计算距离的次数,于是我们先对数据中的x维度进行排序,然后再来计算距离。于是写了“利用坐标变换和排序进行计算”的代码,发现效率提升了90%以上,执行 10次 的平均时间约为 235毫秒。

for (int i = 0; i < lst.Count; i++)
{PointF p1 = lst[i].AfterConversion;for (int j = i + 1; j < lst.Count; j++){PointF p2 = lst[j].AfterConversion;if (p1.X + 1 < p2.X)break;if (Distance(p1, p2) <= 1){lst[i].LstWithin.Add(p2);lst[j].LstWithin.Add(p1);}}
}

这里还有一个插曲,我曾经想利用KD Tree的方式来建立每一帧数据的空间索引结构,这是加速K 邻近算法的方法。由于每帧数据都是变化的,虽然查找的速度很快,但构建这个Tree的过程需要耗费大量的时间,于是放弃了这种方法。


代码实现

记录时间的代码如下:

public class TimeRecord
{[DllImport("Kernel32.dll")]private static extern bool QueryPerformanceCounter(out long lpFrequency);[DllImport("Kernel32.dll")]private static extern bool QueryPerformanceFrequency(out long lpFrequency);private long _startTime, _stopTime;private readonly long _freq;/// <summary>/// 创建一个TimeRecord类的新实例/// </summary>public TimeRecord(){_startTime = 0;_stopTime = 0;if (QueryPerformanceFrequency(out _freq) == false){throw new Win32Exception();}}/// <summary>/// 开始计时/// </summary>public void Start(){Thread.Sleep(0);QueryPerformanceCounter(out _startTime);}/// <summary>/// 停止计时/// </summary>public void Stop(){QueryPerformanceCounter(out _stopTime);}/// <summary>/// 获取从Start()->Stop()中间的精确时间间隔/// 单位:毫秒/// 计数次数/计数频率/// </summary>public double DurationMs{get{return (double)(_stopTime - _startTime) * 1000 / _freq;}}
}

构造数据存储结构的代码如下:

public class Data
{/// <summary>/// 编号/// </summary>public int Id;/// <summary>/// 原先点/// </summary>public PointF Original;/// <summary>/// 转换后的点/// </summary>public PointF AfterConversion;/// <summary>/// 范围内的点/// </summary>public List<PointF> LstWithin = new List<PointF>();
}

暴力求解算法的代码如下:

static List<Data> LoadData(string[] fileNames)
{List<Data> result = new List<Data>();foreach (string file in fileNames){string[] strs = File.ReadAllLines(file);foreach (string str in strs){string[] temp = str.Split(new char[] {','});Data data = new Data{Id = int.Parse(temp[0]),Original = new PointF(float.Parse(temp[1]), float.Parse(temp[2]))};result.Add(data);}}return result;
}static float Distance(PointF p1, PointF p2)
{float d = (p1.X - p2.X)*(p1.X - p2.X) + (p1.Y - p2.Y)*(p1.Y - p2.Y);return d;
}static double Test01(string[] files)
{TimeRecord tr = new TimeRecord();tr.Start();float r = 0.004f;List<Data> lst = LoadData(files);for (int i = 0; i < lst.Count; i++){PointF p1 = lst[i].Original;for (int j = i + 1; j < lst.Count; j++){PointF p2 = lst[j].Original;if (Distance(p1, p2) <= r * r){lst[i].LstWithin.Add(p2);lst[j].LstWithin.Add(p1);}}}tr.Stop();return tr.DurationMs;
}

利用坐标变换进行计算的代码如下:

static List<Data> LoadData(string[] fileNames,float d)
{List<Data> result = new List<Data>();foreach (string file in fileNames){string[] strs = File.ReadAllLines(file);foreach (string str in strs){string[] temp = str.Split(new char[] { ',' });Data data = new Data();data.Id = int.Parse(temp[0]);float x = float.Parse(temp[1]);float y = float.Parse(temp[2]);data.Original = new PointF(x, y);data.AfterConversion = new PointF(x*d, y*d);result.Add(data);}}return result;
}static double Test02(string[] files)
{TimeRecord tr = new TimeRecord();tr.Start();List<Data> lst = LoadData(files, 250);for (int i = 0; i < lst.Count; i++){PointF p1 = lst[i].AfterConversion;for (int j = i + 1; j < lst.Count; j++){PointF p2 = lst[j].AfterConversion;if (p1.X + 1 < p2.X || p1.X - 1 > p2.X || p1.Y + 1 < p2.Y || p1.Y - 1 > p2.Y)continue;if (Distance(p1, p2) <= 1){lst[i].LstWithin.Add(p2);lst[j].LstWithin.Add(p1);}}}tr.Stop();return tr.DurationMs;
}

利用坐标变换和排序进行计算的代码如下:

static double Test03(string[] files)
{TimeRecord tr = new TimeRecord();tr.Start();List<Data> lst = LoadData(files, 250);lst = lst.OrderBy(a => a.AfterConversion.X).ToList();for (int i = 0; i < lst.Count; i++){PointF p1 = lst[i].AfterConversion;for (int j = i + 1; j < lst.Count; j++){PointF p2 = lst[j].AfterConversion;if (p1.X + 1 < p2.X)break;if (Distance(p1, p2) <= 1){lst[i].LstWithin.Add(p2);lst[j].LstWithin.Add(p1);}}}tr.Stop();return tr.DurationMs;
}

测试代码如下:

delegate double Test(string[] files);static double Run(Test test)
{string[] files = new string[]{"40000weizhi.csv","41000weizhi.csv","41500weizhi.csv","42000weizhi.csv"};double[] r = new double[10];for (int i = 0; i < 10; i++){r[i] = test(files);}return r.Average();
}static void Main(string[] args)
{double r1 = Run(Test01);double r2 = Run(Test02);double r3 = Run(Test03);Console.WriteLine("方法1耗费时间:{0}", r1);Console.WriteLine("方法2耗费时间:{0}", r2);Console.WriteLine("方法3耗费时间:{0}", r3);
}

测试结果如下:


总结

这篇图文记录了我尝试提升算法执行效率的过程,不知道能否达到这位朋友的要求,毕竟比起他们现在的算法,缩短了90%以上的时间。今天就这样吧!See You!大家如果有什么好的方法,欢迎给我留言啊,我们一起把这个问题解决掉。


往期活动

LSGO软件技术团队会定期开展提升编程技能的刻意练习活动,希望大家能够参与进来一起刻意练习,一起学习进步!

  • Python基础刻意练习活动即将开启,你参加吗?
  • Task01:变量、运算符与数据类型
  • Task02:条件与循环
  • Task03:列表与元组
  • Task04:字符串与序列
  • Task05:函数与Lambda表达式
  • Task06:字典与集合
  • Task07:文件与文件系统
  • Task08:异常处理
  • Task09:else 与 with 语句(1day)
  • Task10:类与对象
  • Task11:魔法方法
  • Task12:模块

相关文章:

B树,B+树,B-树和B*树

B树 即二叉搜索树&#xff1a; 1.所有非叶子结点至多拥有两个儿子&#xff08;Left和Right&#xff09;&#xff1b; 2.所有结点存储一个关键字&#xff1b; 3.非叶子结点的左指针指向小于其关键字的子树&#xff0c;右指针指向大于其关键字的子树&#xff1b; 如&#xff1a; …

JS对象直接量,数组直接量和函数直接量

对象直接量创建一个对象&#xff1a; var obj {x:[1,2],y:23}; 代码跟下面是一样的。 var objnew Object(); obj.xnew Array(1,2); obj.y23; 测试&#xff1a;for(var i in obj) alert(obj[i]); 函数直接量&#xff1a;它是一个表达式而不是语句。 (function(){})() 如下例&am…

学习Java编程培训的书籍有哪些

学习java技术除了线上线下的培训学习&#xff0c;书籍的知识也是非常重要的&#xff0c;今天小编为大家整理的就是学习Java的一些书籍&#xff0c;Java书籍是程序员学习提升技能的重要学习渠道&#xff0c;通过书籍Java程序员可以学习当前流行、重要的相关技能。下面我们一起来…

Datawhale组队学习:数据结构与算法课程任务

背景 Datawhale 是国内很有名的一个开源学习组织。这个组织将渴望改变的学习者以及一群有能力有想法的青年人集结在一起&#xff0c;营造出一种互促高效的学习环境&#xff0c;一起为开源学习付出努力。 Datawhale 近期将推出三门课程的组队学习。我先将 数据结构与算法&…

live555学习笔记2-基础类

二 基础类 讲几个重要的基础类&#xff1a; BasicUsageEnvironment和UsageEnvironment中的类都是用于整个系统的基础功能类&#xff0e;比如UsageEnvironment代表了整个系统运行的环境&#xff0c;它提供了错误记录和错误报告的功能&#xff0c;无论哪一个类要输出错误&#x…

自己写的小工具集合

2019独角兽企业重金招聘Python工程师标准>>> 文件夹大小查看工具 用于查看文件夹下每个子文件和子文件夹的大小.以前想统计文件夹大小,只能点右键看属性,而且只能看到文总大小。这个小工具可以通过右键启动,而且能查看文件夹下所有文件和文件夹的大小. 以前用过类似…

哪些人适合学软件测试呢

软件测试是现在很多企业的一个刚需岗位&#xff0c;所以软件测试的发展前景是非常好的&#xff0c;想要了解哪些人适合学软件测试呢?来看看下面的详细介绍就知道了。 哪些人适合学软件测试呢? 1.无编程基础 测试的代码量仅为20%左右&#xff0c;无论是文科生还是非计算机专业…

javabean和EJB的区别

Java Bean 是可复用的组件&#xff0c;对Java Bean并没有严格的规范&#xff0c;理论上讲&#xff0c;任何一个Java类都可以是一个Bean。但通常情况下&#xff0c;由于Java Bean是被容器所创建(如Tomcat)的&#xff0c;所以Java Bean应具有一个无参的构造器&#xff0c;另外&am…

Datawhale组队学习:初级算法梳理课程任务

背景 Datawhale 是国内很有名的一个开源学习组织。这个组织将渴望改变的学习者以及一群有能力有想法的青年人集结在一起&#xff0c;营造出一种互促高效的学习环境&#xff0c;一起为开源学习付出努力。 Datawhale 近期将推出三门课程的组队学习。我先将 初级算法梳理 的任务…

CSS将长文字换行的方法 (转)

大家都知道连续的英文或数字能是容器被撑大&#xff0c;不能根据容器的大小自动换行&#xff0c;下面是 CSS如何将他们换行的方法&#xff01; 对于div 1.&#xff08;IE浏览器&#xff09;white-space:normal; word-break:break-all;这里前者是遵循标准。 #wrap{white-space:n…

学Java的软件哪些比较好用

很多java程序猿在工作的时候都会用一些辅助工具&#xff0c;辅助工具可以很好的帮助程序猿高效率的完成工作&#xff0c;那么具体学Java的软件哪些比较好用呢?来看看下面的详细介绍。 学Java的软件哪些比较好用? 1. Eclipse Eclipse做为一款开发源代码的Java扩展性开发平台&a…

DataTable的Compute功能详解

在为筛选器创建表达式时&#xff0c;用单引号将字符串括起来&#xff1a;"LastName Jones"下面的字符是特殊字符&#xff0c;如下面所解释的&#xff0c;如果它们用于列名称中&#xff0c;就必须进行转义&#xff1a;\n (newline)\t (tab)\r (carriage return)~()#\…

Datawhale第九期组队学习计划

Datawhale 组队学习 第九期Datawhale组队学习计划马上就要开始啦&#xff01; 这次共组织三个组队学习&#xff0c;涵盖了编程、机器学习理论以及动手实践的内容&#xff0c;大家可以按照需要选择参加。 数据结构与算法&#xff08;上&#xff09; 内容设计&#xff1a;光城…

Hibernate获取数据java.lang.StackOverflowError

原因&#xff1a;因为在重写toString()方法时&#xff0c;把关联的属性也放入到toString方法中了&#xff0c;去掉就可以了。 如&#xff1a;重写的toString方法中不能有关联关系IDCard属性idCard public class Person {private Integer id;private String name;private IDCard…

UI设计培训之UI设计系统知识

最近有很多小伙伴都在学习UI设计的相关知识&#xff0c;很多同学都是东边一学习一下&#xff0c;西边学习一下&#xff0c;根本没有明确的学习方法&#xff0c;对于这个问题小编为大家整理了一下学习UI设计的系统知识&#xff0c;一起看看吧! UI设计培训之UI设计系统知识&#…

java 中的 Enumeration 在Vector,Hashtable和web中的应用

public interface Enumeration<E> 实现 Enumeration 接口的对象&#xff0c;它生成一系列元素&#xff0c;一次生成一个。连续调用 nextElement方法将返回一系列的连续元素。 例如&#xff0c;要输出 Vector<E> v的所有元素&#xff0c;可使用以下方法&#xff1a;…

Datawhale组队学习 Task01:数组(1天)

Task01&#xff1a;数组&#xff08;1天&#xff09; 1. 数组的定义 数组是具有一定顺序关系的若干对象组成的集合&#xff0c;组成数组的对象称为数组元素。 例如&#xff1a; 向量对应一维数组矩阵对应二维数组 数组名表示群体的共性&#xff0c;即具有同一种数据类型&a…

IOS一些显示效果和动画效果资料

2019独角兽企业重金招聘Python工程师标准>>> 1.基于AutoLayout的UIScrollView悬停Tab 转载于:https://my.oschina.net/zhugenqiang/blog/1551389

零基础小白学Java难度大不大

零基础小白学Java难度大不大?有很多人都是非常关心这个问题的&#xff0c;如今java在IT互联网行业的快速发展&#xff0c;引起了很多人的注意&#xff0c;那么&#xff0c;哪些人适合入行Java?零基础学习Java难度大吗?下面&#xff0c;小编就为大家解答这些问题。 零基础小白…

Datawhale组队学习 Task02:顺序表和链表(2天)

Task02 顺序表和链表&#xff08;2天&#xff09; 1. 线性表的定义与操作 1.1 线性表的定义 线性表&#xff08;Linear List&#xff09;是由n&#xff08;n > 0&#xff09;个相同类型的数据元素a1,a2,...,an 组成的有序序列。即表中除首尾元素外&#xff0c;其它元素有…

腾讯联姻开心网意欲何为

今天杨长升在新浪科技上看到这样一条信息“腾讯日前已收购开心网部分股份&#xff0c;有意成为开心网大股东。”据了解&#xff0c;早在8月就曾有消息称&#xff0c;腾讯已收购开心网部分股份&#xff0c;现有一位投资界人士处证实了最新的消息&#xff1a;“腾讯参股开心网确有…

在Excel单元格中使用下拉框

文章出处: http://www.cnblogs.com/huangcong/archive/2010/05/21/1740539.html 有时候我们只希望在Excel中的某个单元格中只允许输入某几个限定的数据,这时候我们就可能希望把该单元格设置成为下拉框的形式了,如下图所示: 下面就看看是怎么实现的吧. 1.我们选择一个单元格--数…

JavaScript中常见的错误,你犯了几个?

初学者在学JavaScript这门语言的时候&#xff0c;最害怕看到的&#xff0c;应该就是控制台出现的红色错误信息!其实解决这些错误并不难&#xff0c;这是大多数初学者难以跨越的一个心理障碍而已。 你只要认真看一看错误信息&#xff0c;其实解决错误是非常简单的。别说你英语不…

Datawhale组队学习 Task03:栈与递归(2天)

Task03&#xff1a;栈与递归&#xff08;2天&#xff09; 栈是我们经常使用的一种数据结构&#xff0c;如下图所示&#xff0c;手枪发射子弹的顺序与子弹压入弹夹的顺序是相反&#xff0c;即后压入弹夹的子弹先发射出来。 比如我们使用的Word、Excel、Photoshop等软件系统中的…

sql_trace的介绍

sql_trace的介绍 --打开trace文件设置&#xff0c;把sql trace设置为true&#xff0c;就会在udump目录中增加一个trc文件。alter session set sql_tracetrue;show parameter sql_trace;&#xff08;select * from v$parameter where namesql_trace;&#xff09;修改后不生效呢&…

Console-算法[if,while]-一输入两个正整数m和n,求其最大公约数和最小公倍数

ylbtech-Arithmetic:Console-算法[if,while]-一输入两个正整数m和n&#xff0c;求其最大公约数和最小公倍数1.A&#xff0c;Demo(案例)输入两个正整数m和n&#xff0c;求其最大公约数和最小公倍数。 1.程序分析&#xff1a;利用辗除法。 1.B&#xff0c;Solution(解决方案)usin…

UI设计培训之如何将设计理论与实践相结合

学习UI设计理论知识与实践技术都是要有的&#xff0c;很多人都不爱去听理论知识&#xff0c;这对以后的工作是没有任何帮助的&#xff0c;只有将设计理论与实践相结合才能帮助到自己&#xff0c;那么如何将设计理论与实践相结合?来看看本期下面的详细介绍。 如何将设计理论与实…

Datawhale组队学习 Task04:队列(2天)

Task04&#xff1a;队列&#xff08;2天&#xff09; 队列也是我们经常使用的一种数据结构&#xff0c;如下图所示&#xff0c;购物结账&#xff0c;去食堂打饭等都需要排队&#xff0c;而结账或打饭的顺序与我们排队的顺序是相同的&#xff0c;即谁先排队就为谁先服务。 比如…

ios(iphone/ipad)开发笔记(1)

CGContextRefCGContextRefiphone开发刚刚入门 求个师傅iphone拨号键盘请问自己如果做sdkOpenGL ES 2.0有没有顶点光照的例子&#xff1f;socket通信哪位大侠帮帮忙&#xff1f;如何在tableView中使用自定义的cell&#xff1f;新手求指导Iphone按大圆钮时触发什么事件flash视频转…

如何查看Linq to SQL运行时,实际执行的Sql语句

调试Linq to sql代码是&#xff0c; 如果遇到错误&#xff0c;很难判断错误的原因是什么&#xff0c;如果能够输出实际执行的sql原文&#xff0c;对于我们寻找错误的原因有有很大帮助。 以下是我用到的方法: StringBuilder sql new StringBuilder();try{using (var context n…