数据结构与算法分析(C语言描述)是一本比较全面的算法书籍。我将对书中内容结合其他知识进行总结。
首先,算法(algorithm)是为求解一个问题需要遵循的,被清楚地指定的简单指令的集合。
切记,一个需要1G字节内存的算法是没用的(我做web开发时,自己写的算法总是导致页面崩溃,血的教训啊)。
贯穿全文的四个数学定义:
定义1:如果存在正常数c和n0使得当N>=n0时T(N)=<cf(N),则记为T(N)= O(f(N));(大O)即T(N)的增长率小于等于f(N)的增长率。
定义2:如果存在正常数c和n0使得当N>=n0时T(N)>=cg(N),则记为T(N)=Ω(g(N));T(N)的增长率大于等于g(N)的增长率。
定义3:T(N)=theta(h(N))当且仅当T(N)=O(h(N))和T(N)=(h(N));T(N)的增长率等于h(N)的增长率。
定义4:如果对所有的常数c存在n0使得当N>n0时T(N)<cp(N),则记为T(N)=o(P(N))(小o);T(N)的增长率小于P(N)的增长率。
大O和小o区别在于大O包含增长率相同这种可能性。
更重要的是要记住下面的三个法则:
法则1:如果T1(N)=O(f(N)),T2(N)=O(g(N)),那么
T1(N)+T2(N)= O(f(N)+g(N)),T1(N)*T2(N)=O(f(N)*g(N))
法则2:如果T(N)是一个k次多项式,则T(N)和N的k次方增长率相等。
法则3:对于任意常数k,以2为底N的对数的k次方=O(N)。它告诉我们对数的增长的非常缓慢
运行时间计算的一般法则:
法则1:for循环,一个for循环的运行时间至多是该for循环内语句(包括测试)的运行时间乘以迭代的次数。
法则2:嵌套循环,从里向外分析这种循环,在一组嵌套循环内部的一条语句总得运行时间为该语句的运行时间乘以该组所有循环的大小得乘积。
for(int i =0; i < n; i++)
for(int j = 0; j < n; j++)
K++
该程序为O(N*N)
法则3:顺序语句 将各个语句的运行时间求和。
for(int i = 0; i < n; i++)
a [i] = 0;
for(i = 0; i < n; i++)
for(int j = 0; j < n; j++)
a [i] = a[i-1]+a[i-2];
总量为O(N*N);
法则4:If/Else语句,对于程序片段
if(condition)
S1
else
S2
一个if/else 语句的运行时间从不超过判断再加上S1和S2中运行时间较长者的总得运行时间。
分析的基本策略是从内部(或最深层部分)向外展开工作。
最大子程序和问题的解
算法1
int maxSubsum1(const vector<int>& a)
{
int maxSum = 0;
for(int i = 0; i < a.size(); i++)
for(int j = i; j < a.size(); j++)
{
int thisSum = 0;
for(int k = i; k <= j; k++)
thisSum += a[k];
if(thisSum > maxSum)
maxSum = thisSum;
}
return maxSum;
}
分析算法一可以得到O(N*N*N)
算法二
int maxSubsum1(const vector<int>& a)
{
int maxSum = 0;
for(int i = 0; i < a.size(); i++)
for(int j = i; j < a.size(); j++)
{
int thisSum = 0;
thisSum += a[j];
if(thisSum > maxSum)
maxSum = thisSum;
}
return maxSum;
}
T(N)=O(N*N)
算法三
算法三采用一种递归和相对复杂的解法(NlogN),该方法采用分治(divide-and-conquer)策略。其想法是把问题分成两个大致相等的子问题,然后递归地对他们求解,这是“分”的部分,“治”阶段将两个子问题的解合并一起并可能再做少量的附加工作。最后得到整个问题的解。
int maxSumRect(const vector<int>& a, int left, int right)
{
if(left == right)
if(a[left] > 0)
return a[left];
else
return 0;
int center = (left + right) / 2;
int maxLeftSum = maxSumRect(a, left, center);
int maxRightSum = maxSumRect(a, center+1, right);
int maxLeftBorderSum = 0, leftBorderSum = 0;
for(int i = center; i >= left; i--)
{
leftBorderSum += a[i];
if(leftBorderSum > maxLeftBorderSum)
maxLeftBorderSum = leftBorderSum;
}
int maxRightBorderSum = 0, rightBorderSum = 0;
for(int j = center + 1; j <= right; j++)
{
rightBorderSum += a[j];
if(rightBorderSum > maxRightBorderSum)
maxRightBorderSum = rightBorderSum;
}
return max3(maxLeftSum, maxRightSum, maxLeftBorderSum + maxRightBorderSum);
}
有程序分析可以得到:T(N)=O(NlogN)
算法四
int maxSubSum4(const vector<int>& a)
{
int maxSum = 0,thisSum = 0;
for(int j = 0; j < a.size(); j++)
{
thisSum += a[j];
if(thisSum > maxSum)
maxSum = thisSum;
else if(thisSum < 0)
thisSum = 0;
}
return maxSum;
}
分析可以得到T(N)=O(N)
运行时间中得对数
对数最常出现的规律可概括为下列一般的法则:
如果一个算法用常数时间O(N)将问题的大小削减为某一部分(通常是1/2),那么该算法就是O(logN)
如果使用常量时间只是把问题减少一个常数的数量(例如将问题减少1),那么这种算法就是O(N)
Example1:二分搜索(binary search)
template <typename Comparable>
int binarySearch(const vector<Comparable>& a, const Comparable& x)
{
int low = 0,high = a.size - 1;
while(low <=high)
{
int mid = (low + high)/2;
if(a[mid] < x)
low = mid + 1;
else if(a[mid] > x)
high = mid - 1;
else
return mid;
}
return -1;
}
Example 2 欧几里得算法
计算最大公因数的欧几里得算法。
long gcb(long m, long n)
{
while(n != 0)
{
long rem = m%n;
m = n;
n = rem;
}
return m;
}
3 幂运算
long pow(long x, int n)
{
if( n == 0)
return 1;
if( n== 1)
return x;
if(isEven(n))
return pow(x*x,n/2);
else
return pow(x*x, n/2)*x;
}