普林斯顿大学算法课程第三周个人总结
2013-09-18 13:47
281 查看
这次的总结拖到现在才写,主要是因为刚刚才拿到第三周编程作业的满分,算法效率的最后一个测试迟迟不达标,昨晚又在论坛里翻看了很多讨论帖,今早进行了一些优化,终于通过了最后一个测试。
经过三周以后的学习,总结起来,算法课就是在课堂上讲解很多理论知识,从算法的产生到历经多年的改进与优化等,重在思想的领会,而编程作业则是要求忽略算法实现细节,使用Java现有的算法库,重在算法的实际使用。
第三周是应用最为广泛的两个排序算法:merge-sort和quick-sort,不过这次总结的重点是折磨了我很多天的编程作业。作业任务的要求是在一个平面上,给定任意N个位置的点,定位出所有能通过4个以上点(包括4个点)的线段。当然不能出现线段覆盖的情况,比如有线段通过5个点,那么该线段上前4个点和后4个点构成的线段就不能被计算在内。(GitHub仓库页面:https://github.com/Revil/algs-collinear,仓库地址:https://github.com/Revil/algs-collinear.git)
最简单粗暴的算法应该就是以其中一个点作为起点,然后计算出与其他点的斜率,斜率相同的点达到4个,就构成了一条线段。实现起来很简单,4个for循环,从前往后依次遍历所有点。当然效率也是极差的(N^4),而且避免不了上面提到的线段覆盖的问题。不过在这里我还犯了一个错误:没有在开始之前将所有的点按照坐标排序,以至于后面即使定位到了线段,但是线段的起点选在了中间,导致线段绘制的时候出错。
根据编程作业的说明,快速的实现方式是选取某一个点之后,按照这个点与其他点的斜率大小进行排序,这样当排序完成以后,相同斜率的点就靠在了一起,只要相邻近的3个以上的点斜率相同,那么这些点和起点在一起就构成了一条线段。
这种实现方式乍看起来很容易实现,而且算法效率也可以达到N^2* logN的效果,不过实际做起来陷阱重重。
为了避免出现线段起点选在线段中间的情况,依然是需要先将所有的点按照坐标进行排序。然后从前往后遍历,每选一个点,就将其后续的所有点按照与该起点的斜率进行排序。然后定位到3个以上相邻且斜率相同的点。当然为了避免按斜率排序扰乱后续点的原先顺序,需要对按坐标排序的点进行一次备份,每次重新选起点的时候,就将之前的顺序再重新恢复,以便进行下一次的斜率排序。
这是最开始的做法,基本达到了要求,但是没有避免掉线段覆盖的情况,比如有线段a->b->c->d->e,坐标依次递增,当遍历到b的时候,b->c->d->e也构成一个线段,但是实际上这条线段之前已经选出,所以还需要判断b之前的点有没有与b到c斜率相同的点。起初改进的方式是每次检测到可以构成线段的时候,从0到起点遍历所有点与当前起点的斜率,一旦发现斜率相同的点,则忽略该线段。
但是这种做法,最好的时候也无法通过最后一个效率测试。在寻找重复线段的过程,还可以进一步优化,在选定某一起点后,除了对其后续的点按照与该起点的斜率排序以外,前面的点也按照这种方式重新排序,并且在前面的点中也使用一个索引(称其为前索引)从0到起点,和后面的索引一起向前移动,当在后续点中找到一个线段之后,就移动前索引直到遇见大于或等于该斜率值的点时就停止移动,前者表示该线段有效,而后者表示线段已经存在,直接忽略。这样保证了对于同一起点来说,每找到一个新的线段,不必再从0开始搜索,直接从前索引当前位置继续向前寻找即可。按照这种方式修改之后,顺利通过了最后一个测试。
注意所有的这些操作都是在,所有点按照自然顺序(也就是按照坐标排序)的基础上,才能保证不会出现遗漏或者重复的情况。
除了这些基本的思路以外,还有很多时候需要提防边界性的错误,这些边界值往往也能决定程序能不能够按照预定思路正常执行。
经过三周以后的学习,总结起来,算法课就是在课堂上讲解很多理论知识,从算法的产生到历经多年的改进与优化等,重在思想的领会,而编程作业则是要求忽略算法实现细节,使用Java现有的算法库,重在算法的实际使用。
第三周是应用最为广泛的两个排序算法:merge-sort和quick-sort,不过这次总结的重点是折磨了我很多天的编程作业。作业任务的要求是在一个平面上,给定任意N个位置的点,定位出所有能通过4个以上点(包括4个点)的线段。当然不能出现线段覆盖的情况,比如有线段通过5个点,那么该线段上前4个点和后4个点构成的线段就不能被计算在内。(GitHub仓库页面:https://github.com/Revil/algs-collinear,仓库地址:https://github.com/Revil/algs-collinear.git)
最简单粗暴的算法应该就是以其中一个点作为起点,然后计算出与其他点的斜率,斜率相同的点达到4个,就构成了一条线段。实现起来很简单,4个for循环,从前往后依次遍历所有点。当然效率也是极差的(N^4),而且避免不了上面提到的线段覆盖的问题。不过在这里我还犯了一个错误:没有在开始之前将所有的点按照坐标排序,以至于后面即使定位到了线段,但是线段的起点选在了中间,导致线段绘制的时候出错。
for (int i = 0; i < N; i++) for (int j = i+1; j < N; j++) for (int k = j+1; k < N; k++) for (int l = k+1; l < N; l++) if (a[i].slopeTo(a[j]) == a[i].slopeTo(a[k]) && a[i].slopeTo(a[j]) == a[i].slopeTo(a[l])) { StdOut.println(a[i].toString() + " -> " + a[j].toString() + " -> " + a[k].toString() + " -> " + a[l].toString()); a[i].drawTo(a[l]); }
根据编程作业的说明,快速的实现方式是选取某一个点之后,按照这个点与其他点的斜率大小进行排序,这样当排序完成以后,相同斜率的点就靠在了一起,只要相邻近的3个以上的点斜率相同,那么这些点和起点在一起就构成了一条线段。
这种实现方式乍看起来很容易实现,而且算法效率也可以达到N^2* logN的效果,不过实际做起来陷阱重重。
为了避免出现线段起点选在线段中间的情况,依然是需要先将所有的点按照坐标进行排序。然后从前往后遍历,每选一个点,就将其后续的所有点按照与该起点的斜率进行排序。然后定位到3个以上相邻且斜率相同的点。当然为了避免按斜率排序扰乱后续点的原先顺序,需要对按坐标排序的点进行一次备份,每次重新选起点的时候,就将之前的顺序再重新恢复,以便进行下一次的斜率排序。
/* first sort the array by coordinates */ Arrays.sort(a); Point[] aux = new Point ; /* use one point each as the origin, sort the array using slope order */ /* once all the segments related with the point is over, just leave it */ for (int i = 0; i < N-3; i++) { for (int j = i; j < N; j++) aux[j] = a[j]; Arrays.sort(aux, i+1, N, aux[i].SLOPE_ORDER); int head = i+1; int tail = i+2; while (tail < N) { /* if equal slopes encountered, just move tail forward */ while (tail < N && aux[i].slopeTo(aux[head]) == aux[i].slopeTo(aux[tail])) tail++; /* on slopes not equal, check if we have a segment */ if (tail - head >= 3) { /* we have a segment here */ aux[i].drawTo(aux[tail-1]); String output = aux[i].toString() + " -> "; for (int l = head; l < tail-1; l++) output += (aux[l].toString() + " -> "); output += aux[tail-1].toString(); StdOut.println(output); } /* move head to last not equal slope point */ head = tail; tail = tail+1; } }
这是最开始的做法,基本达到了要求,但是没有避免掉线段覆盖的情况,比如有线段a->b->c->d->e,坐标依次递增,当遍历到b的时候,b->c->d->e也构成一个线段,但是实际上这条线段之前已经选出,所以还需要判断b之前的点有没有与b到c斜率相同的点。起初改进的方式是每次检测到可以构成线段的时候,从0到起点遍历所有点与当前起点的斜率,一旦发现斜率相同的点,则忽略该线段。
/* we have a segment here */ /* make sure it's not a duplicate or overlapping one */ int k; for (k = 0; k < i; k++) if (aux[k].slopeTo(aux[i]) == aux[i].slopeTo(aux[head])) break; if (k >= i) { aux[i].drawTo(aux[tail-1]); String output = aux[i].toString() + " -> "; for (int l = head; l < tail-1; l++) output += (aux[l].toString() + " -> "); output += aux[tail-1].toString(); StdOut.println(output); } }
但是这种做法,最好的时候也无法通过最后一个效率测试。在寻找重复线段的过程,还可以进一步优化,在选定某一起点后,除了对其后续的点按照与该起点的斜率排序以外,前面的点也按照这种方式重新排序,并且在前面的点中也使用一个索引(称其为前索引)从0到起点,和后面的索引一起向前移动,当在后续点中找到一个线段之后,就移动前索引直到遇见大于或等于该斜率值的点时就停止移动,前者表示该线段有效,而后者表示线段已经存在,直接忽略。这样保证了对于同一起点来说,每找到一个新的线段,不必再从0开始搜索,直接从前索引当前位置继续向前寻找即可。按照这种方式修改之后,顺利通过了最后一个测试。
/* sort points before and after point[i] by slope */ Arrays.sort(aux, i+1, N, aux[i].SLOPE_ORDER); Arrays.sort(aux, 0, i, aux[i].SLOPE_ORDER);
注意所有的这些操作都是在,所有点按照自然顺序(也就是按照坐标排序)的基础上,才能保证不会出现遗漏或者重复的情况。
除了这些基本的思路以外,还有很多时候需要提防边界性的错误,这些边界值往往也能决定程序能不能够按照预定思路正常执行。
相关文章推荐
- 普林斯顿大学算法课程第二周个人总结
- 普林斯顿大学算法第一周个人总结2
- 普林斯顿大学算法第一周个人总结2
- 普林斯顿大学算法第一周个人总结1
- 普林斯顿大学算法第一周个人总结1
- 机器学习常见算法个人总结
- PCA降维算法总结以及matlab实现PCA(个人的一点理解)
- 数据挖掘十大经典算法个人总结
- 普林斯顿大学算法Week1: Percolation 渗透(98分)--总结及代码
- 机器学习常见算法个人总结(面试用)
- PCA降维算法总结以及matlab实现PCA(个人的一点理解)
- 课程个人总结
- 算法——排序算法个人总结
- 深度学习个人总结之四----自编码算法(AutoEncoder)
- 算法课程Leetcode作业第三周技术博客
- 机器学习常见算法个人总结
- 【算法基础个人常用总结】<-------持续更新------->
- PCA降维算法总结以及matlab实现PCA(个人的一点理解)
- 牛客网左程云老师的算法视频个人总结
- 算法分析与设计课程总结