一:堆
堆数据结构是一种数组结构可以看做是一个完全二叉树;
堆的数据存储分为二种
大堆:每个父亲节点都大于孩子节点
小堆:每个父亲节点都小于汉子节点
接下来就是建堆,建堆实际就是讲一个二叉树调整成我们所需要的结果。
方法:从第一个非叶节点开始,找到它的最大值(大堆调整)或者是最小值(小堆调整),然后再与父节点比较;
我们通过仿函数,来选择我们需要的是大堆还是小堆。
template<class T>
class Less
{
public:
bool operator()(const T&a, const T&b)
{
return a < b;
}
};
template<class T>
class Greater
{
public:
bool operator()(const T&a, const T&b)
{
return a>b;
}
};
二:建堆:
调整分三步:
1:选出第一个非叶节点,选左孩子和右孩子的最大值,这里我们以大堆为例;
2:比较选出的孩子的大小与父亲比较,再交换,最后依次调整父亲和孩子的位置;
3:每个父亲节点都大于孩子节点则不用调整;
void _AjustDown(int root)
{
Compare comFuc;
int parent = root;
int child = parent * 2 + 1;//左孩子
while (child < _a.size())
{
//选较大的孩子
if (child + 1 < _a.size() && comFuc(_a[child + 1], _a[child]))
{
++child;
}
//比较父亲与孩子的大小
if (comFuc(_a[child], _a[parent]))
{
swap(_a[child], _a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
建堆的时间复杂度是O(
N*logN);
三:堆的插入:
前面我们知道堆可以抽象为完全二叉树,堆的插入主要影响当前节点到根节点的路径。所以只需对路径调整即可;
堆也是一种数组,对数组的插入可以参考vector的实现;
这里我们用向上调整算法,比较插入节点与父亲节点的大小,选择大的作为当前节点再与父亲节点比较,直到根节点;
//插入
void Push(const T&x)
{
_a.push_back(x);
_AjustUp(_a.size()-1);
}
//向上调整
void _AjustUp(int child)
{
assert(!_a.empty());
int parent = (child - 1) >> 1;
while (child>0)
{
//如果孩子节点的值大于父节点的值
Compare comFunc;
if (_a[child] > _a[parent])
{
swap(_a[child], _a[parent]);
child = parent;
parent = (child - 1) >> 1;
}
else
{
break;
}
}
}
插入算法的时间复杂度是O(logN )
四:堆的删除:
如果直接尾删,堆的顺序就会被打乱不能保证它还继续为一个小堆,那怎么解决呢?
其实就是把根节点和最后一个节点交换,再尾删这样就保证堆的结构不会被破坏。
void Pop()
{
assert(!_a.empty());
swap(_a[0], _a[_a.size() - 1]);//根节点和最后一个叶子节点交换
_a.pop_back();
_AjustDown(0);
}
删除的复杂度O(1);
五:测试和实现:
#pragma once
#include<iostream>
#include<vector>
#include<assert.h>
using namespace std;
template<class T>
class Less
{
public:
bool operator()(const T&a, const T&b)
{
return a < b;
}
};
template<class T>
class Greater
{
public:
bool operator()(const T&a, const T&b)
{
return a>b;
}
};
//大堆:任意一个节点是它子树的最大节点
template<class T,class Compare = Greater<T>>
class Heap
{
public:
Heap()
{}
Heap(T*a ,size_t n)
{
_a.reserve(n);//增容(可以直接拷贝数据)
for (size_t i = 0; i < n; i++)
{
_a.push_back(a[i]);
}
//调整成堆
for (int j =(_a.size()-2)/2; j >= 0; --j)
{
//向下调整
_AjustDown(j);
}
}
//插入
void Push(const T&x)
{
_a.push_back(x);
_AjustUp(_a.size()-1);
}
//删除
void Pop()
{
assert(!_a.empty());
swap(_a[0], _a[_a.size() - 1]);//根节点和最后一个叶子节点交换
_a.pop_back();
_AjustDown(0);
}
const T& Top()
{
assert(!_a.empty());
return _a[0];
}
size_t Size()
{
return _a.size();
}
bool Empty()
{
return _a.empty();
}
protected:
//插入算法,主要影响root路径,向上
void _AjustUp(int child)
{
assert(!_a.empty());
int parent = (child - 1) >> 1;
while (child>0)
{
//如果孩子节点的值大于父节点的值
Compare comFunc;
if (_a[child] > _a[parent])
{
swap(_a[child], _a[parent]);
child = parent;
parent = (child - 1) >> 1;
}
else
{
break;
}
}
}
protected:
//向下调整成堆
void _AjustDown(int root)
{
Compare comFuc;
int parent = root;
int child = parent * 2 + 1;//左孩子
while (child < _a.size())
{
//选较大的孩子
if (child + 1 < _a.size() && comFuc(_a[child + 1], _a[child]))
{
++child;
}
//比较父亲与孩子的大小
if (comFuc(_a[child], _a[parent]))
{
swap(_a[child], _a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
protected:
vector<T>_a;
};
void TestHeap()
{
int a1[] = {10, 16, 18, 12, 13, 15, 17, 14, 19 };
size_t len = sizeof(a1) / sizeof(a1[0]);
Heap<int,Greater<int>> h1(a1,len);
h1.Top();
h1.Push(25);
h1.Pop();
while (!h1.Empty())
{
cout << h1.Top() << " ";
h1.Pop();
}
cout << endl;
}