《算法导论》之 Problem 2.3-7 (集合,查找)
问题叙述:
*2.3-7 请给出一个运行时间Θ(n lg n )的算法,使之能在给定一个由n个整数构成的集合S和另一个整数x时,判断出S中是否存在有两个其和等于x的元素。
算法分析:
1)方法一:穷举
对于集合S,需要找出2个元素,其和为x。可以穷举S中任意2元素,求其和,若有和为x,返回true;若无,返回false。
1 bool is_sum_exist( vector< int > &S, int x )
2 {
3 if( S.empty() )
4 return false;
5 for( vector< int >::iterator it_i = S.begin(); it_i != --S.end(); ++it_i )
6 for( vector< int >::iterator it_j = it_i; it_j != --S.end(); )
7 {
8 ++it_j;
9 if( *it_i + *it_j == x )
10 return true;
11 }
12 return false;
13 }
算法时间复杂度为O( n * n )。
2)方法二:利用排序探求更高效算法
一般说来,穷举法是一个多项式可解问题的最坏解法。对于一个未知分布的结合,我们可用穷举求解;若换作一个已知分布,如非递减排序集合,求解复杂度是否有所改观呢?
对于已排序序列S,要找出两元素和为x,可用序列首部first和尾部last两元素求和:
若*first + *last < x, 则中间所有元素和*first求和都小于x;
同理,若*first + *last > x,则中间所有元素和*last求和都大于x;
1 bool is_sum_exist_permuted( vector< int > &permS, int x ) //已排序序列
2 {
3 if( permS.empty() )
4 return false;
5 vector< int >::iterator first = permS.begin();
6 vector< int >::iterator last = --permS.end();
7 while( first != last )
8 {
9 if( *first + *last < x )
10 ++first;
11 else if( *first + *last > x )
12 --last;
13 else
14 return true;
15 }
16 return false;
17 }
可知,对于已排序序列,寻找和为x的两元素只需遍历该序列一遍,复杂度为Θ( n )。
对于一个任意输入序列,可利用不同的排序算法对序列进行排序,然后调用 is_sum_exist_permuted()即可。利用quick_sort(), heap_sort(), merge_sort()可在O( n lg n )时间内完成排序;也有些有特殊适用范围排序算法,如count_sort(), radix_sort(), bucket_sort()可视情况使用。
3)方法三:线性解探求
我们知道,方法二中的时间复杂度为O( n + 排序时间 ),排序时间视算法不同有个波动范围O( n ) ~ O( n * n )。但线性排序算法,如count_sort()等,在适用上又有非常大的局限性。故而,在排序上寻求线性并不理想。hash是线性处理问题的一大利器,我们可以尝试一下。
对于序列S,进行一次遍历,将所有元素加入hash_table——O( n );
遍历该hash_talbe——O( n ),对于每个iterator,检查元素 x - *iterator是否在hash_table中——O( 1 );
该方法的主要问题是:如何处理冲突!不同的冲突处理,hash_talbe.find( x )的时间复杂度会不同。
4)其他方法讨论
数据结构是静态的算法!
A. 将S导入STL_multiset中,遍历该multiset,并find( x - *ite );(set利用rb_tree实现,复杂度为O( n lg n ) ; 也可用hashset,复杂度为O( n ) )
B. 若事先将S中所有元素对的和存储起来,可二分查找x是否存在;(但预求和却为O( n * n ) )。
本问题可参考《编程之美》:: 2.12 快速寻找满足条件的两个数。