数据
数据:是描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识别,并输入给计算机处理的符号集合
数据元素
数据元素:是组成数据的、有一定意义的基本单位,在计算机中通常作为整体处理,也被称为记录
数据项
数据项:一个数据元素可以由若干个数据项组成,数据项是数据不可分割的最小单位
数据对象
数据对象:是性质相同的数据元素的集合,是数据的子集
数据结构
不同数据元素之间不是独立的,而是存在特定的关系,我们将这些关系称为结构
数据结构:是相互之间存在一种或多种特定关系的数据元素的集合
数据结构
逻辑结构
逻辑结构:是指数据对象中数据元素之间的相互关系
集合结构
集合结构:集合结构中的数据元素除了同属于一个集合外,它们之间没有其他关系。各个数据元素是”平等”的,类似于数学中的集合
线性结构
线性结构:线性结构中的数据元素之间是一对一的关系
树形结构
树形结构:树形结构中的数据元素之间存在一种一对多的层次关系
图形结构
图形结构:图形结构的数据元素是多对多的关系
物理结构
物理结构(存储结构):是指数据的逻辑结构在计算机中的存储形式
顺序存储结构
顺序存储结构:是把数据元素存放在地址连续的存储单元里,其数据间的逻辑关系和物理关系是一致的
链式存储结构
链式存储结构:是把数据元素存放在任意的存储单元里,这组存储单元可以是连续的,也可以是不连续的,数据元素的存储关系并不能反映其逻辑关系,因此需要用一个指针存放数据元素的地址,这样通过地址就可以找到相关数据元素的位置
数据类型
数据类型:是指一组性质相同的值的集合及定义在此集合上的一些操作的总称
在C语言中,按照取值的不同,数据类型可以分为两类
原子类型
是不可以再分解的基本类型,包括整型、字符型等
结构类型
由若干个类型组合而成,是可以再分解的,例如整型数组
抽象数据类型
抽象数据类型(Abstract Data Type, ADT):是指一个数学模型及定义在该模型上的一组操作,如”整数”类型->整型
抽象数据类型体现了程序设计中问题分解、抽象和信息隐藏的特性
算法
算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作
算法的特性
算法具有五个基本特性:输入、输出、有穷性、确定性和可行性
- 算法具有零个或多个输入
- 算法至少有一个或多个输出
- 算法在执行有限的步骤后,自动结束而不会出现无限循环,并且每一个步骤在可接受的时间内完成
- 算法的每一步骤都具有确定的含义,不会出现二义性
- 算法的每一步都必须是可行的,也就是说,每一步都能够通过执行有限次数完成
正确性
正确性:算法的正确性是指算法至少应该具有输入、输出和加工处理无歧义性、能正确反映问题的需求、能够得到问题的正确答案
正确性大体分为以下四个层次:
- 算法程序没有语法错误
- 算法程序对于合法的输入数据能够产生满足要求的输出结果
- 算法程序对于非法的输入数据能够产生满足规格说明的结果(一般情况下,将此作为判断一个算法是否正确的标准)
- 算法程序对于精心选择的,甚至刁难的测试数据都有满足要求的输出结果
可读性
可读性:算法设计的另一个目的是为了便于阅读、理解和交流
健壮性
健壮性:当输入数据不合法时,算法也能做出相关处理,而不是产生异常或莫名其妙的结果
时间效率高和存储量低
设计算法应该尽量满足时间效率高和存储量低的需求
算法效率的度量方法
事后统计方法
这种方法主要是通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序的运行时间进行比较,从而确定算法效率的高低
缺陷较大,不予采纳
事前分析估算方法
在计算机程序编制前,依据统计方法对算法进行估算
一个程序的运行时间,依赖于算法的好坏和问题的输入规模
在分析程序的运行时间时,最重要的是把程序看成是独立于程序设计语言的算法或一系列步骤
函数的渐近增长
函数的渐近增长:给定两个函数f(n)和g(n),如果存在一个整数N,使得对于所有的n > N,f(n)总是比g(n)大,那么,我们说f(n)的增长渐近快于g(n)
判断一个算法的效率时,函数中的常数和其他次要项常常可以忽略,而更应该关注主项(最高阶项)的阶数
算法时间复杂度
定义
在进行算法分析时,语句总是执行次数T(n)是关于问题模型n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级,算法的时间复杂度,也就是算法的时间量度,记作:T(n)=O(f(n))。它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐近时间复杂度,简称为时间复杂度,其中f(n)是问题规模n的某个函数
一般情况下,随着n的增大,T(n)增长最慢的算法为最优算法
常数阶:O(1)
不管n为多少,执行的次数都是恒定的,不会随着n的变大而发生变化,其时间复杂度为O(1)
线性阶:O(n)
循环结构中的代码需要执行n次,其时间复杂度为O(n)
for(i = 0; i < n; i++){ }
对数阶:O(logn)
有多少个2相乘后大于n,则会退出循环
2^x=n
int count = 1; while(count < n){ count = count * 2; }
平方阶
两层循环嵌套
int i,j; for(i = 0; i < n; j++){ for(j = i; j < n; j++){ } }
常见的时间复杂度
算法空间复杂度
算法的空间复杂度通过计算算法所需的存储空间实现,算法空间复杂度的计算公式记作:S(n)=O(f(n)),其中,n为问题的规模,f(n)为语句关于n所占存储空间的函数
当不用限定词地使用”复杂度”时,通常都是指时间复杂度
线性表
定义
线性表(List):零个或多个数据元素的有限序列
除第一个元素外,每一个元素有且只有一个直接前驱元素,除了最后一个元素外,每一个元素有且只有一个直接后继元素,数据元素之间的关系是一对一的关系
在较复杂的线性表中,一个数据元素可以由若干个数据项组成
线性表的顺序存储结构
线性表的顺序存储结构,指的是用一段地址连续的存储单元一次存储线性表的数据元素
顺序存储方式
在内存中占据一定的内存空间,然后把相同数据类型的数据元素依次存放在这块空地中
属性:
- 存储空间的起始位置:数组data,它的存储位置就是存储空间的存储位置
- 线性表的最大存储容量:数组长度MaxSize
- 线性表的当前长度:length
存储器中的每个存储单元都有自己的编号,这个编号称为地址
对于每个线性表位置的存入或者取出数据,对于计算机来说都是相等的时间( LOC(ai)=LOC(a1)+(i−1)∗c ),也就是一个常数,因此它的时间复杂度为O(1)
顺序存储结构的插入与删除
插入:
- 如果插入位置不合理,抛出异常
- 如果线性表长度大于等于数组长度,则抛出异常或动态增加容量
- 从最后一个元素开始向前遍历到第i个位置,分别将它们都向后移动一个位置
- 将要插入元素填入位置i处
- 表长加1
删除:
- 如果删除位置不合理,抛出异常
- 取出删除元素
- 从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置
- 表长度减1
插入或删除时,平均移动次数和最中间的那个元素移动次数相等,为(n-1)/2
插入和删除时,时间复杂度为O(n)
线性表顺序存储结构的优缺点
优点:
- 无须为表示表中元素之间的逻辑关系而增加额外的存储空间
- 可以快速地存取表中任一位置的元素
缺点:
- 插入和删除操作需要移动大量元素
- 当线性表长度变化较大时,难以确定存储空间的容量
- 造成存储空间的”碎片”
线性表的链式存储结构
线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的,这就意味着,这些数据元素可以存在内存未被占用的任意位置
链式结构中,除了要存储数据元素信息外,还要存储它的后继元素的存储地址
链表中第一个结点的存储位置叫做头指针,之后的每一个结点,其实就是上一个的后继指针指向的位置,最后一个结点的指针为null
为了更加方便地对链表进行操作,有时会在单链表的第一个结点前附设一个结点,称为头结点,头结点的数据域可以不存储任何信息,也可以存储如线性表的长度等附加信息,头结点的指针域存储指向第一个结点的指针
头指针与头结点的异同:
- 头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针
- 头指针具有标识作用,所以常用头指针冠以链表的名字
- 无论链表是否为空,头指针均不为空,头指针是链表的必要元素
- 头结点是为了操作的统一和方便而设立的,放在第一元素的结点之前,其数据域一般无意义(也可存放链表的长度)
- 有了头结点,对在第一元素结点前插入结点和删除第一结点,其操作与其它结点的操作就统一了
- 头结点不一定是链表必须要素
单链表的读取
单链表中查找某一个元素,必须要从头开始找
获得链表第i个数据的算法思路:
- 声明一个结点p指向链表第一个结点,初始化j从1开始
- 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累加1,
- 若到链表末尾p为空,则说明第i个元素不存在
- 否则查找成功,返回结点p的数据
时间复杂度为O(n)
单链表的插入与删除
在ai和ai+1之间插入一个数据,只需要
s->next=p->next;
p->next=s;
单链表第i个数据插入结点的算法思路:
- 声明一结点p指向链表第一个结点,初始化j从1开始
- 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一结点,j累加1
- 若到链表末尾p为空,则说明第i个元素不存在
- 否则查找成功,在系统中生成一个空结点s
- 将数据元素e赋值给s->data
- 单链表的插入标准语句s->next=p->next;p->next=s;
- 返回成功
在ai-1和ai+1之间删除ai结点,只需要
q=p->next;
p->next=q->next;
单链表第i个数据删除结点的算法思路:
- 声明一结点p指向链表第一个结点,初始化j从1开始
- 当j<i时,就遍历链表,让p的指针向后移动,不断指向下一个节点,j累加1
- 若到链表末尾p为空,则说明第i个元素不存在
- 否则查找成功,将欲删除的结点p->next赋值给q
- 单链表的删除标准语句p->next=q->next
- 将q结点中的数据赋值给e,作为返回
- 释放q结点
- 返回成功
单链表在查询、插入和删除操作上的时间复杂度都是O(n),但是如果需要从第i个位置,插入10个元素,对于顺序存储结构意味着,每一次插入都需要移动n-i个元素,每次都是O(n),而单链表,只需要在第一次时,找到第i个位置的指针,此时为O(n),接下来的时间复杂度都是O(1),所以对于插入或者删除数据越频繁的操作,单链表的效率优势就越是明显
单链表的整表创建
单链表整表创建的算法思路:
- 声明一结点p和计数器变量i
- 初始化一空链表L
- 让L的头结点的指针指向NULL,即建立一个带头结点的单链表
循环:
- 生成一新结点赋值给p
- 随机生成一数字赋值给p的数据域p->data
- 将p插入到头结点与前一新结点之间(头插法,始终让新结点在第一的位置),也可以将p插入到终端结点的后面(尾插法)
单链表的整表删除
单链表整表删除的算法思路如下:
- 声明一结点p和q
- 将第一个结点赋值给p
循环
- 将下一结点赋值给q
- 释放p
- 将q赋值给p
单链表结构与顺序存储结构优缺点
存储分配方式
- 顺序存储结构用一段连续的存储单元依次存储线性表的数据元素
- 单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素
时间性能
查找
- 顺序存储结构O(1)
- 单链表O(n)
插入和删除
- 顺序存储结构需要平均移动表长一半的元素,时间为O(n)
- 单链表在线出某位置的指针后,插入和删除时间仅为O(1)
空间性能
- 顺序存储结构需要预分配存储空间,分大了,浪费,分小了易发生上溢
- 单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制
静态链表
用数组描述的链表叫做静态链表(数组中的元素由两个数据域组成,data和cur)
数组中的第一个元素(下标为0)的cur存放备用链表的第一个结点的下标(即下一个元素插入存放的位置),数组的最后一个元素的cur则存放第一个有数值的元素的下标(即存放链头的位置)
静态链表的插入操作
将元素”丙”插入到”乙”和”丁”之间
静态链表的删除操作
将元素”甲”删除
静态链表优缺点
优点
- 在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中的插入和删除操作需要移动大量元素的缺点
缺点
- 没有解决连续存储分配带来的表长难以确定的问题
- 失去了顺序存储结构随机存储的特性
循环链表
将单链表中终端结点的指针端由空指针改为指向头指针,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表
双向链表
双向链表是在单链表的每个结点中,再设置一个指向其前驱结点的指针域
非空的循环的带头结点的双向链表如下图所示
双向链表在插入和删除时,需要更改两个指针变量
s->prior = p;
s->next = p->next;
p->next->prior = s;
p->next = s;
删除结点p,只需要下面两步骤
p->prior->next = p->next;
p->next->prior = p->prior;
free(p);