编程珠玑I算法总结
寒假抽空看了下编程珠玑,最大的感受便是编程也是一门艺术,期间也引导我思考了一些问题,回学校后打算做一个总结,主要是根据编程珠玑后面的Algorithm附录总结了一下这本书里面的经典算法。
1 辗转相减求最大公约数
思想:最大公约数能整除i和j,则其一定也能整除i-j(if i>j)
int gcd(int i, int j)
{
while(i != j)
{
if(i > j) i-=j;
else j-=i;
}
return i;
}
2 快速求取x的n次方
思想:充分利用了已经计算出来的数据防止重复计算来减少了算法运行时间
function exp(x,n)
//pre n>=0 //post result = x^n
{
if n=0 return 1;
else if even(n)
return square(exp(x,n/2));
else return x*exp(x,n-1);
}
3 计算 y的多项式,
思想:迭代利用前面计算的结果计算后面
y = a[n];
for i in [n-1,0]
y = y*x + a[i];
4 从[l,u]随机取数,数的范围可到30bit。
32位机上RAND_MAX是0x7fff
int bigrand() { return RAND_MAX*rand() + rand(); }
int randint(int l, int u) { return l + bigrand()%(u-l+1); }
5 从n中随机地取m个元素,并按序输出
洗牌算法:
void genshuf(int m, int n)
{
int *x = new int[n];
for(int i=0; i < n; i++)
x[i] = i;
for(int i=0; i < m; i++)
{
j = randint(i,n-1); //洗牌的精髓
swap(x[i],x[j]);
}
sort(x,x+m);//保证按序输出
for(int i=0; i < m; i++)
cout<<x[i]<<endl;
}
来算算概率,开始整个数组以固定方式初始化。接着每个元素以rand方式和所有元素交换,这保证了随机,时间复杂度O(nlogn)
Knuth算法,来自《计算机程序设计艺术》:
//////////////////////////////////////////////////////////////////////////
//pre: 0<=m<=n , n is 0 to n-1
//post: print m numbers selected from n
void GenKnuth(int m, int n)
{
for(int i=0; i < n; i++)
{
if((rand()%(n-i)) < m)//此时选取概率为toSelected/Remaining
{
std::cout<<i<<" ";
m--;
}
}
}
时间复杂度O(n)。
集合算法:
//
Initialize set S to empty
size = 0
while size < m do
t = bigrand() % n
if t is not in S
insert t into S
size++
print the elements of S in sorted order
这个算法的缺点在于如果m和n接近的时候,比较失败(已在集合的概率)会很大从而降低性能,时间复杂度也为O(nlogn)
改进集合算法:
//
void genfloyd(int m, int n)
{
set<int> S;
set<int>::iterator i;
for(int j=n-m; j < n; j++)
{
int t = bigrand()%(j+1);
if(S.find(t) == S.end())
{
S.insert(t); // t not in S
}
else
{
S.insert(j); // t in S
}
}
输出所有数字
}
6 Problem: Rotate a one-dimensional vector of n elements left by i positions.
■常规解法:1 copy the first i elements of x to a temporary array, movint the remaining n - i elements left i places, and then copying the first i from temporary array back to the last positions in x.这个算法需要额外的内存。2 we could define a function to rotate x left one position and call it i times,这个算法很耗时。
■Juggling act,杂耍算法: move x[0] to the temporary t, then move x[i] to x[0], x[2i] to x[i], and so on (taking all indices into x modulo n),until we come back to taking an element from x[0], at which point we instead take the element from t and stop the process. 这个算法结合了常规两个算法的优点,其中最关键的在于对需要执行次数的证明,即移动多少回(回到原来起点)才能覆盖所有数组?答案是,gcd(i,n),证明请点这里:
■翻转算法,这个算法起源于这样一个思想:Rotating the vector x is really just swapping the two segments of the vector ab to be the vector ba, where a represents the first i elements of x.由这引发了如下算法, 定义reverse(i,j),指把vector中i到j的元素翻转,那么这个代码如下:
reverse(0,i-1)
reverse(i,n-1)
reverse(0,n-1)
7 Problem: 转置矩阵
在每个矩阵数据前面加一个行号列号的数据,对这些数据先按列号进行排序再按行号排,去除前置数据。
8 Problem: maxmum sum of any contiguous subvector of a vector.
四种解法见#bookmark=id.v94teif5zuqe
9 查找字典中的回文
1 对字典中每个单词中的字母按字典序排序
2 对字典中的每个单词按字典序排序
3 回文的单词最后都被排在了一起。
10 求文本中的最长重复子串
利用后缀数组进行求解
char* FindLCS(const char* str, int len)
{
//构建后缀数组
const char** suffix = new const char*[len];
int i=0;
for(; i < len; i++)
{
suffix[i] = &str[i];
}
qsort(suffix,i,sizeof(char*),pstrcmp);//对后缀数组排序
//通过查找相邻字符串,求最长重复子串
int maxlen = 0;
int maxi = -1;
for(int l=0; l < i-1; l++)
{
int temp = 0;
if((temp = comlen(suffix[l],suffix[l+1])) > maxlen)
{
maxlen = temp;
maxi = l;
}
}
char* result = new char[len];
if(maxi != -1)
{
strncpy_s(result,len,suffix[maxi],maxlen);
}
delete [] suffix;
return result;
}
其中用到如下函数:
//给qsort用的比较函数
//pre: p1,p2指向字符串,并以'\0'结尾
//post: 如果函数的第一个参数小于第二个参数,返回负值;如果等于返回零值;如果大于返回正值
int pstrcmp(const void* p1, const void* p2)
{
return strcmp( *(char* *)p1, *(char* *)p2);
}
//求出p和q的最长公共字符数
//pre: p为母串,q为查找串,p比q长,p最后以'\0'结尾
//post: 返回所求数字
int comlen(const char* p,const char* q)
{
int i=0;
while(*p && *p++ == *q++)
i++;
return i;
}
11 寻找第k小的数字
改进快速排序算法
12 堆排序的操作 siftdown siftup(比创建堆,调整堆更原子点的操作,感觉归纳的 挺好)
见书
13 查找问题
一般解法:
顺序搜索, 可用哨兵优化
二分搜索,伪代码:
l = 0; u = n-1;
loop
if(l>u)
p = -1;break;
m = l + (u-l)/2;
case
x[m]<t: l = m+1;
x[m]>t: u = m-1;
x[m] ==t: p=m; break;
用二分求第一个出现的数据(有重复数据的时候)
l = -1; u = n;
while l+1 != u
{
m = (l + u) / 2;
if(x[m] < t)
l = m;
else
u = m;
}
p = u;
if(p >= n || x[p] != t)
p = –1
14 排序问题
一般解法:
插入排序,O(n^2), 部分有序时非常快速能在O(n)时间内解决,故快速排序再分治到小规模数据时用插入排序能够提高效率。稳定的排序算法。
for i = [1,n) t = x[i]; for(j=i; j>0 && x[j-1]>t; j--) x[j] = x[j-1]; x[j] = t;
(5行代码搞定)
快速排序,平均情况O(nlogn),最坏情况(O(n^2),空间O(n)(递归堆栈)
两种代码,前者为算法导论采用,后者是前者的优化版,比前者快常数因 子。
MIT’s(11行代码):
//pre: x[l..u]
//post:x[l..u] in increasing order
void qsort(l,u)
{
if(l >= u)
return;
//partition
i = l;
for j = [l+1,u]
/*invariant: x[l+1..i]<x[l] && x[i+1..j-1]>=x[l]*/
if(x[j] < x[l])
swap(x[++i],x[j]);
swap(x[i],x[l]);
qsort(l,i-1);
qsort(i+1,u);
}
Better Version(15行代码):
void qsort(l,u)
{
if(l >=u)
return;
t = x[l]; i = l; j = u+1;
loop
do i++ while i<=u && x[i]<t
do j -- while x[j]>t
if(i>j)
break;
swap(x[i],x[j]);
swap(x[l],x[j]);
qsort(l,j-1);
qsort(j+1,u);
}
Heap Sort:时间一直是O(nlogn),是渐进最优的排序算法。
for i = [2,n)
siftup(i);
for(i=n; i>=2; i--)
swap(1,i);
siftdown(i-1);
(5行代码搞定)
Bitmap Sort
一般需满足很数的范围在一定区间以及不重复(可不满足)等限制
for i = [0,n)
bit[i] = 0
for each i in the input file
bit[i] = 1
for i = [0,n)
if bit[i] == 1
write i on the output file