数据结构基本概念和算法评价
抽象数据类型(ADT)描述了数据的逻辑结构与抽象运算,通常用三元组(数据对象,数据关系,基本操作集)来表示(或者说包括数据和操作),所以可以定义一个完整的数据结构。
数据结构是一个二元组(D,R),D为数据元素的有限集,R为D上关系的有限集。
抽象数据类型按其值的不同特性可分为:原子类型(int、char)、可变聚合类型(序列,长度可变)、固定聚合类型(复数,两个实数组成,次序确定)。
数据的三个层次:数据、数据元素、数据项,其中数据元素是组成数据的基本单位。
数据结构是相互之间存在一种或多种特定关系的数据元素的集合。
数据的逻辑结构独立于存储结构。
同一逻辑结构中的所有数据元素具有相同的特性,这意味着不仅数据元素所包含的数据项的个数要相同,而且对应数据项的类型要一致。
数据的存储结构是逻辑结构在计算机中的映射,依赖于计算机语言。
数据结构中有四种基本结构:集合结构、线性结构、树、图。
邻接表不是线性表,邻接表是存储结构,线性表是逻辑结构。
散列存储通过散列函数映射到物理空间,不能反应数据之间的逻辑关系。
线性结构中,存在唯一一个被称作“第一个”的数据元素,除第一个元素外,集合中的每个数据元素均只有一个前驱。
集合是最原始的数据结构。
字典可以采用线性表、散列表、跳表的组织方式。
算法的可行性是指算法中的操作都可以通过已经实现的基本运算执行有限次来实现。
输入、输出不是衡量算法的标准。
算法的时间复杂度不能作为其执行时间的绝对度量。
算法与程序的关系:
- 算法是代表对问题的解,程序是算法在计算机上特定的实现。
- 计算机程序是算法实现的手段,但不是唯一手段,密码机也可以实现算法。
一个原地算法是一种使用小的,固定数量的额外之空间来转换资料的算法。
在相同规模n下,复杂度O(n)的算法在时间上总是优于复杂度O()。
所谓时间复杂度是指在最坏情况下,估算算法执行时间的一个上界。
同一个算法,执行效率与语言级别无关。
算法的时间复杂度取决于待处理数据的状态和问题的规模。
递归定义的数据结构(列表、树)通常用递归算法来实现对它的操作。
理论上讲,所有的递归都可以换成非递归。
数据的高位放在低地址,低位放在高地址为大端模式。
线性表
数组是不能频繁进行插入和删除操作的结构。
顺序表中,在逻辑上相邻的元素在物理位置上也相邻。
假设线性表有n个元素,当想要交换表中两个元素时,顺序表比链表效率高,顺序表需要3次交换操作,而链表需要找到两个元素的前驱。当想要顺序输出这n个元素时,顺序表和链表的效率一样,都是O(n)。
长度为n的非空线性表采用顺序存储结构,在表的第i个位置插入一个数据元素,则i的取值范围是,在n+1处插入即在表尾插入。
链式存储用指针表示逻辑结构,而指针的设置是任意的,故可以很方便地表示各种逻辑结构。
顺序存储方式不仅适用于存储线性结构,同样适用于树和图等非线性结构。
顺序存储方式并不用于各种逻辑结构的存储表示。
某线性表用带头结点的循环单链表存储,头指针为head,当head->next->next==head成立时,线性表的长度可能是 0 或 1。
已知链表的存储空间不需要动态分配(静态链表),未知或变化的链表的存储空间才需要动态分配(单链表、线性链表)。
需要分配较大的空间,插入和删除不需要移动元素的线性表的存储结构是静态链表,单链表不行,因为单链表的存储空间是动态分配的。
单链表头指针为head,没有头结点,判空条件为head==0.
静态链表中指针表示的是下一元素在数组中的位置。
数据结构中不存在基本运算,比如多维数组没有插入、删除操作,栈和队列不需要查找。
原地逆序操作中,数组比链表速度更快。
返回头部结点,数组和链表一样快。
线性表 <= 纯表(树)<= 再入表(兄弟结点之间有连线) <= 递归表(图)
链表并不是每个结点中都恰好包含一个指针。
栈和队列
n个不同的元素进栈,出栈元素不同排列的个数为。
采用非递归方式重写递归程序时不一定需要使用栈,例如计算斐波那契数列只需要循环即可实现。
在用单链表实现队列时,队头设在链表的链头位置,为了方便删除队头元素。
栈是一种特殊的线性结构,要求限定在表尾进行插入删除操作,
链队、循环队列等数据结构要注意队列最多能存储几个元素,M个还是M-1个。
栈的应用:表达式求值,括号匹配,递归,进制转换,迷宫求解,xml校验节点是否闭合。
队列的应用:层次遍历,打印缓冲区,CPU资源竞争,广度有限搜索,页面替换算法。
数组与矩阵的压缩存储
在用三元组存储稀疏矩阵时,除了数据之外,要用额外三个单元存储矩阵的行数、列数、总元素数。
广义表是一种多层次、非线性、共享、递归单链表。
当广义表不是空表时,称第一个数据(原子或子表) 为表头,剩下的数据构成新的广义表为表尾。
除非广义表为空表,否则广义表一定具有表头和表尾,且广义表的表头可能是一个元素也可能是一个广义表,表尾一定是一个广义表。
字典是一种特殊的数据类型,不能进行交并差以及相等判断的操作,可以进行插入删除和赋值操作。
数组不能进行插入删除操作,只能进行存取和修改。
数组在编译之前就需要预先存储好所需要的存储空间。
稀疏矩阵不属于特殊矩阵。
树和二叉树
树的表示方法:直观表示法、嵌套集合表示法、凹入表示法、广义表表示法、括号表示法、文氏图标表示法、树状表示法。
树的存储形式:双亲表示法、孩子表示法、孩子兄弟表示法。
孩子兄弟表示法=二叉树表示法,使用二叉链表作为树的存储结构,使用左孩子右兄弟的方法,根结点没有兄弟结点,所以右孩子指针为空。
二叉树是一种逻辑结构,线索二叉树是加上线索之后的链表结构,即它是二叉树在计算机内部的一种存储结构,所以是一种物理结构。
二叉树是由左子树、右子树、根结点构成的(没有叶结点)。
线索二叉树是利用二叉树的n+1个空指针存放节点的前驱和后继信息的。
后序线索树的遍历需要栈的支持。
结点按中序遍历为SYZ的二叉树可能有5种。
对于完全二叉树中的任一结点,若其右分支下的子孙最大层次为h,则其左分支下的子孙最大层次为h或h+1。
线索化二叉树中,结点t没有左子树的充要条件是t->ltag==1(即ltag的含义是节点的左指针是否为空)。
二叉树线索化之后,前序遍历和中序遍历最后访问的结点都是左或右叶结点,叶结点没有子树,所以可以使用空出的指针域存放线索,但是后序遍历最后访问的是子树的根结点,根结点的两个指针域都指向子树,不能用来存放线索。
非空二叉树一定满足:某节点若有左孩子,则其中序前驱一定没有右孩子。
树的数组表示法中兄弟结点的编号不一定是连续的,但是满二叉树和完全二叉树是连续的。
图
概念复习:弧、简单图、多重图、完全图、有向完全图、连通图、非连通图、极大/小连通子图、连通分量、强连通图、极大强连通子图、强连通分量、生成树、生成森林。
图是由顶点和相邻顶点序偶构成的边所形成的的序列。
完全图分为有向完全图和无向完全图。
图的遍历要注意两点:1.每个顶点只访问一次 2.图中各点可能不连通
强连通有向图的任何顶点到其他所有顶点都有路径,但是未必有弧。
回路对应于路径,简单回路对应于简单路径。
若一个有向图的邻接矩阵的对角线以下的元素为0,则该图的拓扑序列必定存在。
连通分量是无向图的极大连通子图,需要将依附于连通分量中的顶点的所有边都加上,所以连通分量中可能存在回路,不能作为图的生成树,图的生成树是图的极小连通子图。
无向图中有权值相同的边,其生成树也不一定不唯一,因为这个图可能本身就是一棵树。
最短路径一定是简单路径。
Dijkstra算法适合求解有回路的带权图的最短路径,也可以求解任意两个顶点的最短路径,不适合求带负权值的最短路径。
判断有向图是否有环的方法:
- 深度优先遍历:在向深度查找的过程中,如果存在一个顶点有一条边指向已经遍历过的顶点,且这个顶点不是上一步访问过的顶点,则存在环。
- 拓扑排序:遍历过程中,图中还有顶点,但是无法找到下一个可以加入拓扑序列的顶点,说明存在回路。
- 并查集只能判断无向图是否存在环,无法判断有向图是否存在环。
在拓扑排序中为暂存入度为0的顶点,可以使用栈,也可以使用队列。
一个有向图的顶点不能排成一个拓扑序列,则代表图中有顶点数量大于1 的回路,该回路构成一个强连通分量。
强连通图是针对有向图的,n个点强连通图至少n条边(形成回路),至多n(n-1)条边。
有向图的连通包括强连通、弱连通、多侧连通。
在图形结构中,结点之间的关系可以是任意的,图中任意两个数据元素之间都可能相关。
AOE网络中,加速某一关键活动不一定会使工程提前完成,但是延迟任意关键活动一定会使工程延期。
AOE网络中,可能同时存在几条关键路径,所有关键路径通过的有向边称为桥。
图中各个定点的编号是人为的,可以根据需要进行改变。
在无向图中定义定义顶点vi和vj之间的路径为从vi到vj的一个顶点序列。
图可以没有边 但不能没有顶点。
当边权值都相等时,BFS算法可用于解决单源最短路径问题。
在任何图中必定有偶数个度数为奇数的结点。
查找
静态查找不涉及插入和删除操作,动态查找设计插入和删除操作,所以两种查找方式的根本区别是施加的操作不同。
折半查找和二叉排序树的时间性能可能不相同。折半查找的性能分析可以用二叉判定树来衡量,平均查找长度和最大查找长度都是O(),二叉排序树的查找性能和数据的输入顺序有关,最好情况下的平均查找长度与折半查找相同,但最坏情况即形成单支树,查找长度为O(n)。
长度为16的有序顺序表,使用折半查找不存在的元素,最多比较5次。
平衡二叉树结点数的递推公式:=0,
=1,
=3,
=1+
+
,h为平衡二叉树高度,
为构造次高度的平衡二叉树所需的最少结点数。
B+树支持顺序查找和随机查找,B树仅支持随机查找,且B树和B+树都可以作为文件索引结构。
B+树是应文件系统所需而产生的B树的变形,前者比后者更加适用于实际应用中的操作系统文件索引和数据库文件索引,前者的磁盘读写代价更低,查询效率更加稳定。
编译器中的词法分析使用有穷自动机和语法树。
网络中的路由表快速查找主要依靠高速缓存,路由表压缩技术和快速查找算法。
系统一般使用空闲空间链表管理磁盘空闲块。
在开放定址法中散列到同一个地址而引起的“堆积”问题是由于同义词之间或非同义词之间发生冲突引起的。
使用开放定址法处理散列表的冲突时,其平均查找长度高于链接法,或者说开散列法比闭散列法效率高。
在散列法中采取开散列法解决冲突时,其装载因子的取值可以大于1。
三阶B树一定是平衡的三路搜索树,但是三路搜索树不一定是三阶B树。
在一棵B树中所有叶结点都在同一层上,所有叶结点中空指针数等于所有关键码总数+1。(类似于满n叉树中的非叶结点数+1=叶结点数)。
使用再散列法处理冲突时不易产生聚集。
同义词冲突不等于聚集,链地址法处理冲突时将同义词放在同一个链表中,不会引起聚集现象。
采用开放定址法解决冲突的散列查找中,发生聚集的原因主要是解决冲突的方法选择不当。
Hash操作能够根据散列值直接定位数据的存储地址,设计良好的Hash表能够在常数级时间下找到需要的数据,更适合于内存中的查找。
STL_Map的内部实现是一棵红黑树,在内存中建立,不能用于磁盘操作,查找性能版不如Hash查找。
B树的插入过程中,若根节点发生分裂,则树高一定+1。
内部排序
通常,取大量数据中的k个最大\最小元素时,优先使用堆排序。
对10TB的数据文件进行排序,应该使用归并方法进行排序。
在内部排序时,没有选择插入排序而是选择归并排序,则可能的理由是:归并排序的运行效率更高。
基数排序不能对float和double类型的实数进行排序。
希尔排序和堆排序都使用了顺序存储随机访问的特性,若将顺序存储更换为链式存储,算法的时间效率会降低。
对大部分元素已有序的数组进行排序,直接插入排序(最坏O(n))比简单选择排序(最坏O(n2))的效率更高,是因为直接插入排序过程中元素之间的比较次数少。
快速排序在被排序数据完全无序的时候最易发挥长处,在数据基本有序的情况下,会退化为冒泡排序。
折半插入排序方法在排序过程中比较次数的是固定的,与初始排列无关。
不稳定的排序算法:快、选、堆、希。
增加归并路数,可以减少归并趟数,进而减少读写磁盘的次数,提高外部排序速度。