一、树
大于等于一个结点的有限节点,有层次关系的集合,满足下面三个条件:
- 有且只有一个结点,没有父结点,称为根。
- 除了根外,其他结点都都有且只有一个父节点。
- 树中的每个结点都构成以它为根的数。
二、二叉树
在满足树的定义的前提下,还要满足:
- 每个最多有两个孩子
- 孩子有左右只分,不能颠倒
特性:
- 二叉树第i成上的结点数目最多为 2 i − 1 2^{i-1} 2i−1(i >= 1)
- 深度为k的二叉树至多有 2 k − 1 2^k - 1 2k−1个结点(k>=1)
- 包含n个结点的二叉树的高度至少为 log 2 ( n + 1 ) \log_{2}{(n+1)} log2(n+1)
- n 0 = n 2 + 1 ( n 0 度 为 0 的 结 点 , n 2 度 为 2 的 结 点 ) n_0=n_2+1(n_0度为0的结点,n_2度为2的结点) n0=n2+1(n0度为0的结点,n2度为2的结点)(结点的,子树个数称为度)
三、完全二叉树
满足二叉树的前提下:
- 除了最后一层都是满的。
- 最后一层会从第一个结点开始,依次向后增加结点
线性特性,完全二叉树,如果按照层序遍历,可以线性索引;索引满足以下性质:
-
下标为i的元素的,i从1开始:
左孩子下标:left_child(i) = 2i ;
右孩子下标:right_child(i) = 2i+1;
下标为i的元素的,i从0开始 (编程一般这个公式) :
左孩子下标:left_child(i) = 2i+1 ;
右孩子下标:right_child(i) = 2i+2; -
第k层第i个元素,的下标 2 k + i 2^{k}+i 2k+i (k 从0 开始)
层序遍历:就是从第一层开始从左向右数,数完后从第二层开始,从左向右,然后第三层,第四层。。。直到最后一层。
可以线性索引:按照层序遍历数,下标位置是固定,如:第三层的第2元素,下标一定是5。
四、 二叉堆
分为最大堆和最小堆
最大堆:各个父结点的值总是大于,子结点的值。
最小堆:各个父结点的值总是小于,子结点的值。
二叉堆的操作:
- make_heap() 创建堆
- insert_heap() 堆中插入数据
- pop_heap() 删除堆顶元素
- sort_heap() 对堆进行排序。
五、二叉堆的操作
1. make_heap(最大堆):
(1)情况1:左右子树满足,根不满足,执行heap_down操作(又叫下沉操作):
根,和左右结点比较,选出最大的,交换。
如果子树,都满足大堆顶,完成
如果不满足,不满足的结点,和他的左右子树,执行上面的操作
void heap_down(std::vector<int> &a, int i) {
while(i < a.size()) {//只要i不超过数组长度,就不断向下调整
int left = 2*i+1 //计算i的左下标
int right = 2*i+2 //计算i的右下标
int maxium = i //设置maxium存储i和左右子结点较大结点的下标,初始化i
if (left < a.size() && a[maxium] < a[left]) {
maxium = left
}
if(right<a.size()&&a[maxium] < a[right]) {
maxium = right;
}
if (maxium==i) {
break;
}
//交换
int tmp = a[i]
a[i] = a[maxium]
a[maxium] = tmp
//步进
i = maxium
}
}
(2)make_heap,调用heap_down实现
void make_heap(std::vector<int> &a) {
for (int i = a.size()-1;i>0;i--) { //其实从1/2开始比较就可以。
heap_down(a,i); // 对i为开始,a最后一元素的一段,执行heap_down 开始只有一个元素,满足,大对顶。
}
}
2. heap_insert()
它可以假定我们事先不知道有多少个元素,又叫上浮操作,算法步骤如下:
- 首先增加堆的长度,在最末尾的地方加入最新插入的元素。
- 比较当前元素和它的父结点值,如果比父结点值大,则交换两个元素,否则返回。
- 重复步骤2.
void heap_insert(int arr[],int index)
{
if index == 0 {
return
}
//注意数组下标越界判断省略
while(arr[index] > arr[(index-1)/2]) //大堆顶
{
swap(arr[index],arr[(index-1)/2]);
index = (index-1)/2;
}
}
void make_heap(int arr[]) {
length = len(length)
for(int i = 0;i < length;i++ )
{
heap_insert(arr,i);//用for循环传入要处理的index
}
}
3. heap_pop
heap_pop假设已经是堆,使用的是heap_down操作(可以看做是heap_pop的一个步骤):
- 堆长度减1
- 数组第1个元素,和堆尾,后面一元素交换。
- 执行heap_down
void heapify(int arr[],int index,int heapsize)
{
int left = 2*index+1;
while (left < heapsize)
{
int largest = left+1<heapsize && arr[left+1]>arr[left]?
left+1:left;
largest = arr[index] < arr[largest]?largest:index;
if(index == largest)
break;
swap(arr[index],arr[largest]);
index = largest;
left = 2*index+1;
}
}
void heap_pop(int arr[],int heapsize) {
swap(arr[0],arr[heapsize-1]);
heapsize--;
heapify(arr,0,heapsize);
}
4.heap_sort
heap_sort使用的是heap_down操作:
- 堆长度设置成和数组长度一样,数组make_heap为大堆顶。
- 堆长度减1
- 数组第1个元素,和堆尾,后面一元素交换。
- 执行heap_down
- 重复 2,3, 4一直到 堆长度为1。
void heapify(int arr[],int index,int heapsize)
{
int left = 2*index+1;
while (left < heapsize)
{
int largest = left+1<heapsize && arr[left+1]>arr[left]?
left+1:left;
largest = arr[index] < arr[largest]?largest:index;
if(index == largest)
break;
swap(arr[index],arr[largest]);
index = largest;
left = 2*index+1;
}
}
void heap_sort(int arr[],int heapsize)
{
//make_heap操作略过。
while(heapsize > 1)
{
swap(arr[0],arr[heapsize-1]);
heapsize--;
heapify(arr,0,heapsize);
}
}
六、优先队列
优先队列和堆是相关的,单并不是同一个概念。但是相互关联我们一起介绍。
1.什么是优先队列?
是一种抽象数据类型,每个元素都有一个优先级,优先级决定了出队列的顺序。
注意:优先队列中的数据必须是可比较的。
2.优先队列的操作
插入add:元素进入队列
出队列 pull: 选出优先级最大的元素,出队列。
大部分优先队列 都是基于堆实现的,出队列和入队列的时间复杂度都是O(logn)
3.优先队列的使用场景
- 用于Dijkstra最短路径算法
- 当你需要动态获取下一“最好”或者“最差”的元素时。
- 用于哈夫曼编码(常用于无损压缩)。
- 用于最佳优先算法(Best First Search,BFS),可以通过PQ不断获取下一个最又希望的节点。
- 用于最小生成树算法()
4.优先队列的程序复杂度,基于二叉堆
构建二叉堆 | O(n) |
---|---|
取出(polling) | O(logn) |
查看(peeking) | O(1) |
添加(Adding) | O(logn) |