您的位置:首页 > 其它

第8章 高效算法设计读书笔记

2015-05-05 15:43 260 查看
算法分析初步

8.1.1 渐进时间复杂度 ——最大连续和,给出一个序列,找到i,j使连续和尽量大

使用枚举思想:枚举每一个可能的序列首和序列尾

for( i = 1; i <= n; i++){
for( j = i; j <= n; j++){
int sum = 0;
for( k = i; k <= j; k++){           // 求和
sum += a[k];
tot++;                      // 计算基本操作的数量,T(n)和n的3次方同阶
}
if( sum > best) best = sum;
}
}
8.1.2 上界分析 ——三重循环,最坏情况下内层循环需要n次,故T(n) = O(n的3次方)(上界)

对这个程序进行简化:连续子序列之和等于两个前缀和之差

for( i = 1; i <= n; i++){
for( j = i; j <= n; j++){
best = max( s[j] - s[i-1], best);  // 遍历数组,更新best值,T(n)和n的2次方同阶
}
}
8.1.3 分治法 ——分治法一般分为3个步骤

1、划分问题:划分成子问题 ; 把序列分成个数相等的两半

2、递归问题:递归解决子问题; 分别求出完全位于左半或者右半的最佳序列

3、合并问题:合并子问题的解 求出起点位于左半终点位于右半的序列,并和子问题最优解比较

递归方程 T(n) = 2T(n/2) + O(n); T(1) = 1; 的解是T(n) = O(n log n)

再谈排序和搜索

8.2.1 归并排序 ——也可以用分治法分成三个步骤

1、划分问题:划分成子问题 ; 把序列分成个数相等的两半

2、递归问题:递归解决子问题; 两半分别排序

3、合并问题:合并子问题的解 把两个有序表合并成一个

#include<stdio.h>
void sort( int* a, int x, int y, int* t);
int a[] = { 20, 12, 13 };
int t[100];
int main(void){
int i;
sort( a, 0, 3, t);							// 第三个参数是3确保指向空位置
for( i = 0; i < 3; i++) printf("%d ", a[i]);
return 0;
}
void sort( int* a, int x, int y, int* t){
int m, p, q, i;
if( y-x > 1){								// xy之间序列不为空
m = x + (y-x) / 2;						// 划分
p = x, q = m, i = x;					// 三个指针
// 事实上p和q都好理解,i = x 可以理解为这是将由x为起点的数组重新排序
sort( a, x, m, t);						// 左递归
sort( a, m, y, t);						// 右递归
while( p < m || q < y){					// 序列非空、继续合并
if( q >= y || ( p < m && a[p] <= a[q])){
/*
q >= y : 右序列为空(此时左序列不为空)
或
p < m && a[p] <= a[q])
p < m  : 左序列不为空 且
a[p] <= a[q] :  左小于右
(此时认为p为左序列最小值,而q为右序列最小值)
*/
t[i++] = a[p++];				// 把P赋值给t[i],并比较下一个元素
}
else{

/*
右序列不为空 或 左大于右
*/
t[i++] = a[q++];				// 把P赋值给t[i],并比较下一个元素
}
}
for( i = x; i < y; i++){				// 赋值回函数
a[i] = t[i];
}
}
}
逆序对数(尚没有仔细看懂)

8.2.2 快速排序 ——暂时略

8.2.3 二分查找 ——折半查找的思想,不过只针对有序序列

#include<stdio.h>
void sort( int* a, int x, int y, int* t);                              // 利用之前的分治方法进行排序
int main(){
int a[9] = { 20, 12, 13, 18, 19, 4, 7, 16, 5};
int t[100];
int x, y, m, v, i, num;
scanf("%d", &m);
sort( a, 0, 9, t);
for( i = 0; i < 9; i++) printf("%d ", a[i]);
printf("\n");
x = 0; y = 8;
num = 0;
while( x < y){                                                 // 一般二分查找写成非递归形式
v = x + (y-x) / 2;
if( a[v] == m){
printf("序号是%d\n", v+1);
break;
}
else if( a[v] <= m) x = v;
else y = v;
}
return 0;
}
void sort( int* a, int x, int y, int* t){
int m, v, i, p, q;
if( y - x > 1){
m = x + (y-x) / 2;
sort( a, x, m, t);
sort( a, m, y, t);
p = x; q = m; i = x;
while( p < m || q < y){
if( q >= y || ( p < m && a[p] < a[q])) t[i++] = a[p++];
else t[i++] = a[q++];
}
for( i = x; i < y; i++) a[i] = t[i];
}
}
如果序列中有重复,且重复的为所求数字,那么用原来的二分法就只能求得最中间的位置,需要进行一些小的修改

while( x < y){                                  // 二分查找求上界
v = x + (y-x) / 2;
if( a[v] <= m){
x = v+1;			// 此时这里很重要,否则会发生死循环
}
else y = v;				// 若求下界则是当相等时改变y
}
范围统计:给出一个序列,以及m个询问,对于每个询问(a, b),求闭区间 [a,b] 内个数

即求 a 所在的下界 L 和 b 所在的上界 R ,而后求区间 [ L, R]长度

递归与分治 ——【TBC】

贪心法 ——排序、取当前最优解

8.4.1 最优装载问题 ——装轻的比装重的划算,故将所有物体按重量从小到大排列

8.4.2 部分背包问题 ——部分选取,一定能让总重量恰好为C。

8.4.3 乘船问题 ——两个下表i,j来表示当前最重和最轻的人

If(最轻的人+最重的人 < 标准重) { num++; i++; j--;} //浪费最少

Else { num++; j--;} //此时最重的只能自己乘一条船,往下寻找能和最轻的人同乘的 最重的人

8.4.4 选择不相交区间 —— 有n个区间(Ai, Bi)。如果区间a能够完全覆盖b则选取a就是
不划算的,依据Bi将所有区间排序,那么第一个元素一定是在最优解中

排序后B1 < B2

①A1 >= A2时,区间1被区间2完全包含,所以选取区间2是不划算的

②A1 < A2时,区间1中位于A2左边的部分对结果并没有影响,真正有影响
的是区间1 中A2到B1部分,这一部分被区间2完全包含,所以选取区间2是不划算的

所以应该选取第一个区间,然后需寻找此后与此区间不相交的第一个区间

8.4.5 区间选点问题 ——小区间被满足时大区间也会被满足,所以在区间包含的情况下大区 间不用考虑。依据Bi将所有区间排序,然后取最后一个点

8.4.6 区间覆盖问题 ——进行一个预处理,所有区间在线段[s,t]外的部分都切掉,因为存在 无意义。依据Ai将所有区间排序,如果第一个区间起点不是s,则无解。否则选取最长区间,随后设置此区间尾Bi为新的起点。

8.4.7 Huffman编码 ——证明用Huffman树可以得出最优解需要以下两个结论:

①如果编码a是编码b的前缀,那么a对应的结点一定为b所对应结点的祖先。
而两个叶结点不存在祖先后代的关系

②最有前缀码一定可以写成二叉树

构造一颗最优编码树(Huffman算法):每棵子树权值等于相应字符的频率,每次 选择权值最小的树组成一颗权值为两树之和的新树,放回集合中。满足以下两个性质

①设x,y是使用频率最小的两个字符,存在前缀码使二者具有相同码长,仅有 最后一位不同(保留了最优解)

②设z为x,y的父结点,把z看成频率具有x,y频率之和的字符,那么此树是
包含z而无x,y字符的集合的最优解(最优子结构性质)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: