重学数据结构笔记(3)
5.串,由零个或多个字符组成的序列,又叫字符串
串的比较是通过组成字符串的字符之间的编码来进行的,而字符串编码指的是字符在对应字符集中的符号。
串的存储结构与线性表相同分两种
串的顺序存储结构
串的顺序存储结构是用一组地址连续的存储单元来存储串中的字符序列的。按照预定义的大小,为每个定义的串变量分配一个固定长度的存储区域,一般使用定长数组来定义
串的顺序存储方式其实是有问题的,因为串的插入和替换都会引起长度超过数组长度。
串的链式存储结构,与线性表链式存储结构相似,不过每个节点存一个字符,太浪费空间,可以一个节点存多个字符,不够的用#补满
串的链式存储结构除了在链接串与串操作时有一定方便之外,总的来说不如顺序存储灵活,性能也不如顺序好。
朴素模式匹配算法
子串的定位操作通常称作串的模式匹配,算是串中最重要的操作了。
对主串的每个字符做为子串的开头,与要匹配的字符串匹配。对主串做大循环。直到全部遍历匹配成功为止。(效率太低)
平凡子串不包括自身
KMP算法(有点难,最后攻克)
6.树,树是n(n>=0)个结点的有限集,n=0时为空树,在任意一课非空树中:(1)有且仅有一个特定的称为根的结点,(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集t1,t2,t3,t4.....tm,其中每一个集合本身又是一棵树,并且称为根的子树。
注意点,当n>0时根结点是唯一的,不可能存在多个根结点;;当m>0时,子树的个数没有限制,但他们一定是互相不交互的。
树的节点包含一个数据元素及若干指向其子树的分支。结点用有的子树数称为结点的 度 。度为0的结点称为 叶结点或终端结点;度不为0的结点称为非终端结点或分支结点。除根节点之外,分支结点也称为内部结点,树的度是数内各结点的度的最大度。
结点间的关系
结点的子树的根称为该结点的 孩子(Child),相应的,该结点称为孩子的双亲(Parent),同一个双亲的孩子之间互为兄弟(SibLing)
结点的层次从根开始定义起,根为第一层,根的孩子为第二层,以此类推,树中最大层次称为树的深度(Depth)或高度。
如果将树中结点的各个子树从左至右是有顺序的,那么他就是有序树,否则叫无序树。
森林(Forest)是m(m>0)课不相交树的集合。
树的存储结构、
双亲表示法:在每个结点中,附设一个指示器指示其双亲结点到数组中的位置。也就是说每个结点除了知道自己是谁外,还知道他的双亲在哪里。
在双亲表示法的基础上,如果有需要还可以增加孩子结点指针。
存储结构的设计是一个非常灵活的过程,一个存储结构设计的是否合理,取决于基于该存储结构的运算是否合适,是否方便,时间复杂度好不好等。。。
孩子表示法:把每个结点的孩子结点排列起来,以单链表作存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空,然后n个头指针又组成一个线性表,采用顺序存储结构,存放进一个一维数组中。
换一种完全不同的考虑方法,由于树中每个结点可能有多颗子树,可以考虑用多重链表,即每个结点有多个指针域,其中每个指针指向一科子树的根节点,我们把这中方法叫多重链表表示法。
不过由于每个结点的度不同,所以设计两种方案解决。
方案一,指针域的个数等于树的度。(会造成空间浪费,不过各结点的度相差较小时,意味着会充分利用)
方案二,每个结点的指针域个数等于该结点的度。(空间利用率高了,不过运算时间损耗相对大)
可以把双亲表示法和孩子表示法结合,双亲孩子表示法。
孩子兄弟表示法:任意一棵树,他的结点的第一个孩子存在就是唯一的,他的右兄弟存在也是唯一的。因此,我们设置两个指针,分别指向该结点的第一个孩子和此结点的右兄弟(第二个指针是由第一个孩子结点指向其右兄弟)。
二叉树(Binary Tree):是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两颗互不相交的、分别称为根结点的左子树和右子树的二叉树组成。
特点:每个结点最多有两颗子树,所以二叉树中不存在度大于2的结点;;左子树和右子树是有顺序的,次序不能任意颠倒,就想人有左右手,不能颠倒;;即使树中某结点只有一棵树,也要区分他是左子树还是右子树(左手受伤和右手受伤,对生活的影响是不同的)
二叉树的五种基本形态:空二叉树;只有一个根节点;根节点只有左子树;根节点只有右子树;根结点左子树右子树都有;
特殊二叉树
斜树:所有结点只有左结点的二叉树叫左斜树,所有结点只有右节点的二叉树叫右斜树(其实与线性表结构一样,线性表结构可以理解为树的一种极其特殊的表现形式)
满二叉树:在一棵二叉树中,所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,我们叫它满二叉树。(特点:叶子只能出现在最下一层;非叶子结点的度一定是2;同深度的二叉树,满二叉树结点最多,叶子最多)
完全二叉树:对一棵具有n个结点的二叉树按层编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这课二叉树称为完全二叉树。(满二叉树一定是完全二叉树,完全二叉树不一定是满二叉树,关键词:按层次编号!!)
完全二叉树特点:叶子的结点只能出现在最下层;最下层的叶子一定集中在左部连续位置;倒数第二层若有叶子结点,一定都在右部连续位置;如果结点度为1,则该节点只有左孩子不存在只有右孩子的情况;同样结点数的二叉树,完全二叉树深度最小;;
二叉树的五个性质:(用数据归纳法论证)
①在二叉树的第i层上至多有2i-1(2的i-1次方)个结点(i>=1)
②深度为k的二叉树至多有2k-1(2的k次方-1)个结点(k>=1)
③对于任意一课二叉树T,如果其终端结点(叶子)数量为n,度为2的结点个数为m,则 n=m+1。
④具有n个结点的完全二叉树的深度为|log2n|+1(log以2为底数n的对数+1)(|X|表示不大与X的最大整数,,,也就是取整)(由完全二叉树和满二叉树推到出来)
⑤如果对一棵有n个结点的完全二叉树(深度是④)的结点按层序编号(从1层到④层,每层从左到右),对任一结点i(1=<i<=n)有:
⑴若i=1,则结点i是二叉树的根,无双亲;若i>1,则其双亲是结点|i/2|
⑵如果2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i
⑶如果2i+1>n,则结点i无右孩子;否则右孩子是结点2i+1
二叉树顺序存储结构:将二叉树放入数组,下标对应二叉树的层序编号(特殊:一棵树深度为k的右斜树,他只有k个结点,却要分配2k-1空间,浪费),二叉树顺序存储结构只用于完全二叉树。
二叉链表:二叉树每个结点最多有两个孩子,所以设计一个数据域和两个指针域是比较自然的想法,我们称这样的链表叫二叉链表。(如同 树 的链式存储结构,如果有需要还可以加一个指向双亲的指针域,称为三叉链表)
二叉树的遍历:是指从二叉树的根结点出发,按照某种次序一次访问所有结点,使得每个结点仅被访问一次。关键词:访问 次序
二叉树遍历方法
前序遍历:若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。ABDGHCEIF(先打印结点,再左右递归)
中序遍历:若二叉树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。GDHBAEICF(先左递归再打印结点再右递归)
后序遍历:若二叉树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访问左右子树,最后是访问根结点。GHDBIEFCA(先左右递归再打印结点)
层序遍历:若二叉树为空,则空操作返回,否则从树的第一层,也就是根结点开始访问,从上而下逐层遍历,在同一层中,按从左到右的顺序对结点逐个访问。ABCDEFGHI
我们以图的形式来看树很直观和容易理解,但计算机只能处理,循环、判断,也就是说他只能循环线性列,所以有了不同的遍历方法,不同的遍历提供了对结点一次处理的不同方式,可在遍历过程中对结点进行各种处理。
二叉树遍历的性质:已知前序遍历序列和中序遍历序列,可以唯一确定一棵二叉树;;已知后序遍历序列和中序遍历序列,可以唯一确定一棵二叉树;;但是,已知前序序列和后序序列,是不能确定一棵二叉树的(因为不能知道那个是左子树,那个是右子树)
二叉树的建立,在内存中,为了让每个结点都有左右孩子,对不齐全的二叉树,为每个空指针引出一个虚拟结点,比如设置为“#”,这种二叉树称为原二叉树的扩展二叉树,扩展二叉树就可以做到一个遍历序列确定一棵二叉树,如AB#D##C##
生成二叉树,也是利用了递归的原理,只不过在原来打印结点的地方,改成了生成结点,赋值操作而已,跟3种遍历的原理一样。
建立二叉树后,因为是链式结构,而每个结点均有指向其左右孩子的指针,所以会出现空指针,造成空间浪费;
线索二叉树:我们把指向前驱和后继的指针称为线索,加上线索的二叉链表称为线索链表,相应的二叉树就称为线索二叉树;
我们对二叉树以某种次序遍历使其变为线索二叉树的过程称作是线索化。
由于二叉树线索化后,无法知道其指针指向的是哪个,所以要再加两个标志域ltag rtag,当ltag为0时指向该节点的左孩子,当为1时指向该节点的前驱;rtag同理(右孩子)
线索化的过程就是在遍历的过程中修改空指针的过程。
在实际的问题中,如果所用的二叉树需要经常遍历或查找结点时需要某种遍历序列中的前驱和后继,那么采用线索二叉链表的存储结构是非常不错的。
树转为二叉树步骤:
①加线,在所有兄弟结点之间加一条线;
②去线,对树中每个结点,只保留它与第一个孩子的结点的连线,删除它与其他孩子结点之间的连线。
③层次调整,以树的根结点为轴心,将整棵树顺时针旋转一定的角度,使之结构层次分明,!注意第一个孩子是二叉树结点的左孩子,兄弟转换过来的孩子是结点的右孩子。
森林是由很多树组成
森林转为二叉树:
①把每个树转为二叉树;
②第一颗树不动,从第二颗二叉树开始,一次把后面一棵二叉树的根结点作为前一颗二叉树的根结点的右孩子,用线连接起来,当所有二叉树都连接起来后转换完成;
二叉树转为树:(树转为二叉树的逆向过程)
①加线,若某结点的左孩子存在,将这个左孩子的右孩子结点、右孩子孩子的结点、右孩子孩子孩子的结点......反反正是左孩子的n个右孩子结点都作为此结点的孩子,将该结点与这个右孩子结点用线连接起来。
②去线,删除原二叉树中的所有结点与右孩子结点的连线;
③层次调整;
二叉树转为森林:(判断一棵树二叉树是转换成一棵树还是森林,标准很简单,那就是只要看这课二叉树的根结点有没有右孩子,有就是森林,没有就是一棵树)
①从根结点开始,若右孩子存在,则删除根结点与右孩子的连线,再查看分离后的二叉树,若根结点右孩子存在,则删除根结点与右孩子的连线,一次类推,直到所有右孩子的连线都删除为止,得到分离的二叉树;
②再将分离的后的二叉树转为树即可;
树与森林的遍历
树:
一种是先根遍历,即是先访问树的根结点,再访问树的其他结点
一种是后根遍历,先访问遍历没颗子树,最后再访问根结点
森林:
前序遍历:先访问森林中第一棵树的根结点,再依次先根遍历根的没颗子树,再用同样的方式遍历其他树;
后序遍历:先访问森林中第一棵树,后根遍历每颗子树,再访问根结点,再用同样方式遍历其他树;
赫夫曼树
从树种一个结点到另一个结点之间的分支构成两个结点之间的路径,路径上的分支数目称作路径长度。
树的路径长度就是从树根到每一结点的路径长度之和
假设有n个权值,构造一棵有n个叶子结点的二叉树,每个叶子结点带权w,每个叶子的路径长度为lk,则其中带权路径长度wpl最小的二叉树称作赫夫曼树。也有不少书中称为最优二叉树。