目录
priority_queue初步介绍
priority_queue的性质是:每个数据都会携带一个优先级,优先级较高的数据会优先出队列
这里的优先级可以是数据的大小或者其他,总之这个优先级一定要是可以比较大小的东西
这里的优先级队列的实现方式我们采用堆的方式!
堆
堆是一种特殊的二叉树
堆一般分为大根堆和小根堆
大根堆:每一个根节点都比其左右子树所对应的值要大
如下图所示为一个大根堆
小根堆:每一个根节点都比其左右子树所对应的值要小
如下图所示为一个小根堆
一般存储堆我们是通过vector/deque来存储的
原因是vector/deque可以减少结点指针的消耗,并且也能满足堆的需求
那么我们用vector存储堆的话如何通过父节点找到子节点呢?子节点又怎么找到父节点呢?看下面的三个式子
父节点的下标=(子结点的下标-1)/2
左孩子的下标=父节点的下标*2+1
右孩子的下标=父节点的下标*2+1
有了以上的式子我们就可以自己来实现一个堆
堆的模拟实现
我们先模拟实现一个小根,后面再来进行修改成泛型
template<class T>
class Heap
{
public:
//接口实现
private:
vector<int> _heap;
};
接口实现
插入(push)
如图所示为一个小根堆
此时如果我们插入一个数,会出现两种情况
第一种:比父节点要大,此时没有破坏这个小根堆的性质,不做处理
第二种:比父节点要小,此时破坏了这个小根堆的性质,要做处理
这里的处理指的是实现一个向上排序算法
不论如何,我们先把数据插入进去再说,直接调用容器的插入即可
void push(const T& x)
{
_heap.push_back(x);
AdJustUp(_heap.size()-1);
//因为我们是vector,所以直接传一个下标过去,找到对应要调整的新插入的数据
}
AdJustUp(向上排序)接口
思路:我们先把要调整的结点和他的父节点相比
如果小于父节点则两个数进行交换(循环控制)
如果大于等于父节点/父节点不存在则循环结束跳出循环
代码实现:
void AdJustUp(size_t n)
{
size_t child = n;
size_t parent = (child-1)/2;
while(child)//当child走到根(下标0)的位置就结束
{
//判断大小
if(_heap[child] > _heap[parent])
{
//走到此处的话,此时的_heap符合堆的特性
break;
}
//走到此处的话,_heap不符合堆的特性
std::swap(_heap[child],_heap[parent]);
//更新child、parent再判断
child = parent;
parent = (child-1)/2;
}
}
删除(pop)
当我们删除的时候,是不能直接删除根结点的,如果我们直接把根节点删了,那么树就分为了两个,此时不仅堆的性质被破坏了,而且还是不可逆的
当我们删除的时候规则是:
交换堆顶和最后一个数据
此时我们把删除堆顶的数据转化为删除堆底的数据
此时再对根节点进行处理,让结构继续保持堆的性质
这里的处理指的是AdJustDown(向下排序算法)
void pop()
{
std::swap(_heap[0],_heap[_heap.size()-1]);
//删堆底
_heap.pop_back();
AdJustDown();
}
AdJustDown(向下排序算法)
思路:首先,在我们交换完堆顶元素和堆底元素后,对于发生改变的只有根结点和最后一个结点
但最后一个结点已经被删了(不用管了),那么发生了改变的就只有堆顶的元素
须知,在我们没有交换元素之前,这个容器中一定是堆。
交换元素后,根结点的左右子树也一定是堆
此时我们把根节点的元素放到合适的位置,那么这棵树就会又变成一个堆
小根堆为例:
我们先选出左子树和右子树中最小的那个记为child,根节点记为parent
如果child下标结点 > parent下标结点,那么此时树就是一个小根堆,跳出循环
反之,树还不是一个小根堆,再把child和parent进行交换
再把parent赋为child,更新child
循环继续
void AdJustDown()
{
size_t parent = 0;
size_t child = 2*parent + 1;
while(_heap.size() && child < _heap.size())
{
if(_heap[child] > _heap[child]+1)
{
//走到此处说明右孩子的值比左孩子要小,更新child
child++;
}
if(_heap[parent] < _heap[child])
{
//走到此处说明此时树是一个堆
break;
}
//走到此处,说明树还不是一个堆
std::swap(_heap[child],_heap[parent]);
parent = child;
child = 2*parent+1;
}
}
适配器堆总结
在我们完成了堆的插入和删除后,基本上其他接口与stack和queue一样直接调用底层容器的接口
在栈和队列中,我们讲过适配器的概念,而堆其实也可以理解为一个适配器
而优先级队列的底层是一个堆来实现的,那么优先级队列也可以理解为适配器
注意:容器要适配出一个堆/优先级队列首先就要实现有[]的重载
template<class T,class Container = vector<T>>
class Heap
{
private:
void AdJustUp(size_t n)
{
size_t child = n;
size_t parent = (child-1)/2;
while(child)//当child走到根(下标0)的位置就结束
{
//判断大小
if(_heap[child] > _heap[parent])
{
//走到此处的话,此时的_heap符合堆的特性
break;
}
//走到此处的话,_heap不符合堆的特性
std::swap(_heap[child],_heap[parent]);
//更新child、parent再判断
child = parent;
parent = (child-1)/2;
}
}
void AdJustDown()
{
size_t parent = 0;
size_t child = 2*parent + 1;
while(_heap.size() && child < _heap.size())
{
if(_heap[child] > _heap[child]+1)
{
//走到此处说明右孩子的值比左孩子要小,更新child
child++;
}
if(_heap[parent] < _heap[child])
{
//走到此处说明此时树是一个堆
break;
}
//走到此处,说明树还不是一个堆
std::swap(_heap[child],_heap[parent]);
parent = child;
child = 2*parent+1;
}
}
public
void push(const T& x)
{
void push(const T& x)
_heap.push_back(x);
AdJustUp(_heap.size()-1);
}
void pop()
{
std::swap(_heap[0],_heap[_heap.size()-1]);
_heap.pop_back();
AdJustDown();
}
size_t size()const
{
return _heap.size();
}
const T& top()const
{
return _heap[0];
}
T& top()
{
return _heap[0];
}
bool empty()
{
return _heap.empty();
}
private:
Container _heap;
};
仿函数
仿函数是用一个类来封装,并且把这个类的运算符“()”进行重载,由于调用形式酷似函数的调用,故而称为仿函数
下面我们用代码实现一个仿函数
template<class T>
struct MyLess
{
bool operator()(const T& x,const T& y)
{
return x < y;
}
};
int main()
{
//因为仿函数是对特定类的()进行重载,所以我们需要先实例化出一个对象
MyLess<int> com;
int x = 1;
int y = 2;
std::cout << com(x,y) << std::endl;
//输出1
return 0;
}
用仿函数对堆进一步封装
在堆中,通常大根堆还是小根堆是由向上/向下排序的值比较规则确定的
那么我们可以使用模板来接收这个规则的仿函数
如果我们使用模板来接收一个仿函数,那么这个比较规则就可以由使用者自由控制,也就提高了容器的泛型化
template<class T>
struct MyLess
{
bool operator()(const T& x,const T& y)
{
return x < y;
}
};
//compare接收的是一个仿函数
template<class T,class Container = vector<T>,class compare = MyLess<T>>
class Heap
{
private:
void AdJustUp(size_t n)
{
compare com;
//实例化仿函数对象
size_t child = n;
size_t parent = (child-1)/2;
while(child)//当child走到根(下标0)的位置就结束
{
//判断大小
if(com(_heap[child],_heap[parent]))
{
//走到此处的话,此时的_heap符合堆的特性
break;
}
std::swap(_heap[child],_heap[parent]);
child = parent;
parent = (child-1)/2;
}
}
void AdJustDown()
{
compare com;
//实例化一个仿函数对象
size_t parent = 0;
size_t child = 2*parent + 1;
while(_heap.size() && child < _heap.size())
{
if(child+1 < _heap.size() && com(_heap[child],_heap[child+1]))
{
//走到此处说明右孩子的值比左孩子要小,更新child
child++;
}
if(com(_heap[child],_heap[parent]))
{
//走到此处说明此时树是一个堆
break;
}
//走到此处,说明树还不是一个堆
std::swap(_heap[child],_heap[parent]);
parent = child;
child = 2*parent+1;
}
}
public
void push(const T& x)
{
_heap.push_back(x);
AdJustUp(_heap.size()-1);
}
void pop()
{
std::swap(_heap[0],_heap[_heap.size()-1]);
_heap.pop_back();
AdJustDown();
}
size_t size()const
{
return _heap.size();
}
const T& top()const
{
return _heap[0];
}
T& top()
{
return _heap[0];
}
bool empty()
{
return _heap.empty();
}
private:
Container _heap;
};
用堆进行封装优先级队列
优先级队列的底层就是堆,所以我们直接用堆的接口进行封装优先级队列
template<class T , class Container=vector<T> , class compare = std::less<T>>
class PriorityQueue
{
public:
bool empty()
{
_con.empty();
}
const T& top()const
{
return _con.top();
}
T& top()
{
return _con.top();
}
void push()
{
_con.push();
}
void pop()
{
_con.pop();
}
size_t size()const
{
return _con.size();
}
private:
Heap<T,Container,compare> _con;
};
代码总结
template<class T>
struct MyLess
{
bool operator()(const T& x,const T& y)
{
return x < y;
}
};
//堆
//compare接收的是一个仿函数
template<class T,class Container = vector<T>,class compare>
class Heap
{
private:
void AdJustUp(size_t n)
{
compare com;
//实例化仿函数对象
size_t child = n;
size_t parent = (child-1)/2;
while(child)//当child走到根(下标0)的位置就结束
{
//判断大小
if(com(_heap[child],_heap[parent]))
{
//走到此处的话,此时的_heap符合堆的特性
break;
}
std::swap(_heap[child],_heap[parent]);
child = parent;
parent = (child-1)/2;
}
}
void AdJustDown()
{
compare com;
//实例化一个仿函数对象
size_t parent = 0;
size_t child = 2*parent + 1;
while(_heap.size() && child < _heap.size())
{
if(child+1 < _heap.size() && com(_heap[child],_heap[child+1]))
{
//走到此处说明右孩子的值比左孩子要小,更新child
child++;
}
if(com(_heap[child],_heap[parent]))
{
//走到此处说明此时树是一个堆
break;
}
//走到此处,说明树还不是一个堆
std::swap(_heap[child],_heap[parent]);
parent = child;
child = 2*parent+1;
}
}
public
void push(const T& x)
{
_heap.push_back(x);
AdJustUp(_heap.size()-1);
}
void pop()
{
std::swap(_heap[0],_heap[_heap.size()-1]);
_heap.pop_back();
AdJustDown();
}
size_t size()const
{
return _heap.size();
}
const T& top()const
{
return _heap[0];
}
T& top()
{
return _heap[0];
}
bool empty()
{
return _heap.empty();
}
private:
Container _heap;
};
template<class T , class Container=vector<T> , class compare = MyLess<T>>
class PriorityQueue
{
public:
bool empty()
{
_con.empty();
}
const T& top()const
{
return _con.top();
}
T& top()
{
return _con.top();
}
void push()
{
_con.push();
}
void pop()
{
_con.pop();
}
size_t size()const
{
return _con.size();
}
private:
Heap<T,Container,compare> _con;
};
那么这期优先级队列的模拟实现就到这了,感谢大家的支持