数据结构与算法分析(C语言描述)第二章 算法分析【总结】

重点:大O记法,最大子序列和(4种算法),对数级算法(3个例子:对分查找、欧几里德算法、幂运算)



算法

算法(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运行时间。

看个例子:

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

声明不耗时间,忽略函数调用和返回值的开销,总共耗时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)。

 1 int maxSequenceSum1(const int A[], int N)
 2 {
 3     int i, j, k, maxSum, thisSum;
 4
 5     maxSum = 0;
 6     for (i = 0; i < N; i++)
 7     {
 8         for (j = i; j < N; j++)
 9         {
10             thisSum = 0;
11             for (k = i; k <= j; k++)
12                 thisSum += A[k];
13
14             if (thisSum > maxSum)
15                 maxSum = thisSum;
16         }
17     }
18   return maxSum;
19 }

2.撤销一个for循环,降低立方级的运行时间。考虑到∑jk=i A= ∑j-1k=i A+ Aj。修改如下。算法复杂度O(N2)。

 1 int maxSequenceSum2(const int A[], int N)
 2 {
 3     int i, j, maxSum, thisSum;
 4
 5     maxSum = 0;
 6     for (i = 0; i < N; i++)
 7     {
 8         thisSum = 0;
 9         for (j = i; j < N; j++)
10         {
11             thisSum += A[j];
12
13             if (thisSum > maxSum)
14                 maxSum = thisSum;
15         }
16     }
17     return maxSum;
18 }

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)。

 1 int maxSubSum(const int A[], int left, int right)
 2 {
 3     int maxLeftSum, maxRightSum;
 4     int maxLeftBorderSum, maxRightBorderSum;
 5     int leftBorderSum, rightBorderSum;
 6     int center, i;
 7
 8     if (left == right)    /*Base case*/
 9     {
10         if (A[left] > 0)
11             return A[left];
12         else
13             return 0;
14     }
15
16     center = (left + right) / 2;
17     maxLeftSum = maxSubSum(A, left, center);
18     maxRightSum = maxSubSum(A, center + 1, right);
19
20     maxLeftBorderSum = 0;    leftBorderSum = 0;
21     for (i = center; i >= left; i--)
22     {
23         leftBorderSum += A[i];
24         if (leftBorderSum > maxLeftBorderSum)
25             maxLeftBorderSum = leftBorderSum;
26     }
27
28     maxRightBorderSum = 0;    rightBorderSum = 0;
29     for (i = center + 1; i <= right; i++)
30     {
31         rightBorderSum += A[i];
32         if (rightBorderSum > maxRightBorderSum)
33             maxRightBorderSum = rightBorderSum;
34     }
35     return max(maxLeftSum, maxRightSum,
36         maxLeftBorderSum + maxRightBorderSum);
37 }
38
39 int maxSequenceSum3(const int A[], int N)
40 {
41     return maxSubSum(A, 0, N - 1);
42 }

4.联机算法:每个数据只访问一次。仅需要常量空间并以线性时间运行的联机算法几乎是完美的算法。

 1 int maxSequenceSum4(const int A[], int N)
 2 {
 3     int i, maxSum, thisSum;
 4
 5     maxSum = 0; thisSum = 0;
 6     for (i = 0; i < N; i++)
 7     {
 8         thisSum += A[i];
 9
10         if (thisSum> maxSum)
11             maxSum = thisSum;
12         else if (thisSum < 0)
13             thisSum = 0;
14     }
15     return maxSum;
16 }

时间复杂度中的对数规律

  某些分治算法将以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。

 1 int binarySearch(const int A[], int N, int X)
 2 {
 3     int low, high, mid;
 4
 5     low = 0;high = N - 1;
 6     while (low <= high)
 7     {
 8         mid = (low + high) / 2;
 9         if (A[mid] < X)
10             low = mid + 1;
11         else if (A[mid] > X)
12             high = mid - 1;
13         else
14             return mid;
15     }
16     return -1;        //not found
17 }

2.欧几里得算法:

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

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

 1 unsigned int gcd(unsigned int M, unsigned int N)
 2 {
 3     int rem;
 4
 5     while (N > 0)
 6     {
 7         rem = M % N;
 8         M = N;
 9         N = rem;
10     }
11     return M;
12 }

3.幂运算:

  计算XN

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

 1 long pow(long X, unsigned int N)
 2 {
 3     if (N == 0)
 4         return 1;
 5     if (N == 1)
 6         return X;
 7
 8     if (isEven(N))
 9         return pow(X * X, N / 2);
10     else
11         return pow(X * X, N / 2) * X;
12 }
时间: 2024-10-08 21:19:36

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

《数据结构与算法分析—C语言描述》pdf

下载地址:网盘下载 内容简介 编辑 <数据结构与算法分析:C语言描述(原书第2版)>内容简介:书中详细介绍了当前流行的论题和新的变化,讨论了算法设计技巧,并在研究算法的性能.效率以及对运行时间分析的基础上考查了一些高级数据结构,从历史的角度和近年的进展对数据结构的活跃领域进行了简要的概括.由于<数据结构与算法分析:C语言描述(原书第2版)>选材新颖,方法实用,题例丰富,取舍得当.<数据结构与算法分析:C语言描述(原书第2版)>的目的是培养学生良好的程序设计技巧和熟练的算

数据结构与算法分析 c语言描述 pdf 高清下载

网盘下载:数据结构与算法分析 c语言描述 pdf 高清下载 – 易分享电子书PDF资源网 作者: [美] Mark Allen Weiss 出版社: 机械工业出版社 副标题: C语言描述 原作名: Data Structures and Algorithm Analysis in C:Second Edition 译者: 冯舜玺 出版年: 2004-1-1 页数: 391 定价: 35.00元 装帧: 平装 内容简介 · · · · · · 本书是<Data Structures and Alg

数据结构与问题求解-Java语言描述(第三版)

数据结构对程序的重要性不言而喻,用java语言来实现常见的一些数据结构,以及在相应数据结构上的操作对学习java的同学来说是必须掌握的. 本系列博文参考<数据结构与问题求解-Java语言描述(第三版)>来实现 在自己学习的过程中,更希望有机会与大家交流. PS :本人是菜鸟,只是用博客的方式激励自己.请轻喷.Fighting!

数据结构与算法分析_Java语言描述(第2版)高清版pdf免费下载

下载地址:网盘下载 备用地址:网盘下载 内容简介编辑“数据结构”是计算机专业的基础与核心课程之一,Java是现今一种热门的语言.本书在编写过程中特别考虑到了面向对象程序设计(OOP)的思想与Java语言的特性.它不是从基于另一种程序设计语言的数据结构教材简单地“改编”而来的,因此在数据结构的实现上更加“地道”地运用了Java语言,并且自始至终强调以面向对象的方式来思考.分析和解决问题.本书是为数据结构入门课程(通常课号是CS-2)而编写的教材.作者Frank Carrano在编写过程自始至终特别

go语言总结第二章

var和const :变量和常量的声明 var varName type 或者 varName : = value package and import: 导入 func: 用于定义函数和方法 return :用于从函数返回 defer someCode :在函数退出之前执行 go : 用于并行 select 用于选择不同类型的通讯 interface 用于定义接口 struct 用于定义抽象数据类型 break.case.continue.for.fallthrough.else.if.swi

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

表.栈和队列是最简单和最基本的三种数据结构.基本上,每一个有意义的程序都将明晰地至少使用一种这样的数据结构,比如栈在程序中总是要间接地用到,不管你在程序中是否做了生命. 本章学习重点: 理解抽象数据类型(ADT)的概念 学习如何对表进行有效的操作 熟悉栈ADT及其在实现递归方面的应用 熟悉队列ADT及其在操作系统和算法设计中的应用 ADT 抽象数据类型(abstract data type)是一个操作的集合,是数学的抽象,在ADT中不涉及如何实现操作的集合,这可以看作是模块化设计的扩充. 对于每

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

对于一个初学者来说,作者的Solutions Manual把太多的细节留给了读者,这里尽自己的努力给出部分习题的详解: 不当之处,欢迎指正. 1.  按增长率排列下列函数:N,√2,N1.5,N2,NlogN, NloglogN,Nlog2N,Nlog(N2),2/N,2N,2N/2,37,N2logN,N3.指出哪些函数以相同的增长率增长. 答:排列如下2/N < 37 < √2 < N < NloglogN < NlogN < Nlog(N2) < Nlog2

《数据结构与算法Python语言描述》习题第二章第三题(python版)

ADT Rational: #定义有理数的抽象数据类型 Rational(self, int num, int den) #构造有理数num/den +(self, Rational r2) #求出本对象加r2的结果 -(self, Rational r2) #求出本对象减r2的结果 *(self, Rational r2) #求出本对象乘以r2的结果 /(self, Rational r2) #求出本对象除以r2的结果 num(self) #取出本对象的分子 den(self) #取出本对象的

《数据结构与算法Python语言描述》习题第二章第二题(python版)

ADT Date: #定义日期对象的抽象数据类型 Date(self, int year, int month, int day) #构造表示year/month/day的对象 difference(self, Date d2) #求出self和d2的日期差 plus(self, int n) #计算出日期第self之后n天的日期 num_date(self, int year, int n) #计算year年第n天的日期 adjust(self, int n) #将日期d调整n天(n为带符号整