基本概念:
1、满二叉树:满二叉树是一种特殊的的完全二叉树,所有层的结点都是最大值。
2、一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树。
满二叉树由于其满的特点,无法插入和移除,业务上一般极少使用。
堆定义:
1、堆是一颗完全二叉树;
2、堆中的某个结点的值总是大于等于(最大堆)或小于等于(最小堆)其子结点的值。
3、堆中每个结点的子树都是堆树。
堆是一颗完全二叉树,但是二叉树不一定是一个堆。因为堆是具备排序特性。
堆不是一个完全有序的数组,但是他可以保证根节点是最大或者最小的节点。用来持续pop最大值或者最小值,比如众多寻路算法中不断取得权值最小的节点,一般就用最小堆来实现。
常规的树一般基于链表实现。
堆,由于其非叶子节点为满,叶子节点都集中在左侧。如下图
所以可以基于数组实现。
基于数组的堆实现,可以非常方便的获取某一个节点的父节点和子节点,所以节点里面无需存储父节点指针和子节点指针,这也是有别于链表实现的地方。
插入逻辑:尾部插入,尾部上升。
在数组的末尾添加元素(节点),表示其处于最右侧的叶子节点,然后依次与其父节点((index-1)/2)比较,不符合大小限定则交换,直至上升至根节点,即可完成插入。
删除逻辑:头部删除,交换尾部至头部,头部下降。
只可删除根节点(也就是数组的第一个,为该堆的最大(最小)值),首先取出根节点,待返回用,然后将数组最末尾节点也就是最右侧叶子几点交换至根节点,最后将该根节点与其两个孩子节点比较,交换至较小的叶子节点中。轮询或递归至叶子节点即可。
代码实现
struct Node{
int value_;
Node(int v){
value_ = v;
}
};
class sort_tree{ //min
public:
sort_tree(){}
bool push(int value);
shared_ptr<Node> pop();
bool empty(){return root.empty();}
void print();
private:
void up();
void down();
vector<shared_ptr<Node> > root;
};
void sort_tree::up()
{
int cur = root.size() -1;
while(cur >= 0){
int parent = (cur-1)/2;
if(parent < 0)break;
if(root[parent]->value_ > root[cur]->value_){
swap(root[parent], root[cur]);
cur = parent;
continue;
}
else{
break;
}
}
}
void sort_tree::down()
{
int cur = 0;
while(cur < root.size())
{
int left = cur*2+1;
int right = (cur+1)*2;
int min_child_idx = cur;
if(right < root.size()){
if(root[left]->value_ < root[right]->value_){
min_child_idx = left;
}else{
min_child_idx = right;
}
}else if(left < root.size()){
min_child_idx = left;
}
if(min_child_idx != cur){
if(root[cur]->value_ > root[min_child_idx]->value_){
swap(root[cur], root[min_child_idx]);
cur = min_child_idx;
continue;
}else{
break;
}
}
else{
break;
}
}
}
bool sort_tree::push(int value)
{
shared_ptr<Node> n = make_shared<Node>(value);
if(!n)
return false;
root.push_back(n);
up();
return true;
}
shared_ptr<Node> sort_tree::pop()
{
if(root.empty())return nullptr;
shared_ptr<Node> n = root[0];
root[0] = root[root.size()-1];
root.pop_back();
down();
return n;
}
void sort_tree::print(){
if(root.empty())return;
for(auto n : root){
printf("%d ", n->value_);
}
printf("\n");
}
void test(){
vector<int> v{10, 2, 3, 45,1, 11, 13};
sort_tree st;
for(auto value: v){
st.push(value);
}
st.print();
while(!st.empty()){
shared_ptr<Node> n = st.pop();
if(n){
printf("%d\n", n->value_);
}
}
}
运行结果:
./main
1 2 3 45 10 11 13
1
2
3
10
11
13
45
相比于快排等排序手法,对于固定个数的元素,平均时间开销上是不如的,但是比较稳定,插入最坏为O(nlog2n),而移除最小固定为O(nlog2n)。
空间效率为O(1)。