数据结构学习第一章绪论

第一章—绪论

一、基本概念

1.1数据、数据元素、数据项和数据对象

1.数据(Data)—能够输入计算机且能被计算机处理的各种符号集合。

​ 数据是信息的载体,也是对客观事物符号化的表示,能够被计算机识别、存储和加工。

​ 数据包括:

​ *数值型数据:整数、实数等可以进行加减乘除的数。

​ *非数值型数据:文字、图像、图形、声音等。

2.数据元素(Data Element)—是数据的基本单位(个体)

​ 在计算机程序中通常作为一个整体进行考虑和处理。例如学生表中,一个数据元素中包含了学号、姓名、性别等信息。或者说,计算机对这些信息作为一个整体继续考虑与处理,这个整体成为一个数据元素。

​ *也称为元素,有时候称为记录(一个表中的一个记录)、结点(树中的一个结点)或者顶点(图中的一个顶点)。

​ *一个数据元素由若干个数据项组成。

3.数据项(Data Item)—构成数据元素不可分割的最小单位。

​ 例如:学生表中,有学号、姓名、性别等数据项。

​ 关系:数据 > 数据元素 > 数据项,如:学生表 > 个人记录 > 学号、姓名……

​ 即数据是由数据元素组成,数据元素是由数据项组成。

4.数据对象(Data Object)—性质相同数据元素的集合,是数据的一个子集。

​ 例如:*整数集合N

​ *字母字符集合(ASCII)

​ *学籍表可看做一个数据对象

1.2数据结构(Data Structure)

1.数据结构:数据元素相互之间存在着某种关系,这种相互之间的关系称为结构(Structure)。即相互之间存在一种或多种特定关系的数据元素集合。换句话来说,数据结构是带结构的数据元素集合。

​ 数据结构包括三个内容:

​ (1)逻辑结构—数据元素之间的逻辑关系

​ (2)存储结构—数据元素及关系在计算机内存中的表示(映像),称为数据的物理结构或数据结构。

​ (3)数据的运算和实现—对数据元素施加操作以及这些操作在存储结构上的实现。

​ 注:逻辑结构是数据结构的抽象,存储结构是数据结构的实现。二者综合起来建立了数据元素之间的结构关系。

2.逻辑结构

​ *逻辑结构是描述数据元素之间的逻辑关系

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

​ *从具体问题抽象出来的数学模型

划分方法一:

(1)线性结构:有且仅有一个开始和一个终端结点,并且所有结点最多只有一个前驱和一个直接后继。

​ 如:线性表、栈和队列(有限制的线性表)、串

(2)非线性结构:一个结点可能有多个直接前驱和直接后继。(一对多或者多对多的关系—1:n或m:n)

​ 如:树、图

划分方法二:

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

(2)线性结构:结构中存在一对一的线性关系。

(3)树结构:结构中存在一对多的层次关系。

(4)图结构:结构中存在多对多的任意关系。

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

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

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

四种基本存储结构:

*顺序存储:用一组“连续”的存储单元按照顺序依次存储数据元素。逻辑关系用“元素位置”表示。例如C语言中用“数组”实现顺序存储结构。

*链式存储:用一组“任意”的存储单元存储数据元素,逻辑关系用“指针”表示。例如C语言中“指针”来实现链式存储结构。(指针就是一个地址)

*索引存储:在存储结点信息同时,建立附加的索引表。一般形式是:(关键字,地址)

*散列存储:根据结点的关键字直接计算出结点的存储地址。也称”哈希存储“。

二.算法

2.1数据类型和抽象数据类型

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

如C语言中:

(1)int,char,float,double等基本数据类型

(2)数组,结构,共用体,枚举等构造数据类型

(3)指针,空类型

(4)也可以用typedef自己定义数据类型

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

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

*主句类型的作用:约束变量或常量的取值范围;约束变量或常量的操作。

1.数据类型(Data Type):一组性质相同的值的集合以及定义域这个值集合上一组操作的总称。

数据类型 = 值的集合+值集合上的一组操作。

2.抽象数据类型(Abstract Data Type , ADT):是指一个数学模型以及定义在此数学模型上的一组操作。

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

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

*不考虑在计算机内部如何存储。

3.抽象数据类型的形式定义:(D,S,P)三元组表示,D数据对象,S是D上关系集,P是对D的基本操作集。

ADT 抽象数据类型名{
    数据对象:<数据对象定义>   //伪代码描述
    数据关系:<数据关系定义>   //伪代码描述
    基本操作:<基本操作定义>   
    //基本操作定义格式:
        //基本操作名(参数表)
        //初始条件:<初始条件描述>
        //操作结果:<操作结果描述>
}ADT 抽象数据类型名

基本操作:

参数表:*值传递:赋值参数,只为操作提供输入值。

​ *引用传递:引用参数,以&开头,除了提供输入值以外,还将返回操作结果。

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

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

//ADT定义格式
ADT 抽象数据类型名{
    Data 
       数据对象的定义
       数据元素之间逻辑关系的定义
    Operation
        操作1
          初试条件
          操作结果描述
        操作2
          ……
        操作n
          ……
}ADT 抽象数据类型名

举例1:抽象数据类型定义一个圆

ADT Circle{
    数据对象:D={r,x,y| r,x,y均为实数}
    数据关系:S={<r,x,y>| r是半径,<x,y>是圆心坐标}
    基本操作:
        Circle(&C,r,x,y)
           操作结果:构造一个圆。
        double Area(C)
           初试条件:圆已存在。
           操作结果:计算面积。
        double Circumference(C)
           初试条件:圆已存在
           操作结果:计算周长。
        ……
}ADT Circle

举例2:抽象一个复数定义

ADT Complex{
    D = {r1,r2 | r1,r2都是实数}
    S={<r1,r2>|r1是实部,r2是虚部}
    assign(&C,v1,v2)
       初始条件:空的复数C已存在
       操作结果:构造复数C,r1、r2分别被赋值v1、v2
    destory(&C)
       初始条件:复数C已存在
       操作结果:复数C被销毁
     getReal(Z,&realPart)
       初始条件:复数Z已存在
       操作结果:用realPart返回实部值
     getImag(Z,&imagPart)
       初始条件:复数Z已存在
       操作结果:用imagPart返回虚部值
     Add(z1,z2,&sum)
       初始条件:复数z1和z2已存在
       操作结果:用sum返回两个复数z1,z2的和。
}ADT Complex

2.2抽象数据类型的表示与实现

用C语言真正实现抽象数据类型的定义

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

typedef struct{
    float realpart; //实部
    float imagpart; //虚部 
}Complex //定义“复数”抽象类型
    
void assign(Complex *A,float real,float imag); //赋值
void add(Complex *C,Complex A,Complex B)

用C语言实现"assign"和"add"操作

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

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

​ 带*:表示这是一个指针变量,它是指向Complex类型的指针。调用例子“ C->realpart ”。

​ 不带*:表示这是一个Complex的普通变量。调用例子“ A.realpart ”。

用C语言实现实现求上面复数 z= (8 + 6i)(4 + 3i) / [(8 + 6i) + (4 + 3i)]

complex z1,z2,z3,z4,z;
float realPart,imagPart;
assign(z1,8.0,6.0); //构造复数  8+6i
assign(z2,4.0,3.0);  //构造复数  4+3i
add(&z3,z1,z2);    //z1,z2两复数相加赋值给z3
multiply(&z4,z1,z2);  //z1,z2两复数想乘赋值给 z4

if(divide(z4,z3,&z)){  //加判断,若分母为0,除数不存在为false/0,没法获取。
                       //若可以进行除操作,为true/1。
    Getreal(z,realPart); //获取结果实部
    GetImag(z,ImagPart);  //获取结果虚部
}

2.3算法和算法分析

*算法:解决问题的一种方法或过程,考虑的是如何输入转换成输出。且一个问题可以有多种算法。

*程序:用某种程序设计语言对算法的具体实现。

程序 = 数据结构(通过算法实现操作) + 算法(根据数据结构设计程序)

*算法分析:通过考虑算法效率(时间、空间)来判断算法的优劣。

1.算法时间复杂度(时间效率)

通常,算法时间复杂度随“问题规模”增长而增长。

例如:两个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*n次
    }

我们把算法所耗费时间定义"为该算法中每条语句的频度之和",则上述算法时间消耗T(n)=2n³ + 3n² + 2n + 1

为了方便比较不同算法的时间效率,我们仅比较它们的“数量级”。这里n*n的矩阵相乘的算法的时间复杂度为“ O(n³) ”。(只看幂最高的)

数学解释(极限):设有辅助函数f(n),当n趋向于无穷大时(执行次数无穷次),T(n) / f(n)的极限值为不等于“零”的常数,则称f(n)是T(n)的同数量级。记作T(n) = O(f(n)),这里称O(f(n))为算法的渐进时间复杂度,简称“时间复杂度”。

定理1.1:忽略所有低次幂项和高次幂系数。只考虑最高次幂。
若 f ( n ) = a m n m + a m − 1 n m − 1 + … … + a 1 n + a 0 是 m 次多项式,则 T ( n ) = O ( n m ) 。 若 f(n) = a_m n^m + a_{m-1} n^{m-1} +…… +a_1 n + a_0是m次多项式,则T(n) = O(n^m)。 f(n)=amnm+am1nm1+……+a1n+a0m次多项式,则T(n)=O(nm)
分析算法时间复杂度基本方法:

(1)找出语句频度最大的语句作为基本语句(即找嵌套最多的)

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

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

【例1.1】常量阶示例:O(1)

x++;
s=0;

算法执行时间是一个与问题规模n无关的常数,所以算法时间复杂度T(n)=O(1)。

实际上,如果算法的执行时间不随问题规模n的增加而增长,算法中语句频度就是某个常数,即使这个常数再大,时间复杂度也是O(1)。

for(i=0;i<10000;i++){
    x++;
    s=0;
}

这个算法的时间复杂度也是O(1)

【例1.2】线性阶示例:O(n)

for(i=0;i<n;i++){
    x++;
    s=0;
}

循环内两条基本语句频度均f(n)=n,所以时间复杂度是O(n)。

【例1.3】平方阶示例:O(n²)

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

这里,x++的时间复杂度为O(n),y++的时间复杂度为O(n²)。而总体的时间复杂度为O(n²)。

这里有一个加法规则:T(n) = T1(n) + T2(n)=O( f(n) ) +O( g(n) ) = O(max { f(n) , g(n) })。

【例1.4】立方阶示例:O(n³)

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

这里计算语句频度
Σ i = 1 n Σ j = 1 i Σ k = 1 j 1 = Σ i = 1 n Σ j = 1 i j = Σ i = 1 n i ( i + 1 ) 2 = [ n ( n + 1 ) ( 2 n + 1 ) 6 + n ( n + 1 ) 2 ] 2 \Sigma_{i=1}^{n}\Sigma_{j=1}^{i}\Sigma_{k=1}^{j}1=\Sigma_{i=1}^{n}\Sigma_{j=1}^{i}j=\Sigma_{i=1}^{n}\frac{i(i+1)}{2}=\frac{[\frac{n(n+1)(2n+1)}{6}+\frac{n(n+1)}{2}]}{2} Σi=1nΣj=1iΣk=1j1=Σi=1nΣj=1ij=Σi=1n2i(i+1)=2[6n(n+1)(2n+1)+2n(n+1)]
可见语句频度最高次是 n³。所以算法时间复杂度T(n)= O(n³)。

【例1.5】对数阶示例:O(㏒₂n)

for(i=1;i<n;i=i*2){
    x++;
    s=0;
}

这里设循环内两条基本语句频度f(n),则2^f(n) ≤ n,所以f(n) ≤ ㏒₂n。因此这个算法的时间复杂度为O(㏒₂n)。

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

这里与上面的同理,只是循环语句不同。所以算法时间复杂度也是O(㏒₂n)。

【总结】

除了【例1.3】中有一个加法规则:

加法规则:T(n) = T₁(n) + T₂(n)=O( f(n) ) +O( g(n) ) = O(max { f(n) , g(n) })

还有一个乘法规则:

T(n) = T₁(n) * T₂(n) = O(f(n)) * O(g(n))=O(f(n) * g(n))

常见的时间复杂度高低关系(常对幂指阶)n作为数:常数<对数<幂数<指数<阶层
O ( 1 ) < O ( log ⁡ 2 n ) < O ( n ) < O ( n log ⁡ 2 n ) < O ( n 2 ) < O ( n 3 ) < O ( 2 n ) < O ( n ! ) < O ( n n ) O(1)< O(\log_2n)<O(n)<O(n\log_2n)< O(n^2)<O(n^3)<O(2^n)<O(n!)<O(n^n) O(1)<O(log2n)<O(n)<O(nlog2n)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)
例题1:

x=0;y=0;
for(int k = 0;k<n;k++)     // n+1次
    x++;   // n次        
for(int i = 0; i< n ;i++)  // n+1次
    for(int j = 0; j < n ;j++) //n*(n+1)次
        y++;  // n*n次

所以以上算法时间复杂度T(n) = O(n²)。

例题2:

void exam(float x[][],int m,int n ){
    float sum[];
    forint i = 0; i< m; i++){
        sum[i] = 0.0;
        for(j = 0;j < n;j++)
            sum[i] += x[i][j];
    }
    for(i = 0;i<m;i++)
        println("i:%f",sum[i]);
}

以上算法时间复杂度T(n) = O(m *n)。

例题3:

for(i=1;i<=n;i++)
    for(j=1;j<=n;j++){
        c[i][j]=0;
        for(k=1;k<=n;k++)
            c[i][j] = c[i][j] + a[i][k]*b[i][j];
    }

以上算法时间复杂度T(n)=O(n³)。

2.算法空间复杂度(空间效率)

算法所需要耗费的存储空间度量,记作S(n) = O(f(n))。其中n为问题规模。

算法要耗费(占据)空间:

*本身占据的空间:输入/输出,指令,常数,变量

*算法使用的辅助空间

【例】将一组数组a中的n个数逆序存放到原数组中。

//算法1:把第一个和最后一个交换,把第二个和倒数第二个交换,以此类推。
for(i=0;i<n/2;i++){
    t=a[i];  //辅助空间只有一个t变量
    a[i]=a[n-i-1];
    a[n-i-1]=y;
}

//算法2:把a数组倒置放到b数组中,再把b全部搬到a数组中。
for(i=0;i<n;i++)
    b[i]=a[n-i-1];  //辅助空间是一整个b[i]数组
for(i=0;i<n;i++)
    a[i]=b[i];

算法1是原地工作:S(n)=O(1)。也称常数阶算法是原地工作

算法2:S(n) =O(n)。

学习视频:数据结构——王卓
参考文献:数据机构C语言版第2班——严蔚敏

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值