一.堆(Heap)
1.优先队列(Priority Quene):特殊的“队列”,取出元素的顺序是按照元素的优先权(关键字)大小,而不是元素进入队列的先后顺序。
- 若采用数组或链表实现优先队列,
- 数组:
- 插入–元素总是插入尾部 — O(1)
- 查找–查找关键字— O(n)
- 删除–从数组中删除元素,需要将删除元素后的x
x元素往前挪动–O(n)- 链表:
- 插入–插在链表头部位置—O(1)
- 删除–查找关键字 — O(n)
- 删除–只需将指针改写即可–O(1)
- 采用完全二叉树表示,满足以下两个条件:
- 结构性:是完全二叉树(用数组存储)
- 有序性:任一结点的关键字是其子树所有结点的最大值(或最小值)
- 最大堆(MaxHeap):最大值
- 最小堆(MinHeap):最小值
从根节点到任一结点的路径具有有序性- 插入 O(logN)(参见网易云课堂数据结构陈越mooc)先将元素放入数组最后一位,然后再与其父结点比较,根据大小互换位置
- 删除 O(logN) 删除最大元素,空出的位置由最后一个元素替补,然后最后一个元素与其他元素比较,小则互换位置,直到满足最大堆条件位置(以最大堆做解释,最小堆同理)
- 最大堆的建立(后面堆排序就是建立在最大堆的基础上)
- 方法1:通过插入操作,将N个元素一个个插入初始为空的堆里去,时间代价是O(NlogN),每插入一个元素复杂度是O(logN),所以插入N个就是O(NlogN)。
- 方法2:在线性时间复杂度下建立最大堆(O(N))
- 将N个元素按输入顺序(这里的顺序不是大小顺序,只是按照位置顺序(1,2,3,4···)存入数组而已)存入,先满足完全二叉树的结构特性
- 调整各结点位置,以满足最大堆的有序特性。(自底向上,使任一结点的左右子树都是一个堆)
1 | //最小堆的实现(创建,插入,删除,查找路径) |
二.哈夫曼树和哈夫曼编码
- 带权路径长度(WPL,Weight Path Length):设二叉树有n个叶子结点,每个叶子结点带有权值Wk,从根节点到每个叶子结点的长度为Lk,则每个叶子结点的WPL之和:WPL = W0*L0 + ?? + Wi*Li +??+ Wn*Ln(i = 0???N)
- 哈夫曼树(最优二叉树):WPL最小的二叉树
哈夫曼树的构造:每次将权值最小的两棵二叉树合并。
例如:1 2 3 4 5
构造如下:
1 2合并得3,在3 3 4 5中选择最小的,3 3合并得6 4 5,在其中选择最小的4 5得6 9合并得15
- 代码实现中,如何选取最小的两个元素,排序不如最小堆效率高,因为每次排完序,还要将合并后的元素放入序列中重新排序。构造最小堆,每次删除最小堆中的两个元素,然后插入合并后的元素,最终构造哈夫曼树的时间复杂度为O(NlogN)
哈夫曼树的特点
- 没有度为1的结点
- 如果有N个叶子结点,哈夫曼树则共有2*N-1个结点
- 哈夫曼树的任一非叶节点的左右子树交换后仍是哈夫曼树
- 对同一组权值{W1,W2,W3???Wn}是否存在不同构的两棵哈夫曼树呢?(存在)
例如{1,2,3,3}就存在。(见MOOC课件)- 哈夫曼编码
- 不等长编码,频率出现高的编码长度短一点,频率出现低的编码长度高一点。
但是这样会出现二义性,例如:a:1,e:0,s:10,t:11
那么对于1011的解释就有不止一种,可以解释成st或者aet,如何避免二义性?- 前缀码(prefix code):任何字符的编码都不是另一字符的编码的前缀。
上述例子中a的编码是t的编码的前缀,这样就造成了二义性。- 二叉树进行编码:
(1):左右分支:0,1(左为0,右为1)
(2):字符只在叶子结点上,不能出现在非叶子结点上- 给定一些字符的频率,根据频率先将哈夫曼树构造出来,给向左的边标记0,向右的标记1,这样所有的字符的哈夫曼编码就出来了,这样的二叉树查找代价也是最小的。
集合及运算
- 并查集
采用何种结构来存储集合,采用树来存储,所有结点均指向根节点,根节点代表该集合。然后用数组实现(链表也能实现)
| 下标 | Data | parent |
|---|---|---|
| 0 | 1 | -1 |
| 1 | 2 | 0 |
| 2 | 3 | -1 |
| 3 | 4 | 0 |
| 4 | 5 | 2 |
| 5 | 6 | -1 |
| 6 | 7 | 0 |
| 7 | 8 | 2 |
| 8 | 9 | 5 |
| 9 | 10 | 5 |
数据1的子节点有2,4,7
数据3的子节点有5,8
数据6的子节点有9,10
数据1,3,6分属三个不同的集合,因为他们的父结点为-1,在列表中没有,下标是元素在数组中的位置,这是在数组中存储的形式。