【初识C++】4.4STL-stack queue priority_queue

容器适配器

在这里插入图片描述
在这里插入图片描述

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;

}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白在进击

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值