算法

算法(algorithm)是为求解一个问题需要遵循的、被清楚地指定的简单指令的集合。

数学基础

四个定义

1. 大O表示法:

如果存在正常数 c 和 n使得当 N ≥ n0时,T(N) ≤ cf(N),则记为T(N) = O(f(N))。

(描述了T(N)的相对增长率小于等于f(N)的相对增长率。)

2. 大Ω表示法:

如果存在正常数 c 和 n使得当 N ≥ n0时,T(N) ≥ cf(N),则记为T(N) = Ω(f(N))。

(描述了T(N)的相对增长率大于等于f(N)的相对增长率。)

3. 大Θ表示法:

如果 T(N) = O(f(N)) 且 T(N) = Ω(f(N)),则 T(N) = Θ(f(N))。

(描述了T(N)的相对增长率等于f(N)的相对增长率。)

4. 小o表示法:

如果 T(N) = O(f(N)) 且 T(N) ≠ Θ(f(N)),则 T(N) = o (f(N))。

(描述了T(N)的相对增长率小于f(N)的相对增长率。)

三个结论

1. 如果T1(N) = O(f(N)) 且 T2(N) = O(g(N)),那么

  (a). 加法法则:T1(N) + T2(N) = max(O(f(N)), O(g(N)));【大O的和等于大O的最大值】

  (b). 乘法法则:T1(N) * T2(N) = O(f(N) * g(N)).【大O的积等于积的大O】

2. 如果T(N) 是一个k次多项式,则T(N) =  Θ(Nk).

3. 对任意常数k,logkN = O(N)。它告诉我们对数增长得非常缓慢。

时间复杂度

一个算法在输入规模为N时运行的耗时称为时间复杂度,常用大O表示。一般来说,它描述了最坏情况下的时间复杂度(平均情况下的时间复杂度需要更加复杂的数学分析)。

为了简化分析,约定:不存在特定的时间单位。因此,常抛弃一些常数系数和低阶项,从而便于计算大O运行时间。

看个例子:

int sum(int N)
{
int i, partialSum; partialSum = 0;  //1 个时间单元
for (i = 1; i < N; i++)  //初始化耗时 1 个时间单元,测试比较耗时 N + 1 个时间单元,自增运算耗时 N 个时间单元
partialSum += i * i * i;    //4 个时间单元(2 次乘,1 次加,1 次赋值),循环 N 次耗时 4N 个时间单元
return partialSum;   //1 个时间单元
}

声明不耗时间,忽略函数调用和返回值的开销,总共耗时1 + 1 + N + 1 + N + 4N+ 1 = 6N + 4。按照之前的约定,忽略低阶项4和常系数6,我们说该函数是O(N),时间复杂度是线性级。

这仅仅是一个小函数,如果有一个较大的程序,那么计算时间复杂度需要的工作量就太琐碎繁杂了。考虑大O的结果,它只关注得到的最高阶项。常数级运行时间相对于有关输入规模N的语句的耗时是很小的(无关紧要),所以忽略掉常数级O(1)的语句第5行、第7行、第8行,跟输入规模N有关的耗时主要是for循环,循环大小为N,所以该函数的运行时间就是O(N)线性级的。

计算时间复杂度的一般法则

法则1——for循环

一次for循环的运行时间至多是该for循环内语句(包括测试)的运行时间乘以迭代的次数。

法则2——嵌套的for循环

从里向外分析这些循环。在一组嵌套循环内部的一条语句总的运行时间为该语句的运行时间乘以该组所有for循环的大小的乘积。

法则3——顺序语句

将各个语句的运行时间求和即可(这意味着,其中的最大值就是所得的运行时间)

法则4——if/else 语句

一个if/else语句的运行时间从不超过判断再加上分支语句中运行时间长者的总的运行时间。显然在某些情况下这么估计有些过高,但绝不会估计过低。

最大子序列和问题

问题描述:给定整数A1,A2,,... ,AN(可能有负数),求∑jk=i Ak的最大值(为方便起见,如果所有整数均为负数,则最大子序列和为0)。

下面给出四种算法。

1. 穷举法

枚举所有的子序列之和,返回最大值。时间复杂度O(n3)。

int maxSequenceSum1(const int A[], int N)
{
int i, j, k, maxSum, thisSum; maxSum = 0;
for (i = 0; i < N; i++)
{
for (j = i; j < N; j++)
{
thisSum = 0;
for (k = i; k <= j; k++)
thisSum += A[k]; if (thisSum > maxSum)
maxSum = thisSum;
}
}
  return maxSum;
}

2. 撤销一个for循环,降低立方级的运行时间

考虑到∑jk=i A= ∑j-1k=i A+ Aj。修改如下。算法复杂度O(N2)。

int maxSequenceSum2(const int A[], int N)
{
int i, j, maxSum, thisSum; maxSum = 0;
for (i = 0; i < N; i++)
{
thisSum = 0;
for (j = i; j < N; j++)
{
thisSum += A[j]; if (thisSum > maxSum)
maxSum = thisSum;
}
}
return maxSum;
}

3. 分治算法

把一个问题分成两个大致相等的子问题,然后递归地对它们求解,这是“分”部分。“治”阶段将两个子问题的解合到一起并可能再做少量的附加工作,最后得到整个问题的解。

思路:最大子序列和只可能出现在三处:左半部分、右半部分、跨越并穿过中间而占据左右两半部分。前两种情况可以递归求解,第三部分的最大和可以通过求出前半部分的最大和(包括前半部分最后一个元素)以及后半部分的最大和(包括后半部分第一个元素)而得到,然后将这两个和加在一起。时间复杂度O(logN)。

考虑下列输入:

前半部分 后半部分
4 -3 5 -2 -1 2 6 -2

其中前半部分的最大子序列和为6(从元素A1到A3)而后半部分的最大子序列和为8(从元素A6到A7)。

前半部分包含其最后一个元素的最大和是4(从元素A1到A4),而后半部分包含其第一个元素的最大和是7(从元素A5到A7)。因此,跨越这两部分且通过中间的最大和为4+7 = 11(从元素A1到A7)。

int maxSubSum(const int A[], int left, int right)
{
int maxLeftSum, maxRightSum;
int maxLeftBorderSum, maxRightBorderSum;
int leftBorderSum, rightBorderSum;
int center, i; if (left == right) /*Base case*/
{
if (A[left] > 0)
return A[left];
else
return 0;
} center = (left + right) / 2;
maxLeftSum = maxSubSum(A, left, center);
maxRightSum = maxSubSum(A, center + 1, right); maxLeftBorderSum = 0; leftBorderSum = 0;
for (i = center; i >= left; i--)
{
leftBorderSum += A[i];
if (leftBorderSum > maxLeftBorderSum)
maxLeftBorderSum = leftBorderSum;
} maxRightBorderSum = 0; rightBorderSum = 0;
for (i = center + 1; i <= right; i++)
{
rightBorderSum += A[i];
if (rightBorderSum > maxRightBorderSum)
maxRightBorderSum = rightBorderSum;
}
return max(maxLeftSum, maxRightSum,
maxLeftBorderSum + maxRightBorderSum);
} int maxSequenceSum3(const int A[], int N)
{
return maxSubSum(A, 0, N - 1);
}

4.联机算法

每个数据只访问一次。仅需要常量空间并以线性时间运行的联机算法几乎是完美的算法。所以联机算法的时间复杂度是O(N)

int maxSequenceSum4(const int A[], int N)
{
int i, maxSum, thisSum; maxSum = 0; thisSum = 0;
for (i = 0; i < N; i++)
{
thisSum += A[i]; if (thisSum> maxSum)
maxSum = thisSum;
else if (thisSum < 0)
thisSum = 0;
}
return maxSum;
}

时间复杂度中的对数规律

某些分治算法将以O(NlogN)运行。除分治算法外,可将对数最常出现的规律概括为以下一般法则:

如果一个算法用常数时间O(1)将问题的大小削减为其一部分(通常是1/2),那么该算法就是O(logN)的。另一方面,如果使用常数时间只是把问题减少一个常数(如将问题减少1)那么这种算法那就是O(N)的。

具有对数特点的三个例子

三个例子的时间复杂度均为O(logN)。

1.对分查找

给定一个整数X和A0,A1,... ,AN-1,后者已经预先排序并在内存中,求使得Ai = X的下标i,如果X不在数据中,则返回i = -1。

int binarySearch(const int A[], int N, int X)
{
int low, high, mid; low = 0;high = N - 1;
while (low <= high)
{
mid = (low + high) / 2;
if (A[mid] < X)
low = mid + 1;
else if (A[mid] > X)
high = mid - 1;
else
return mid;
}
return -1; //not found
}

2. 欧几里算法

计算最大公因数。两个整数的最大公因数(Gcd)是同时整除两者的最大整数。

算法通过连续计算余数直到为 0 时停止,最后的非零余数就是最大公因数。

unsigned int gcd(unsigned int M, unsigned int N)
{
int rem; while (N > 0)
{
rem = M % N;
M = N;
N = rem;
}
return M;
}

3.幂运算

计算XN

如果N是偶数,则X= X(N/2) * X(N/2);如果N是奇数,则X= X(N-1/2) * X(N-1/2) * X。

long pow(long X, unsigned 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;
}

(完)

【数据结构与算法分析——C语言描述】第二章总结 算法分析的更多相关文章

  1. 最小正子序列(序列之和最小,同时满足和值要最小)(数据结构与算法分析——C语言描述第二章习题2.12第二问)

    #include "stdio.h" #include "stdlib.h" #define random(x) (rand()%x) void creat_a ...

  2. 数据结构与算法分析——C语言描述 第三章的单链表

    数据结构与算法分析--C语言描述 第三章的单链表 很基础的东西.走一遍流程.有人说学编程最简单最笨的方法就是把书上的代码敲一遍.这个我是头文件是照抄的..c源文件自己实现. list.h typede ...

  3. C语言学习书籍推荐《数据结构与算法分析:C语言描述(原书第2版)》下载

    维斯 (作者), 冯舜玺 (译者) <数据结构与算法分析:C语言描述(原书第2版)>内容简介:书中详细介绍了当前流行的论题和新的变化,讨论了算法设计技巧,并在研究算法的性能.效率以及对运行 ...

  4. 《数据结构与算法分析——C语言描述》ADT实现(NO.00) : 链表(Linked-List)

    开始学习数据结构,使用的教材是机械工业出版社的<数据结构与算法分析——C语言描述>,计划将书中的ADT用C语言实现一遍,记录于此.下面是第一个最简单的结构——链表. 链表(Linked-L ...

  5. 《数据结构与算法分析-Java语言描述》 分享下载

    书籍信息 书名:<数据结构与算法分析-Java语言描述> 原作名:Data Structures and Algorithm Analysis in Java 作者: 韦斯 (Mark A ...

  6. 数据结构与抽象 Java语言描述 第4版 pdf (内含标签)

    数据结构与抽象 Java语言描述 第4版 目录 前言引言组织数据序言设计类P.1封装P.2说明方法P.2.1注释P.2.2前置条件和后置条件P.2.3断言P.3Java接口P.3.1写一个接口P.3. ...

  7. 数据结构(Java语言描述)-第一章:概述

    第一章 概述 1.0 序言 自己为啥要学数据结构嘞,我觉得主要有以下三个原因: 前段时间在看并发编程时,发现aqs,corrunthashmap等底层都用到了数据结构,主要的有队列,还有链表,学习数据 ...

  8. 《数据结构与算法分析:C语言描述_原书第二版》CH3表、栈和队列_reading notes

    表.栈和队列是最简单和最基本的三种数据结构.基本上,每一个有意义的程序都将明晰地至少使用一种这样的数据结构,比如栈在程序中总是要间接地用到,不管你在程序中是否做了声明. 本章学习重点: 理解抽象数据类 ...

  9. 《数据结构与算法分析:C语言描述_原书第二版》CH2算法分析_课后习题_部分解答

    对于一个初学者来说,作者的Solutions Manual把太多的细节留给了读者,这里尽自己的努力给出部分习题的详解: 不当之处,欢迎指正. 1.  按增长率排列下列函数:N,√2,N1.5,N2,N ...

随机推荐

  1. Git超级实用使用教程

    一篇git入门实用教程,原文地址http://www.cnblogs.com/tugenhua0707/p/4050072.html 一:Git是什么? Git是目前世界上最先进的分布式版本控制系统. ...

  2. HDU 1950 Bridging signals

    那么一大篇的题目描述还真是吓人. 仔细一读其实就是一个LIS,还无任何变形. 刚刚学会了个二分优化的DP,1A无压力. //#define LOCAL #include <iostream> ...

  3. asp.net限时发送手机验证码

    html代码 <p> <strong>手机验证码:</strong> <asp:TextBox ID="code" runat=" ...

  4. 五款好玩又好用的Linux网络测试和监控工具

    五款好玩又好用的Linux网络测试和监控工具 [51CTO精选译文]在这篇介绍几款Linux网络测试实用工具的文章中,我们使用Bandwidthd.Speedometer.Nethogs.Darkst ...

  5. Python [Leetcode 141]Linked List Cycle

    题目描述: Given a linked list, determine if it has a cycle in it. 解题思路: 快的指针和慢的指针 代码如下: # Definition for ...

  6. HDU 2026 首字母变大写

    #include<cstdio> #include<cstring> #include<algorithm> using namespace std; int ma ...

  7. Linux apt-get

    apt-get apt-get是一条linux命令,适用于deb包管理式的操作系统,主要用于自动从互联网的软件仓库中搜索.安装.升级.卸载软件或操作系统. apt-get命令一般需要root权限执行, ...

  8. 关于"user.dir"的认识

    最近阅读了一些tomcat源码,看到tomcat在读取jar包外配置文件,是将“user.dir”的路径作为home path文件即 通过System.getProperty("user.d ...

  9. requirejs之demo (转)

    具体的理论就不讲了,可以参考 http://www.ruanyifeng.com/blog/2012/10/javascript_module.html http://www.ruanyifeng.c ...

  10. 【转】 iOS开发之打包上传到App Store——(一)各种证书的理解

    OK,有日子没写iOS开发的相关文章啦,主要是最近的精力都没在这上面,不过既然产品已经快要出来了,就有必要了解一下各种证书啥的(众所周知iOS的一堆证书可是很让人头大呀),最近确实被这个搞得头大,然后 ...