算法和数据结构 - 绪论

文章目录

  • 绪论
    • 基本概念和术语
      • 数据结构的两个层次
      • 逻辑结构的种类
      • 存储结构的种类
      • 数据类型和抽象数据类型
      • 抽象数据类型的表示和实现
      • 算法和算法分析
      • 算法时间效率的比较
      • 渐进空间复杂度

绪论

基本概念和术语

数据结构的两个层次

1.逻辑结构

·描述数据元素之间的逻辑关系

·与数据的存储无关,独立于计算机

·是从具体问题抽象出来的数学模型

2.物理结构(存储结构)

·数据元素及其关系在计算机存储器中的结构(存储方式)

·是数据结构在计算机中的表示

逻辑结构与存储结构的关系

·存储结构是逻辑关系的映像与元素本身的映像

·逻辑结构是数据结构的抽象,存储结构是数据结构的实现

·两者综合起来建立了数据元素之间的结构关系

逻辑结构的种类

划分方法一

(1)线性结构

有且仅有一个开始和一个终端结点,并且所有结点都最多只有一个直接前驱和一个直接后继

例如:线性表、栈、队列、串

(2)非线性结构

一个结点可能有多个直接前驱和直接后继

例如:树、图

划分方法二

(1)集合结构:

结构中的数据元素之间除了同属于一个集合的关系外,无任何其他关系

(2)线性结构:

结构中的数据元素之间存在着一对一的线性关系

(3)树形结构:

结构中的数据元素之间存在着一对多的层次关系

(4)图状结构或网状结构

结构中的数据元素之间存在着多对多的任意关系

存储结构的种类

顺序存储结构:

用一组连续的存储单元依次存储数据元素,数据元素之间的逻辑关系由元素的存储位置来表示

C语言中用数组来实现顺序存储机构

链式存储结构:

用一组任意的存储单元存储数据元素,数据元素之间的逻辑关系用指针来表示

C语言中用指针来实现链式存储结构

索引存储结构:

在存储结点信息的同时,还建立附加的索引表

索引表中的每一项称为一个索引项

索引项的一般形式是:(关键字,地址)

关键字是能唯一标识一个结点的哪些数据项

若每个结点在索引表中都有一个索引项,则该索引表称之为稠密索引(Dense Index)。若一组结点在索引表中只对应一个索引项,则该索引表称之为稀疏索引(Sparse Index).

散列存储结构:

根据结点的关键字直接计算出该结点的存储地址

数据类型和抽象数据类型

在使用高级程序设计语言编写程序时,必须对程序中出现的每个变量,常量或表达式,明确说明它们所属的数据类型

例如,C语言中:

提供int、char、float、double、等基本数据类型

数组、结构、共用体、枚举等构造数据类型

还有指针,空(void)类型

用户也可以用typedef自己定义数据类型

一些最基本数据结构可以用数据类型来实现,如数组、字符串等

而另一些常用的数据结构,如栈、队列、树、图等,不能直接用数据类型来表示

抽象数据类型

是指一个数学模型以及定义在此数学模型上的一组操作

1.由用户定义,从问题抽象出数据模型(逻辑结构)

2.还包括定义在数据模型上的一组抽象运算(相关操作)

3.不考虑计算机内的具体存储与运算的具体实现算法

抽象数据类型的形式定义

抽象数据类型可用(D,S,P)三元组表示。

其中:

​ D是数据对象

​ S是D上的关系集合

​ P是对D的基本操作集

一个抽象数据类型的定义格式:

ADT 抽象数据类型名{

​ 数据对象:<数据对象的定义>

​ 数据关系:<数据关系的定义>

​ 基本操作:<基本操作的定义>

}ADT 抽象数据类型名

其中:数据对象、数据关系的定义用伪代码描述

基本操作的定义格式为:

- 基本操作名(参数表)
- 初始条件:<初始条件描述>
- 操作结果:<操作结果描述>

基本操作定义格式说明:

参数表:赋值参数只为操作提供输入值

​ 引用参数以&打头,除可提供输入值外,还将返回操作结果

初始条件:描述操作执行之前数据结构和参数应满足的条件,若不满足,则操作失败,并返回相应出错信息。若初始条件为空,则省略之

操作结果:说明操作正常完成之后,数据结构的变化状况和应返回的结果

抽象数据类型定义举例:Circle的定义

ADT 抽象数据类型名{Data

​		数据对象的定义

​		数据元素之间逻辑关系的定义

​	Operation

​		操作1

​			初始条件

​			操作结果描述

​		操作2......

​		操作n

​			......

}
ADT Circle{

​	数据对象: D = {r,x,y|r,x,y均为实数}

​	数据关系: R = {<r,x,y>|r是半径,<x,y>是圆心坐标}

​	基本操作:Circle(&C,r,x,y)

​		操作结构:构造一个圆

​	double Area(C)

​		初始条件:圆已存在

​		操作结构:计算面积

​	double Circumstance(C)

​		初始条件:圆已存在

​		操作结果:计算周长

}

抽象数据类型的表示和实现

ADT 抽象数据类型名{Data

​		数据对象的定义

​		数据元素之间逻辑关系的定义

​	Operation

​		操作1

​			初始条件

​			操作结果描述

​		操作2......

​		操作n

​			......

}

C语言实现抽象数据类型

用已有的数据类型定义描述它的存储结构

用函数定义描述它的操作

就可以在程序中使用

例如:抽象数据类型“复数”的实现

typedef struct{
	float realpart;         /*实部*/
    float imagpart;          /*虚部*/
}Complex
void assign(Complex *A,float real,float imag){    
	A->realpart = real;			/*实部赋值*/
    A->imagepart = imag;		/*虚部赋值*/	
}							  /*End of assign()*/
void add(Complex *c,Complex A,Complex B){        /*c = A + B */
    c->realpart = A.realpart + B.realpart;		/*实部相加*/
    c->imagpart = A.imagpart + B.imagpart;		/*虚部相加*/
} 										     /*End of assign()*/

注意:Complex是我们定义的一个结构体类型

带*:指针变量,它是指向Complex类型的指针

不带*:Complex类型的普通变量

#include 
typedef struct{
    float realpart;         /*实部*/
    float imagpart;          /*虚部*/
}Complex;
void assign(Complex *A,float real,float imag){
    A->realpart = real;			/*实部赋值*/
    A->imagpart = imag;		/*虚部赋值*/
}
void add(Complex *c,Complex A,Complex B){        /*c = A + B */
    c->realpart = A.realpart + B.realpart;		/*实部相加*/
    c->imagpart = A.imagpart + B.imagpart;		/*虚部相加*/
}
int main(void){
    Complex z1,z2,z3,z4,z;
    float RealPart,ImagePart;
    assign(&z1,8.0,6.0);
    assign(&z2,4.0,3.0);
    add(&z3,z1,z2);
    multiply(z1,z2,z4);
    if(divide(&z,z4,z3)){
        GetReal(&z,RealPart);
        GetImag(&z,ImagePart);
    }
    return 0;
}

以上代码由类C语言实现,仍有不全之处,读者自行补充

算法和算法分析

算法特性:一个算法必须具备以下五个重要特性

  • 有穷性:一个算法必须总是在执行有穷步之后结束,且每一步都在又穷时间内完成
  • 确定性:算法中的每一条指令必须有确切的含义,没有二义性,在任何条件下,只有唯一的一条执行路径,即对于相同的输入只能得到相同的输出。
  • 可行性:算法是可执行的,算法描述的操作可以通过已经实现的基本操作执行有限次来实现
  • 输入:一个算法有零个或多个输入
  • 输出:一个算法有一个或多个输出

算法设计的要求

  • 正确性
  • 可读性
  • 健壮性
  • 高效性

算法效率以下两个方面来考虑:

1.时间效率:指的是算法所消耗的时间

2.空间效率:指的是算法执行的过程中所消耗的存储空间

时间效率和空间效率有时候是矛盾的

算法时间效率的度量

算法时间效率可以用一句该算法编制的程序在计算机上执行所消耗的时间来度量

两种度量方法

事后统计:

将算法实现后,测算其时间和空间开销

事前统计:

对算法所消耗资源的一种估算方法

事前分析方法

一个算法的运行时间是指一个算法在计算机上运行所耗费的时间大致可以等于计算机执行一种简单的操作(如赋值、比较、移动等)所需的时间与算法中进行的简单操作次数乘积

算法运行时间 = 一个简单操作所需的时间 × 简单操作次数

也即算法中每条语句的执行时间之和

算法运行时间 = ∑每条语句的执行次数 × 该语句执行一次所需的时间

每条语句执行一次所需的时间,一般是由机器而异的。取决于机器的指令性能、速度以及编译的代码质量。是由机器本身软硬件环境决定的,它与算法无关。

所以,我们可假设执行每条语句所需的时间均为单位时间。此时对算法的运行时间的讨论就可转化为讨论该算法中所有语句的执行次数,即频度之和了。

  • 例如:两个n×n矩阵相乘的算法可描述为:

    for(i = 1;i <= n; i++){				//n+1次
        for(j = 1; j <= n; j++){		 //n(n+1)次
            c[i][j] = 0;			     //n*n次	
            for(k = 0; k < n; k++){		  //n*n*(n+1)次
                c[i][j] = c[i][j] + a[i][k]*b[k][j];//n*n*n4次
            }
        }
    }
    
    • 我们把算法所消耗的时间定义为该算法中每条语句的频度之和,则上述算法的时间消耗T(n)为:

      T(n) = 2n³+3n²+2n+1

为了便于比较不同算法的时间效率,我们仅比较它们的数量级

例如:两个不同的算法,时间消耗分别是:

T1(n) = 10n² T2(n) = 5n³

若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限值为不等于零的常数,则成f(n)是T(n)的同数量级函数。记作T(n) = O(f(n)),称 O(f(n))为算法的渐进时间复杂度(O是数量级的符号),简称时间复杂度

算法时间复杂度定义

算法中基本语句重复执行的次数问题规模n的某个函数f(n),算法的时间量度记作:T(n) = O(f(n))

基本语句:

算法中重复执行次数和算法的执行时间成正比的语句

对算法运行时间的贡献最大

执行次数最多

n越大算法的执行时间越长

排序:n为记录数

矩阵:n为矩阵的阶数

多项式:n为多项式的项数

集合:n为元素个数

树:n为树的结点个数

图:n为图的定点数或边数

定理1.1

若f(n) = am n^m + am-1 n^m-1 + … + a1 n + a0是m次多项式

则T(n) = O(n^m)

忽略所有低次幂项和最高次幂系数,体现出增长率的含义

分析算法时间复杂度的基本方法

1.找出语句频度最大的那条语句作为基本语句

2计算基本语句的频度得到问题规模n的某个函数f(n)

3.取其数量级用符号"O"表示

例1:

x = 0; y =0;
for (int k = 0;k < n; k++)
    x ++;
for (int i = 0;i < n;i++)
    for(int j = 0;j < n;j++)
        y ++;

时间复杂度是由嵌套最深语句的频度决定的

例2:

for(i = 1ji <= n;i++)
    for(j = 1;j <= i;j ++)
        for(k = 1;k <=j; k ++)
            x = x + 1;


语 句 频 度 = ∑ i = 1 n ∑ j = 1 i ∑ k = 1 j 1 = ∑ i = 1 n ∑ j = 1 i j = ∑ i = 1 n i ( i + 1 ) 2 语句频度=\sum_{i=1}^n\sum_{j=1}^i\sum_{k=1}^j1=\sum_{i=1}^n\sum_{j=1}^ij=\sum_{i=1}^n\frac{i(i+1)}{2} =i=1nj=1ik=1j1=i=1nj=1ij=i=1n2i(i+1)

例3:

i = 1;①
while(i <= n) ②    
	i = i * 2;

关键是要找出来执行次数x与n的关系,并形成n的函数
若 循 环 执 行 1 次 : i = 1 ∗ 2 = 2 , 若 循 环 执 行 2 次 : i = 2 ∗ 2 = 2 2 , 若 循 环 执 行 3 次 : i = 2 ∗ 2 ∗ 2 = 2 3 , 若 循 环 执 行 x 次 : i = 2 x 设 语 句 ② 执 行 次 数 为 x 次 , 由 循 环 条 件 i ≤ n ∴ 2 x ≤ n ∴ x ≤ l o g 2 n 2 f ( n ) ≤ n 即 f ( n ) ≤ l o g 2 n 取 最 大 值 f ( n ) = l o g 2 n 所 以 该 程 序 段 的 时 间 复 杂 度 T ( n ) = O ( l o g 2 n ) 若循环执行1次:i = 1*2=2,\\ 若循环执行2次:i = 2*2=2^{2},\\ 若循环执行3次:i = 2*2*2=2^{3},\\ 若循环执行x次:i = 2^{x}\\ 设语句②执行次数为x次,由循环条件\\ i \leq n\\ ∴ 2^{x} \leq n\\ ∴ x \leq {log_2{n}}\\ 2^{f(n)} \leq n\\ 即 f(n) \leq {log_2{n}}\\ 取最大值 f(n) = {log_2{n}}\\ 所以该程序段的时间复杂度T(n) = O({log_2{n}}) 1i=12=2,2i=22=22,3i=222=23,xi=2xxin2xnxlog2n2f(n)nf(n)log2nf(n)=log2nT(n)=O(log2n)
请注意:有的情况下,算法中基本操作重复执行的次数还随问题的输入数据集不同而不同

例:顺序查找,在数组a[i]中查找值等于e的元素,返回其所在位置

for(i = 0;i < n;i ++)
    if(a[i] == e) return i+1;
return 0;

最好情况:1次

最坏情况:n

平均时间复杂度:O(n)

最坏时间复杂度:指在最坏情况下,算法的时间复杂度

平均时间复杂度:指在所有可能输入实例在等概率出现的情况下,算法的期望运行时间

最好时间复杂度:指在最好情况下,算法的时间复杂度

一般总是考虑在最坏情况下的时间复杂度,以保证算法的运行时间不会比它更长

对于复杂的算法,可以将它分成几个容易估算的部分,然后利用大O加法法则和乘法法则,计算算法的时间复杂度:

a)加法规则

T(n) = T1(n) + T2(n) = O(f(n)) + O(g(n)) = O(max(f(n),g(n)))

b)乘法规则

T(n) = T1(n) × T2(n) = O(f(n)) × O(g(n)) = O(f(n)×g(n))

算法时间效率的比较

当n取得很大时,指数时间算法和多项式时间算法在所需时间上非常悬殊

时间复杂度T(n)按数量级递增顺序为:
常 数 阶 O ( 1 ) 对 数 阶 O ( l o g 2 n ) 线 性 阶 O ( n ) 线 性 对 数 阶 O ( n l o g 2 n ) 平 方 阶 O ( n 2 ) . . . . . . . . . 立 方 阶 O ( n k ) 指 数 阶 O ( 2 n ) 常数阶 O(1)\\ 对数阶 O({log_2{n}})\\ 线性阶 O(n)\\ 线性对数阶 O(n {log_2{n}})\\ 平方阶 O(n^2)\\ .........\\ 立方阶 O(n^k)\\ 指数阶 O(2^n) O(1)O(log2n)线O(n)线O(nlog2n)O(n2).........O(nk)O(2n)

渐进空间复杂度

  • 空间复杂度:算法所需存储空间的度量

记作 S(n) = O(f(n))

其中n为问题的规模

  • 算法要占据的空间

算法本身要占据的空间,输入/输出,指令,常数,变量等

算法要是用的辅助空间

例:将一维数组a中的n个数逆序存放到原数组中

【算法1】

for( i = 0; i < n/2; i++){    t = a[i];    a[i] = a[n-i-1];    a[n-i-1] = t;}

S(n) = O(1)

【算法2】

for(i = 0;i < n;i ++)
    b[i] = a[n-i-1]
for(i = 0; i < n;i ++)
    a[i] = b[i]

S(n) = O(n)

你可能感兴趣的:(算法和数据结构,数据结构,算法)