文章目录
容器适配器
STL定义Stack和queue时,相比其他容器它们多加了一个缺省模板参数Container,这就是容器适配器。
在C语言的学习过程中,我们发现部分数据结构如栈、队列的结构并不唯一,他可能是链表也有可能是顺序表。为了使栈和队列的结构更加灵活,增加容器适配器,传什么结构那么得到的栈和队列就是什么结构。默认使用deque数据结构。当然我们自己模拟是,也可以改变默认初始化结构
template<class T, class Container = std::vector<T>>
class stack
{
};
显示定义stack结构
stack<int, vector<int>> st1;
Stack模拟实现
//stack.h
#pragma once
#include <iostream>
#include <vector>
#include <list>
using namespace std;
namespace clx
{
template<class T, class Container = std::vector<T>>
class stack
{
public:
//判空
bool empty() const
{
_con.empty();
}
//有多少个元素
size_t size() const
{
_con.size();
}
//栈顶的数据
const T& top() const
{
return _con.back();
}
//插入
void push(const T& x)
{
_con.push_back(x);
}
//删除
void pop()
{
_con.pop_back();
}
//交换栈
void swap(stack<T>& x)
{
swap(x);
}
private:
//成员变量
Container _con;
};
void stack_test();
}
void clx::stack_test()
{
stack<int> st;
st.push(1);
st.push(2);
st.push(3);
st.push(4);
while (!st.empty())
{
cout << st.top() << " ";
st.pop();
}
cout << endl;
}
queue模拟实现
#pragma once
#include <iostream>
#include <vector>
#include <deque>
using namespace std;
namespace clx
{
template<class T, class Container = deque<T>>
class queue
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_front();
}
T& front()
{
return _con.front();
}
T& back()
{
return _con.back();
}
size_t size()
{
return _con.size();
}
bool empty()
{
return _con.empty();
}
void swap(deque<T>& q)
{
swap(q);
}
private:
Container _con;
};
}
priority_queue介绍
priority_queue:优先级队列,其底层结构是一个堆,其并不是队列,因为其并不满足先进先出的规则,在优先级队列中,优先级最高的元素在队顶。
#include <iostream>
#include <vector>
#include <list>
#include <queue>
#include <stack>
using namespace std;
int main()
{
priority_queue<int> pq;
pq.push(1);
pq.push(2);
pq.push(3);
pq.push(4);
while(!pq.empty())
{
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
}
[clx@VM-20-6-centos queue]$ make
g++ -o test test.cpp -std=c++11
[clx@VM-20-6-centos queue]$ ./test
4 3 2 1
进入队列的顺序是1 2 3 4,但是出队列的顺序却是 4 3 2 1这是因为在STL优先级队列中默认数据大的优先级高,所以打印的顺序是 4 3 2 1
仿函数
在priority_queue的模板参数中Compare就是我们的仿函数。仿函数并非函数而是一个类,但是它可以像函数一样使用
接下来看看仿函数和函数的对比
struct my_compare //创建类
{
//()运算符重载(仿函数)
bool operator()(const int a, const int b)
{
return a > b;
}
};
bool compare(const int a, const int b)
{
return a > b;
}
int main()
{
my_compare com; //创建类对象
int a = 10;
int b = 30;
cout << com(a, b) << endl; //仿函数
cout << compare(a, b) << endl; //函数
return 0;
}
[clx@VM-20-6-centos lesson_8_9]$ ./test
0
0
我们发现仿函数和函数在调用的时候形式是一摸一样的,仿函数就是新建一个类来重载opeartor(),然后通过创建类对象调用operator()函数来达到直接调用函数的目的,那么为何要使用仿函数呢?,创建一个类对象来实现函数功能和直接实现又有什么不同?这些在priotity_queue的模拟实现中会一一讲解
priority_queue模拟实现
主要类函数和类成员
#pragma once
#include <iostream>
#include <vector>
using namespace std;
namespace clx
{
//仿函数用于判断大小
template<class T>
struct Less
{
bool operator()(const T& l, const T& r) //仿函数1
{
return l < r;
}
};
template<class T>
struct greater
{
bool operator()(const T& l, const T& r) //仿函数2
{
return l > r;
}
};
//T为存储的数据类型,容器适配器默认vector,实现仿函数需要的类
template<class T, class Container = vector<T>, class Compare = less<typename Container::value_type>>
class priority_queue
{
public:
//向上调整
void AdjustUp(size_t child);
//插入
void push(const T& x);
//向下调整
void AdjustDown(size_t parent);
//删除
void pop();
//读取栈顶元素
T& top();
//判空
bool empty();
private:
//成员变量是一个类型为Container的容器对象
Container _con;
};
}
push函数
在图示大堆的尾部插入45,此时我们要将尾结点的值和父结点的值进行比较,若满足child > parent,则需要向上调整,一直到子节点小于父节点时或子节点到达根节点处结束
template<class T, class Container = vector<T>, class Compare = less<typename Container::value_type>>
class priority_queue
{
public:
void AdjustUp(size_t child)
{
Compare com;
size_t parent = (child - 1) / 2; //找到父节点的位置
while (child > 0)
{
if (com(_con[parent], _con[child])) //比较子节点和父节点大小
{
swap(_con[parent], _con[child]); //交换子节点和父节点
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void push(const T& x)
{
_con.push_back(x); //首先先在队尾插入x
//向上调整
AdjustUp(_con.size() -1);
}
pop函数
如图这是一个大堆,当要删掉顶部结点时,若直接删掉,则必须从两个子节点中选出一个放上去,势必会破坏原树形结构。所以我们将尾部结点和头节点交换,然后让尾结点向下调整,保证删除头节点,并维持树形结构
pop();
交换头尾结点位置,删除尾结点
头节点向下调整(选取子节点中最小的,迅速到达底部)
void AdjustDown(size_t parent)
{
Compare com;
size_t child = parent * 2 + 1; //计算child结点下标
//判断child的结点是否还存在
while (child < _con.size())
{
//判断两个子节点大小,选择向下调整的方向
if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
{
child += 1;
}
if (com(_con[parent], _con[child]))
{
swap(_con[parent], _con[child]);
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
//向下调整
AdjustDown(0);
}
empty和top
T& top()
{
return _con[0];
}
bool empty()
{
return _con.empty();
}
小测试
void clx::priority_queue_test()
{
clx::priority_queue<int, vector<int>, greater<int>> pq1;
pq1.push(3);
pq1.push(5);
pq1.push(6);
pq1.push(1);
pq1.push(4);
clx::priority_queue<int, vector<int> ,less<int>> pq2;
pq2.push(3);
pq2.push(5);
pq2.push(6);
pq2.push(1);
pq2.push(4);
cout << "pq1" << endl;
while (!pq1.empty())
{
cout << pq1.top() << " ";
pq1.pop();
}
cout << endl;
cout << "pq2" << endl;
while (!pq2.empty())
{
cout << pq2.top() << " ";
pq2.pop();
}
cout << endl;
}
输出
[clx@VM-20-6-centos lesson_8_9]$ ./test
pq1
1 3 4 5 6
pq2
6 5 4 3 1
在小测试中,两个优先级队列插入相同的数据输出却不同,这是因为第三个模板参数导致的
template<class T, class Container = vector<T>, class Compare = less<typename Container::value_type>>
clx::priority_queue<int, vector<int>, greater<int>> pq1;
clx::priority_queue<int, vector<int> ,less<int>> pq2;
在类型实例化时,我们传入不同的Compare类,就可以在队列的成员函数中通过Compare类创建的对象,调用其operator()函数来进行大小判断,控制堆的排列。
当然我们可以直接使用>或<符号,但是这样我们就得写两个堆,使用模板可以自动生成。
if (com(_con[parent], _con[child])) //比较子节点和父节点大小
if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
仿函数和函数:
那为什么不用函数,因为函数指针过于复杂,在每次优先级队列实例化时我们要传函数指针
//compare函数
template<class T>
bool my_compare(const T& a, const T& b
{
return a < b;
}
//compare函数指针
template<class T>
bool (*Compare)(const T& a, const T& b) = my_compare;
//qriority_queue实例化
clx::priority_queue<int, vector<int>, greater<int>> pq1; //使用仿函数
clx::priority_queue<int, vector<int>, bool (*Compare)(const int& a, const int& b);//使用函数指针
我们发现函数指针非常的长,并且可读性很差,用起来很不方便,所以我们选择较为小巧便捷的类,对其进行函数重载,以获得函数的功能。
priority_queue源代码
#pragma once
#include <iostream>
#include <vector>
using namespace std;
namespace clx
{
template<class T>
struct Less
{
bool operator()(const T& l, const T& r)
{
return l < r;
}
};
template<class T>
struct greater
{
bool operator()(const T& l, const T& r)
{
return l > r;
}
};
template<class T, class Container = vector<T>, class Compare = less<typename Container::value_type>>
class priority_queue
{
public:
void AdjustUp(size_t child)
{
Compare com;
size_t parent = (child - 1) / 2;
while (child > 0)
{
if (com(_con[parent], _con[child]))
{
swap(_con[parent], _con[child]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void push(const T& x)
{
_con.push_back(x);
//向上调整
AdjustUp(_con.size() -1);
}
void AdjustDown(size_t parent)
{
Compare com;
size_t child = parent * 2 + 1;
while (child < _con.size())
{
if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
{
child += 1;
}
if (com(_con[parent], _con[child]))
{
swap(_con[parent], _con[child]);
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
void pop()
{
swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
//向下调整
AdjustDown(0);
}
T& top()
{
return _con[0];
}
bool empty()
{
return _con.empty();
}
private:
Container _con;
};
void priority_queue_test();
}
void clx::priority_queue_test()
{
clx::priority_queue<int, vector<int>, greater<int>> pq1;
pq1.push(3);
pq1.push(5);
pq1.push(6);
pq1.push(1);
pq1.push(4);
clx::priority_queue<int, vector<int> ,less<int>> pq2;
pq2.push(3);
pq2.push(5);
pq2.push(6);
pq2.push(1);
pq2.push(4);
cout << "pq1" << endl;
while (!pq1.empty())
{
cout << pq1.top() << " ";
pq1.pop();
}
cout << endl;
cout << "pq2" << endl;
while (!pq2.empty())
{
cout << pq2.top() << " ";
pq2.pop();
}
cout << endl;
}