背景
经理叫我优化一段代码. 因为发现当数据量太大时 运行时间则会变得很久. 这个代码在软件里已经存在 4年之久了. 最开始我们都不知道是谁写的这代码 但之后发现 竟然是经理自己 4年前写的代码. 他自己都不记得了(代码里没有注释).
算法的目的是将一个密集点集重新按照一定的分辨率筛选. 使得任意两点之间的距离要大于这个采样分辨率 并且点的个数要尽量的多.
原始的实现算法
经理原始的算法 是通过迭代每次选出一个点(为了方便 就选第一个点) 然后把候选集中和此点距离大于分辨率的点记录下来做为下一轮的候选集. 实现则用了LINQ的where 看起来只有O(n) 循环但是 其实是 O(n^2) 因为每次LINQ从当前候选集选出新的点这个操作就是 O(n).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public List<Point3F> Resampling(double resolution, List<Point3F> tempPtList) { var samplePtList = new List<Point3F>(); Point3F currentPt; double T = resolution * resolution; while (tempPtList.Count != 0) { currentPt = new Point3F(tempPtList[0].X, tempPtList[0].Y, tempPtList[0].Z); tempPtList = tempPtList.Where(x => (Math.Pow(x.X - currentPt.X, 2) + Math.Pow(x.Y - currentPt.Y, 2) + Math.Pow(x.Z - currentPt.Z, 2)) >= T).ToList(); samplePtList.Add(currentPt); } return samplePtList; } |
public List<Point3F> Resampling(double resolution, List<Point3F> tempPtList) { var samplePtList = new List<Point3F>(); Point3F currentPt; double T = resolution * resolution; while (tempPtList.Count != 0) { currentPt = new Point3F(tempPtList[0].X, tempPtList[0].Y, tempPtList[0].Z); tempPtList = tempPtList.Where(x => (Math.Pow(x.X - currentPt.X, 2) + Math.Pow(x.Y - currentPt.Y, 2) + Math.Pow(x.Z - currentPt.Z, 2)) >= T).ToList(); samplePtList.Add(currentPt); } return samplePtList; }
O(n) 哈希表 实现
HASH 哈希表是非常有用的数据结果 因为它的查询和添加记录的时间复杂度可以内为是O(1) 常量级. 就因为这样 很多算法在用了哈希表的话都能降至少一个数量级 比如 O(n^2) 到 O(n). 你可以自己写哈希算法 但是.NET系统提供的已经很好了 碰撞的概率已经比较小了.
小于分辨率 相邻的点就应该有一样的哈希键值. 我们可以假设这些点在3维空间中被放在间隔分辨率的网格中. 那么分配在一个网络里的点就一定是相邻的(小于一个分辨率的). 但是反过来却不一定. 有可能相邻的点却分配在邻居. 我们可以通过两轮筛选. 第一轮通过哈希排队绝大部分的点. 第二轮则轻轻松松(穷举就可以了) 排除漏网之鱼.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | public List<Point3F> Resampling(double resolution, List<Point3F> tempPtList) { var samplePtList = new List<Point3F>(); var added = new Hashtable(); for (var i = 0; i < tempPtList.Count; i ++) { var cur = tempPtList[i]; // 三维网络中的位置 int x = (int)Math.Round(cur.X / resolution); int y = (int)Math.Round(cur.Y / resolution); int z = (int)Math.Round(cur.Z / resolution); var key = x + "," + y + "," + z; // hash key if (added.ContainsKey(key)) continue; // 已经添加过了 不再添加 var currentPt = new Point3F(tempPtList[i].X, tempPtList[i].Y, tempPtList[i].Z); samplePtList.Add(currentPt); added.Add(key, key); // 记录一下这个点 } var T = resolution*resolution; for (var i = 0; i < samplePtList.Count; i ++) // 第二轮过滤 { var cur = samplePtList[i]; samplePtList.RemoveAll( // 排除相邻的点 x => ( ((x != cur) && ((x.X - cur.X) * (x.X - cur.X) + (x.Y - cur.Y) * (x.Y - cur.Y) + (x.Z - cur.Z) * (x.Z - cur.Z)) < T ) )); } return samplePtList; } |
public List<Point3F> Resampling(double resolution, List<Point3F> tempPtList) { var samplePtList = new List<Point3F>(); var added = new Hashtable(); for (var i = 0; i < tempPtList.Count; i ++) { var cur = tempPtList[i]; // 三维网络中的位置 int x = (int)Math.Round(cur.X / resolution); int y = (int)Math.Round(cur.Y / resolution); int z = (int)Math.Round(cur.Z / resolution); var key = x + "," + y + "," + z; // hash key if (added.ContainsKey(key)) continue; // 已经添加过了 不再添加 var currentPt = new Point3F(tempPtList[i].X, tempPtList[i].Y, tempPtList[i].Z); samplePtList.Add(currentPt); added.Add(key, key); // 记录一下这个点 } var T = resolution*resolution; for (var i = 0; i < samplePtList.Count; i ++) // 第二轮过滤 { var cur = samplePtList[i]; samplePtList.RemoveAll( // 排除相邻的点 x => ( ((x != cur) && ((x.X - cur.X) * (x.X - cur.X) + (x.Y - cur.Y) * (x.Y - cur.Y) + (x.Z - cur.Z) * (x.Z - cur.Z)) < T ) )); } return samplePtList; }
结果
相同的输入数据下 新算法闪电般的就完成了 而在优化前 则需要运行 20分钟.
更新 使用 Hashset 数据结构就可以了 这样内存更加有效率.
英文: https://helloacm.com/refactoring-re-sampling-algorithm-using-on-hashtable/
强烈推荐
- 英国代购-畅购英伦
- TopCashBack 返现 (英国购物必备, 积少成多, 我2年来一共得了3000多英镑)
- Quidco 返现 (也是很不错的英国返现网站, 返现率高)
- 注册就送10美元, 免费使用2个月的 DigitalOcean 云主机(性价比超高, 每月只需5美元)
- 注册就送10美元, 免费使用4个月的 Vultr 云主机(性价比超高, 每月只需2.5美元)
- 注册就送10美元, 免费使用2个月的 阿里 云主机(性价比超高, 每月只需4.5美元)
- 注册就送20美元, 免费使用4个月的 Linode 云主机(性价比超高, 每月只需5美元) (折扣码: PodCastInit2022)
- PlusNet 英国光纤(超快, 超划算! 用户名 doctorlai)
- 刷了美国运通信用卡一年得到的积分 换了 485英镑
- 注册就送50英镑 – 英国最便宜最划算的电气提供商
- 能把比特币莱特币变现的银行卡! 不需要手续费就可以把虚拟货币法币兑换
微信公众号: 小赖子的英国生活和资讯 JustYYUK