C++ 之 【模拟实现 优先级队列】
目录
1.前提准备
2.完整模拟实现
2.1构造函数
2.2向下调整法
2.3 push 及 向上调整法
2.4 pop
2.5其他操作
3.自定义仿函数的需求
1.前提准备
(1)
创建一个PriorityQueue.h文件来保存模拟实现的优先级队列
同时,因为C++库里面已经有了优先级队列的实现,所以我们为了避免命名冲突
创建一个新的命名空间来封装模拟实现的优先级队列
(2)
与STL库保持一致,模拟实现时默认使用容器 vector 和比较器 less
同时将 堆 作为优先级队列的底层数据结构
namespace dfq1 {template<class T, class Container = vector<T>, class Compare = less<T>>class priority_queue{//....... private:Container _con;Compare _com; }; }
2.完整模拟实现
2.1构造函数
//默认构造:创建空的优先级队列priority_queue(){ }//区间创建优先级队列template<class InputIterator>priority_queue(InputIterator first, InputIterator last){//进数据while (first != last){_con.push_back(*first);++first;}//默认建大堆for (int i = (_con.size() - 1 - 1) / 2; i >= 0; --i){AdjustDown(i);}}
优先级队列拥有两个构造函数,因为我们显示写了区间创建优先级队列的构造函数,所以我们也需要手写无参的构造函数,这样在创建空的优先级队列时才会有默认构造函数被调用
默认构造函数会自动调用其成员的构造函数以完成初始化
区间创建优先级队列时,我们将迭代器作为模板参数,这样就可以做到类型通用
首先进数据,因为是vector作为底层容器,所以直接尾插 push_back
然后建堆,优先选择向下调整法建堆 (不懂概念的朋友可进入我的主页搜索 堆 进行了解)
2.2向下调整法
void AdjustDown(int parent){int child = parent * 2 + 1;//孩子存在就比较while (child < _con.size()){//判断左右孩子,左孩子比右孩子“”,++childif (child + 1 < _con.size() && _com(_con[child], _con[child + 1]))++child;//父亲比孩子“”,就交换.......if (_com(_con[parent], _con[child])){swap(_con[parent], _con[child]);parent = child;child = parent * 2 + 1;}else break;}
}
向下调整法的思想、代码逻辑这里不再赘诉 ,(不懂概念的朋友可进入我的主页搜索 堆 进行了解)
C语言中,根据建堆的需要,大小堆使用的向下调整法需要各写一份,此时C++传入比较器的野心就显现了,同一份代码,仅仅比较器不同就可以实例化出不同的向下调整法
建大堆时,
父亲节点需要孩子节点中的较大节点进行比较,左孩子小于右孩子,child++
parent 小于 child 此时就需要进行交换等一系列后续操作
所以 建大堆 用 less
建小堆时,
父亲节点parent需要孩子中的较小节点进行比较,左孩子大于右孩子,child++
parent 大于 child 此时就需要进行交换等一系列后续操作
所以 建小堆 用 greater
_com是函数对象(仿函数)的实例,其可以像普通函数一样进行使用
_con 默认是 vector<T> 的对象,即可以根据下标进行随机访问操作
_com(_con[child], _con[child + 1])
这里就是在比较_con[child] 与_con[child + 1] 的大小并返回bool值
2.3 push 及 向上调整法
void push(const T& val){//进数据_con.push_back(val);//向上调整AdjustUp(_con.size() - 1);
}
void AdjustUp(int child){int parent = (child - 1) / 2;//parent始终大于等于0while (child > 0){//父亲比孩子“”,就交换.......if (_com(_con[parent], _con[child])){swap(_con[parent], _con[child]);child = parent;parent = (child - 1) / 2;}else break;}
}
向上调整法的思想、代码逻辑这里不再赘诉 ,(不懂概念的朋友可进入我的主页搜索 堆 进行了解)
插入数据的思路就是先插入到尾部,再进行向上调整操作
大堆进行向上调整时
父亲节点小于孩子节点才进行交换等一系列后续操作,所以 大堆 用 less小堆进行向上调整时
父亲节点大于孩子节点才进行交换等一系列后续操作,所以 小于堆 用 greater
_com是函数对象(仿函数)的实例,其可以像普通函数一样进行使用
_con 默认是 vector<T> 的对象,即可以根据下标进行随机访问操作
_com(_con[parent], _con[child])
这里就是在比较_con[parent] 与_con[child] 的大小并返回bool值
2.4 pop
void pop()
{swap(_con[0], _con[_con.size() - 1]);//出数据_con.pop_back();//向下调整AdjustDown(0);
}
删除数据就是删除堆顶数据,思路就是首尾交换->尾删->向下调整
2.5其他操作
bool empty()const{return _con.empty();
}size_t size()const{return _con.size();
}T& top(){return _con[0];
}const T& top()const{return _con[0];
}
这些操作实现的是对适配容器的封装,底层实现时直接调用即可
3.自定义仿函数的需求
less、greater已经满足大多数优先级队列的创建要求,但还是有些例外,如下
class Date{
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}bool operator<(const Date& d)const{return (_year < d._year) ||(_year == d._year && _month < d._month) ||(_year == d._year && _month == d._month && _day < d._day);}bool operator>(const Date& d)const{return (_year > d._year) ||(_year == d._year && _month > d._month) ||(_year == d._year && _month == d._month && _day > d._day);}friend ostream& operator<<(ostream& _cout, const Date& d);
private:int _year;int _month;int _day;
};
ostream& operator<<(ostream& _cout, const Date& d){_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}
上述代码是自定义类型Date类
void test_Date(){priority_queue<Date> pq;pq.push(Date(2025, 8, 1));pq.push(Date(2025, 5, 1));pq.push(Date(2025, 9, 1));while (!pq.empty()){cout << pq.top() << ' ';pq.pop();}cout << endl;priority_queue<Date*> pq1;pq1.push(new Date(2025, 8, 1));pq1.push(new Date(2025, 5, 1));pq1.push(new Date(2025, 9, 1));while (!pq1.empty()){cout << *pq1.top() << ' ';pq1.pop();}
}
上述测试用例中,
pq存储的类型是Date,而Date类重载了>、<运算符,使得pq得以根据需要进行创建
pq1存储的类型是Date*,仿函数的对象实例只会根据地址大小创建优先级队列,不符合我们根据年月日大小进行建堆的期望
所以此时需要自定义仿函数
struct LessPDate{bool operator()(const Date* p1, const Date* p2){return *p1 < *p2;}
};priority_queue<Date*, vector<Date*>, LessPDate> pq1;
总结:
当优先级队列存储自定义类型时,该类型需重载
<
或>
运算符,以便默认的 less 、 greater仿函数能够正确比较元素若存储的是指针类型,由于指针的比较行为可能与需求不符(如默认按地址比较),此时需自定义仿函数来定义排序规则