程序员必备数据结构:堆
前言
自从写完了上一篇:程序员必备数据结构:栈之后,就一直盘算着写一篇“堆”,今天动手了。
堆,是什么?
二叉堆是完全二叉树或者是近似完全二叉树。
二叉堆满足二个特性:
1.父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
2.每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。
当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。当父结点的键值总是小于或等于任何一个子节点的键值时为最小堆。
下图用一个数组来表示堆:
由于其它几种堆(二项式堆,斐波纳契堆等)用的较少,一般将二叉堆就简称为堆。
二叉堆的应用
奈何我比较笨,网上五花八门的介绍,我就看出来三个字:堆排序。关于堆的一切应用,也都是基于堆排序的基础上衍生的,所以,本篇不废话,就围绕堆排序展开。
堆的插入
别问我怎么建堆,看完插入,自己去想,反正不是先排序,再建堆。
先看个图: 假设要在这个二叉堆里入队一个单元,键值为2,那么只需在数组末尾加入这个元素,然后尽可能把这个元素往上移动,直到挪不动,经过了这种复杂度O(logn)的操作,二叉堆还是二叉堆。
代码实现
#include<iostream>
#include<vector>
using namespace std;
void push_heap(vector<int>& vec, int num) {
vec.push_back(num);
int sz = vec.size()-1;
int parent = sz / 2 - 1;
while (vec[sz] < vec[parent]) {
vec[sz] ^= vec[parent];
vec[parent] ^= vec[sz];
vec[sz] ^= vec[parent];
sz = parent;
parent /= 2;
}
}
int main() {
vector<int> vec = {1,3,5,11,4,6,7,12,15,10,9,8};
push_heap(vec,2);
for (int i = 0; i < 13; i++) {
cout << vec[i]<<" ";
}
cout << endl;
return 0;
}
重新看这篇文章
在往下看之前,请诸君自行实现上面那个堆的插入与调整,因为我们接下来要进入源码了。
源码之前,了无秘密
向下调整算法
向下调整算法的说明:
*要建一个大堆,即最后每一个堆的节点的值都大于它的孩子
*我们先找左右孩子中最大的一个
*然后让最大的一个孩子和父节点进行比较:
如果孩子大于父节点的值,那么进行交换,并将孩子的值赋给父节点,孩子的值也随父节点的值变化,否则就结束。
*进行向下调整是从根节点开始的,因此,最终的大循环是孩子的值要小于数组存储的元素的值
代码实现(大堆)
//建堆
//建堆所用数组:vector<T> _a
Heap(T* a, size_t n)
:_a(a,a+n)
{
for(int i = (_a.size()-2)/2; i>=0; i--)
{
_Adjustdown(i);
}
}
//向下调整
void _Adjustdown(int root)
{
int parent = root;
int child = parent*2+1;
//此处的条件有两个:
//一个是当孩子的值小于父母的值时候这个已经在break处理过了
//第二个条件就是当是叶子节点的时候
while(child<_a.size())
{
//找左右孩子中值最大的
if(child+1<_a.size() && _a[child+1]>_a[child])
{
++child;
}
//将孩子和父母做比较
if(_a[child]>_a[parent])
{
swap(_a[child],_a[parent]);
parent = child;
child = parent*2+1;
}
else
{
break;
}
}
}
向上调整算法
和向下调整算法很相似
*直接让孩子和父节点进行比较
如果孩子比父节点大的话,就将父亲赋给孩子,然后父亲的值进行改变,一直向上调整,否则结束
*调整结束的另一个结束条件是当调整到最上面的堆顶的时候结束,即就是孩子的值<0
上面那个尾插算法就是个向上调整的。
代码实现(大堆)
//尾插
void Push(const T& x)
{
_a.push_back(x);
_Adjustup(_a.size()-1);
}
//向上调整
void _Adjustup(int i)
{
int child = i;
int parent = (i-1)/2;
while(child >= 0)
{
if(_a[child] > _a[parent])
{
swap(_a[child],_a[parent]);
child = parent;
parent = (parent-1)/2;
}
else
{
break;
}
}
}
堆顶元素删除
*先将要删除的堆顶的元素和最后一个元素进行交换
*然后删除尾部的元素
*再进行向下调整
代码实现
//尾删
void Pop()
{
swap(_a[0],_a[_a.size()-1]);
_a.pop_back();
_Adjustdown(0);
}
堆排序(大堆)
*如果我们是升序的话,要建大堆
*将第一个元素和最后一个元素进行交换,然后进行向下调整
*然后循环第二步,知道堆里剩一个元素,结束
代码实现
#include<iostream>
#include<vector>
using namespace std;
template<typename T>
class Heap
{
public:
//构造函数
Heap()
{}
//建堆
Heap(T* a, size_t n)
:_a(a,a+n)
{
for(int i = (_a.size()-2)/2; i>=0; i--)
{
_Adjustdown(i);
}
}
//堆排
void HeapSort()
{
//升序,建大堆
for(int i = (_a.size()-2)/2; i>=0; --i)
{
_Adjustdown(i);
}
int end = _a.size()-1;
while(end>0)
{
swap(_a[0],_a[_a.size()-1]);
_Adjustdown(end);
--end;
}
}
protected:
//向下调整
void _Adjustdown(int root)
{
int parent = root;
int child = parent*2+1;
//此处的条件有两个:
//一个是当孩子的值小于父母的值时候这个已经在break处理过了
//第二个条件就是当是叶子节点的时候
while(child<_a.size())
{
//找左右孩子中值最大的
if(child+1<_a.size() && _a[child+1]>_a[child])
{
++child;
}
//将孩子和父母做比较
if(_a[child]>_a[parent])
{
swap(_a[child],_a[parent]);
parent = child;
child = parent*2+1;
}
else
{
break;
}
}
}
//向上调整
void _Adjustup(int i)
{
int child = i;
int parent = (i-1)/2;
while(child >= 0)
{
if(_a[child] > _a[parent])
{
swap(_a[child],_a[parent]);
child = parent;
parent = (parent-1)/2;
}
else
{
break;
}
}
}
protected:
vector<T> _a;
};
- JavaScript中的数据类型
- Logistic回归算法及Python实现
- <script>元素在XHTML中的用法
- 有趣的算法(四)——一致性Hash算法模拟redis集群
- ASP.NET5 中静态文件的各种使用方式服务端的静态文件开启目录浏览呈现默认文件使用UseFileServer方法文件类型基于IIS的考虑最佳实践
- 使用ASP.NET Identity以手机短信实现双重验证创建一个ASP.NET 5项目运行应用程序使用SMS短信进行双重验证开启双重验证使用双重验证登陆应用程序禁用账户来防止暴力破解
- ASP.NET 5 之 错误诊断和它的中间件们配置错误处理页面在Development阶段使用错误页面运行时信息页面欢迎页面
- 有趣的算法(五) ——Dijkstra双栈四则运算
- CSS深入理解学习笔记之float
- 轻松初探 Python 篇(五)—dict 和 set 知识汇总
- 全面解析C#中的异步编程为什么要异步过去糟糕的体验一个新的方式Tasks基于任务的异步编程模型Async和await时间处理程序和无返回值的异步方法结束语
- CSS深入理解学习笔记之absolute
- 5个经典的JavaScript面试题
- 轻松初探 Python 篇(四)—list tuple range 知识汇总
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法