给定一个无重复元素的数组A[0…N-1],求找到一个该数组的局部最大值。规定:在数组边界外的值无穷小。即:A[0]>A[-1],A[N-1] >A[N]。c++
显然,遍历一遍能够找到全局最大值,而全局最大值显然是局部最大值,时间复杂度为O(N)。算法
能否有更快的办法?数组
首先给出“高原数组”定义(邹博老师自创的)。函数
定义:若子数组Array[from,…,to]知足优化
称该子数组为“高原数组”。spa
例如对于数组3,5,1,2,6,7,14,由于2<6,14>4,能够认为6,7,14就是其中的一个高原数组。
用图像形象地表示为:3d
若高原数组长度为1,则该高原数组的元素为局部最大值。code
所以寻找局部最小值的算法就变成了寻找长度为1的高原数组。
(这里特别注意!题中只要求找到一个局部最大值便可!)blog
算法描述:
使用索引left、right分别指向数组首尾,根据定义,该数组为高原数组。排序
该算法的时间复杂度为O(logN),最重要的是给了咱们一些思惟上的启发,即特别大的数据,我并不须要彻底遍历一遍,就能知道这个数据中的某些有效的局部特征。
/** * 寻找一个局部最小值 */ int local_maximum(const int* a, int n){ int left = 0; int right = n - 1; int mid; while(left < right){ mid = (right - left) / 2 + left; // 防溢出,等价于mid = (left + right) / 2 cout<<a[mid]<<endl; if(a[mid] > a[mid+1]) right = mid; else left = mid + 1; } return a[left]; }
已知数组A[0…N-1],给定某数值sum,找出数组中的若干个数(能够为0个或n个),使得这些数的和为sum。
(假定数组中的元素都大于0:A[i]>0)
例如,
对于数组1,2,3,4,5,给定sum=10,则知足条件的若干个数有:
1, 2, 3, 4
1, 4, 5
2, 3, 5
显然,这个问题是一个NP彻底问题。
针对此题,能够设置布尔向量x[0…N-1](标记数组)来表示取了哪一个元素,x[i]=0表示不取A[i],x[i]=1表示取A[i],与0-1背包问题的设置方法相似。
首先上场的是DFS递归遍历,利用函数的参数i表示当前进行到的位置,用has表示已经加入的元素当前的和,代码以下:
int a[] = {1,2,3,4,5}; int n = sizeof(a) / sizeof(int); int sum = 10; /** * 直接递归 * x[]为最终解,i为考察a[i]是否加入,has表示当前的和 */ void enum_sum_number(bool *x, int i, int has){ if(i>=n) return; if(has + a[i] == sum){ x[i] = true; print(a,x); // 自定义的输出函数 x[i] = false; } x[i] = true; enum_sum_number(x, i + 1, has + a[i]); x[i] = false; enum_sum_number(x, i + 1, has); }
而后考虑对DFS进行优化,便有了分支限界法。
考虑如何对分支进行限界,前提:数组A[0…N-1]的元素都大于0。
考察向量x[0…N-1],假定已经肯定了前i个值,如今要断定第i+1个值x[i]为0仍是1。
假定由x[0…i-1]肯定的A[0…i-1]的和为has;A[i,i+1,…N-1]的和为residue(简记为r);
has + a[i] ≤ sum
而且 has + r ≥ sum
:x[i]能够为1;has + (r - a[i]) >= sum
:x[i]能够为0;(注意,在编写代码进入分支的时候,要注意避免重复进入分支!)
代码以下:
/** * 分支限界 */ void enum_sum_number2(bool *x, int i, int has, int residue){ if(i>=n) return; if(has + a[i] == sum){ x[i] = true; print(a,x); x[i] = false; } else if(has + residue >= sum && has + a[i] <= sum){ // 注意此处是 else if ,由于若进入了 has + a[i] == sum分支, // 意味着已经选了a[i]了,因此此处不须要重复选择a[i]了 x[i] = true; enum_sum_number2(x, i + 1, has + a[i], residue - a[i]); } if(has + residue - a[i] >= sum){ x[i] = false; enum_sum_number2(x, i + 1, has, residue - a[i]); } }
数理逻辑的重要应用:分支限界的条件
思考:
(学会该方法,比此问题自己更重要)
给出一个例子:数组 -3,-5,-2,4,2,1,3 , 给定sum = 5,
符合要求的数有
-3, -2, 4, 2, 1, 3
-3, 4, 1, 3
-5, 4, 2, 1, 3
-2, 4, 2, 1
-2, 4, 3
4, 1
2, 3
DFS由于是枚举,确定可以获得正确解,关键是如何对负数的状况进行分支限界?
下面给出一种方法:
可对整个数组A[0…N-1]进行正负排序,使得负数都在前面,正数都在后面(只须要保证负数部分在前面便可,不须要保证负数部份内部也是有序的),使用剩余正数的和做为分支限界的约束:
代码以下:
/** * 一种正负排序的实现,并计算negative和positive */ void positive_negative_sort(int a[], int n, int &negative, int &positive){ int k = 0; negative = positive = 0; for(int i=0; i<n; i++) if(a[i] < 0){ negative += a[i]; swap(a[i], a[k]); ++k; } else positive += a[i]; } /** * 含有负数的分支限界 */ void enum_sum_number3(bool x[], int i, int has, int negative, int positive){ if(i >= n) return; if(has + a[i] == sum){ x[i] = true; print(a,x); x[i] = false; } if(a[i] > 0){ // 正数的状况 if(has + positive >= sum && has + a[i] <= sum){ x[i] = true; enum_sum_number3(x, i + 1, has + a[i], negative, positive - a[i]); x[i] = false; } if(has + positive - a[i] >= sum){ x[i] = false; enum_sum_number3(x, i + 1, has, negative, positive - a[i]); } } else { // 负数的状况 if(has + x[i] + positive >= sum){ x[i] = true; enum_sum_number3(x, i + 1, has + a[i], negative - a[i], positive); x[i] = false; } if(has + negative <= sum && has + positive >= sum){ x[i] = false; enum_sum_number3(x, i + 1, has, negative - a[i], positive); } } }