再来看看经典的多项式求值问题:
给定一串实数An,An-1,...,A1,A0 和一个实数X,计算多项式Pn(x)的值
著名的Horner公式:
已经如何计算:
显然有:
这样只需要 N次乘法+N次加法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
//多项式求值 //N次乘法+N次加法搞定,伟大的改进! function
horner(A, x) { var
n = A.length - 1 var
p = A[n]; for
( var
j = 0; j < n; j++) { p
= x * p + A[n - j - 1]; } return
p; } //计算:
y(2) = 3x^3 + 2x^2 + x -1; var
A = [-1, 1, 2, 3]; var
y = horner(A, 2); alert(y); //33 |
多数问题:
一个元素个数为n的数组,希望快速找出其中大于出现次数>n/2的元素(该元素也称为多数元素)。通常可用于选票系统,快速判定某个候选人的票数是否过半。最优算法如下:
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
35
36
37
38
39
|
//找出数组A中“可能存在”的多数元素 function
candidate(A, m) { var
count = 1, c = A[m], n = A.length - 1; while
(m < n && count > 0) { m++; if
(A[m] == c) { count++; } else
{ count--; } } if
(m == n) { return
c; } else
{ return
candidate(A, m + 1); } } //寻找多数元素 //时间复杂度O(n) function
majority(A) { var
c = candidate(A, 0); var
count = 0; //找出的c,可能是多数元素,也可能不是, //必须再数一遍,以确保结果正确 for
( var
i = 0; i < A.length; i++) { if
(A[i] == c) { count++; } } //如果过半,则确定为多数元素 if
(count > Math.floor(A.length / 2)) { return
c; } return
null ; } var
m = majority([3, 2, 3, 3, 4, 3]); alert(m); |
以上算法基于这样一个结论:在原序列中去除两个不同的元素后,那么在原序列中的多数元素在新序列中还是多数元素
证明如下:
如果原序列的元素个数为n,多数元素出现的次数为x,则 x/n > 1/2
去掉二个不同的元素后,
a)如果去掉的元素中不包括多数元素,则新序列中 ,原先的多数元素个数/新序列元素总数 = x/(n-2) ,因为x/n > 1/2 ,所以 x/(n-2) 也必然>1/2
b)如果去掉的元素中包含多数元素,则新序列中 ,原先的多数元素个数/新序列元素总数 = (x-1)/(n-2) ,因为x/n > 1/2 =》 x>n/2 代入 (x-1)/(n-2) 中,
有 (x-1)/(n-2) > (n/2 -1)/(n-2) = 2(n-2)/(n-2) = 1/2
下一个问题:全排列
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
35
|
function
swap(A, i, j) { var
t = A[i]; A[i]
= A[j]; A[j]
= t; } function
println(msg) { document.write(msg
+ "<br/>" ); } //全排列算法 function
perm(P, m) { var
n = P.length - 1; if
(m == n) { //完成一个新排列时,输出 println(P); return ; } for
( var
j = m; j <= n; j++) { //将起始元素与后面的每个元素交换 swap(P,
j, m); //在前m个元素已经排好的基础上 //再加一个元素进行新排列 perm(P,
m + 1); //把j与m换回来,恢复递归调用前的“现场", //否则因为递归调用前,swap已经将原顺序破坏了, //导致后面生成排序时,可能生成重复 swap(P,
j, m); } } perm([1,
2, 3], 0); //1,2,3 //1,3,2 //2,1,3 //2,3,1 //3,2,1 //3,1,2 |
分治法:
要点:将问题划分成二个子问题时,尽量让子问题的规模大致相等。这样才能最大程度的体现一分为二,将问题规模以对数折半缩小的优势。
|
//打印输出(调试用) function
println(msg) { document.write(msg
+ "<br/>" ); } //数组中i,j位置的元素交换(辅助函数) function
swap(A, i, j) { var
t = A[i]; A[i]
= A[j]; A[j]
= t; } //寻找数组A中的最大、最小值(分治法实现) function
findMinMaxDiv(A, low, high) { //最小规模子问题的解 if
(high - low == 1) { if
(A[low] < A[high]) { return
[A[low], A[high]]; } else
{ return
[A[high], A[low]]; } } var
mid = Math.floor((low + high) / 2); //在前一半元素中寻找子问题的解 var
r1 = findMinMaxDiv(A, low, mid); //在后一半元素中寻找子问题的解 var
r2 = findMinMaxDiv(A, mid + 1, high); //把二部分的解合并 var
x = r1[0] > r2[0] ? r2[0] : r1[0]; var
y = r1[1] > r2[1] ? r1[1] : r2[1]; return
[x, y]; } var
r = findMinMaxDiv([1, 2, 3, 4, 5, 6, 7, 8], 0, 7); println(r);
//1,8 //二分搜索(分治法实现) //输入:A为已按非降序排列的数组 //x
为要搜索的值 //low,high搜索的起、止索引范围 //返回:如果找到,返回下标,否则返回-1 function
binarySearchDiv(A, x, low, high) { if
(low > high) { return
-1; } var
mid = Math.floor((low + high) / 2); if
(x == A[mid]) { return
mid; } else
if
(x < A[mid]) { return
binarySearchDiv(A, x, low, mid - 1); } else
{ return
binarySearchDiv(A, x, mid + 1, high); } } var
f = binarySearchDiv([1, 2, 3, 4, 5, 6, 7], 4, 0, 6); println(f);
//3 //将数组A,以low位置的元素为界,划分为前后二半 //n为待处理的索引范围上限 function
split(A, low, n) { if
(n >= A.length - 1) { n
= A.length - 1; } var
i = low; var
x = A[low]; //二个指针一前一后“跟随”, //最前面的指针发现有元素比分界元素小时,换到前半部 //后面的指针再紧跟上,“夫唱妇随”一路到头 for
( var
j = low + 1; j <= n; j++) { if
(A[j] <= x) { i++; if
(i != j) { swap(A,
i, j); } } } //经过上面的折腾后,除low元素外,其它的元素均以就位 //最后需要把low与最后一个比low位置小的元素交换, //以便把low放在分水岭位置上 swap(A,
low, i); return
[A, i]; } var
A = [5, 1, 2, 6, 3]; var
b = split(A, 0, A.length - 1); println(b[0]);
//3,1,2,5,6 //快速排序
function
quickSort(A, low, high) { var
w = high; if
(low < high) { var
t = split(A, low, w); //分治思路,先分成二半 w
= t[1]; //在前一半求解 quickSort(A,
low, w - 1); //在后一半求解 quickSort(A,
w + 1, high); } } var
A = [5, 6, 4, 7, 3]; quickSort(A,
0, A.length - 1); println(A);
//3,4,5,6,7 |
split算法的思想应用:
设A[1..n]是一个整数集,给出一算法重排数组A中元素,使得所有的负整数放到所有非负整数的左边,你的算法的运行时间应当为Θ(n)
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
35
36
|
function
sort1(A) { var
i = 0, j = A.length - 1; while
(i < j) { if
(A[i] >= 0 && A[j] >= 0) { j--; } else
if
(A[i] < 0 && A[j] < 0) { i++; } else
if
(A[i] > 0 && A[j] < 0) { swap(A,
i, j); i++; j--; } else
{ i++; j--; } } } function
sort2(A) { if
(A.length <= 1) { return ;
} var
i = 0; for
( var
j = i + 1; j < A.length; j++) { if
(A[j] < 0 && A[i] >= 0) { swap(A,
i, j); i++; } } } var
a = [1, -2, 3, -4, 5, -6, 0]; sort1(a); println(a); //-6,-2,-4,3,5,1,0 var
b = [1, -2, 3, -4, 5, -6, 0]; sort2(b); println(b); //-2,-4,-6,1,5,3,0 |