02、二叉堆、堆排序和优先队列

一、树

大于等于一个结点的有限节点,有层次关系的集合,满足下面三个条件:

  • 有且只有一个结点,没有父结点,称为根。
  • 除了根外,其他结点都都有且只有一个父节点。
  • 树中的每个结点都构成以它为根的数。

二、二叉树

在满足树的定义的前提下,还要满足:

  • 每个最多有两个孩子
  • 孩子有左右只分,不能颠倒

特性:

  1. 二叉树第i成上的结点数目最多为 2 i − 1 2^{i-1} 2i1(i >= 1)
  2. 深度为k的二叉树至多有 2 k − 1 2^k - 1 2k1个结点(k>=1)
  3. 包含n个结点的二叉树的高度至少为 log ⁡ 2 ( n + 1 ) \log_{2}{(n+1)} log2(n+1)
  4. 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(n00n22)(结点的,子树个数称为度)

三、完全二叉树

满足二叉树的前提下:

  • 除了最后一层都是满的。
  • 最后一层会从第一个结点开始,依次向后增加结点

线性特性,完全二叉树,如果按照层序遍历,可以线性索引;索引满足以下性质:

  1. 下标为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;

  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()

它可以假定我们事先不知道有多少个元素,又叫上浮操作,算法步骤如下:

  1. 首先增加堆的长度,在最末尾的地方加入最新插入的元素。
  2. 比较当前元素和它的父结点值,如果比父结点值大,则交换两个元素,否则返回。
  3. 重复步骤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
  2. 数组第1个元素,和堆尾,后面一元素交换。
  3. 执行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操作:

  1. 堆长度设置成和数组长度一样,数组make_heap为大堆顶。
  2. 堆长度减1
  3. 数组第1个元素,和堆尾,后面一元素交换。
  4. 执行heap_down
  5. 重复 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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值