目录
前言
一、堆的向上调整算法
二、堆的向下调整算法
三、优先队列模拟实现
Ⅰ、接口总览
Ⅱ、各个接口实现
1.构造函数
2.仿函数
3.向上调整
4.向下调整
5.其余接口
Ⅲ、完成代码
前言
上节内容我们简单的介绍了关于priority_queue的使用内容,我们明白了它的默认容器是vector,以及优先队列实际上默认就是个大堆等相关知识,那么接下来就来看看底层的模拟实现究竟是什么样的!但在此之前先简单介绍两个堆算法,向上调整和向下调整算法!
一、堆的向上调整算法
我们在数据结构中都知道堆在物理空间上是采用数组去存储的,但是呢在逻辑上我们可以将其看作一棵完全二叉树,形如:
以上这个是大堆,小堆反之,下面以大堆为例介绍向上调整算法!
向上调整:
①在大堆的末尾插入一个数据,然后和其父亲结点去比较!
②如果大于父亲结点,那就和父亲结点交换位置,并更新父亲结点,直到比父亲结点小;如果比父亲结点小,那就停止交换!此时就是大堆了!
小堆过程相反!!把小的向上调即可
注意:在数据结构的树与二叉树中提到过父亲结点和孩子结点的下标关系
左孩子=父亲*2+1;
右孩子=父亲*2+2;
例如,在上述堆中插入一个数据77。过程如下:
和其父亲结点比较发现,比父亲结点大,那就交换!
在去新的父节点比较,即和66相比,比它大,交换!
到这里就调整完毕了,此时就是个大堆了!
具体代码如下:
//建大堆
void AdjustUp(vector<int>& v1 int child)
{
int parent = (child - 1) / 2;//通过父子下标关系得出
while (child > 0)
{
if (v[child] > v[parent])
{
swap(v[child], v[parent]);//交换
child = parent;//更新孩子
parent = (child - 1) / 2;//更新父亲
}
//至此已成堆
else
{
break;
}
}
}
二、堆的向下调整算法
同样还是以大堆为例,进行向下调整,但是这里有个前提:一定要保证左右子树是一个大堆,才可以进行向下调整!建小堆,也是要保证左右子树都是小堆才可以!
向下调整:
①从堆顶向下,先选出当前父节点的左右孩子中的最大节点,然后再用当前节点去和最大节点的比较!
②如果父节点小于最大孩子节点,那就交换父子节点,并重新更新父子节点;如果大于最大节点,那就不能交换,此时就是大堆了!
小堆就是相反的,实际就是把大的向下调!大堆就是把小的向下调!!
例如,上图先找出左右孩子中的最大节点,即77作为最大孩子。22与77相比,22比77小,那就交换!
再重复上述步骤,因为只有33这个节点,并且22小于33,即父亲小于孩子,那就交换!
至此已经来到了末尾,交换结束,此时的结构就是大堆!!!!
具体实现代码如下:
void Adjustdown(vector<int>& a, int size, int parent)
{
int child = parent * 2 + 1;//左孩子
while (child<size)
{
//找左右孩子哪个大,把大给child
if (child + 1 < size && a[child + 1] > a[child])
{
child = child + 1;
}
//比较孩子和父亲
if (a[child] > a[parent])
{
swap(a[child], a[parent]);
parent=child;//更新父亲
child = parent * 2 + 1;//更新孩子
}
//至此已成大堆
else
{
break;
}
}
}
总结一下:向上调整就是拿孩子去比父亲,所以参数得是孩子的;向下调整实际就是拿父亲去比孩子,所以参数得父亲的下标!!
注意:实际应用中,大多数都是采用向下调整建堆,因为时间复杂度为O(N),而向上调整时间复杂度为O(N*logN);
三、优先队列模拟实现
有上面两个算法的铺垫,接下来的模拟实现就简单很多了!
Ⅰ、接口总览
#include<vector>
namespace Pq
{
//仿函数,控制比较方式
template <class T>
class less
{
public:
bool operator()(const T& x, const T& y);
};
template <class T>
class greater
{
public:
bool operator()(const T& x, const T& y);
};
template <class T, class Container = vector<T>, class Compare = less<T> >
class priority_queue
{
public:
//构造空队列
priority_queue();
//迭代器区间构造队列
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last);
void push(const T& x);
void pop();
bool empty() const;
size_t size() const;
const T& top() const;
private:
Container c;
Compare comp;
//向上调整算法
void AdjustUp(size_t child);
//向下调整算法
void Adjustdown(size_t parent);
};
};
注意:一样的,模拟实现,毕竟只是模拟,一定要记得在自己的空间里面去模拟哦!同时我们这里为了更真实的去模拟,我们将向上调整和向下调整设置为私有函数!!!因为平时去调用时,根本就看不见这两个函数,是吧哥们!
Ⅱ、各个接口实现
1.构造函数
- 构造空队列
//构造空队列
priority_queue()
:c()
{}
- 迭代器区间初始化
写法一:
//迭代器区间构造队列
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
while (first != last)
{
c.push_back(*first);
first++;
}
//插入数据应该要继续保持堆结构
//这里是将一堆已经存在的数据进行建堆
//向下调整建大堆时间复杂度更低
for (int i = (c.size() - 1 - 1) / 2; i >= 0; i--)
{
Adjustdown(i);
}
}
这样的写法实际和vector、list等模拟实现相类似,都是通过尾插操作去实现的!但是要注意一点,优先队列就是个堆结构,插入数据时应该要调整它的结构,前面也说过向下调整时间复杂度低,所以这里采用向下调整算法建堆,但是一定要注意向下调整是有前提的,必须要求左右子树都是一个大堆(或者小堆),因此我们应该从最后一个非叶子结点开始去调整,也就是最后一个父结点开始向下调整!!!!
写法二:
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last)
:c(first,last)
{
for (int i = (c.size() - 1 - 1) / 2; i >= 0; i--)
{
Adjustdown(i);
}
}
这个写法就是利用优先队列实际上是一个容器适配器,也就是说用别人的东西去创造自己,也就是它的成员变量实际上就是对应容器,相当与一个自定义类型,那么对于自定义类型,他就会去调用自己的构造函数完成初始化工作!
例如:当传进来的是vector容器时,优先队列里面的成员变量就是vector示例化出来的对象,对这个对象进行初始化工作,实际上就是在调用vector的默认成员函数完成构造!!!
2.仿函数
这里在前面的优先队列介绍中就有涉及,要注意一点仿函数可以控制比较逻辑,在优先队列的底层,大堆(less)实际上是用<比较,小堆(greater)实际上是用>比较!
//大堆,<比较
template <class T>
class less
{
public:
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
//小堆,>比较
template <class T>
class greater
{
public:
bool operator()(const T& x, const T& y)
{
return x > y;
}
};
3.向上调整
//向上调整算法(大堆为例)
void AdjustUp(size_t child)
{
size_t parent = (child - 1) / 2;
while (child > 0)
{
//if (_con[parent] < _con[child])
if (comp(c[parent], c[child]))
{
swap(c[parent], c[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
注意:整体逻辑和上面讲到的差不多,只不过这里的比较逻辑采用了仿函数,comp实际上是仿函数实例化出来的对象,在成员变量里面了!
4.向下调整
//向下调整算法(默认大堆)
void Adjustdown(size_t parent)
{
size_t child = 2 * parent + 1;
while (child < c.size())
{
if (child + 1 < c.size() && comp(c[child], c[child + 1]))//仿函数控制比较逻辑
{
child = child + 1;
}
//用仿函数
if (comp(c[parent], c[child]))
{
swap(c[parent], c[child]);
parent = child;
child = 2 * parent + 1;
}
//至此已成大堆
else
{
break;
}
}
}
5.其余接口
void push(const T& x)
{
c.push_back(x);
AdjustUp(c.size() - 1);//最后一个元素向上调整
}
void pop()
{
swap(c[0], c[c.size() - 1]);
c.pop_back();
//在使用向下调整堆结构
Adjustdown(0);
}
bool empty() const
{
return c.empty();
}
size_t size() const
{
return c.size();
}
const T& top() const
{
return c[0];
}
需要注意的是堆的删除操作(pop),它实际上就是先把堆顶元素与最后一个元素交换,然后在把最后一个元素不看做堆的元素,也就是删除,最后在采用向下调整堆结构!!
例如:
Ⅲ、完成代码
#pragma once
#include<vector>
namespace Pq
{
template <class T>
class less
{
public:
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
//建小堆,>比较
template <class T>
class greater
{
public:
bool operator()(const T& x, const T& y)
{
return x > y;
}
};
template <class T, class Container = vector<T>, class Compare = less<T> >
class priority_queue
{
public:
//构造空队列
priority_queue()
:c()
{
}
//迭代器区间构造队列
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
while (first != last)
{
c.push_back(*first);
first++;
}
for (int i = (c.size() - 1 - 1) / 2; i >= 0; i--)
{
Adjustdown(i);
}
}
void push(const T& x)
{
c.push_back(x);
AdjustUp(c.size() - 1);//最后一个元素向上调整
}
void pop()
{
swap(c[0], c[c.size() - 1]);
c.pop_back();
Adjustdown(0);
}
bool empty() const
{
return c.empty();
}
size_t size() const
{
return c.size();
}
const T& top() const
{
return c[0];
}
private:
Container c;
Compare comp;
void AdjustUp(size_t child)
{
size_t parent = (child - 1) / 2;
while (child > 0)
{
//if (_con[parent] < _con[child])
if (comp(c[parent], c[child]))
{
swap(c[parent], c[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void Adjustdown(size_t parent)
{
size_t child = 2 * parent + 1;
while (child < c.size())
{
if (child + 1 < c.size() && comp(c[child], c[child + 1]))
{
child = child + 1;
}
if (comp(c[parent], c[child]))
{
swap(c[parent], c[child]);
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
};
};
今天就分享到这里,如果对你有帮助,请多多支持,你的支持是我更新的动力!!