参考文档(有些随手查一两个名词的,未被记录,有遗漏):
数据结构与算法(java版)
霍夫曼树 和霍夫曼编码原理
30张图带你彻底理解红黑树 - 简书
无序数组和有序数组
无序数组的优点:插入快,如果知道下标,可以很快的存取
无序数组的缺点:查找慢(挨个比对),删除慢,大小固定。
有序数组的优点:查找效率高(二分法)
有序数组的缺点:删除和插入慢(当插入一个元素时,首先要判断该元素应该插入的下标,然后对该下标之后的所有元素后移一位,才能进行插入),大小固定
后进先出(只允许访问一个数据项:即最后插入的数据。移除这个数据项后才能访问倒数第二个插入的数据项)
栈通常用于解析某种类型的文本串(如分隔符匹配)
先进先出(栈的插入和删除数据项的命名方法很标准,成为push和pop,队列的方法至今也没有一个标准化的方法,插入可以称作put、add或enque等,删除可以叫作delete、get、remove或deque等)
环绕式处理(让队尾指针回到数组的第一个位置),形成循环队列(也成为缓冲环)。虽然在存储上是线形的,但是在逻辑上它是一个首尾衔接的环形
双端队列(队列的每一端都可以进行插入和移除操作,是队列和栈的综合体。如果限制双端队列的一段只能插入,而另一端只能移除,就变成了平常意义上的队列;如果限制双端队列只能在一端进行插入和移除,就变成了栈)
优先级队列(优先级队列有一个队头和一个队尾,并且也是从队头移除数据,从队尾插入数据,不同的是,在优先级队列中,数据项按关键字的值排序,数据项插入的时候会按照顺序插入到合适的位置)通常使用一种称为堆的数据结构来实现
插入和删除都比较快的数据结构,缺点是查找比较慢。
除非需要频繁的通过下标来随机访问数据,否则在很多使用数组的地方都可以用链表代替。
在链表中,每个数据项都包含在“链结点”中,一个链结点是某个类的对象。每个链结点对象中都包含一个对下一个链接点的引用,链表本身的对象中有一个字段指向第一个链结点的引用
链表的删除指定链结点,是通过将目标链结点的上一个链结点的next指针指向目标链结点的下一个链结点。(实际上删除掉的元素的next指针还是指向原来的下一个元素的,只是它不能再被单链表检索到而已,JVM的垃圾回收机制会在一定的时间之后回收它)
添加链结点比删除复杂一点,首先我们要使插入位置之前的链结点的next指针指向目标链结点,其次还要将目标链结点的next指针指向插入位置之后的链结点。
与单链表的区别在于它不只第一个链结点有引用,还对最后一个链结点有引用。对最后一个链结点的引用允许像插入表头那样在表尾插入一个链结点,使用双端链表更适合于一些单链表不方便操作的场合,队列的实现就是这样一个情况
双端链表可以插入表尾,但是仍然不能方便的删除表尾,因为我们没有方法快捷地获取倒数第二个链结点,双端链表没有逆向的指针,这一点就要靠双向链表来解决了
按关键值排序。删除链头时,就删除最小(or最大)的值,插入时,搜索插入的位置。
插入时需要比较O(N),平均O(N/2),删除最小(or最大)的在链头的数据时效率为O(1)
优先级队列 也可以使用有序链表来实现
有序链表优于有序数组的地方在于插入的效率更高(不需要像数组那样移动元素),另外链表可以灵活地扩展大小,而数组的大小是固定的。但是这种效率的提高和灵活的优势是以算法的复杂为代价的
有序链表可以用于一种高效的排序机制。假设有一个无序数组,如果从这个数组中取出数据插入有序链表,他们会自动按照顺序排列,然后把它们从有序链表中重新放入数组,该数组就变成了有序数组。这种排序方法比在数组中常用的插入排序效率更高,因为这种方式进行的复制次数少一些
双向链表的插入和删除会变得更复杂,因为同时要处理双倍的指针变化;
对于删除操作,如果要删除表头或表尾,比较简单,如果要删除指定值的链结点比较复杂,首先需要定位到要删除的链结点,然后改变了其前后链结点的指针
从循环链表中的任何一个结点出发都能找到任何其他结点。循环链表的操作和单链表的操作基本一致,差别仅仅在于算法中的循环条件有所不同
有序数组可以利用二分查找法快速的查找特定的值,时间复杂度为O(log2N),但是插入数据时很慢,时间复杂度为O(N);
链表的插入和删除速度都很快,时间复杂度为O(1),但是查找特定值很慢,时间复杂度为O(N);
既能像有序数组那样快速的查找数据,又能像链表那样快速的插入数据:树(不过依然是以算法的复杂度为代价)
树其实是范畴更广的 图 的特例
如果树的每个节点最多有两个子节点,则称为二叉树。如果节点的子节点可以多于两个,称为多路树
树(路径、根、父节点、子节点、叶节点、子树、层),从树的根到任意节点有且只有一条路径可以到达。
二叉搜索树(一个节点的左子节点的关键字值小于这个节点,右子节点的关键字值大于或等于这个父节点)
平衡树与非平衡树(非平衡:树的大部分节点在根的一边)
树的不平衡是由数据项插入的顺序造成的。如果关键字是随机插入的,树会更趋向于平衡,如果插入顺序是升序或者降序,则所有的值都是右子节点或左子节点,这样生成的树就会不平衡了。非平衡树的效率会严重退化
查找 从根节点入手,如果该元素值小于根节点,则转向左子节点,否则转向右子节点,以此类推,直到找到该节点,或者到最后一个叶子节点依然没有找到,则证明树中没有该节点
插入 插入一个新节点首先要确定插入的位置,这个过程类似于查找一个不存在的节点,找到要插入的位置之后,将父节点的左子节点或者右子节点指向新节点即可
三种简单的方法遍历树:前序遍历(本左右)、中序遍历(左本右)、后序遍历(左右本)
最常用的方法是中序遍历,中序遍历二叉搜索树会使所有的节点按关键字升序被访问到
遍历树最简单的方法是递归。用该方法时,只需要做三件事(初始化时这个节点是根):
1、调用自身来遍历节点的左子树
2、访问这个节点
3、调用自身来遍历节点的右子树
遍历可以应用于任何二叉树,而不只是二叉搜索树。遍历的节点并不关心节点的关键字值,它只看这个节点是否有子节点
前序遍历:先访问父节点,然后左子节点,最后右子节点;
后序遍历:先访问左子节点,再访问右子节点,最后访问父节点。
所谓的前序、中序、后序是针对父节点的访问顺序而言的
查找最值 从根循环访问左子节点,直到该节点没有左子节点为止,该节点就是最小值;从根循环访问右子节点,直到该节点没有右子节点为止,该节点就是最大值
删除节点
需要考虑三种情况考虑:
1、该节点没有子节点(只需将父节点指向它的引用设置为null)
2、该节点有一个子节点(有两个连接需要处理:父节点指向它的引用和它指向子节点的引用。无论要删除的节点下面有多复杂的子树,只需要将它的子树上移。特殊情况需要考虑,就是要删除的是根节点,这时就需要把它唯一的子节点设置成根节点)
3、该节点有两个子节点(中序后继)
(第三种情况)找到比欲删除节点的关键字值大的集合中的最小值后继节点(从树的结构上来说,就是从欲删除节点的右子节点开始,依次跳到下一层的左子节点,直到该左子节点没有左子节点为止)
后继节点一定没有左节点。从这一个特点就能看出来,后继结点有可能存在右节点,也有可能没有任何节点
后继节点提到删除的节点位置,后继结点的子右节点(如果有)提到它原来的位置。
(java的写法,删除节点主要是对后继节点 和 被删除节点的父节点与子节点 之间进行处理,被删除的节点在操作过程中,所有代码中都没有处理它的上下级关系,因为通过其他节点已经无法引用到该节点了,所以被删除的节点最后能被 GC 正常回收。)
在英文中,字母E的使用频率最高,而字母Z的使用频率最低,但无论使用频率高低,一律使用相同位数的编码来存储有些浪费空间。
如果对使用频率高的字母用较少的位数来存储,而对使用频率低的字母用较多的位数来存储,会大大提升存储效率,霍夫曼编码就是根据以上的设想来处理数据压缩。
霍夫曼树是最优二叉树。树的带权路径长度(Weighted Path Length of Tree,简记为WPL)规定为所有叶子结点的带权路径长度之和,带权路径长度最短的树,即为最优二叉树。在最优二叉树中,权值较大的结点离根较近。
(结点的权:在一些应用中,赋予树中结点的一个有某种意义的实数.
结点的带权路径长度:结点到树根之间的路径长度与该结点上权的乘积.
树的带权路径长度(Weighted Path Length of Tree):定义为树中所有叶结点的带权路径长度之和
其中:
n表示叶子结点的数目
wi和li分别表示叶结点ki的权值和根到结点ki之间的路径长度.
树的带权路径长度亦称为树的代价。)
(左子叶的权小于右子叶的权)
哈夫曼树的在编码中的应用:
在电文传输中,需要将电文中出现的每个字符进行二进制编码。在设计编码时需要遵守两个原则:
(1)发送方传输的二进制编码,到接收方解码后必须具有唯一性,即解码结果与发送方发送的电文完全一样;
(2)发送的二进制编码尽可能地短。
为了设计长短不等的编码,以便减少电文的总长,还必须考虑编码的唯一性,即在建立不等长编码时必须使任何一个字符的编码都不是另一个字符的前缀,这宗编码称为前缀编码(prefix code)
(1)利用字符集中每个字符的使用频率作为权值构造一个哈夫曼树;
(2)从根结点开始,为到每个叶子结点路径上的左分支赋予0,右分支赋予1,并从根到叶子方向形成该叶子结点的编码
通过哈夫曼树来构造的编码称为哈弗曼编码(huffman code)
红黑树就是一种解决非平衡树的方法,它是增加了某些特点的二叉搜索树
对树中的每个节点,它左边的后代数量和它右边的后代数量应该大致相等
1、每一个节点不是黑色就是红色
2、根总是黑色的
3、每个叶子的节点都是黑色的空节点
4、如果节点是红色的,则它的子节点必须是黑色的,反之则不一定成立
5、从根到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点
规则5中的空子节点是指非叶节点可以接子节点的位置。换句话说,就是一个有右子节点的可能接左子节点的位置,或者是有左子节点的节点可能接右子节点的位置。
这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。结果是这个树大致上是平衡的。
规则5导致了路径不能有两个毗连的红色节点就足够了。最短的可能路径都是黑色节点,最长的可能路径有交替的红色和黑色节点。因为根据规则5所有最长的路径都有相同数目的黑色节点,这就表明了没有路径能多于任何其他路径的两倍长。
从根节点到叶节点路径上的黑色节点的数目被称为黑色高度
算法分析
首先,当插入第一个节点时,这个节点就是根节点,所以必须是黑色的
在增加新的节点的时候,先默认新节点都是红色的,因为插入一个红色节点违背红黑规则的可能性比插入一个黑色节点的要小;另外,如果插入节点的父节点是黑色节点,不会违背父子节点同时为红色的规则,如果插入节点的父节点是红色节点才会违背这一规则,这个时候就需要对树进行变换来适应规则。
违背规则4(父子节点都是红色)比违背规则5(黑色高度不同)要容易修正。
颜色变换
旋转
如果大部分节点都在某个参照点左侧,就需要把一些节点移到右侧,成为右旋;
如果大部分节点都在某个参照点右侧,就需要把一些节点移到左侧,成为左旋。
这里所说的左旋与右旋是相对于参照点而言的
旋转的时候仍然要遵循搜索二叉树的规则(比该节点小的值只能在该节点则左下方或者左上方;比该节点大的值只能在该节点的右下方或者右上方)
横向移动
37就被称为50节点右旋操作的内侧子孙,而12就是外侧子孙。
整棵子树也会发生集体的移动
在进行颜色变化或旋转的时候,往往要涉及祖孙三代节点
删除
红黑树结点的删除
数据结构之红黑树(三)——删除操作
(说真的,看晕了,直接贴)