算法学习:起点
2015-05-24 21:23
183 查看
从接触软件开始,老师就教过程序=数据结构+算法。这一著名的公式概括了程序的本质。可是我在工作并没有用到什么算法,现在仔细想想就是不停的堆砌已有的API,编写一堆所谓的代码而已,在发现身边的人工作了几年却还在和我做同样的事情之后,我开始越来越不安。尽管现在我对自己也谈不上有什么规划,但我清楚这绝不是我想做的工作。所以现在通过一个简单的算法优化过程,再次学习这程序的二分之一。
闲话少说,进入正题。
话说如果有个需求:将写有数字k1,k2…kn的n个纸片放入口袋中,可以从口袋中去4次纸片,每次几下纸片上数字后将纸片再放回口袋。如果四个数字的和是m,就输出true,否则就输出false。检查是否有输出true的可能性。
样例1:输入{ 1, 3, 5 },10,则输出true
样例2:输入{ 1, 3, 5 },9,则输出false
以上解法的时间复杂度可以很容易得出—O(n4)。
可是,程序的运行都是有时间限制的。对于以上的解决方法n<50不用1秒应该就能运行完成。但是如果n<1000呢?将1000带入n4得到1012,可见四重循环还是需要运行一段时间的。这个时候我们就应该思考是否有更高效的方法来实现需求。
通过观察发现,上述四重循环最内侧做的事情就是:
检查是否有d使得n[a] + n[b] + n[c] + n[d] = m
通过对式子进行移向,得到:
检查是否有d使得n[d] = m - n[a] - n[b] - n[c]
换句话说就是,检查数组中是否有元素m - n[a] - n[b] - n[c]。
这时候,我们就可以考虑一种快速检查的方法了。
二分查找每次将查找的区域缩小至原来的一半,因此,要判断长度为n的有序数列k中是否有x,只要反复执行log2n次就可以完成了。所以二分查找的时间复杂度是O(logn),这种阶的运行时间被称为对数时间。这样即使n变得恨到,对数时间也会非常迅速。
这样的话,将内侧循环替换为二分查找之后,就变成
排序时间:O(nlogn)
循环时间:O(n3logn)
O(n3logn)比O(nlogn)大,所以算法的时间复杂度就是O(n3logn)。
以上,是对四重循环的最内侧循环,如果着眼于内层的两个循环。
一刚才同一思路,
检查是否有c和d使得n[d] + n[c] = m - n[a] - n[b]
很明显,这样不能直接使用刚才的二分查找了。但是,如果可以预先将n[d] + n[c]的结果枚举并排好序,刚才的思路就行的通了。
将n[d] + n[c]结果枚举,去除重复后个数为n(n+1)/2个。
这时:
排序时间:O(n2logn)
循环时间:O(n2logn)
总的来说,算法的时间复杂度就是O(n2logn)。这样的话,n=1000也能够妥善应对了。
完整代码:抽签算法代码
闲话少说,进入正题。
话说如果有个需求:将写有数字k1,k2…kn的n个纸片放入口袋中,可以从口袋中去4次纸片,每次几下纸片上数字后将纸片再放回口袋。如果四个数字的和是m,就输出true,否则就输出false。检查是否有输出true的可能性。
样例1:输入{ 1, 3, 5 },10,则输出true
样例2:输入{ 1, 3, 5 },9,则输出false
一、O(n4)
对于这个问题,最简单的就是采用多重循环来完成,也是做容易的想到的。private static boolean drawMethod(int[] n, int m) { boolean result = false; // 循环枚举所有可能 for (int a = 0; a < n.length; a++) { for (int b = 0; b < n.length; b++) { for (int c = 0; c < n.length; c++) { for (int d = 0; d < n.length; d++) { if (n[a] + n[b] + n[c] + n[d] == m) { result = true; } } } } } return result; }
以上解法的时间复杂度可以很容易得出—O(n4)。
可是,程序的运行都是有时间限制的。对于以上的解决方法n<50不用1秒应该就能运行完成。但是如果n<1000呢?将1000带入n4得到1012,可见四重循环还是需要运行一段时间的。这个时候我们就应该思考是否有更高效的方法来实现需求。
通过观察发现,上述四重循环最内侧做的事情就是:
检查是否有d使得n[a] + n[b] + n[c] + n[d] = m
通过对式子进行移向,得到:
检查是否有d使得n[d] = m - n[a] - n[b] - n[c]
换句话说就是,检查数组中是否有元素m - n[a] - n[b] - n[c]。
这时候,我们就可以考虑一种快速检查的方法了。
二、O(n3logn)
关于在查找,比较容易想到,速度也比较快的就是二分查找。二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好;其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。/** * 递归二分查找 * * @param Array * @param low * @param high * @param key * @return */ private static boolean binarySearch(int Array[], int low, int high, int key) { if (low <= high) { int mid = (low + high) / 2; if (key == Array[mid]) return true; else if (key < Array[mid]) // 移动low和high return binarySearch(Array, low, mid - 1, key); else if (key > Array[mid]) return binarySearch(Array, mid + 1, high, key); } return false; }
二分查找每次将查找的区域缩小至原来的一半,因此,要判断长度为n的有序数列k中是否有x,只要反复执行log2n次就可以完成了。所以二分查找的时间复杂度是O(logn),这种阶的运行时间被称为对数时间。这样即使n变得恨到,对数时间也会非常迅速。
private static boolean drawMethod2(int[] n, int m) { boolean result = false; Arrays.sort(n); // 进行排序 for (int a = 0; a < n.length; a++) { for (int b = 0; b < n.length; b++) { for (int c = 0; c < n.length; c++) { if (binarySearch(n, 0, n.length - 1, m - n[a] - n[b] - n[c])) { result = true; } } } } return result; }
这样的话,将内侧循环替换为二分查找之后,就变成
排序时间:O(nlogn)
循环时间:O(n3logn)
O(n3logn)比O(nlogn)大,所以算法的时间复杂度就是O(n3logn)。
三、O(n2logn)
但是,将1000带入O(n3logn),会发现这依然是有很大的时间开销,必须对算法进一步优化。以上,是对四重循环的最内侧循环,如果着眼于内层的两个循环。
一刚才同一思路,
检查是否有c和d使得n[d] + n[c] = m - n[a] - n[b]
很明显,这样不能直接使用刚才的二分查找了。但是,如果可以预先将n[d] + n[c]的结果枚举并排好序,刚才的思路就行的通了。
将n[d] + n[c]结果枚举,去除重复后个数为n(n+1)/2个。
private static boolean drawMethod3(int[] n, int m) { boolean result = false; int nn[] = new int[n.length*n.length] ; // 枚举n[c]+n[d]的和 for (int c = 0; c < n.length; c++) { for (int d = 0; d < n.length; d++) { nn[c * n.length + d] = n[c] + n[d]; } } Arrays.sort(nn); for (int a = 0; a < n.length; a++) { for (int b = 0; b < n.length; b++) { if (binarySearch(nn, 0, nn.length - 1, m - n[a] - n[b])) { result = true; } } } return result; }
这时:
排序时间:O(n2logn)
循环时间:O(n2logn)
总的来说,算法的时间复杂度就是O(n2logn)。这样的话,n=1000也能够妥善应对了。
四、小结
通过以上一步步的优化,总的来说在时间复杂度上已经下降很多,也算是我的第一步,当然很多事情都不能一蹴而就,积跬步才能至千里,希望可以在这条路上走的更远。完整代码:抽签算法代码
相关文章推荐
- CCF ADL 78 深度学习讲习班回顾:这几个大热领域的算法到应用,8位学术大牛带你掌握人工智能前沿技术
- MachineLearning-----感知器学习算法
- Surf算法学习心得(三)——OpenCV:SURF算法浅析
- 学习iOS、React Native、算法开发的建议
- 数据结构排序算法_选择排序算法学习
- C++ Primer 学习笔记_45_STL实践与分析(19)--泛型算法的结构
- 数据科学之机器学习5:分类之k-近邻算法
- 学习算法的一点体会
- Java虚拟机学习 - 垃圾收集算法
- 算法与数据结构学习 10 链表操作
- 算法学习笔记:FFT
- C 字节对齐.我的算法学习之路
- 【算法学习笔记】80.二维动态规划 SJTU OJ 3022 二哥要翘课
- 算法学习的轨迹
- 算法学习之Asymptotic Analysis
- 推荐算法学习-推荐系统老司机的十条经验
- 【算法学习】线性时间排序-计数排序、基数排序和桶排序详解与编程实现
- 路径规划算法学习网址
- 我的算法学习之路
- 【算法学习笔记】17.暴力求解法05 隐式图搜索1 迭代加深搜索 埃及分数