大话数据结构
1 初识概念
(1)数据结构:是相互之间存在一种或多种特定关系的数据元素的集合。
(2)逻辑结构(面向问题)和物理结构(面向计算机)。
(3)逻辑结构:是指数据对象中数据元素之间的相互关系。(集合结构,线性结构,树形结构,图形结构)
(4)物理结构(存储结构):是指数据的逻辑结构在计算机中的存储形式。(顺序存储和链式存储)
(5)顺序存储结构:连续的存储单元,逻辑与物理一致
(6)链式存储:任意存储单元,可连续,可不连续。
(7)抽象数据类型(Abstract Data Type, ADT):是指一个数学模型及定义在该模型上的一组操作。
(8)算法:解决确定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。
(9)算法的基本特性:输入,输出,有穷性,确定性和可行性。
(10)好的算法:时间效率高和存储量低。
(11)算法效率关注主项(最高阶项)的阶数。
(12)算法的时间复杂度。大O记法,O(1)常数阶,O(n)线性阶,O(n2)平方阶,O(logn)对数阶。
(13)O(1)<O(logn)<O(n)<O(nlogn)<O(n2)<O(n3)<O(2n)<O(n!)<O(nn)
(14)算法的空间复杂度: S(n)=O(f(n)), n表示问题规模,f(n)为语句关于n所占存储空间的函数。
2线性表(List)
(1)零个或多个数据元素的有限序列。
(2)前驱元素,后继元素。第一个元素无直接前驱,最后一个元素无直接后继,中间元素仅有单一直接前驱和直接后继。
(3)线性表的顺序存储结构:用一段地址连续的存储单元依次存储线性表的数据元素。它的存取时间性能为O(1),具有这一特点的存储结构称为随机存取结构。插入或删除为O(n)。
(4)线性表的链式存储。结点(node):数据域,指针域。
(5)单链表:每个结点中只包含一个指针域。链表中第一个结点的存储位置叫做头指针。线性链表的最后一个结点指针为“空”(NULL或“^”)
(6)在单链表的第一个结点前附设一个结点,称为头结点。头结点的数据域可不存储任何信息。也可以存储线性表长度等公共数据。
(7)若线性表为空,则头结点的指针域为“空”。
单链表结构 | 顺序存储结构 | |
---|---|---|
存储分配方式 | 任意存储单元存放线性表 | 连续存储空间,依次存储 |
时间性能 | 查找O(n);插入和删除O(1) | 查找O(1) ;插入和删除O(n) |
空间性能 | 不需要分配存储空间,元素个数不受限制 | 预分配空间大了浪费,小了上溢 |
(8)静态链表:用数组描述的链表。(游标实现法)
(9)在动态链表中,结点的申请和释放分别借用malloc()和free()两个函数实现。在静态链表中则不行。
(10)静态链表的优缺点
优点 | 缺点 |
---|---|
在插入与删除时,只改游标cur;不做大量移动 | 未解决连续存储分配带来的表长难以确定的问题;失去了顺序存储结构随机存取的特性 |
(11)循环链表:终端结点的指针由空指向头结点,单循环链表。尾指针。
(12)双向链表(double linked list):在单链表中,再设置一个指向前驱结点的指针域。
3栈与队列
3.1栈
(1)栈(stack):后进先出(Last In f=First Out),LIFO结构,仅在表尾进行插入和删除操作的线性表;栈顶(top),栈底(bottom),空栈;
(2)表尾即栈顶(top),进栈(压栈或入栈)(push);出栈(弹栈)(pop);
(3)小例子:
1,2,3依次进栈,不存在312这样的出栈顺序,思考?
(4)栈的顺序存储结构,顺序栈。
(5)两栈共享空间(两居室);++top1 --top2;一个栈在增长,一个栈在缩短。
(6)栈的链式存储结构,(链栈),基本不存在栈满的情况;将头结点作为栈顶指针top;
(7)栈的大小不可预料,多用链栈;大小在可控范围内,则使用顺序栈,好一些。
(8)栈的应用1:递归(前行和回退),,,,,,,斐波拉契数列(fibonacci)两种实现方法:迭代和递归。
迭代是循环结构,递归是选择结构。递归更简洁,但大量递归调用会建立函数的副本,会耗费大量的时间和内存。迭代不需要反复调用函数和占用额外的内存。
(9)栈的应用2:四则运算表达式求值,,,,
一种不需要口号的后缀表达法,(逆波兰(Reverse Polish Notion,RPN)).931-3*+102/+ 遇到数字就进栈,遇到符号就将处于栈顶的两个数字出栈,计算,再将计算结果进栈。从而获得最后的计算结果。
我们平时所用标准四则运算表达式,叫中缀表达式。9+(3-1)*3+10/2
3.2队列(queue)
(1)队列:先进先出(FIFO),仅在一端插入(队尾),另一端删除(队头)的线性表;
(2)队列顺序存储的不足,,也有front,rear。后排满,前排空,的“假溢出”。
(3)循环队列(顺序存储):队头(front)指向队头元素。队尾(rear)指向队尾元素的下一个位置。头尾相接的顺序存储结构。
(4)判断循环队列满的方法
- 标志变量flag
- 保留一个元素空间
(5)考虑第2种,保留一个空间。
- 队列满的条件:(rear + 1)%QueueSize == front
- 计算队列长度的公式:(rear - front + QueueSize)%QueueSize
(6)链队列:队列的链式存储
(7)确认队列长度时,用循环队列;无法预估队列长度,用链队列。
4 串
(1)串:由零个或多个字符组成的有限序列,又叫字符串。
(2)空串(null string):
(3)在计算机中存在一个自由存储区,焦作“堆”。这个堆可由C语言中的malloc()和free()来管理。
(4)串的顺序存储结构与链式存储结构。其中,链式存储结构中,一个结点可以存放一个字符,也可以存放多个字符。若一个结点未占满,可用“#”或其他非串值字符补全。
(5)串的模式匹配:子串定位操作。
(6)朴素的模式匹配算法:单个字符逐一匹配,单个字符往后移动,挨个遍历。
(7)KMP模式匹配算法:仅当模式与主串之间存在许多“部分匹配”的情况下才体现出它的优势,否则两者差异并不明显。
(8)改进KMP算法,参考算法导论,第2版32章字符串匹配。
5树(Tree)
5.1基本概念
(1)树是n(n>=0)个节点的有限集。n=0为空树。
(2)在任意非空树中:
- 有且仅有一个特定的称为**根(Root)**的结点。
- 当n>1时,其余结点可分为m(m>0)个互不相交的有限集,其中每一个集合本身又是一棵树,并称为根的子树(SubTree)
(3)度(Degree):结点拥有的子树数;
(4)叶结点或终端结点(Leaf):度为0的结点;
(5)非终结点或分支结点:度不为0;
(6)内部结点:根结点,分支结点。树的度是树内部结点的度的最大值。
(7)结点的子树称为该结点的孩子(child),相应地,该结点称为孩子的双亲(parent)。同一个双亲的孩子之间互称兄弟(Sibling)。结点的祖先是从根到该结点****所经分支上的所有结点。以某结点为根的子树中的任一结点都称为该结点的子孙。
(8)结点的层次(level):根为第一层,根的孩子为第二层。双亲在同一层的,互称为堂兄弟。
(9)树的深度(depth)或高度:树中结点的最大层次。
(10)有序树:树中结点的各子树看成从左至右是有次序的,不能互换的。否则,称为,无序树。
(11)森林(forest):m(m>=0)棵互不相交的树的集合。
线性结构 | 树结构 |
---|---|
第一个数据元素:无前驱 | 根结点:无双亲,唯一 |
最后一个数据元素:无后继 | 叶结点:无孩子,可以多个 |
中间元素:一个前驱一个后继 | 中间结点:一个双亲多个孩子 |
5.2树的存储结构
(1)双亲表示法:每个结点中,附设一个指示器指示其双亲结点在数组中的位置。
data | parent |
---|
data是数据域,存储结点的数据信息;parent是指针域,存储该结点的双亲在数组中的下标。
data | parent | firstchild |
---|
增加一个长子域,存储长子的下标,对于有0或1个孩子的结点来说,这样的结构解决了要找结点孩子的问题,甚至是有2个孩子,知道长子是谁,另一个当然就是次子。
data | parent | rightsib |
---|
增加右兄弟域,体现兄弟关系。存储右兄弟的下标。
小结:上述结构可以组合,存储结构的设计是一个非常灵活的过程,一个存储结构设计得是否合理,取决于该存储结构的运算是否适合、是否方便,时间复杂度好不好等。
(2)孩子表示法:
多重链表表示法:每个结点有多个指针域,其中每个指针指向一棵子树的根结点。
data | child1 | child2 | child3 | … | childd |
---|
data是数据域,child1到childd是指针域,用来指向该结点的孩子结点。
方案一:指针域的个数等于树的度。
方案二:指针域的个数等于该结点。
data | degree | child1 | child2 | child3 | … | childd |
---|
其中degree为度,存储该结点的孩子结点的个数。
**具体解决办法:**把每个结点的孩子结点排列起来,用单链表做存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空。然后,n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中。
但,在这个结构中,要知道某个结点的双亲,需要遍历所有结点。此处可以把双亲表示法与孩子表示法结合一下。双亲孩子表示法。
(3)孩子兄弟表示法
data | firstchild | rightsib |
---|
任意一棵树,它的结点的第一个孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。data数据域,firstchild指向该结点的第一个孩子结点,rightsib指向该结点的右兄弟结点。
这种方法将复杂的树,表示成了一棵二叉树。
5.3二叉树(binary Tree)
5.3.1二叉树的基本概念
(1)定义:二叉树是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树的二叉树组成。
(2)二叉树的特点:
- 每个结点最多有两棵子树
- 左、右子树有序,即使只有一棵子树也做不同区分
(3)五种基本形态:空二叉树、只有一个根结点、根结点只有左子树、根结点只有右子树、根结点既有左子树又有右子树。
5.3.2特殊的二叉树
(1)斜树(左斜树,右斜树)
(2)满二叉树:
- 叶子结点只出现在最下一层
- 非叶子结点的度为2
- 在同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多
(3)完全二叉树:二叉树按层序编号,与满二叉树中对应位置编号完全相同,则称为完全二叉树。(编号不能出现空挡,否则就不是完全二叉树)。特点:
- 叶子结点只能出现在最下两层
- 最下层的叶子一定集中在左部连续位置
- 倒数第二层,若有叶子结点,一定都在右部连续位置
- 如果结点度为1,则该结点只有左孩子,即不存在只有右子树的情况。
- 同样结点数的二叉树,完全二叉树的深度最小。
5.4二叉树
5.4.1二叉树的性质
(1)第i层,最多有2i-1个结点
(2)深度为K,至多有2k-1个结点
(3)终端结点数为n0度为2的结点数为n2,则n0=n2+1。利用分支数推导
(4)具有n个结点的完全二叉树的深度为[log2n]+1。([x]表示不大于x的最大整数)
(5)如果对一棵有n个结点的完全二叉树,对任意结点i
- 若i=1,则i是二叉树的根,无双亲;若i>1,则其双亲是结点[i/2]
- 若2i>n,则结点i无左孩子(结点i为叶子结点);否则,其左孩子是结点2i
- 若2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1
5.4.2二叉树的存储结构
(1)二叉树的顺序存储结构
完全二叉树按层序编号存储在数组中;对于一般二叉树,可以对没有结点的地方留空“^”
(2)二叉链表
lchild | data | rchild |
---|
5.4.3二叉树遍历(递归)
(1)二叉树的遍历,所有结点,访问一次且仅被访问一次。
(2)二叉树遍历方法:
- 前序遍历:根结点–左子树–右子树
- 中序遍历:左子树–根结点–右子树
- 后序遍历:从左至右先叶子后结点的方式,访问左右子树,最后访问根结点
- 层序遍历:从上至下逐层遍历,同一层从左到右。
注意:
- 已知前序遍历和中序遍历,可以唯一确定一棵二叉树
- 已知后续遍历和中序遍历,可以唯一确定
但已知前序和后续,则不行。如前序ABC,后续CBA,则有4种情况
5.5二叉树的建立(递归)
(1)普通二叉树;
(2)扩展二叉树:每个结点的空指针引出一个虚结点“#”。
5.6线索二叉树
(1)n个结点的二叉树,(二叉链表存储),有n-1条分支,存在2n-(n-1)=n+1个空指针域。
(2)指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树就称为线索二叉树。将二叉树转变成一个双向链表。
(3)对二叉树以某种次序遍历使其变为线索二叉树的过程承做是线索化。
lchild | ltag | data | rtag | rchild |
---|
ltag=0,该结点指向左孩子,=1指向前驱
rtag=0,该结点指向右孩子,=1指向后继
(4)线索化的过程就是遍历的过程中修改空指针的过程。
5.7树、森林与二叉树的转换
(1)树转二叉树:
- 加线:兄弟之间
- 去线:树中结点,只保留它与第一个孩子结点的连线
- 层次调整:第一个孩子是左孩子,兄弟转化来的是右孩子
(2)森林转化为二叉树
- 每棵树转换为二叉树
- 第一棵二叉树不动,从第二棵树开始,依次把后一颗树的根结点作为前一棵二叉树根结点的右孩子,用线连起来。
(3)二叉树转换成树(逆过程)
- 加线:某结点左孩子存在,则将其左孩子的所有右孩子作为此结点的孩子
- 去线:删除原二叉树中所有结点与其右孩子结点的连线。
- 层次调整
(4)二叉树转换成森林
判断一棵二叉树转换成一棵树,还是森林,看这个二叉树的根结点有没有右孩子即可。有则可以转换成森林,没有则转换成树
- 从根节点开始,若右孩存在,则删除连线
- 然后将分离的二叉树转换成树
(5)树的遍历
- 先根遍历树(类似于前序):先访问树的根结点,然后依次先根遍历根的没棵子树
- 后根遍历(类似于后序):先依次后根遍历每棵子树,然后访问根结点。
(6)森林的遍历
- 前序遍历:依次先根遍历每棵树
- 后序遍历:依次后根遍历每棵树
5.8赫夫曼树及其应用
(1)最基本的压缩编码方法
(2)路径长度:一个结点到另一个结点之间路径上的分支数。
(3)树的路径长度:根结点到每一结点的路径长度之和。
(4)结点的带权路径长度:从该结点到树根之间的路径长度与结点上权的乘积
(5)树的带权路径长度:根结点到每一结点的带权路径长度之和
(6)带权路径长度WPL最小的二叉树称作赫夫曼树。(最优二叉树)
(7)赫夫曼算法描述:
- 1)n个权值{ w1,w2…wn},n棵二叉树集合F={T1,T2,T3…Tn}(仅有根结点)
- 2)选两个权值最小的根结点做左右子树,构造新的二叉树,且置新的二叉树的根结点的权值为左右子树权值之和.
- 3)在F中删除这两棵树,加入新二叉树
- 4)重复2)和3)步骤,直到F只含一棵树为止。
(8)前缀编码:任一字符的编码都不是另一个字符的编码的前缀。(设计长短不等的编码)
(9)赫夫曼编码:
- 字符集
- 电文出现的频率集
- 以字符作为叶子结点,以频率作为权重
- 进行赫夫曼编码,
- 规定做分支为0,右分支为1;对字符进行编码
6图
6.1图的基本概念
(1)图的表示:G(V,E)。V为图G中顶点(Vertex或Node)的集合,要求:有穷非空;E是图G中边的集合,可以为空。
(2)无向边(Edge):无序偶对(vi,vj)表示;无向图
(3)有向边:也称为弧(Arc),有序偶<vi,vj>表示,vi弧尾(Tail),vj弧头(Head)。有向图。
(4)简单图:不存在顶点到自身的边,不存在同一条边重复出现。
(5)无向完全图:任意两个顶点之间都存在边的无向图。共有n(n-1)/2条边(因计算有重复)
(6)有向完全图:同上,共有n(n-1)条边。
(7)稀疏图:有很少条边或弧。反之,为稠密图
(8)边或弧相关的数叫做权(Weight),带权图称之为网。
(9)子图(Subgraph),与子集概念类似。
(10)(v,v’)属于E,则称顶点v和v’互为邻接点(Adjacent),(v,v’)与顶点v和v’相关联。顶点v的度是和v相关联的边的数目。TD(v)。
(11)有向图中<v,v’>:入度ID(v),出度OD(v),顶点v的度TD(v)=ID(v)+OD(v)
(12)路径(path):一个顶点序列。路径的长度是路径上的边或弧的数目。
(13)回路或环(Cycle):第一个顶点到最后一个顶点相同的路径。
(14)简单路径:序列中顶点不重复出现的路径
(15)简单回环或简单环:除第一和最后顶点外,其余点不重复的回路。
(16)连通图:任意两个顶点连通的无向图,任意两顶点之间有路径,称之为连通。
(17)无向图中的极大连通子图:连通分量。
(18)在有向图中,任意两点间存在路径,称为强连通图。有向图中的极大强连通子图称为有向图的强连通分量。
(19)连通图的生成树:一个极小的连通子图。n个顶点,n-1条边
(20)有向图中,恰有一个顶点入度为0,其余顶点的入度均为1,则是一棵有向树。有向图生成的森林:由若干棵有向树组成,含有图中全部顶点,但只有足以构成若干棵不相交的有向树的弧。
6.2图的存储结构
6.2.1图的邻接矩阵存储方式:
(1)一个一维数组存储图中顶点信息,一个二维数组(邻接矩阵)存储边或弧的信息。无向图对称,有向图不对称。
(2)网:没有边的权值设定为极大值。
6.2.2邻接表的存储方式
(1)将数组与链表相结合的存储方式
(2)处理方式:
- 数组存放顶点信息,每个顶点数据中还需要存储指向第一个邻接点的指针。
- 每个顶点的所有邻接点构成一个线性表,用单链表存储,无向图叫边表,有向图叫弧尾的出边表。
注:在有向图中,有逆邻接表,即以顶点为入度关系的边表;可以在边表汇总增加一个权值数据域。
6.2.3 十字链表(有向图)
(1)邻接表:关心出度问题;逆邻接表:关心入度问题。两者结合成十字链表。容易获得顶点的出度与入度。
顶点表的结构如下:
data | firstin | firstout |
---|
firstin表示入边头指针,firstout表示出边头指针。
tailvex | headvex | headlink | taillink |
---|
tailvex 指弧起点在顶点表的下标,headvex指弧终点在顶点表的下标;headlink指入边表指针域,指向终点相同的下一条边。taillink指边表指针域,指向起点相同的下一条边。如果是网,可增加Weight。
6.2.4邻接多重表
(1)在无向图中,删除边操作。
重新定义边表结点:
ivex | ilink | jvex | jlink |
---|
ivex,jvex表示与某条边所依附的两个顶点在顶点表中的下标。ilink指向依附顶点ivex的下一条边;jlink指向依附顶点jvex的下一条边。
6.2.5边集数组
(1)由两个一维数组组成。一个存储顶点信息,一个存储边的信息。每个边数组由一条边的起点下标(begin),终点下标(end)和权(weight)
适合于对边进行处理操作。
6.3图的遍历
(1)访遍所有顶点,仅访问一次
(2)深度优先遍历(Depth_First_Search),DFS.
- 类似于一棵树的前序遍历
- 连通图,可一次完成;对于非连通图,则对其连通分量分别进行深度优先遍历。
- 邻接矩阵的时间:O(n2)
- 邻接表的时间:O(n+e),n个顶点,e条边。适用于点多边少时,效率大大提高
(3)广度优先遍历(Breadth_First_Search),BFS
- 类似于树的层序遍历
小结:深度优先更适合目标比较明确,以找到目标为主要目的的情况;
而广度优先更适合在不断扩大遍历范围时找到相对最优解的情况。
6.4最小生成树
(1)一个连通图的生成树是一个极小的连通子图。包括图中所有顶点,只有足以构成一棵树的(n-1)条边
(2)最小生成树:构造连通网的最小代价生成树。
(3)经典方法:普里姆算法(Prim)(O(n2))与克鲁斯卡尔(Kruskal)(O(eloge))算法
- Prim:以某顶点为起点,逐步找各顶点上最小权值的边来构建最小生成树。适用于稠密图,边比较多的情况。
- Kruskal:直接找权值最小的边来构建生成树,过程中防止形成环路。边比较少的,稀疏图。
6.5最短路径
(1)最短路径:对于非网图即两顶点之间边数最少;对于网图即两点之间边上权值和最小。源点,终点
(2)迪杰斯特拉(dijkstra)算法(O(n2)):基于已经求出的最短路径的基础之上,求得更远顶点的最短路径。每一步最优。要求任意顶点到其余所有顶点的最短距离时(O(n3)).
(3)佛洛依德(Floyd)算法(O(n3)):求所有顶点至所有顶点的最短路径的问题。
6.6 拓扑排序
(1)无环的图应用;即图中没有回路。
(2)AOV网(Activity On Vertex Network):表示工程的有向图,顶点表示活动,弧表示活动之间的优先关系。
(3)G=(V,E)为n个顶点的有向图,V中的顶点序列,若vi到vj有一条路径,则在顶点序列中顶点vi必须在顶点vj之前。我们称这样的顶点序列为一个拓扑序列。
(4)拓扑排序:对一个有向图构造拓扑序列的过程。
输出结果有两个:
- 一是全部顶点被输出,说明他是不存在环(回路)的AOV网;
- 二是顶点数少了,说明这个网存在环(回路),不是AOV网;
(5)拓扑排序算法思路:
- 从AOV网中选择一个入度为0的顶点输出,然后删去此顶点,并删除以此顶点为尾的弧,
- 继续重复此步骤,知道输出全部顶点或AOV网中不存在入度为0的顶点为止。
- 时间复杂度:O(n+e)
- 解决一个工程能否顺序进行的问题
6.7关键路径
(1)AOE网(Activity On Edge Network):在一个表示工程的带权有向图中,用顶点表示事件,用有向边表示活动,用边上的权值表示活动的时间,这种有向图的边表示活动的网。
(2)始点或源点(没有入边);终点或汇点(没有出边)。
(3)路径长度:路径上各个活动所持续的时间之和;
(4)关键路径:从源点到汇点具有最大长度的路径;在关键路径上的活动,叫关键活动。
(5)该算法对于工程的前期工期估算和中期的计划调整都有很大的帮助。对于有几条关键路径的有向无环图,需要同时在几条路径上提高速度才行。
7查找(Search)
7.1基本概念
(1)查找表(Search Table):同一类型的数据元素(或记录)构成的集合。
(2)关键字(Key):数据元素中某个数据项的值,又称为键值,用它可以标记一个数据元素。也可以标识一个记录的某个数据项(字段),我们称之为关键码。
(3)主关键字(Primary key):可以唯一地标识一个记录。主关键字所在的数据项称为主关键码。
(4)次关键字(Secondary key):可以识别多个数据元素(或记录)的关键字。次关键字所对应的数据项称为次关键码
(5)查找:即给定某个值,在查找表中确定一个其关键字等于给定值得数据元素(或记录)。
(6)静态查找表(Static Search Table):只作查找操作的查找表
- “特定的”数据是否在表中
- 检索“特定的”数据元素和各种属性
(7)动态查找表(Dynamic Search Table)
- 查找时插入数据元素
- 查找时删除数据元素
7.2顺序表查找(O(n))
(1)顺序查找(Sequential Search)又叫线性查找。从表中第一个(或最后一个)记录开始,逐一比较,查找。
(2)优化:设置哨兵,从尾部比较
7.3有序表查找
(1)折半查找(Binary Searc
h)又称二分查找。(O(logn))
- 前提:关键码有序,线性变必须采取顺序存储。
(2)插值查找(interpolation search)
- 适用于关键字分布比较均匀的查找
- 插值公式
mid=low+a[high]−a[low]key−a[low](high−low)
(3)斐波拉契查找(Fibonacci Search)
- 利用了黄金分割原理实现
- 时间复杂度:O(logn),平均性能优于折半查找,特殊情况下,也有效率低于折半查找的情况
小结:折半查找时加法与除法运算,插值查找进行复杂的四则运算,斐波拉契查找只是简单的加减法运算。
7.4线性索引查找
(1)数据按先后顺序存储,海量数据。
(2)索引:是为了加快查找速度而设计的一种数据结构。即把一个关键字与对应的记录相关联的过程。一个索引由若干个索引项构成,每个索引项至少包含关键字和其相对应的记录在存储器中位置等信息。
(3)索引按结构分类:线性索引,树形索引和多级索引。
(4)线性索引:将索引项集合组织为线性结构,也称为索引表。
(5)三种线性索引:稠密索引,分块索引和倒序索引。
(6)稠密索引:
- 在线性索引中,将数据集中的每个记录对应一个索引项。
- 索引项是按照关键码有序排列的。(这是稠密索引的优点,但当数据集非常大,比如上亿,那就意味着索引也得有同样的数据集长度规模,对于内存有限的计算机来说,可能就需要反复去访问磁盘,查找性能大大下降)
- 可用到折半,插值,斐波拉契等有序查找算法
(7)分块索引:
- 分块有序,是把数据集的记录分成了若干块,并且这些块需要满足两个条件:
- 1)块内无序
- 2)块间有序
- 分块索引的索引项结构:
- 最大关键码
- 块中的记录个数
- 用于指向块首数据元素的指针
- 分块索引查找步骤:
- 1)块间有序,可用折半查找,插值等算法
- 2)块内无序,则用顺序查找
- 算法的时间复杂度:若n=t时,则为O(n)
(8)倒排索引
记录号表存储具有相同次关键字的所有记录的记录号(可以是指向记录的指针或是该记录的主关键字)
- 1)最基础的搜索技术
- 2)索引项的通用结构:次关键码和记录号表
- 查找记录非常快。但维护比较困难,插入或删除操作都需要作相应的处理。
小注:做好这方面的研究,可否进Google或百度做搜索引擎的软件工程师。
7.5二叉排序树(Binary Sort Tree)
(1)又称二叉查找树(中序遍历即可得到从小到大的排列)
- 若左子树不空,则左子树上所有值均小于它的根结点值
- 若右子树不空,则右子树上所有值均大于它的根结点值
- 它的左右子树分别为二叉排序树
(2)二叉排序树的删除:
- 叶子结点
- 仅有左或右子树的结点
- 左右子树均有的结点(找到直接前驱或后继,替换该结点,然后删除该结点)
(3)二叉排序树的查找性能取决于二叉排序树的形状,但其形状是不确定的,应尽可能的使其深度与完全二叉树相同,均为[log2n]+1,那么查找时间复杂就是O(logn),近似于折半查找,若时左或右斜树,则为O(n),和顺序查找没有区别。
7.6平衡二叉树(AVL树)
(1)一种二叉排序树,其中每个结点的左右子树的高度差至多等于1。
(2)一种高度平衡的二叉树
(3)将二叉树上每一个结点的左子树深度减去右子树深度的值称为平衡因子BF(Balance Factor)。即BF=-1,0,1则是平衡二叉树
(4)最小不平衡树:距离插入结点最近的,且平衡因子的绝对值大于1的结点为根的子树。
(5)平衡二叉树构建的基本思想:在构建二叉排序树的过程中,每当插入一个结点时,先检查是否因插入而破坏了树的平衡性,若是,则找出最小不平衡树。在保证二叉排序树特性前提下,调整最小不平衡树中各结点之间的链接关系,进行相应的旋转,使之成为新的平衡子树。
(6)查找时间复杂度:O(logn),插入和删除也一样。
二叉排序树还有其他平衡方法:红黑树(Red Black Tree)
7.7多路查找树(Multi-way search tree)(B树)
(1)每个结点的孩子数可以多于两个,且每个结点处可以存储多个元素。由于是查找树,所有元素之间存在某种特定的排序关系。
(2)每个结点存储多少个元素,以及它的孩子数是非常关键的部分。4种特殊形式:2-3树,2-3-4树,B树,B+树
(3)2-3树
- 概念:每个结点都具有2个孩子(2结点)或3个孩子(3结点)。
- 一个2结点包含一个元素和两个孩子(或没有孩子);不能只有一个孩子。(序列大小关系类似二叉排序树)
- 3结点:一小一大两元素和三个孩子(或没有孩子)。左子树包含小于较小元素的元素,右子树包含大于较大元素的元素,中间子树包含介于两元素之间的元素。
- 2-3树的叶子要求都在同一层次上。
(4)2-3-4树
- 类比于2-3树,对其概念的拓展
(5)B树(B-tree)
- 一种平衡的多路查找树
- 2-3树和2-3-4树都是B树的特例。
- 结点最大的孩子数目称为B树的阶(order)
- 2-3树是3阶B树,2-3-4树是4阶B树
- B树的数据结构是为内外存的数据交互准备的
(6)B+树
- B+树是应文件系统所需而出的一种B树的变形树
- 特别适用于带有范围的查找
7.8散列表查找(哈希表)
7.8.1基本概念
(1)散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。.
(2)f称为散列函数,又称为哈希(Hash)函数。
(3)采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表(Hash table)。
(4)散列技术既是一种存储方法,也是一种查找方法。
(5)散列技术最适合的求解问题是查找与给定值相等的记录。
(6)不适用的地方:
- 同样的关键字对应很多记录
- 范围查找
(7)冲突:两关键字key1与key2不相等,但f(key1)与f(key2)却相等。此时,key1与key2被称为散列函数的同义词。
7.8.2散列函数的构造
(1)要求:计算简单、散列地址分布均匀
(2)常用方法1:直接定址法
- 取关键字的某个线性函数值为散列地址,即f(key)=a*key+b,(a,b均为常数)
- 适用查找表较小且连续的情况
(3) 常用方法2:数字分析法
- 适合处理关键字位数比较大的情况。抽取,之后可对数字进行反转,右环位移,左环位移,等操作
- 如果事先知道关键字的分布且关键字的若干位分布较均匀,可以考虑用此法
(4)常用方法3:平方取中法
- 适用于不知道关键字的分布,而位数又不是很大的情况
(5)常用方法4:折叠法
- 事先不需要知道关键字的分布,适合关键字位数较多的情况。
(6)常用方法5:除留余数法
- 对散列表长度为m的散列函数公式为:f(key)=key mod p (p<=m)
- p的取值十分关键,经验通常p为小于或等于表长(最好接近m)的最小质数或不包含小于20质因子的合数。
(7)常用方法6:随机数法
- f(key)=random (key)
- 当关键字的长度不等时,采用这个方法较合适
小结:综合考虑的因素构造散列表
- 计算散列表地址所需要的时间
- 关键字的长度
- 散列表的大小
- 关键字的分布情况
- 记录查找的频率
7.8.3处理散列冲突的方法
(1)开放定址法
- 只要发生冲突,就寻找下一个空的散列地址,前提是散列表未填满
- 公式:fi(key)=(f(key)+di)MOD m (di=1,2,3,…,m-1)
- 又称线性探测发
- 上诉方法会导致,不是同义词却需要争夺一个地址的情况,被称为堆积。
- 二次探测:fi(key)=(f(key)+di)MOD m (di=12,-12,22,-22,…,q2,-q2, q<=m/2)
- 随机探测:对di采用随机函数得到。采用的是伪随机数,采用同样的随机种子,使存储和读取时,产生同样的随机数。
(2)再散列函数法
- 当冲突时,就换新的散列函数(RHi(key) )
- fi(key)=RHi(key) (i=1,2,…k)
- 增加了计算时间
(3)链地址法
- 在冲突位置给单链表增加结点
- 存在在查找时需要遍历单链表的性能损耗
(4)公共溢出区法
- 新建溢出表,存储冲突元素,在溢出表中顺序查找。
- 在溢出较少的情况下,公共溢出区的结构对查找性能来说还是非常高的
7.8.4散列表查找的实现
(1)散列表查找性能分析
- 散列函数是否均匀
- 处理冲突的方法
- 散列表的填装因子(α):=填入表中的记录数/散列表长度。
- 散列表的时间复杂度:O(1),通常将散列表的空间设置的比查找集大,以空间换时间,提高查找效率。
8排序
(1)
文章最后发布于: 2018-11-20 21:43:33
相关阅读
岁月匆匆如流水,一如梦幻十几载,从开服一直断断续续玩到现在,情怀依在,一直在游戏里就属于屌丝行列(游戏舍不得花钱^_^),梦幻的外挂还是
日复一日的人像临摹练习使得画家能够仅凭几个关键特征画出完整的人脸。同样地,我们希望机器能够通过低清图像有限的图像信息,推断出
常见数据结构 HashMap、Hashtable、 ConcurrentHashMap HashMap 底层实现:HashMap底层整体结构是一个数组,数组中的每个元素又是一
scikit-learn机器学习(五)--条件概率,全概率和贝叶斯定理
在理解贝叶斯之前需要先了解一下条件概率和全概率,这样才能更好地理解贝叶斯定理 一丶条件概率 条件概率定义:已知事件A发生的条
本小节文字比较少,但是理解起来内容还是蛮难的,花了我整整一天的时间把代码和前面的原理搞明白,其实最难的部分就在本小节的代码上。