410 likes | 552 Views
第一章 绪 论. 1 、历史沿革 2 、计算机科技的两大支柱 3 、数据类型和数据结构 4 、算法 5 、时间复杂性的度量 6 、有效算法的重要性. 历史沿革. 1968 年 D·E·Knuth :“ Art of computer programming” IEEE 68 教程 1983 IEEE 83 教程 1991 IEEE 91 教程 2000 IEEE 2000 教程 国内在 78 年 开设. 计算机科技的两大支柱. · 1 、 数据结构 2 、 算法 · 程序 = 数据结构 + 算法.
E N D
第一章 绪 论 1、历史沿革 2、计算机科技的两大支柱 3、数据类型和数据结构 4、算法 5、时间复杂性的度量 6、有效算法的重要性
历史沿革 • 1968年 D·E·Knuth :“ Art of computer programming” • IEEE 68 教程 • 1983 IEEE 83 教程 • 1991 IEEE 91 教程 • 2000 IEEE 2000 教程 • 国内在 78 年 开设
计算机科技的两大支柱 • · 1、数据结构2、算法 • · 程序 = 数据结构 + 算法 输入未处理的数据 算法 输出加工过的数据
数据类型和数据结构 • ·数据值:atomic data value: 不可再分解。如3、2、5等。 • non-atomic data value: 可以再分解, • 其成分称为data element 或 element。如 set: { 32.99, -1.03, 0, 23} 。 • ·类型:同一类别可供识别的一组个体。 • ·数据类型:data type = data value + operation 或者定义为 • 1、A set of value • 2、A set of operations on the these value。
数据类型的类别 • 1、原子数据类型: • 数据值是原子数据值。如:int • 2、结构数据类型: • 数据值是可以再分解。如:an array[1..3 ] of real • value1 value2 value3 • [1] 0 [1] 1.9 [1] 1.9 • [2] 1.9 [2] 0 [2] 1.9 • [3] 3.4 [3] 3.4 [3] 6.8 • value1 value2 • 数据值的进一步分解为数据元素,它们之间有一个关系。
数据类型的类别 • 1、原子数据类型: • 数据值是原子数据值。如:int • 2、结构数据类型: • 数据值是可以再分解。如:an array[1..3 ] of real • value1 value2 value3 • [1] 0 [1] 1.9 [1] 1.9 • [2] 1.9 [2] 0 [2] 1.9 • [3] 3.4 [3] 3.4 [3] 6.8 • operation: 1、在 data value上:var value1, value2, value 3; • value3 = value1 + value2; • 2、在 元素上:var value1; • value1 [1] = 1.2;
数据结构 • 是一种数据类型,其数据值: • 1、可以进一步分解为数据元素的集合,该数据元素可以是原子数据值,也可以是 • 另外一种数据结构。 • 2、数据元素之间有一个关系的集合。 • 注意:在许多数据结构的书籍(课本)之中,通常将数据元素称之为结点 • ·数据结构的分类: • 1、线性结构:如:线性表:空或一个元素或:除第一个结点外,都有直接前驱结点, • 除最后一个结点外,都有直接后继结点。 • 其它如:栈、队列等。 • 2、非线性结构:树、二叉树、图等。
数据结构的实现 • 1.ADT(Abstract Data Type): • 认为 DT 仅存在于想象之中。注意力集中在感兴趣的性质上,不关心数据的表示形式、操作的具体代码等等。给出规范或说明。 • 2.VDT(Virtual Data Type): • DT 存在于 Virtual processor 上。 • 如: c++ = O.S. + hardware + c++ compiler 的 Virtual processor。其他如 SQL Virtual processor 等 。给出表示和实现。如:用类、类属数据类型、模板等。 • 3.PDT(Physical Data Type): • DT 存在于物理机器上。
例:线性表 • 在C++中数据结构的构造:以一个线形表为例说明。 线形表实例: 3 22 5 45 23 8
抽象数据类型 表1.1: 表seqlist 的抽象数据类型(ADT) ADT seqlist is Data一个数据元素的表seqlist,其数据元素或结点可由序号进行标识。 Operations Constructor 构造一个空表。 ListSize 前提:无。 结果:给出表seqlist的规模。 ListEmpty 前提:无。 结果:若表seqlist为空返回True,否则False。 ClearList 前提:无。 结果:若表seqlist非空,则清空表seqlist。 Find 前提:给定数据元素或结点。 结果:若查找成功则返回True, 否则返回False。 Insert 前提: 给定要插入的数据元素。 结果:将该数据元素插入到表seqlist的末尾,表的规模增加1。 Delete 前提: 给定要删除的数据元素之值。 结果:查找该数据值的结点,查找成功则删除之,表seqlist的规模减少1,否则删除失败。 DeleteFront 前提: 表seqlist非空。 结果:删除队首结点并返回结点的数据值,表seqlist的规模减少1。 GetData 前提: 数据元素或结点的位置或序号,其序号在0和表的大小减1之间。 结果:给出相应表seqlist位置序号的结点的数据值。
虚拟数据类型 表1.2: 用 C++表示的表 seqlist 的规范( VDT ) class seqlist { private: int size;// size of the seqlist. It is 0 when the seqlist is empty. DataType * list; // DataType is the data type of the component elements of seqlist. public: // the seqlist access methods. seqlist( ); int ListSize( ) const; int ListEmpty( ) const; int Find( int & item ) const; DataType GetData(int pos) const; // the seqlist modification method. void Insert( const int & item ); void Delete( const int & item ); void ClearList( ); }; 通常数据结构和算法的教科书,直接给出VDT。
例:有序表 3 22 5 45 23 线形表实例: 8 3 5 8 22 23 有序表实例: 45 表1.3:有序表orderlist 的抽象数据类型(ADT) ADT orderlist is Data < same as the seqlist ADT > Operations Constructor < executes the base class constructor > ListSize < same as the seqlist ADT > ListEmpty < same as the seqlist ADT > ClearList < same as the seqlist ADT > Find < same as the seqlist ADT > Insert < same as the seqlist ADT > Delete < same as the seqlist ADT > DeleteFront < same as the seqlist ADT > GetData < same as the seqlist ADT > Insert 前提: 给定被插入的数据元素的值。 结果:在表Orderlist中按序插入新的数据元素,且表Orderlist的规模增大1。
例:有序表 • 有序表的构造,分析和线形表之间的关系:经过分析大部分操作是一样的。 表1.4: 有序表orderlist 的规范 class orderlist : public seqlist { public: orderlist( ); // initialize base class to creat an empty list. int Insert( const int & item ); //insert a new item in order. }; • 通过继承关系,可以很容易得到具有自己特点的数据结构。 • 通过模板,将类型作为参数,可以得到 Generic Data Structures。 • 从而上升到数据结构的更高抽象层次。
欧几里德算法: • 如果 m、n 都是正整数,且 m > n, 则 m = nq + r , 0 < r < n, 此处 r,q 都是正整数。那么,(m,n) = ( n, r) ; // (a,b ) 为 a、b之间的最大公约数。 • 计算m、n 之间最大公约数的算法: • n if mod(m,n) = 0 • g(n, mod(m,n)) if mod(m,n) != 0 g(m,n) = 算法 • 一个算法就是有穷规则的集合,其中的规则规定了一个解决某一个特定问题的运算序列。
程序框图: Input m, n 输入 r = mod(m, n) 求余数 output n 求出 m、n 的最大公约数 r = = 0 ? 更新被除数、除数 m = n; n = r 例:欧几里德算法 • 特征: 1、有穷性:执行有穷步后结束。 • 2、确定性:每一步有确定的含义。 • 3、能行性:原则上能精确的进行,用纸和笔有限次完成。 • 4、输入: • 5、输出:
例:算法实现 算法1.1:求m、n 之间的最大公约数 // 输入两个正整数m、n, 满足关系m > n 。现在要求m 和n 之间的最大公约数。 int maximal_common_divisor( ){ int m,n; int r; // r用于保存m % n 之值。 cout << "Please enter two positive intergers m n ,note: m > n !" << endl; cin >> m >> n; // 输入m 和n 之值。 Exception( m < n, “m <n is ERROR.” ); //若m> n,继续;否则出错,程序中断。 cout << " m is " << m << "!" << " n is " << n << endl; //显示m、n。 while ( 1 ){ r = m % n; // 得到m/n 之后的余数。 if ( r == 0 ) return n; // 若余数为0,那么原m和n之间的最大公约数为n。 else { m = n; // 更新被除数m。 n = r; // 更新除数n。 } } } // 在m > n 时,函数返回正整数m 和n之间的最大公约数。
时间和空间复杂性的度量 • ·问题的规模(n):如:矩阵的阶数、图的结点个数、被分类序列的正整数个数 • ·时间复杂性:算法的所需的时间和问题规模的函数。记为 T(n)。 • 当 n--> ∞ 时的时间复杂性,被称之为渐进时间复杂性。 • ·空间复杂性:算法的所需的空间和问题规模的函数。记为 S(n)。 • 当 n--> ∞ 时的空间复杂性,被称之为渐进空间复杂性。 • ·程序运行时间:1、 输入规模 • 2、利用编译程序生成的目标代码的质量 • 3、计算机程序指令系统的品质和速度 • 4、算法的时间复杂性的函数 • 1、4 点自明。2、3两点表示算法的时间依赖于软件和硬件的环境。用在某一系统 中的绝对时间秒、分… 作为衡量,判断算法的优劣是不正确的。 • ·最坏情况下的时间复杂性和平均情况下的时间复杂性
时间复杂性:大 O 表示法 • 定义: 如果存在着正的常数 c 和自然数 n0,当 n ≥ n0时;有 f (n) ≤ c*g(n) 成立,则 称 f( n ) = O(g( n )) 。 • 在算法分析中, 如果一个的算法的时间复杂性是O(g( n )),读作 g( n ) “ 级 ” 的 或 “ 阶 ” 的。 如: 线性阶的、平方阶的、立方阶的 …… • ·例1、 设 T(n) = (n+1)2 • = n2+2n +1 ≤ n2 + 2n2 + n2; 在 n=1 时,等式成立,n>1 时,< 式成立 • 选 n0 = 1, c=4 ; T(n) ≤ 4n2。所以,T(n) = O(n2)
大 O 表示法 • ·例2、 设 T(n) = 3n3+2n2 • 选 n0 = 0, c=5 ; T(n) ≤ 5n3。所以,T(n) = O(n3) • 同理:选 n0 = 0, c=5 ; T(n) ≤ 5n4。所以,T(n) = O(n4)??? • 注意:符合定义,但在算法分析中是没有意义的。 • 在算法分析中,通常所说的找到了时间复杂性的级别,是指找到了同样级别的最 简单的函数。 • 如:307 n2、n2/2、n2 都是同一级别的函数,最简单的函数是n2。所以, 307 n2、n2/2、n2 的级别都是O(n2 ) 。 • f、g同级别:满足: f = O(g) 且 g = O(f),
紧贴渐进界 • ·例3、设 T(n) = 3n != O(2n) • 注意: f(n)=O(g(n)) 意味着找到了 f(n) 的一个最“ 紧贴” 的上界 g(n) , 或者说最低的上界。从算法的时间复杂性角度来看,像例2 中的 O(n4) 是没有意义的。 • 紧贴渐进界:设存在一个函数 f(n) = O(g(n)),如果对于每一个函数 h(n): • f(n) = O(h(n)) g(n)=O(h(n)),就说 g(n) 是 f(n) 的紧贴渐进界。 • 例4:f(n)=3n+5;f(n)= O(n) 同样根据定义 f(n)= O(n2) 。但是,f(n) 的 • 紧贴渐进界是f(n)= O(n) ,而不是f(n)= O(n2)。 • 反证法:假设 g(n)=n 不是 f(n)=3n+5的紧贴渐进界(意思即为:g(n)=n 是 • f(n)=3n+5的渐进界,而不是 f(n)=3n+5的紧贴渐进界),那么根据定义对于使得 • f(n)=O(h(n)) 的函数 h(n), g(n) != O(h(n))。 • 设存在函数 h (n),使得 f(n)=3n+5= O(h(n)) ,且g(n) != O(h(n))。由于3n+5= O(h(n)) , • 那么根据大 O 法的定义,必存在正数c和n0,使得对于所有的 n ≥ n0 ,3n+5 ≤ c*h(n)。 • 很显然,对一切 n ≥ 0,有 n ≤ 3n+5 ,所以 g(n) ≤ c*h(n)。 这样,根据大O 法 • 的定义有g(n)=O(h(n)) 。与假设相矛盾。 #
求和定理 • 求和定理:假定T1(n)、T2(n)是程序P1、P2的运行时间,并且T1(n)是O(f(n))的,而T2(n)是 • O(g(n))的。那么,先运行P1、再运行P2 的总的运行时间是:T1(n)+T2(n)= • O(MAX(f(n),g(n)) 的。 • 证明:根据定义,对于某些常数c1、n1、及c2、n2,由已知可得: • 在n ≥n1 时,T1(n) ≤ c1 f(n) 成立。 • 在n ≥n2 时,T2(n) ≤ c2 g(n) 成立。 • 设n1和n2之间的最大值为n0,即n0 = MAX(n1,n2)。 • 那么,在n ≥ n0时;T1(n) +T2(n) ≤ c1 f(n) +c2 g(n)成立。所以,T1(n) +T2(n) ≤ • (c1+c2 ) MAX(f(n),g(n))。# • 例5. P1、P2、P3三段程序的运行时间分别为O(n2)、O(n3)及O(nlogn) 。那么,先运行P1,再运行P2的运行时间为O(MAX(n2,n3)=O(n3),接着再运行P3的总的运行时间为 • O(MAX(n3,nlog2n)=O(n3)。 • 即若干个程序顺序运行时,其总的运行时间的级别取决于具有最大值的那个函数。
求积定理 • 求积定理:如果T1(n) 和T2(n)分别是O(f(n))和O(g(n))的,那么T1(n)×T2(n)是O(f(n)×g(n)) 的。 • 证明:已知在n ≥n1 时,T1(n) ≤ c1 f(n) 成立;在n ≥n2 时,T2(n) ≤ c2 g(n) 成立。 • 其中c1、n1、及c2、n2 都是常数。 • 所以,在n ≥ MAX(n1,n2) 时,T1(n)×T2(n) ≤c1c2f(n)g(n), • 因此T1(n)×T2(n)是O(f(n)×g(n)) 的。#
0 1 2 3 4 5 25 12 47 89 36 14 例:起泡分类法 • ·设例子中数组的规模为: Size = 6 算法1.2: 类模板 seqlist 的用于排序的起泡方法 template < class Type > void seqlist<Type> :: BubbleSort( ) { // 将数组p[0]到p[size-1] 逐趟进行比较,size为表的规模。 int j,k; int flag = 1; // 交换标志,flag 为1 表示出现过数据交换,继续进 //行排序。若flag为0,表示未出现过数据交换,数组 // p[0]到p[size-1]已经排好序,排序结束。 for ( j=0; j <= size - 2 && flag; j++ ){ flag = 0; for ( k = size - 1; k > j; k - -) if ( p[k-1] > p[k] ) { Type temp = p[k-1]; p[k-1] = p[k]; p[k] = temp; flag = 1; } } }
0 1 2 3 4 5 25 12 47 89 36 14 例:起泡分类法 • ·设例子中数组的规模为: Size = 6 算法1.2: 类模板 seqlist 的用于排序的起泡方法 template < class Type > void seqlist<Type> :: BubbleSort( ) { // 将数组p[0]到p[size-1] 逐趟进行比较,size为表的规模。 int j,k; int flag = 1; // 交换标志,flag 为1 表示出现过数据交换,继续进 //行排序。若flag为0,表示未出现过数据交换,数组 // p[0]到p[size-1]已经排好序,排序结束。 for ( j=0; j <= size - 2 && flag; j++ ){ flag = 0; for ( k = size - 1; k > j; k - -) if ( p[k-1] > p[k] ) { Type temp = p[k-1]; p[k-1] = p[k]; p[k] = temp; flag = 1; } } }
0 1 2 3 4 5 25 12 47 89 14 36 例:起泡分类法 • ·设例子中数组的规模为: Size = 6 算法1.2: 类模板 seqlist 的用于排序的起泡方法 template < class Type > void seqlist<Type> :: BubbleSort( ) { // 将数组p[0]到p[size-1] 逐趟进行比较,size为表的规模。 int j,k; int flag = 1; // 交换标志,flag 为1 表示出现过数据交换,继续进 //行排序。若flag为0,表示未出现过数据交换,数组 // p[0]到p[size-1]已经排好序,排序结束。 for ( j=0; j <= size - 2 && flag; j++ ){ flag = 0; for ( k = size - 1; k > j; k - -) if ( p[k-1] > p[k] ) { Type temp = p[k-1]; p[k-1] = p[k]; p[k] = temp; flag = 1; } } }
0 1 2 3 4 5 25 12 47 89 14 36 例:起泡分类法 • ·设例子中数组的规模为: Size = 6 算法1.2: 类模板 seqlist 的用于排序的起泡方法 template < class Type > void seqlist<Type> :: BubbleSort( ) { // 将数组p[0]到p[size-1] 逐趟进行比较,size为表的规模。 int j,k; int flag = 1; // 交换标志,flag 为1 表示出现过数据交换,继续进 //行排序。若flag为0,表示未出现过数据交换,数组 // p[0]到p[size-1]已经排好序,排序结束。 for ( j=0; j <= size - 2 && flag; j++ ){ flag = 0; for ( k = size - 1; k > j; k - -) if ( p[k-1] > p[k] ) { Type temp = p[k-1]; p[k-1] = p[k]; p[k] = temp; flag = 1; } } }
0 1 2 3 4 5 25 12 47 14 89 36 例:起泡分类法 • ·设例子中数组的规模为: Size = 6 算法1.2: 类模板 seqlist 的用于排序的起泡方法 template < class Type > void seqlist<Type> :: BubbleSort( ) { // 将数组p[0]到p[size-1] 逐趟进行比较,size为表的规模。 int j,k; int flag = 1; // 交换标志,flag 为1 表示出现过数据交换,继续进 //行排序。若flag为0,表示未出现过数据交换,数组 // p[0]到p[size-1]已经排好序,排序结束。 for ( j=0; j <= size - 2 && flag; j++ ){ flag = 0; for ( k = size - 1; k > j; k - -) if ( p[k-1] > p[k] ) { Type temp = p[k-1]; p[k-1] = p[k]; p[k] = temp; flag = 1; } } }
0 1 2 3 4 5 25 12 47 14 89 36 例:起泡分类法 • ·设例子中数组的规模为: Size = 6 算法1.2: 类模板 seqlist 的用于排序的起泡方法 template < class Type > void seqlist<Type> :: BubbleSort( ) { // 将数组p[0]到p[size-1] 逐趟进行比较,size为表的规模。 int j,k; int flag = 1; // 交换标志,flag 为1 表示出现过数据交换,继续进 //行排序。若flag为0,表示未出现过数据交换,数组 // p[0]到p[size-1]已经排好序,排序结束。 for ( j=0; j <= size - 2 && flag; j++ ){ flag = 0; for ( k = size - 1; k > j; k - -) if ( p[k-1] > p[k] ) { Type temp = p[k-1]; p[k-1] = p[k]; p[k] = temp; flag = 1; } } }
0 1 2 3 4 5 25 12 14 47 89 36 例:起泡分类法 • ·设例子中数组的规模为: Size = 6 算法1.2: 类模板 seqlist 的用于排序的起泡方法 template < class Type > void seqlist<Type> :: BubbleSort( ) { // 将数组p[0]到p[size-1] 逐趟进行比较,size为表的规模。 int j,k; int flag = 1; // 交换标志,flag 为1 表示出现过数据交换,继续进 //行排序。若flag为0,表示未出现过数据交换,数组 // p[0]到p[size-1]已经排好序,排序结束。 for ( j=0; j <= size - 2 && flag; j++ ){ flag = 0; for ( k = size - 1; k > j; k - -) if ( p[k-1] > p[k] ) { Type temp = p[k-1]; p[k-1] = p[k]; p[k] = temp; flag = 1; } } }
0 1 2 3 4 5 25 12 14 47 89 36 例:起泡分类法 • ·设例子中数组的规模为: Size = 6 算法1.2: 类模板 seqlist 的用于排序的起泡方法 template < class Type > void seqlist<Type> :: BubbleSort( ) { // 将数组p[0]到p[size-1] 逐趟进行比较,size为表的规模。 int j,k; int flag = 1; // 交换标志,flag 为1 表示出现过数据交换,继续进 //行排序。若flag为0,表示未出现过数据交换,数组 // p[0]到p[size-1]已经排好序,排序结束。 for ( j=0; j <= size - 2 && flag; j++ ){ flag = 0; for ( k = size - 1; k > j; k - -) if ( p[k-1] > p[k] ) { Type temp = p[k-1]; p[k-1] = p[k]; p[k] = temp; flag = 1; } } }
0 1 2 3 4 5 25 12 14 47 89 36 例:起泡分类法 • ·设例子中数组的规模为: Size = 6 算法1.2: 类模板 seqlist 的用于排序的起泡方法 template < class Type > void seqlist<Type> :: BubbleSort( ) { // 将数组p[0]到p[size-1] 逐趟进行比较,size为表的规模。 int j,k; int flag = 1; // 交换标志,flag 为1 表示出现过数据交换,继续进 //行排序。若flag为0,表示未出现过数据交换,数组 // p[0]到p[size-1]已经排好序,排序结束。 for ( j=0; j <= size - 2 && flag; j++ ){ flag = 0; for ( k = size - 1; k > j; k - -) if ( p[k-1] > p[k] ) { Type temp = p[k-1]; p[k-1] = p[k]; p[k] = temp; flag = 1; } } }
0 1 2 3 4 5 12 25 14 47 89 36 例:起泡分类法 • ·设例子中数组的规模为: Size = 6 算法1.2: 类模板 seqlist 的用于排序的起泡方法 template < class Type > void seqlist<Type> :: BubbleSort( ) { // 将数组p[0]到p[size-1] 逐趟进行比较,size为表的规模。 int j,k; int flag = 1; // 交换标志,flag 为1 表示出现过数据交换,继续进 //行排序。若flag为0,表示未出现过数据交换,数组 // p[0]到p[size-1]已经排好序,排序结束。 for ( j=0; j <= size - 2 && flag; j++ ){ flag = 0; for ( k = size - 1; k > j; k - -) if ( p[k-1] > p[k] ) { Type temp = p[k-1]; p[k-1] = p[k]; p[k] = temp; flag = 1; } } } 在a[6-1]和a[0]范围内最小的元素被放置在正确位置
例:最坏和最好情况下的时间复杂性 在最坏情况下的时间复杂性的分析:n为问题规模 1:交换 Swap 时间 O(1), O(1), O(1), O(1)。总和仍为O(1)。 2:IF 判断需O(1), 执行交换需O(1)。总和仍为O(1)。 3: 再加上 k - - 的时间:O(1), 总和仍为O(1)。 4:内循环所需时间为: O((n-1-j) × O(1) ) 即 O((n-1-j) 5:外循环中的循环一次还需时间 O(1): 一次赋值操作 flag = 0 O(1): 一次判断 j <= size - 2 && flag; O(1): 一次 加 1 操作 j++; 总的需要时间为: O(1) + O((n-1-j) 仍为: O((n-1-j)。 6:总的时间代价为: ∑ (n-1-j) = (n-1) + (n-2) + …… 2 + 1 = O(n2) 0 1 2 3 4 5 12 25 14 47 89 36 在a[6-1]和a[0]范围内最小的元素被放置在正确位置 a[0] 次最小 的元素 被放置 在正确 位置 a[1] 余类推 n-2 j=0 在最好情况下的时间复杂性的分析: 进行一趟检查之后,flag 为 0,已经排好序,无需再做。 故时间代价为:O(n)
·if Lim f(n)/g(n) = c; 这里 c 是常数。 f(n)、g(n) 同级别。 n->∞ • ·if Lim f(n)/g(n) = 0; f(n) 级别低。 n->∞ • ·if Lim f(n)/g(n) = ∞; g(n) 级别低。 n->∞ • 如:Lim logn/n = Lim Ln(n)loge/n • = Lim (loge/n)/1 • = Lim loge/n = 0; logn 级别低。 • 注意:这里使用了罗彼特的求极限的法则。 n->∞ n->∞ n->∞ n->∞ 时间复杂性的级别 • 注意: • 1、时间复杂性函数无时间单位。 • 2、上例采用的是均匀时间耗费。以简单语句的耗费时间为 1 。 • 3、如 if 语句,条件:O(1) + THEN OR ELSE 后的语句的时间耗费之和。 • 4、循环语句,先里后外,逐步求和。 • 5、goto 语句的分析,具体情况,具体对待。 • 时间复杂性的级别的判断:级别越低越好。
例:求 xn的程序的时间复杂性 • 方案1:直接采用循环的做法。 算法1.3: x 和 n 都是正整数,求 xn之值的简单方法 int power01( int x,int n ) { int y;y = x; // 代价 2 while( n > 1 ) // 代价 n { y *= x; n - -; } // 代价2(n-1) cout << “ x^n is ” << y << endl; // 代价 1 return y; // 代价 1 } // x 和n 都是正整数,求xn之值。 注意:根据求和定理,总的时间代价为:O(n),线形阶的。
例:求 xn的程序的时间复杂性 • 方案2:采用一些技巧,且假定不会溢出。 算法1.4: x 和 n 都是正整数,求 xn之值的时间复杂性级别低的算法 int power02( int x, int n ) { int m; m = n; int t = 1; while( m > 0 ) { m /= 2; t *= 2;} m = n; int y = 1; while( t > 1 ) { t /= 2; y *= y; if ( m >= t ) { y *=x; m -=t; } } cout << " x^n is " << y << endl; return y; } // x 和n 都是正整数,求xn之值。时间复杂性级别为O( log n ) Log n + 2 2( Log n +1) Log n + 2 Log n + 1 Log n + 1 代价 <= 3( ) Log n + 1
例:求 xn的程序的时间复杂性 算法2的工作原理,可用求 x55来加以说明: x55= x110111 = x32+16+0+4+2+1来加以说明。 程序的 第一个 while 求出 >= 55 的最小的2的正整数幂 64,64/2 可得到32,在程 序的 第二个 while 中用到它。在程 序的 第二个 while 中: 55 - 32 = 23 得到 x110111 中的 幂指数中的最左位的1 23 - 16 = 7 得到 x110111 中的 幂指数中的左起第二位的1 7 - 8 < 0 得到 x110111 中的 幂指数中的左起第三位的0 7 - 4 = 3 得到 x110111 中的 幂指数中的左起第4位的1 3 - 2 = 1 得到 x110111 中的 幂指数中的左起第5位的1 1 - 1 =0 得到 x110111 中的 幂指数中的左起第 6三位的1 知道了上述求法之后,求 x110111 就很方便了: 先求 x1 ,1 * x=》y 再求 x11 , x11= x10 * x1 = x10 * x1 =》y * y * x =》y 再求 x110 , x110= x11 * x11 =》 y * y 其余,依次类推。
有效算法的重要性 • ·计算机不是万能的,并非所有的算法计算机都能够计算出有用的结果。差的算法不一 定有实际意义。 • 举一个例子加以说明。假定时间复杂性函数的时间单位为 us 函数 n=20 n=50 n=100 n=500 1000n .02s .05s .15s .5s 1000nlogn .09s .3s .6s 4.5s 100n2 .04s .25s 1s 25s 10n3 .02s 1s 10s 21分 nlogn .4s 1.1小时 220天 5× 108世纪 2n/3 .0001s 0.1s 2.7小时 5× 108世纪 2n 1s 35年 3× 104世纪 3n 58分 2×109世纪 易性算法 顽性算法
有效算法的重要性 • ·硬件速度的提高和问题的求解规模之间的关系:硬件速度提高 10 倍,并不意味着求解问题 的规模同样提高 10 倍。由于算法不同,差别很大。设时间复杂性函数的时间单位为ms。 提速之前 在 1 秒内 在 1 分钟内在 1 小时 A1:n 1000 6 * 104 3.6 * 106 A2:nlogn 140 4893 2 * 105 A3: n2 31 244 1897 A4: n3 10 39 153 A5: 2n 9 15 21 例如:设 1 分钟内算法 A5的求解规模为 s5: 则:2s5 = 60 * 103 (ms) 解之:s5 = 15
例如:设提速 10 倍后在时间 t 秒内,A5 的求解规模为 z5: t * 1000 t * 1000*10 解之:z5 = s5 +3.3 2s5 = 2z5 有效算法的重要性 • ·硬件速度的提高和问题的求解规模之间的关系:硬件速度提高 10 倍,并不意味着求解问题 的规模同样提高 10 倍。由于算法不同,差别很大。设时间复杂性函数的时间单位为ms。 提速10倍后 提速10倍前求解规模 提速10倍后求解规模 A1:n s1 10s1 A2:nlogn s2 10s2 A4: n2 s4 3.16s3 A3:n3 s3 2.15s4 A5: 2n s5 s5+3.3
大 Ω 表示法: • ·定义;如果存在着正的常数 c 和自然数 n0,当 n >= n0时;有 f (n) >= Cg(n) 成立,则 称 f( n ) = Ω (g( n )) 。 • 大Θ 表示法: • ·定义;如果存在着正的常数 c1、c2 和自然数 n0,当 n >= n0时;有 • C1 × g(n) <= f (n) <= C2 ×g(n) 成立,则称 f( n ) = Θ (g( n )) 。 • ·例、 设 T(n) = (n+1)2 = Θ ( n2) • 小 o 符号: • ·定义;f(n) = o(g( n )) 当且仅当 f(n) = O(g( n )) 且 f(n) != Ω (g( n )) • ·例、 设 T(n) = 3n+1 = O ( n2) 且 3n+1 != Ω( n2) 。所以, 3n+1 = o ( n2) • 但 T(n) = 3n+1 != o ( n ) 其它表示法 • ·例、 设 T(n) = (n+1)2 • = n2+2n2 +1 >= n2 ; 在 n 为任何数时,所以,T(n) = Ω (n2)