C++手搓完全二叉树和二叉堆
前言
本篇文章为笔者的读书笔记,未经允许请勿转载。如果对你有帮助记得点个赞(●’◡’●)
本文主要讲的完全二叉树和二叉堆的底层结构,以及二叉堆的数据测试;
后面还有make_heap和priority_queue之间的联系;
代码较长有兴趣的可以复制到自己电脑上运行一下;
源码如下:
stdafx.h
// stdafx.h : 标准系统包含文件的包含文件,
// 或是经常使用但不常更改的
// 特定于项目的包含文件
//
#pragma once
#include "targetver.h"
#include <stdio.h>
#include <tchar.h>
// TODO: 在此处引用程序需要的其他头文件
#include<iostream>
#include<string>
#include<functional>
#include<algorithm>
#include<random>
#include <ctime>
#include<queue>
#include "Heap.hpp"
#include "CompleTree.hpp"
using namespace std;
CompleTree.hpp
#pragma once
#include<vector>
#include<cassert>
template <class T>
class CompleTree
{
public:
CompleTree()
{
_vec.resize(1);//完全二叉树下标从1开始的
}
//添加子节点
void addChild(T&& val)
{
_vec.push_back(val);
}
//删除节点
void removeChild()
{
_vec.pop_back();
}
//获取根节点
T& root()
{
assert(_vec.size() > 1);//表达式为假就会出现异常,在release模式不会出现。推荐用异常
return _vec[1];
}
//获取左子树
T& left(size_t index)
{
assert(index * 2 < _vec.size());
return _vec[index * 2];
}
//获取右子树
T& right(size_t index)
{
assert(index * 2 + 1 < _vec.size());
return _vec[index * 2 + 1];
}
//获取父亲节点
T& parent(size_t index)
{
assert(index / 2 > 0);
return _vec[index / 2];
}
private:
std::vector<T> _vec;
};
Heap.hpp
#pragma once
#include<cassert>
#include<algorithm>
template <class T>
class Heap
{
public:
Heap(int length) :length(length + 1), count(1)//二叉堆底层完全二叉树,从下标为1的地方开始存储数据
{
data = new T[this->length]();
//T data[this->length] = { 0 }; 栈内存
//data = new T[this->length](); 堆内存
}
//拷贝构造,搞清楚调用者和实参
Heap(const Heap& heap)
{
this->count = heap.count;
this->length = heap.length;
this->data = new T[this->length]();
for (size_t i = 0; i < this->length;i++)
{
this->data[i] = heap.data[i];
}
}
//堆的当前长度
int size()
{
return cout - 1;
}
//判断是否满了
bool full()
{
return count >= length;
}
//判断是否为空
bool empty()
{
return count <= 1;
}
//插入数据
void insert(const T& val)
{
assert(!full());//表达式为假就会出现异常,在release模式不会出现。
data[count] = val;
shiftUp(count);
count++;
}
//获取堆顶元素
const T& root()
{
return data[1];
}
//取出堆顶元素
T extract()
{
assert(!empty());
T ret = root();//获取堆顶元素
count--;
std::swap(data[1], data[count]);
shiftDown();
return ret;
}
//验证
T* fun()
{
return data;
}
//析构
~Heap()
{
if (data != nullptr)
{
delete[] data;
data = nullptr;
cout << "data析构了" << endl;
}
}
private:
T* data;//声明并定义
int length;//容量
int count;//当前长度
//数据向上移动
void shiftUp(int k)
{ //最大堆
//循环的终点是到达根节点,子节点小于父亲节点
while (k > 1 && data[k] > data[k / 2])
{
std::swap(data[k], data[k / 2]);
k /= 2;//子节点下标到父亲节点位置
}
}
//数据向下移动
void shiftDown()
{
int parent = 1;//堆顶元素的下标为1
//左子树不能越界,左子树越界说明此时该节点没有子节点。所以把它当作循环的进入条件。
while (parent * 2 < count)
{
int child = parent * 2;
if (child + 1 < count&&data[child + 1] > data[child])//右子树不能越界,右子树大于左子树才会选择右子树交换。
{
child++;
}
//父亲节点比子节点大
if (data[parent] > data[child])
{
break;
}
else
{
std::swap(data[parent], data[child]);
parent = child;
}
}
}
};
main
// 5二叉堆.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
//完全二叉树,二叉堆的底层结构
void test()
{
CompleTree<string> comtree;
comtree.addChild("A");
comtree.addChild("B");
comtree.addChild("C");
comtree.addChild("D");
comtree.addChild("E");
comtree.addChild("F");
comtree.addChild("G");
cout << "根节点:" << comtree.root() << endl;
cout << "根节点的左子树:" << comtree.left(1) << endl;
cout << "index=2的右子树:" << comtree.right(2) << endl;
cout << "index=3的父亲节点:" << comtree.parent(3) << endl;
/*A
B C
D E F G*/
}
//手搓的二叉堆
void test1()
{
vector<int> vec({ 1,2,3,4,5,6,7 });
Heap<int> heap(7);
for (auto e:vec)
{
heap.insert(e);
}
//判断是否满了
cout << boolalpha<<heap.full() << endl;
//完成一个堆排序
while (!heap.empty())
{
cout << heap.extract() << " ";
}
cout << endl << "排序完后data里面的数据情况:" << endl;
for (size_t i = 0; i < vec.size() + 1; i++)
{
cout << heap.fun()[i] << " ";
}
cout << endl;
}
//make_heap
void test2()
{
//#include<algorithm>里面的heap;
//创建一个堆;
vector<int> vec({ 1,5,9,3,5,7,8,5,2,1,4,5,5 });
make_heap(vec.begin(), vec.end());
//插入一个数据
vec.push_back(10);
push_heap(vec.begin(),vec.end());
//删除一个数据
pop_heap(vec.begin(), vec.end());
vec.pop_back();
//堆排序
sort_heap(vec.begin(), vec.end());
for (auto e:vec)
{
cout << e << " ";
}
cout << endl;
}
//优先队列
void test3()
{
//#include<queue>里面的heap;
//priority_queue的底层是堆来实现的,其实就是封装make_heap
priority_queue<int> pq;
vector<int> vec({ 3, 6, 5, 4, 9, 8, 7, 1, 10, 23, 34 });
for (auto e:vec)
{
pq.push(e);
}
while (!pq.empty())
{
cout << pq.top() << " ";
pq.pop();//pop的源码,c是vector
/* void pop()
{ // erase highest-priority element
pop_heap(c.begin(), c.end(), comp);
c.pop_back();
}*/
}
cout << endl;
}
//实现一个代码从100,0000数据中取出前100名的数据。
void test4()
{
cout << "stl排序:" << endl;
sort_time(stl_sort);
cout << "heap排序:" << endl;
sort_time(heap_sort);
}
void sort_time(function<void(vector<int>)> sortFn)
{
vector<int> vec(1000000);
random_device r;
for (auto& e : vec)
{
e = r() % vec.size() + 1;
}
auto start = clock();
sortFn(vec);
auto end = clock();
cout << (float)(end - start) / 1000 << "s" << endl;
}
//stl的sort
void stl_sort(vector<int> vec)
{
sort(vec.rbegin(), vec.rend());
for (size_t i = 0; i < 100; i++)
{
cout << vec[i] << " ";
}
cout << endl;
}
//手搓的二叉堆排序
void heap_sort(vector<int> vec)
{
Heap<int> heap(vec.size());
for (const auto& e : vec)
{
heap.insert(e);
}
for (size_t i = 0; i < 100; i++)
{
cout << heap.extract() << " ";
}
cout << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
//test();
//test1();
//test2();
//test3();
//test4();
system("pause");
return 0;
}
测试结果
test:
根节点:A
根节点的左子树:B
index=2的右子树:E
index=3的父亲节点:A
test1:
true
7 6 5 4 3 2 1
排序完后data里面的数据情况:
0 1 2 3 4 5 6 7
data析构了
test2:
1 1 2 3 4 5 5 5 5 5 7 8 9
test3:
34 23 10 9 8 7 6 5 4 3 1
test4:
stl排序:
1000000 999998 999995 999995 999994 999992 999991 999991 999990 999990 999990 999989 999987 999984 999982 999982 999982 999982 999982 999981 999981 999980 999980 999979 999979 999978 999976 999974 999974 999973 999972 999972 999972 999971 999970 999970 999970 999968 999968 999967 999967 999967 999966 999965 999964 999961 999961 999961 999960 999960 999959 999958 999957 999955 999955 999954 999953 999953 999953 999951 999950 999947 999947 999946 999946 999945 999945 999943 999943 999943 999942 999942 999940 999940 999939 999939 999939 999939 999938 999936 999936 999936 999935 999934 999933 999932 999927 999927 999927 999926 999926 999924 999923 999922 999921 999920 999919 999918 999917 999916
93.525s
heap排序:
1000000 1000000 999999 999996 999994 999990 999989 999989 999986 999986 999986 999985 999985 999984 999983 999983 999982 999981 999980 999979 999979 999978 999976 999975 999975 999973 999972 999971 999971 999971 999971 999968 999966 999965 999961 999961 999960 999959 999958 999956 999956 999955 999954 999954 999954 999952 999952 999950 999950 999949 999949 999949 999948 999948 999947 999946 999946 999946 999945 999945 999944 999944 999943 999937 999937 999936 999934 999934 999934 999930 999929 999929 999928 999927 999927 999927 999926 999926 999923 999923 999922 999922 999921 999918 999917 999916 999915 999910 999910 999909 999908 999908 999907 999904 999904 999899 999899 999899 999898 999898
0.545s
如果不懂二叉堆的实现原理可以参考一下
可视化二叉堆
笔者写的二叉堆不是获取一个元素后就将其删掉,而是所有功能完成后在析构中全部释放;
优先队列是获取一个删一个;