C++---stack & queue & 适配器模式 & 优先级队列 & 仿函数

目录

一、容器适配器

1、什么是适配器?

二、栈和队列的介绍和使用

1、stack的介绍

2、stack的使用

3、queue的介绍

4、queue的使用

三、deque的简单介绍(了解)

1、deque的原理

2、deque的缺陷

四、栈和队列的模拟实现

1、栈的模拟实现

2、队列的模拟实现

3、STL库中对stack和queue的实现

五、优先级队列的介绍和模拟实现

1、优先级队列的介绍和使用

2、优先级队列的模拟实现

六、仿函数


一、容器适配器

1、什么是适配器?

  • 设计模式(Design pattern):设计模式代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案,是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
  • 容器适配器:适配器是一种设计模式,该种模式是将一个类的接口转换成客户希望的另外一个接口。简单理解为一个类的底层实现调用了另一个类提供的接口。

二、栈和队列的介绍和使用

栈和队列都是容器适配器,它们的底层是通过调用其他容器类的接口进行实现的,在stack和queue模板类中都有一个缺省参数Container,缺省值为dequeue。

1、stack的介绍

  1. stack是一种容器适配器,专门用于在具有先进后出操作的需求的地方使用,stack只能从容器的一端进行插入和删除数据。
  2. stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些类应支持以下操作:empty、back、push_back、pop_back
  3. 标准的vector、deque、list均符合这些要求,默认情况下如果没有stack指定特定的容器则使用deque。

2、stack的使用

栈常用的接口说明

3、queue的介绍

  1. 队列是一种容器适配器,专门用于“先进先出”上下文中操作,只能从容器的一段插入另一端提取元素。
  2. 底层容器类可以标准容器类模板之一,也可以是其他专门设计的容器类,这些容器类需要有empty、size、front、back、push_back、pop_front接口
  3. queue需要从队头出数据,因此vector等线性数据结构不适合作为它的底层容器类,deque和list可以作为底层容器类。默认情况下如果没有为queue实例化指定容器类,则使用deque。

4、queue的使用

三、deque的简单介绍(了解)

1、deque的原理

deque(双端队列):它是一种两端都可以进行数据的插入和删除的容器且时间复杂度为O(1),与vector相比头插效率较高,与list相比可以支持随机访问且空间利用率高。

vector和list分别是线性结构和链式结构的典型代表,vector的特点是物理空间连续、支持随机访问,但是头删时需要移动数据时间复杂度较高;list物理空间不连续、可以随机插入删除操作且头和尾的插入和删除时间复杂度为O(1),但是它不支持随机访问。deque就是基于vector和list实现的一种不仅可以支持随机访问且头上的插入和删除时间复杂度为O(1)的数据结构。既然如此,为什么很少使用deque呢?

deque并不是真正的连续空间,而是由一段一段的空间拼接而成,类似于一个动态的二维数组,底层结构如下:

map是一个数组指针,存储的是一个个缓冲区的地址。开始map为空时,向map中插入数据会从map的中间位置找一块缓冲区进行插入;当需要进行头插时,从前一个位置的缓冲区插入数据;尾插时从优先向当前缓冲区插入,如果当前缓冲区数据已满则从后一个缓冲区插入数据。

deque迭代器底层实现:

2、deque的缺陷

  1. 与vector相比,deque的优势在于头部的插入和删除效率高,扩容时不需要大量移动数据(只需要对map进行扩容即可)。
  2. 与list相比,deque的优势在于底层空间是连续的,空间利用率较高且支持随机访问。
  3. deque的最大缺陷:不适合遍历---因为在遍历时需要频繁的检测是否移动到某段缓冲区空间的边界来回切换缓冲区,导致效率较低。
  4. deque中间插入删除数据时,效率比vector高,比list低。

四、栈和队列的模拟实现

1、栈的模拟实现

namespace bite
{
     template<class T>
     class stack
     {
     public:
         stack() {}
         //入栈
         void push(const T& x) 
         {
             _c.push_back(x);
         }
         //出栈
         void pop() 
         {
             _c.pop_back();
         }
         //获取栈顶元素
         T& top() 
         {
             return _c.back();
         }
         const T& top()const 
         {
             return _c.back();
         }
         //栈中元素个数
         size_t size()const 
         {
             return _c.size();
         }
         //判断栈空
         bool empty()const 
         {
             return _c.empty();
         }
    private:
         std::vector<T> _c;
    };
}

2、队列的模拟实现

namespace myQueue
{
    template<class T>
    class queue
    {
    public:
        //队列中的成员变量都是自定义类型,默认构造函数会调用自定义类型的默认构造函数
        queue() {}
        //入队
        void push(const T& x) 
        {
            _c.push_back(x);
        }
        //出队
        void pop() 
        {
            _c.pop_front();
        }
        //队尾元素
        T& back() 
        {
            return _c.back();
        }
        const T& back()const
        {
            return _c.back();
        }
        //队头元素
        T& front() 
        {
            return _c.front();
        }
        const T& front()const 
        {
            return _c.front();
        }
        //队列中元素个数
        size_t size()const 
        {
            return _c.size();
        }
        //判断队空
        bool empty()const 
        {
            return _c.empty();
        }
    private:
        std::list<T> _c;
    };
}

3、STL库中对stack和queue的实现

template<class T, class Con = deque<T>>
class stack
{
public:
    stack() {}
    void push(const T& x) 
    {
        _c.push_back(x);
    }
    void pop() 
    {
        _c.pop_back();
    }
    T& top() 
    {
        return _c.back();
    }
    const T& top()const 
    {
        return _c.back();
    }
    size_t size()const 
    {
        return _c.size();
    }
    bool empty()const 
    {
        return _c.empty();
    }
private:
    Con _c;
};

template<class T, class Con = deque<T>>
class queue
{
public:
    queue() {}
    void push(const T& x) 
    {
        _c.push_back(x);
    }
    void pop() 
    {
        _c.pop_front();
    }
    T& back() 
    {
        return _c.back();
    }
    const T& back()const 
    {
        return _c.back();
    }
    T& front() 
    {
        return _c.front();
    }
    const T& front()const 
    {
        return _c.front();
    }
    size_t size()const 
    {
        return _c.size();
    }
    bool empty()const 
    {
        return _c.empty();
    }
private:
    Con _c;
};

五、优先级队列的介绍和模拟实现

1、优先级队列的介绍和使用

  1. 优先级队列(priority_queue)是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大(最小)的。
  2. 优先级队列类似于堆,只能检索堆顶元素。
  3. 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:empty、size、push_back、pop_back、front
  4. 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。
  5. 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作。
优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。注意:默认情况下priority_queue是大堆。
函数声明
接口说明
priority_queue()/priority_queue(fifirst,
last)
构造一个空的优先级队列
empty( )
检测优先级队列是否为空,是返回 true ,否则返回
false
top( )
返回优先级队列中最大(最小元素),即堆顶元素
push(x)
在优先级队列中插入元素
pop ()
删除优先级队列中最大 ( 最小 ) 元素,即堆顶元素
 

 

 

 

 

 

 

 

 

注意:默认情况下,优先级队列是大堆;如果使用优先级队列存储自定义类型,则需要提供>和<的重载

2、优先级队列的模拟实现

优先级队列中,当插入一个数据时,需要保证插入后还是一个堆,因此在插入时需要使用向上调整算法对堆进行调整;当从优先级队列中删除一个数据时,需要先将堆顶和堆的最后一个元素交换删除最后一个元素在对堆进行调整。

namespace myPriorityQueue
{
	//仿函数
	template<class T>
	struct less
	{
		bool operator()(const T& left, const T& right)
		{
			return left < right;
		}
	};

	template<class T>
	struct greater
	{
		bool operator()(const T& left, const T& right)
		{
			return left > right;
		}
	};
	template<class T,class container = vector<T>,class compare = greater<T>>
	class priority_queue
	{
	public:
		//默认构造函数---看似每写任何东西,实际上对于自定义类型构造函数回去调用自定义类型的默认构造函数
		priority_queue(){}
		template<class Iterator>
		priority_queue(Iterator first, Iterator last)
			: _con(first, last)
		{
			int root;
			// 将_con中的元素调整成堆的结构
			//方法1---从上向下使用向上调整算法
			for (root = 0; root < _con.size(); root++)
				AdjustUP(root);
			//方法2---从下向上使用向下调整算法
			/*for (root = (_con.size() - 2) / 2; root >= 0; root--)
				AdjustDown(root);*/
		}
		//插入元素
		void push(const T& data)
		{
			//优先级队列类似与堆,插入数据时需要使用向上调整算法对调整成堆
			_con.push_back(data);
			AdjustUP(_con.size() - 1);
		}
		//删除元素
		void pop()
		{
			//堆删除元素时,将第一个和最后一个元素交换,删除最后一个元素,在堆数组使用向下调整算法调整成堆
			if (empty())
				return;
			swap(_con.front(), _con.back());
			_con.pop_back();
			AdjustDown(0);
		}
		//元素个数
		size_t size()const
		{
			return _con.size();
		}
		//判空
		bool empty()const
		{
			return _con.empty();
		}
		// 堆顶元素不允许修改,因为:堆顶元素修改可以会破坏堆的特性
		const T& top()const
		{
			return _con.front();
		}
		void test()
		{
			for (auto e : _con)
				cout << e << " ";
			cout << endl;
		}
	private:
		//向下调整算法
		void AdjustDown(int root)
		{
			int child = root * 2 + 1;
			while (child < _con.size())
			{
				// 找以parent为根的较大的孩子
				if (child + 1 < _con.size() && compare()(_con[child+1],_con[child]))
					child += 1;
				// 检测双亲是否满足情况
				if (compare()(_con[child], _con[root]))
				{
					swap(_con[child], _con[root]);
					root = child;
					child = root * 2 + 1;
				}
				else
					break;
			}
		}
		//向上调整算法
		void AdjustUP(int child)
		{
			//父子节点比较,如果子节点大于父节点则交换
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				if (compare()(_con[child], _con[parent]))
				{
					swap(_con[child], _con[parent]);
					//向下继续迭代
					child = parent;
					parent = (child - 1) / 2;
				}
				else
					break;
			}
		}
	private:
		container _con;
	};
}

在priority_queue的模拟实现中,有less和greater两个类来控制创建大堆还是创建小堆,这两个类就称为仿函数。

六、仿函数

仿函数是一个类,这个类重载了()操作符,该类可以向函数一样去调用。在C语言中,我们可以使用函数指针来解决priority_queue中创建大堆还是小堆的问题,在C++中可以使用仿函数来解决。

  • 14
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

疯狂嘚程序猿

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

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

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

打赏作者

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

抵扣说明:

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

余额充值