第一章: 数据结构绪论
- 术语
- 数据:是描叙客观事物的符号,是计算机中可以操作的对象,是能被计算机识别,并输入给计算机处理的符号集合
- 数据元素: 是组成数据的、有一定意义的基本单位,在计算机中通常作为整体处理。也被成为记录。
- 数据项:一个数据元素可以由若干个数据项组成。数据项是数据不可分割的最小单位。
- 数据对象:是性质相同的数据元素的集合,是数据的子集。
- 数据结构:是相互之间存在一种或多种特定关系的数据元素的集合
- 逻辑结构与物理结构
- 逻辑结构:是指数据对象中数据元素之间的相互关系
- 集合结构:集合结构中的数据元素
- 线性结构:线性结构中的数据元素之间是一对一的关系
- 树形结构:树形结构中的数据元素之间存在一种一对多的层次关系
- 图形结构:图形结构的数据元素是多对多的关系
- 物理结构: 是指数据的逻辑结构在计算机中的存储形式
- 顺序存储结构: 是把数据元素存放在地址连续的存储单元里,其数据间的逻辑关系和物理关系是一致的
- 链式存储结构:是把数据元素存放在任意的存储单元里,这组存储单元可以是连续的,也可以是不连续的。
- 抽象数据类型
- 数据类型: 是指一组性质相同的值的集合及定义在此集合上的一些操作的总称。
- 原子类型: 是不可以再分解的基本类型, 包括整型、实型、字符型
- 结构类型: 由若干个类型组合而成,是可以再分解的。例如,整型数组是由若干整型数据组成的。
- 抽象:是指抽取出事物具有的普遍性的本质, 意义在于数据类型的数学抽象特性。
- 抽象数据类型(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的某个函数。
- 推导大O阶方法:
- 用常数1取代运行时间中的所有加法常数。
- 在修改后的运行次数函数中,只保留最高阶项。
- 如果最高阶项存在且不是1,则去除与这个项相乘的常数。得到的结果就是大O阶。
- 常数阶
- 线性阶:分析算法的复杂度,关键就是要分析循环结构的运行情况
- 对数阶
- 平方阶
- 常见的时间复杂度
- 常用的时间复杂度所耗费的时间从小到大依次是:
O(1) < O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)) - 最坏情况与平均情况
- 最坏情况运行时间是一种保证,那就是运行时间将不会再坏了。在应用中,这是一种最重要的需求,通常,除非特别指定,我们提到的运行时间都是最坏情况的运行时间。
- 平均运行时间是所有情况中最有意义的,因为它是期望的运行时间。
- 一般在没有特殊说明的情况下,都是指最坏时间复杂度。
- 算法空间复杂度
- 算法的空间复杂度通过计算算法所需的存储空间实现,算法空间复杂度的计算公式记作:S(n)=O(f(n)), 其中,n为为题的规模,f(n)为语句关于n所占存储空间的函数。
- 总结回顾
第三章 线性表
- 线性表的定义
- 线性表:零个或多个数据元素的有限序列
- 若将线性表记为(a1,----,ai-1,an),则表中ai-1领先于ai,ai领先于ai+1,称ai-1是ai的直接前驱元素,ai+1是ai的直接后驱元素。当i=1,2,...,n-1时,ai有且仅有一个直接后继,当i=2,3,...,n时,ai有且仅有一个直接前驱。
- 线性表元素的个数n(n >= 0)定义为线性表的长度, 当n=0时, 称为空表.
- 在较复杂的线性表中,一个数据元素可以由若干个数据项组成。
- 线性表的抽象数据类型
- ADT 线性表(List)
- 线性表的数据对象集合为{a1,a2,...,an},每个元素的类型均为DataType.其中,除第一个元素a1外,每一个元素有且只有一个直接前驱元素,除了最后一个元素an外,每一个元素有且只有一个直接后继元素。数据元素之间的关系是一对一的关系。
- operation:
- InitList(*L):初始化操作,建立一个空线性表L。
- ListEmpty(L): 若线性表为空,返回true,否则返回false.
- ClearList(*L): 将线性表清空。
- GetElem(L,i,*e): 将线性表L中的第i个位置元素值返回给e.
- LocateElem(L,e): 在线性表L中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号表示成功;否则,返回0表示失败。
- ListInsert(*L,i,e):在线性表L中的第i个位置插入新元素e.
- ListDelete(L,i,e): 删除线性表L中第i个位置元素,并用e返回其值。
- ListLength(L): 返回线性表L的元素个数。
- 线性表的顺序存储结构
- 顺序存储定义:线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。
- 地址计算方法:
- 存储器中的每个存储单元都有自己的编号,这个编号称为地址。
- 顺序存储结构的插入与删除
- 线性表顺序存储结构的优缺点
- 优点
- 无需为表示表中元素之间的逻辑关系而增加额外的存储空间
- 可以快速地存取表中任一位置的元素
- 缺点
- 插入和删除操作需要移动大量元素
- 当线性表长度变化较大时,难以确定存储空间的容量
- 造成存储空间的碎片
- 线性表的链式存储结构
- 线性表链式存储结构定义
- 节点由存放数据元素的数据域和存放后继节点地址的指针域组成
- 对于插入和删除数据越频繁的操作,单链表的效率优势就越是明显。
- 单链表结构与顺序存储结构优缺点
- 存储分配方式:
- 顺序存储结构用一段连续的存储单元依次存储线性表的数据元素
- 单链表采用链式存储结构,用一组任意的存储单元存放线性表的元素
- 时间性能
- 查找
- 顺序存储结构O(1)
- 单链表O(n)
- 插入和删除
- 顺序存储结构需要平均移动表长一半的元素,时间为O(n)
- 单链表在线出某位置的指针后,插入和删除时间仅为O(1)
* 空间性能
* 顺序存储结构需要预分配存储空间,分大了,浪费,分小了易发生上溢
* 单链表不需要分配存储空间,只要有就可以分配,元素个数也不受限制。
静态链表
- 用数组描叙的链表叫做静态链表
- 静态链表优缺点
- 优点:在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中的插入和删除操作需要移动大量元素的缺点。
- 缺点:没有解决连续存储分配带来的表长难以确定的问题
- 失去了顺序存储结构随机存取的特性
循环链表: 将单链表中终端结点的指针端由空指针改为指向头结点,就使整个单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表。
双向链表:双向链表(double linked list)是在单链表的每个结点中,再设置一个指向其前驱结点的指针域。
总结
- 线性表
- 顺序存储结构
- 链式存储结构
- 单链表
- 静态链表
- 循环链表
- 双向链表
栈与队列
- 栈与队列
- 栈是限定仅在表尾进行插入和删除操作的线性表。
- 队列是只允许在一端进行插入操作、而在另一端进行删除操作的线性表。
- 栈的插入操作,叫做进栈,也成压栈、入栈。
- 栈的删除操作,叫做出站,也有的叫做弹栈。
- 两栈共享空间
- 栈的链式存储结构及实现
- 栈的链式存储结构,简称为链栈
- 如果栈的使用过程中元素变化不可预料,有时很小,有时非常大,那么最好是用链表,反之,如果它的变化在可控范围内,建议使用顺序栈会更好一些。
- 递归定义: 我们把一个直接调用自己或通过一系列的调用语句间接地调用自己的函数,称作递归函数。
- 每个递归定义必须至少有一个条件,满足时递归不再进行,即不再引用自身而是返回值退出。
- 栈的应用-四则运算表达式求值
- 后缀(逆波兰)表示法定义
- 后缀表达式计算结果
- 中缀表达式转后缀表达式
- 队列的定义
- 队列是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
- 头尾相接的队列称为循环队列
- 队列的链式存储: 队列的链式存储结构,其实就是就是线性表的单链表,只不过它只能尾进头出而已,我们把它简称为链队列。
- 总结
栈 | 队列 |
顺序栈 | 顺序队列 |
两栈共享空间 | 循环队列 |
链栈 | 链队列 |
串
- 串的定义
- 串是有零个或多个字符组成的有限序列,又名叫字符串
- KMP模式匹配算法
- 可以大大避免重复遍历的情况
- next[j] =
{
0, 当j=1时
Max{k|1<k<j, 且'p1...pk-1' = 'pj-k+1...pj-1'}当此集合不空时
1,其他情况
}
树
- 定义: 树(Tree)是n(n>=0)个结点的有限集。n=0时称为空树。在任意一颗非空树中:(1)有且仅有一个特定的称为根(Root)的结点:(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、....、Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。
- n>0 时根结点是唯一的,不可能存在多个根结点,别和现实中的大树混合在一起,现实中的树有很多根须,那是真实的树,数据结构中的树是只能有一个根结点。
- m>0时,子树的个数没有限制,但它们一定是互不相交的。
- 结点分类
- 树的结点包含一个数据元素及若干指向其子树的分支。结点拥有的子树称为结点的度(degree).度为0的结点称为叶节点(Leaf)或终端结点;度不为0的结点称为非终端结点或分支结点。除根结点之外,分支结点也称为内部结点。树的度是树内各结点的度的最大值。
- 结点间关系
- 结点的子树的根称为该结点的孩子(Child),相应地,该结点称为孩子的双亲。同一个双亲的孩子之间互称兄弟。结点的祖先是从根到该结点所经分支上的所有结点。以某结点为根的子树中的任一结点都称为该结点的子孙。
- 树的其他相关概念
- 结点的层次(Level)从根开始定义起,根为第一层,根的孩子为第二层。
- 树中结点的最大层次称为树的深度(Depth)或高度
- 如果将树中的结点的各子树看成从左至右是有次序的,不能互换的,则称该树为有序树,否则称为无序树
- 森林(Forest)是m(m>=0)棵互不相交的树的集合
- 对比线性表与树
线性结构 | 树结构 |
第一个数据元素:无前驱 | 根结点:无双亲,唯一 |
最后一个数据元素:无后驱 | 叶结点:无孩子,可以多个 |
中间元素:一个前驱一个后驱 | 中间结点:一个双亲多个孩子 |
- 树的存储结构
- 双亲表示法
- 存储结构的设计是一个非常灵活的过程。一个存储结构设计得是否合理,取决于基于该存储结构的运算是否适合、是否方便,时间复杂度好不好等。
- 孩子表示法
- 每个结点有多个指针域,其中每个指针指向一颗子树的根结点,我们把这种方法叫做多重链表表示法。
- 把每个结点的孩子结点排列起来,以单链表作存储结构,则n个结点有n个孩子链表, 如果是叶子结点则此单链表为空。然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中。
- 孩子兄弟表示法
- 任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。因此,我们设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟。
- 二叉树的定义
- 二叉树(Binary Tree)是n(n >= 0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两颗互不相交的、分别称为根结点的左子树和右子树的二叉树组成。
- 二叉树特点
- 特殊二叉树
- 斜树
- 所有的结点都只有左子树的二叉树叫左斜树。所有结点都是只有右子树的二叉树叫有斜树。这两者统称为斜树。
- 满二叉树
- 在一颗二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。
- 完全二叉树
- 对一颗具有n个结点的二叉树按层序编号, 如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这颗二叉树称为完全二叉树。
- 二叉树的性质
- 二叉树性质1: 在二叉树的第i层上至多有2**(i-1)个结点。
- 二叉树性质2:深度为k的二叉树至多有2**k-1个结点。(k>=1)
- 二叉树性质3: 对任何一颗二叉树T, 如果其终端结点数为n0,度为2的结点数为n2,则n0=n2+1
- 二叉树性质4: 具有n个结点的完全二叉树的深度为[log2n]+1([x]表示不大于x的最大整数)
- 二叉树性质5:
- 如果对一颗有n个结点的完全二叉树(其深度为[log2n] + 1) 的结点按层序编号(从第1层到第[log2n] + 1层,每层从左到右),对任一结点i(1<=i<=n)有:
- 如果i=1, 则结点i是二叉树的根,无双亲;如果i>1,则其双亲是结点[i/2]。
- 如果2i>n, 则结点i无左孩子(结点I为叶子结点); 否则其左孩子是结点2i。
- 如果2i+1>n, 则结点i无右孩子;否则其右孩子是结点2i+1。
- 二叉树的存储结构
- 二叉树顺序存储结构
- 顺序存储结构一般只用于完全二叉树,其它二叉树很浪费空间
- 二叉链表
- 二叉树遍历原理
- 二叉树的遍历(traversing binary tree)是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问依次且仅被访问依次。
- 二叉树遍历方式
- 前序遍历
- 规则是若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。
- 中序遍历
- 规则是若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点), 中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。
- 后序遍历
- 规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左子树,最后访问根结点。
- 层序遍历
- 规则是若树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。
- 二叉树的建立
- 线索二叉树
- 我们把这种指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树就称为线索二叉树。
- 我们对二叉树以某种次序遍历使其变为线索二叉树的过程称作是线索化。
- 线索化的过程就是在遍历的过程中修改空指针的过程。
- 如果所用的二叉树需经常遍历或者查找结点是需要某种遍历序列中的前驱和后继,那么采用线索二叉链表的存储结构就是非常不错的选择。
- 树与森林的遍历
- 前序遍历
- 后序遍历
- 树的前序遍历和后序遍历跟二叉树的前序遍历和中序遍历一样的。
- 赫夫曼树及其应用
- 赫夫曼树定义与原理
- 从树中一个结点到另一个结点之间的分支构成两个结点之间的路径,路径上的分支数目称作路径长度。
- 树的路径长度就是从树根到每一个结点的路径长度之和。
- 带权路径长度WPL最小的二叉树称作赫夫曼树。
- 赫夫曼编码
- 若要设计长短不等的编码,则必须是任一字符的编码都不是另一个字符的编码的前缀,这种编码称作前缀编码。
图
- 图是由定点(graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E), 其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合。
- 图的定义
- 在图中数据元素,我们则称之为顶点(Vertex)
- 图不允许没有顶点
- 图中,任意两个顶点之间都可能有关系,顶点之间的逻辑关系用边来表示。
- 各种图定义
- 若顶点vi到vj之间的边没有方向,则称这条边为无向边(Edge),用无序偶对(vi,vj)来表示.
- 如果图中任意两个顶点之间的边都是无向边,则称该图为无向图。
- 有向边:若从顶点vi到vj的边有方向,则称这条边为有向边,也称为弧(Arc)
- 如果图中任意两个顶点之间的边都是有向边,则称该图为有向图.
- 连接顶点A到D的有向边就是弧,A是弧尾,D是弧头,<A,D>表示弧,注意不能写成<D,A>。
- 在图中,若不存在顶点到其自身的边,且同一条边不重复的出现,则称这样的图为简单图。
- 在无向图中,如果任意两个顶点之间都存在边,则称该图为无向完全图。
- 在有向图中,如果任意两个顶点之间都存在方向互为相反的两条弧,则称改图为有向完全图。
- 对于具有n个顶点和e条边数的图,无向图0<=e<=n(n-1)/2,有向图0<=e<=n(n-1).
- 有多少条边或弧的图称为稀疏图,反之称为稠密图。
- 与图的边或弧相关的数叫做权(Weight)。
- 这种带权的图通常称为网(Network)。
- 图的顶点与边间关系
- 树中根结点到任意结点的路径是唯一的,但是图中顶点与顶点之间的路径却是不唯一的。
- 在无向图G中,如果从顶点V到顶点V'有路径,则称V和V'是连通的。如果对于图中任意两个顶点都是连通的,则称G是连通图。
- 无向图中的极大连通子图称为连通分量。
- 在有向图G中,从Vi到Vj和从Vj到Vi都存在路径,则称G是强连通图。在有向图中的极大强连通子图称作有向图的强连通分量。
- 一个连通图的生成树是一个极小的连通子图,它含有图中全部的n个顶点,但只有足以构成一棵树的n-1条边。
- 如果一个有向图恰有一个顶点的入度为0,其余顶点的入度均为1。则是一颗有向树。
- 一个有向图的生成森林由若干颗有向树组成,含有图中全部顶点,但只有足以构成若干颗不相交的有向树的弧。
- dfs:Deepness First Search
- bfs:Breadth First Search
- 图的存储结构
- 邻接矩阵
- 图的邻接矩阵(adjacency matrix)存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。
- 邻接表
- 数组与链表相结合的存储方式称为邻接表(adjacency list)。
- 有向图的逆邻接表,即对每个顶点vi都建立一个链接vi为弧头的表。i
- 十字链表
- 邻接多重表
- 边表结点结构如表
其中ivex和jvex是与某条边依附的两个顶点在顶点表中下标。ilink指向依附顶点ivex的下一条边,jlink指向依附顶点jvex的下一条边。这就是邻接多重表结构。
- 边集数组
边集数组是由两个一维数组构成。一个是存储顶点的信息;另一个是存储边的信息,这个边数组每个数据元素由一条边的起点下标(begin)、终点下标(ebd)和权(weight)组成。
- 图的遍历
- 我们希望从图中某一顶点出发访遍图中其余顶点,且使每一个顶点仅被访问依次,这一过程就叫做图的遍历(traversing Graph) 。
- 深度优先遍历
- 深度优先遍历(Depth_First_Search),DFS。
- 它从图中某个顶点v出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。
- 若图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。
- 最小生成树 我们把构造连通网的最小代价生成树称为最小生成树
- 普里姆(prim)算法
- 克鲁斯卡尔(Kruskal)算法
- 最短路径 对于网图来说,最短路径,是指两顶点之间经过的边上权值之和最少的路径,并且我们称路径上的第一个顶点是源点,最后一个顶点是终点。
- 迪杰斯特拉(Dijkstra)算法
- 弗洛伊德(floyd)算法
- 拓扑排序
- 拓扑排序介绍 在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,这样的有向图为顶点表示活动的网,我们成为AOV网(Activity On Vertex Network) 设G={V,E}是一个具有n个顶点的有向图,V中的顶点序列v1,v2,.....,vn, 满足若从顶点vi到vj有一条路径,则在顶点序列中顶点vi必在顶点vj之前。则我们称这样的顶点序列为一个拓扑序列。 所谓拓扑序列,其实就是对一个有向图构造拓扑序列的过程。
- 拓扑排序算法
- 关键路径 在一个表示你工程的带权有向图中,用顶点表示事件,用有向边表示活动,用边上的权值表示活动的持续事件,这种有向图的边表示活动的网,我们称为AOE网(Activity On Edge Network). 我们把路径上各个活动所持续的时间之和称为路径长度,从源点到汇点具有最大长度的路径叫关键路径,在关键路径上的活动叫关键活动。
- 关键路径算法原理
- 关键路径算法
- 查找 查找(searching)就是根据给点的某个值,在查找表中确定一个其关键字等于给定值的数据元素(或记录).
- 查找概论
查找表(search table)是由同一类型的数据元素(或记录)构成的集合。
关键字(Key)是数据元素中某个数据项的值。
若此关键字可以唯一地标识一个记录,则称此关键字为主关键字(primary key)。主关键字所在的数据项称为主关键码。
对于那些可以识别多个数据元素(或记录)的关键字,我们称为此关键字(secondary key)。
静态查找表(static search table):只作查找操作的查找表
动态查找表(dynamic search table):在查找过程中同时插入查找表中不存在的数据元素,或者从查找表中删除已经存在的某个数据元素。 - 顺序表查找
- 顺序查找表优化
- 有序表查找
- 插值查找
mid = low + ((key - a[low])/(a[high)-a[low])*(high -low) - 斐波拉契查找
- 线性索引查找 索引就是把一个关键字与它对应的记录相关联的过程。 索引按照结构可以分为线性索引、树形索引、多级索引。所谓线性索引就是将索引项集合组织为线性结构,也称为索引表。我们重点介绍三种线性索引:稠密索引、分块索引和倒排索引。
- 稠密索引
稠密索引是指在线性索引中,将数据集中的每个记录对应一个索引项。
对于稠密索引这个索引来说,索引项一定是按照关键码有序的排列。 - 分块索引 分块有序,是把数据集的记录分成了若干块,并且这些快需要满足两个条件:
- 块内无序
- 块间有序
我们定义的分块索引的索引项结构分三个数据项: - 最大关键码,它存储每一块中的最大关键字,这样的好处就是可以使得在它之后的下一块的最小关键字也能比这一块最大的关键字还要大
- 存储了块中的记录个数,以便于循环时使用
- 用于指向块首数据元素的指针,便于开始对这一块中记录进行遍历
- 倒排索引
索引项的通用结构是:
* 次关键码
* 记录号表
其中记录号表存储具有相同次关键字的所有记录的记录号(可以是指向记录的指针或者是该记录的主关键字).这样的索引方法就是倒排索引。
- 二叉排序树 二叉排序树(Binary Sort Tree), 又称为二叉查找树。它或者是一颗空树,或者是具有下列性质的二叉树。
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结构的值
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值
- 它的左、右子树也分别为二叉排序树。
- 查找
- 插入
- 删除
- 平衡二叉树
平衡二叉树,是一种二叉排序树,其中每一个节点的左子树和右子树的高度差至多等于1.
我们将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子BF(balance factor)
距离插入结点最近的,且平衡因子的绝对值大于1的结点为根的子树,我们称为最小不平衡子树。 - 多路查找树(B树) 多路查找树(muitl-way search tree), 其每一个结点的孩子树可以多于两个,且每一个结点处可以存储多个元素。
- 2-3树
2-3 树是这样的一颗多路查找树;其中的每一个结点都具有两个孩子(我们称它为2结点)或三个孩子(我们称它为3结点)。
一个2结点包含一个元素和两个孩子(或没有孩子)
左子树包含的元素小于该元素,右子树包含的元素大于该元素
一个3结点包含一小一大两个元素和三个孩子(或没有孩
子)
如果某个3结点有孩子的话,左子树包含小于较小元素的元素,右子树包含大于较大元素的元素,中间子树包含介于两元素之间的元素。 - 2-3-4树
它其实就是2-3树的概念扩展,包括了4结点的使用。一个4结点包含小中大三个元素和四个孩子(或没有孩子),一个4结点要么没有孩子,要么具有四个孩子。如果某个4结点有孩子的话,左子树包含小于最小元素的元素;第二子树包含大于最小元素,小于第二元素的元素;第三子树包含大于第二元素,小于最大元素的元素;右子树包含大于最大元素的元素。 - B树
B树(B-tree)是一种平衡的多路查找树,2-3树和2-3-4树都是B树的特例。结点最大的孩子数目称为B树的阶(order), 因此,2-3树是3阶B数,2-3-4树是4阶B树。
一个m阶的B树具有如下属性:
* 如果根结点不是叶结点,则其至少有两颗子树。
* 每一个非根的分支结点都有K-1个元素和K个孩子,其中[m/2]<=k<=m.每一个叶子结点n都有k-1个元素,其中[m/2]<=k<=m.
* 所有叶子结点都位于同一个层 次。
* 所有分支结点包含下列信息数据(n,A0,k1,A1,k2,A2,...,Kn,An), 其中:Ki(i=1,2,...,n)为关键字,且ki<ki+1..... - B+树
- 散列表查找(哈希表)概述
- 散列表查找定义
散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key).
我们把这种对应关系f称为散列函数,又称为哈希(hash)函数。按这个思想,采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)。 - 散列表查找步骤
散列技术既是一种存储方法,也是一种查找方法。
散列技术最合适的求解问题是查找与给定值相等的记录。
- 散列函数的构造方法 什么才算是好的散列函数了?这里我们有两个原则可以参考。
- 计算简单
- 散列地址分布均匀
- 直接地址法
我们可以取关键字的某个线性函数值为散列地址,即
f(key) = axkey+b(a,b为常数) - 数字分析法
- 平方取中法
- 折叠法
- 除留余数法
因此根据前辈们的经验,若散列表表长为m,通常p为小于或等于表长(最好接近m)的最小质数或不包含小于20质因子的合数。 - 随机数法
- 处理散列冲突的方法
- 开放地址法
所谓的开放地址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能够找到,并将记录存入。
它的公司是:
f(key) = (f(key) + di) mod m(di=1,2,3,.....,m-1)
我们把这种解决冲突的开放地址法称为线性探测法。
增加平方运算的目的是为了不让关键字都聚集在某一块局域。我们称这种方法为二次探测法。
在冲突时,对于位移量di采用随机函数计算得到,我们称之为随机探测法(使用同一个随机种子)。 - 再散列函数法
- 链地址法
链地址法对于可能会造成很多冲突的散列函数来说,提供了绝不会出现找不到地址的保障。当然,这也就带来了查找时需要遍历单链表的性能损耗。 - 公共溢出区法
如果相对于基本表而言,有冲突的数据很少的情况下,公共溢出区的结构对查找性能来说还是非常高的。
- 散列表查找实现
- 散列表查找算法实现
- 散列表查找性能分析
- 散列函数是否均匀
- 处理冲突的方法
相同的关键字、相同的散列函数,但处理冲突的方法不同,会使得平均查找长度不同。比如线性探测处理冲突可能会产生堆积,显然就没有二次探测法好,而链地址法处理冲突不会产生任何堆积,因而具有更佳的平均查找性能。 - 散列表的装填因子
- 排序
- 排序的基本概念与分类 对于组合排序的问题,当然可以先排序总分,若总分相等的情况下,再排序语数外总分,但这是比较土的办法。我们还可以应用一个技巧来实现一次排序即完成组合排序问题,例如把总分与语数外都当层字符串首尾连接在一起。
- 排序的稳定性
假设ki=kj(1<=i<=n, 1<=j<=n,i!=j),且在排序钱的序列中ri领先于rj(即i<j)。如果排序后ri忍领先于rj,则称所有的排序方法是稳定的;反之,若可能使得排序后的序列中rj领先ri,则称所用的排序方法是不稳定的。 - 内排序和外排序
内排序是在排序整个过程中,待排序的所有记录全部放置在内存中。外排序是由于排序的记录个数太多,不能同时放置在内存,整个排序过程需要在内外存之间多次交换数据才能进行。
对于内排序来说,排序算法的性能主要受3个方面影响:
- 时间性能
高效率的内排序算法应该具有尽可能少的关键字比较次数和尽可能少的记录移动次数。 - 辅助空间
辅助存储空间是除了存放待排序所占用的存储空间之外,执行算法所需要的其他存储空间. - 算法复杂性
- 冒泡排序
时间复杂度为O(n2) - 简单选择排序
总的时间复杂度依然为O(n2)
简单选择排序的性能上还是要略优于冒泡排序 - 直接插入排序
直接插入排序的基本操作是将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表。
o(n2)时间复杂度.
直接插入排序法比冒泡和简单选择排序的性能要好一些。 - 希尔排序
所谓的基本有序,就是小的关键字基本在前面,大的基本在后面,不大不小的基本在中间。
将相距某个"增量"的记录组成一个子序列,这样才能保证在子序列内分别进行直接插入排序后得到的结果是基本有序而不是局部有序。
增量序列的最后一个增量值必须等于1才行。 - 堆排序
堆是具有下列性质的完全二叉树: 每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆; 或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
堆排序就是利用堆进行排序的方法。它的基本思想是,将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根结点。将它移走(其实就是将其与堆数组的末尾元素交换, 此时末尾元素就是最大值), 然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次小值。如此反复执行,便能得到一个有序序列了。
不稳定排序 - 归并排序
归并排序(meging sort)就是利用归并的思想实现的排序方法。它的原理是假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到n/2个长度为2或1的有序子序列;再两两归并,....,如此重复,直至得到一个长度为n的有序序列为止,这种排序的方法称为2路归并排序。 - 快速排序
快速排序的基本思想是:通过一趟排序将待排序记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。