C++ STL中的容器适配器实现

1 stack

1.1 stack 介绍

  • stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。
  • stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。
  • stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:empty:判空操作、back:获取尾部元素操作、push_back:尾部插入元素操作、pop_back:尾部删除元素操作
  • 标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器,默认情况下使用deque。

1.2 stack 使用

接口说明

1.3 stack 模拟实现

namespace czh
{
	template<class T, class Container = deque<T>>
	// template<class T, class Container = list<T>>
	// template<class T, class Container = vector<T>>
	class stack
	{
	public:
		void push(const T& x)
		{
			_con.push_back(x);
		}

		void pop()
		{
			_con.pop_back();
		}
        T& top()
		{
			return _con.back();
		}
		const T& top() const
		{
			return _con.back();
		}

		size_t size() const
		{
			return _con.size();
		}

		bool empty() const
		{
			return _con.empty();
		}

	private:
		Container _con;
	};
}

1.4 deque 的简单介绍

原理

1、deque(双端队列):是一种双开口的"连续"空间的数据结构,注意和 queue 没有关系双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高。
2、deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组

3、双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图所示:

缺陷

与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是比 vector 高的。

与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。

但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构。

为何作为stack 和 queue的 默认 实现

stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()pop_back()操作的线性结构,都可以作为stack的底层容器,比如vector和list都可以;queue是先进先出的特殊线性数据结构,只要具有push_backpop_front操作的线性结构,都可以作为queue的底层容器,比如list。

但是STL中对stack和queue默认选择deque作为其底层容器,主要是因为:

stack 和 queue 不需要遍历(因此stack 和 queue没有迭代器),只需要在固定的一段或者两端进行操作。

在 stack 中元素增长时,deque 比 vector 的效率高(扩容时候不需要搬移大量数据);queue 中的元素增长时,deque 不仅效率高,而且内存使用率也高。

所以结合了deque的优点,完美递避开了其缺陷

2 queue

2.1 queue 介绍

  • 队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。
  • 队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列。
  • 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作: empty:检测队列是否为空、size:返回队列中有效元素的个数、front:返回队头元素的引用、back:返回队尾元素的引用、push_back:在队列尾部入队列、pop_front:在队列头部出队列
  • 标准容器类deque和list满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque。

2.2 queue 使用

接口介绍

2.3 queue 模拟实现

因为queue的接口中存在头删和尾插,因此使用vector来封装效率太低,故可以借助list 和 deque 来模拟实现queue

namespace czh
{
	// 设计模式 -- 适配器模式(配接器)
	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();
		}
		const T& front() const
		{
			return _con.front();
		}
		T& back()
		{
			return _con.back();
		}
		const T& back() const
		{
			return _con.back();
		}

		size_t size() const
		{
			return _con.size();
		}

		bool empty() const
		{
			return _con.empty();
		}

	private:
		Container _con;
	};
}

3 priority_queue

3.1 priority_queue 介绍

优先级队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的那一个。
内部实现其实是堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先级队列中位于顶部的元素)。
底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:empty():检测容器是否为空、size():返回容器中有效元素个数、front():返回容器中第一个元素的引用、push_back():在容器尾部插入元素、pop_back():删除容器尾部元素
标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。
需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数,make_heap、push_heap和pop_heap来自动完成此操作。

3.2 priority_queue 使用

优先级队列默认使用 vector 作为其底层存储数据的容器,在 vector 上又使用了堆算法将 vector 中元素构成堆的结构,因此 priority_queue 就是堆,所以所有需要用到堆的位置,都可以考虑使用priority_queue。注意:默认priority_queue 是大堆。通过仿函数可以改变其为小堆。

#include <iostream>
#include <vector>
#include <queue>
#include <functional> // greater算法的头文件
void TestPriorityQueue()
{
 // 默认情况下,创建的是大堆,其底层按照小于号比较
 vector<int> v{3,2,7,6,0,4,1,9,8,5};
 priority_queue<int> q1;
 for (auto& e : v)
 q1.push(e);
 cout << q1.top() << endl;
 // 如果要创建小堆,将第三个模板参数换成greater比较方式
 priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());
 cout << q2.top() << endl; 

 }

3.3 仿函数的引入

仿函数准确来说是一个类,这个类重载了operator(),这个类的对象调用 operator(),可以像函数一样去使用,在优先级队列中的使用可以控制创建的优先级队列中是大堆还是小堆使其可以像水龙头的开关一样可以去控制热水还是凉水。在 STL库中中的两个仿函数的实现如下:

STL 中的优先级队列

举例说明仿函数的应用

比如我们想买个手机,在京东上搜索手机,可以按照价格、销量等标签排序,那么我们可以利用仿函数简单实现,写一个商品类代表手机我们用排序算法 sort 对其排序,但是我们不可以在类的内部重载 > < 运算符,因为我们并不知道如何排序,按照什么标准排序,为了更好说明问题,来盘代码。

#include <iostream>
#include "priority_queue.h"
#include <algorithm>
#include <vector>

using namespace std;

//仿函数的应用
struct Phone{
	int saleNum;
	int price;
	//.....
};
struct LessPhonePrice{
	bool operator()(const Phone& p1, const Phone& p2)
	{
		return p1.price < p2.price;
	}
};
struct LessPhoneSaleNum{
	bool operator()(const Phone& p1, const Phone& p2)
	{
		return p1.saleNum < p2.saleNum;
	}
};
void TestSort()
{
	vector <Phone> gv = { { 1, 3 }, { 5, 2 }, { 2, 10 } };
	sort(gv.begin(), gv.end(),LessPhoneSaleNum());//匿名对象会在STL中调用我自己写的比较方法
	sort(gv.begin(), gv.end(), LessPhonePrice());

}
int main()
{
	TestSort();
	return 0;
}

3.4 priority_queue 模拟实现

因为优先级队列的底层结构就是堆所以对 vector 进行适当封装就可以了,如果不知道堆的知识请参考我的另外一篇文章 https://www.jb51.net/article/210171.htm

#pragma once

//仿函数
template<class T>
class Less{
public:
	bool operator()(const T& t1, const T& t2) const
	{
		return t1 < t2;
	}
};

template<class T>
class Greater{
public:
	bool operator()(const T& t1, const T& t2) const
	{
		return t1 > t2;
	}
};
namespace czh{

	template<class T, class Container = vector<T>, class Compare = Greater<T>>
	class priority_queue {

	public:
		priority_queue() = default;
		/*priority_queue()
		{}*/

		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
			:_con(first, last)
		{
			//利用向下调整算法,从下到上建堆
			/*for (int i = (_con.size() - 2) / 2; i >= 0; i--)
			{
			AdjustDown(i);
			}*/
			利用向上调整算法,从上到下调整
			for (size_t i = 1; i < _con.size(); i++)
			{
				AdjustUp(i);
			}
		}
		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();
		}
		const T& top() const
		{
			return _con.front();
		}
		bool empty() const
		{
			return _con.empty();
		}
	private:
		//向上调整算法
		void AdjustUp(int child)
		{
			Compare com;
			int parent = (child - 1) >> 1;
			while (child > 0)
			{
				/*if (_con[child] > _con[parent])*/
				if (com(_con[child], _con[parent]))
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) >> 1;
				}
				else
				{
					break;
				}
			}
		}
		void AdjustDown(int parent)
		{
			Compare com;
			size_t child = 2 * parent + 1;
			while (child < _con.size())
			{
				/*if (child + 1 < _con.size() && _con[child + 1] < _con[child])*/
				if (child + 1 < _con.size() && com(_con[child + 1], _con[child]))
				{
					child++;
				}
				/*if (_con[child] > _con[parent])*/
				if (com(_con[child], _con[parent]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = 2 * parent + 1;
				}
				else
				{
					break;
				}
			}
		}
		//向下调整算法
		Container _con;
	};
}

4 容器适配器模式

适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口。
上面介绍的3种数据结构都是容器适配器(container adapter)。

到此这篇关于C++ STL中的容器适配器实现的文章就介绍到这了,更多相关C++ STL容器适配器内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++ STL容器stack和queue详解

    stack是一个比较简单的容器,它的使用也很简单,stack是LIFO容器,就是后进先出,最后添加进去的元素,第一个取出来 stack初始化 std::stack<int> first; std::stack<int> second(first); std::stack<int, std;:vector<int>> third; //使用vector初始化stack ### stack常用方法### empty();//判断是否为空 push(Elem e)

  • C++ STL入门教程(1) vector向量容器使用方法

    一.简介 Vectors 包含着一系列连续存储的元素,其行为和数组类似. 访问Vector中的任意元素或从末尾添加元素都可以在O(1)内完成,而查找特定值的元素所处的位置或是在Vector中插入元素则是O(N). 二.完整程序代码 /*请务必运行以下程序后对照阅读*/ #include <vector> #include <iostream> #include <algorithm> #include <stdexcept> using namespace

  • C++ STL array容器访问元素的几种方式

    当 array 容器创建完成之后,最常做的操作就是获取其中的元素,甚至有时还会通过循环结构获取多个元素.本节就对获取容器中元素的方法做个汇总. 访问array容器中单个元素 首先,可以通过容器名[]的方式直接访问和使用容器中的元素,这和 C++ 标准数组访问元素的方式相同,例如: values[4] = values[3] + 2.O*values[1]; 此行代码中,第 5 个元素的值被赋值为右边表达式的值.需要注意的是,使用如上这样方式,由于没有做任何边界检查,所以即便使用越界的索引值去访问

  • C++(STL库)之顺序容器vector的使用

    一.特点 ①总的来说:可变大小数组.支持快速随机访问.在尾部之外的位置插入或删除元素可能很慢 ②元素保存在连续的内存空间中,因此通过下标取值非常快 ③在容器中间位置添加或删除元素非常耗时 ④一旦内从重分配,和原vector相关的指针,引用,迭代器都失效.内存重分配耗时很长 二.头文件.using声明 头文件:#include <vector> using声明:using std::vector; 三.初始化 vector<T>  v1; ==>v1是一个空的vector ve

  • 深入解析C++ STL中的常用容器

    STL是C/C++开发中一个非常重要的模板,而其中定义的各种容器也是非常方便我们大家使用.下面,我们就浅谈某些常用的容器.这里我们不涉及容器的基本操作之类,只是要讨论一下各个容器其各自的特点.STL中的常用容器包括:顺序性容器(vector.deque.list).关联容器(map.set).容器适配器(queue.stac). 1.顺序性容器 (1)vectorvector是一种动态数组,在内存中具有连续的存储空间,支持快速随机访问.由于具有连续的存储空间,所以在插入和删除操作方面,效率比较慢

  • C++ STL关联式容器自定义排序规则的2种方法

    前面在讲解如何创建 map.multimap.set 以及 multiset 容器时,遗留了一个问题,即如何自定义关联式容器中的排序规则? 实际上,为关联式容器自定义排序规则的方法,已经在 <STL priority_queue自定义排序方法>一节中做了详细的讲解.换句话说,为 Priority_queue 容器适配器自定义排序规则的方法,同样适用于所有关联式容器. 总的来说,为关联式容器自定义排序规则,有以下 2 种方法. 1) 使用函数对象自定义排序规则 在掌握此方法之前,读者必须对函数对

  • c++ STL库容器之集合set代码实例

    set 简介 set是STL中一种标准关联容器,其键值就是实值,实值就是键值,不可以有重复,所以我们不能通过set的迭代器来改变set的元素的值.它底层使用平衡的搜索树--红黑树实现,插入删除操作时仅仅需要指针操作节点即可完成,不涉及到内存移动和拷贝,所以效率比较高.set,顾名思义是"集合"的意思,在set中元素都是唯一的,而且默认情况下会对元素自动进行升序排列,支持集合的交(set_intersection),差(set_difference) 并(set_union),对称差(s

  • C++语言 STL容器list总结

    在使用std::list<>链表时,难免会对数据进行添加删除操作.而遍历链表则有两种方式:通过索引访问,象数组一样处理:通过std::list<>::iterator链表遍历器进行访问 STL 中的list 就是一 双向链表,可高效地进行插入删除元素. list不支持随机访问.所以没有 at(pos)和operator[]. list 对象list1, list2 分别有元素list1(1,2,3),list2(4,5,6) .list< int>::iterator

  • c++ STL容器总结之:vertor与list的应用

    STL提供六大组件,彼此可以组合套用 1.容器(containers):各种数据结构,如vertor,list,deque,set,map.从实现的角度来看,STL容器是一种class template 2.算法(algorithms):各种算法如sort,search,copy,earse.STL算法是一种 function template. 3.迭代器(iterators):扮演容器与算法之间的胶合剂,是所谓的"泛型指针".所有STL容器都有自己的专属的迭代器. 4.仿函数(fu

  • C++ STL中的容器适配器实现

    1 stack 1.1 stack 介绍 stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作. stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出. stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:empty:判空操作.back:获取尾部元素操作.pus

  • C++深入分析STL中map容器的使用

    目录 1.map容器 2.map容器原理 3.map容器函数接口 4.使用示例 1.map容器 map是C++ STL的一个关联容器,它提供一对一的数据处理能力.其中,各个键值对的键和值可以是任意数据类型,包括 C++ 基本数据类型(int.double 等).使用结构体或类自定义的类型. 第一个可以称为关键字(key): 第二个可能称为该关键字的值(value): 该容器存储的都是 pair<const K, T> 类型(其中 K 和 T 分别表示键和值的数据类型)的键值对元素. 使用 ma

  • 关于STL中vector容器的一些总结

    1.vector的简单介绍 vector作为STL提供的标准容器之一,是经常要使用的,有很重要的地位,并且使用起来也是灰常方便.vector又被称为向量,vector可以形象的描述为长度可以动态改变的数组,功能和数组较为相似.实际上更专业的描述为:vector是一个多功能的,能够操作多种数据结构和算法的模板类和函数库,vector之所以被认为是一个容器,是因为它能够像容器一样存放各种类型的对象,简单地说,vector是一个能够存放任意类型的动态数组,能够增加和压缩数据.(注:STL的容器从实现的

  • 关于STL中set容器的一些总结

    1.关于set C++ STL 之所以得到广泛的赞誉,也被很多人使用,不只是提供了像vector, string, list等方便的容器,更重要的是STL封装了许多复杂的数据结构算法和大量常用数据结构操作.vector封装数组,list封装了链表,map和set封装了二叉树等,在封装这些数据结构的时候,STL按照程序员的使用习惯,以成员函数方式提供的常用操作,如:插入.排序.删除.查找等.让用户在STL使用过程中,并不会感到陌生. 关于set,必须说明的是set关联式容器.set作为一个容器也是

  • 关于STL中list容器的一些总结

    1.关于list容器 list是一种序列式容器.list容器完成的功能实际上和数据结构中的双向链表是极其相似的,list中的数据元素是通过链表指针串连成逻辑意义上的线性表,也就是list也具有链表的主要优点,即:在链表的任一位置进行元素的插入.删除操作都是快速的.list的实现大概是这样的:list的每个节点有三个域:前驱元素指针域.数据域和后继元素指针域.前驱元素指针域保存了前驱元素的首地址:数据域则是本节点的数据:后继元素指针域则保存了后继元素的首地址.其实,list和循环链表也有相似的地方

  • C++ STL中vector容器的使用

    目录 一.vector (1)区分size()和capacity() (2)迭代器失效 (3)区分const_iterator和constiterator (4)区分reserve()和resize() (5)push_back和emplace (6)关于原位构造(定位new+完美转发) 总结 一.vector (1)区分size()和capacity() size():返回容纳的元素个数 capacity():返回当前分配存储的容量 (2)迭代器失效 (3)区分const_iterator和c

  • 关于STL中的map容器的一些总结

    一.关于map的介绍 map是STL的一个容器,和set一样,map也是一种关联式容器.它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据处理能力,由于这个特性,有助于我们处理一对一数据.这里说下map内部数据的组织,map内部是自建一颗红黑树(一种非严格意义上的平衡二叉树),这颗树具有对数据自动排序的功能,所以在map内部所有的数据都是有序的.学习map我们一定要理解什么是一对一的数据映射?比如:一个班级中,每个学生的学号跟他的姓名就存

  • C++ STL容器适配器使用指南

    目录 适配器 stack容器适配器 ️stack的介绍 ️stack的使用 ️stack的模拟实现 queue ️queue的介绍 ️queue的使用 ️queue的模拟实现 deque容器 priority-queue ️priority-queue的使用 ️priority-queue的模拟实现 适配器 适配器是一种设计模式(设计模式是一套被反复使用的.多数人知晓的.经过分类编目的.代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口.例如: 容器适配器让一种已存在的容

随机推荐