IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 【STL】stack、queue、priority_queue模拟实现 -> 正文阅读

[系统运维]【STL】stack、queue、priority_queue模拟实现

一. deque简单介绍

1.1 deque的功能介绍

deque(双端队列):是一种双开口的连续空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要挪动元素;与list比较,空间利用率比较高。
在这里插入图片描述

1.2 deque的本体框架

虽说是连续性存储空间,但这种连续性只是表面上的,实际上它在堆上分配了一块一块的动态储存区,叫做缓冲区(buff),每一块缓冲区本身是连续的,deque自身的机制把这一块一块的缓冲区虚拟地连在一起,即把它们的首元素地址存在一个指针数组map里,注意这里的map不是STL里的map容器,而是deque的中控器,是个指针数组。

template<class T>                                                                                                             
class deque     
{    
  protected:    
  iterator start; // 指向指针数组第一个元素的迭代器    
  iterator finish;// 指向指针数组最后一个元素的迭代器    
  T** map;        // 中控的指针数组    
  size_t map_size;// 指针数组大小    
};        

为了满足头尾的插入删除,一开始的buff会放到中间位置,这样如果有需要的话,往前往后都可以存buff。当map指向的这块空间不够存放内存指针的时候,就会另觅一块更大的连续性空间,然后把指针一个一个复制过去,并销毁旧的空间。利用这种数据结构,deque就能方便地模拟自身的存储区是连续性空间的假象,并且可以实现双向插入删除的功能。
在这里插入图片描述

1.3 deque的迭代器框架

迭代器就是模拟指针的操作,比如解引用得到具体的数据,箭头访问内部成员变量,自增、自减等等操作。那么deque的迭代器应该具备怎么结构?我们可以考虑以下几点。首先,需要知道该元素位于哪个buff的哪个位置;其次,一旦迭代器前进和后退有可能会跳跃至上一个或下一个缓冲区,为了判断跳跃的条件就需要知道,当前元素所在buff的首尾指针。最后,如果前进或后退必须跳跃至下一个或上一个缓冲区。为了能够正确跳跃,deque必须随时掌握管控中心(map),通过map可以知道跳跃的缓冲区。 所以在迭代器中需要定义如下参数:

template<class T, class Ref, class Ptr, size_t N>    
struct _deque_iterator     
{    
  T* cur;  //指向buff里面当前数据的位置    
  T* first;//buff第一个位置指针    
  T* last; //buff最后一个位置指针    
    
  T** node;//map中控中当前buff的地址    
} 

迭代器和中控器之间的关系如下图所示
在这里插入图片描述

1.2 deque的优缺点

优点

  • 与vector相比:头部和中间的插入删除不需要大量挪动数据,时间复杂的为O(1);扩容时也不需要大量挪动数据。
  • 与list相比:空间利用率较高,内存碎片少,不是需要一个才开辟一个。

缺点
不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下。

总结
因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构。


二. 容器适配器

2.1 什么是适配器

适配器是一种设计模式,该种模式是将一个类进行封装,利用原来类的接口转换成客户希望的另外一个接口。

2.2 STL标准库中的stack和queue底层结构

虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器适配器,这是因为stack和队列只是对其他容器的接口进行了包装,其中STL中stack和queue默认容器使用deque。
在这里插入图片描述
在这里插入图片描述

2.3 stack和queue的模拟实现

2.3.1 stack容器剖析

两者的实现原理一样,下面我们主要分析stack的实现

stack要做到存储不同类型数据,那就应该是个类模板,模板参数是要存储的数据的类型和底层容器的种类。
在这里插入图片描述

注意:容器也是可以作为模板参数的,因为模板作用就是处理不同类型但逻辑相同的问题,我们对传入的不同类型统一管理,像vector,list等容器算自定义类型,我们也可以作为模板参数传入。

stack的框架
成员变量只有一个,就是底层的容器的一个实例化对象

// 这里我们的底层容器默认缺省是deque
template<class T, class Container = deque<T>>
class stack
{
public:
	// 构造函数
	stack()
	     // 调用容器的无参的默认构造函数
		:_con()
	{}

private:
	// 底层结构,即容器对象
	Container _con;
};

入栈
只能栈顶入数据,就是deque的尾插,对应调用底层容器deque的push_back()

PS:直接调用就行,不用考虑空间不够增容的问题,这些归底层容器deque管

// 入栈
void push(const T& data)
{
	_con.push_back(data);
}

出栈
从栈顶出,就是deque的尾删,封装deque的back_pop()

// 出栈
void pop()
{
	_con.pop_back();
}

获取栈顶元素
就是获取deque的最后一个元素,这里要注意不同stack对象调用对应的返回值不同,所以有下面两种写法:const的栈对象返回const的值,非const的栈对象返回非const的值。

// 普通对象获取栈顶元素
T& top()
{
	return _con.back();
}

// const对象获取栈顶元素
const T& top()const
{
	return _con.back();
}

stack的完整模拟实现

template<class T,class Container=deque<T>>
class stack
{
public:
	// 构造函数
	stack()
		:_con()
	{}

	// 入栈
	void push(const T& data)
	{
		_con.push_back(data);
	}

	// 出栈
	void pop()
	{
		_con.pop_back();
	}

	// 判空
	bool empty()
	{
		return _con.empty();
	}

	// 长度
	size_t size()
	{
		return _con.size();
	}

	// 普通对象获取栈顶元素
	T& top()
	{
		return _con.back();
	}

	// const对象获取栈顶元素
	const T& top()const
	{
		return _con.back();
	}

private:
	// 底层容器
	Container _con;
};

2.3.2 queue的完整模拟实现

与stack大同小异,由于两者的特性不同(stack是后进先出,queue是先进先出),对应容器调用的接口也就不一样。

template<class T,class Container=deque<T>>
class queue
{
public:
	// 构造函数
	queue()
		:_con()
	{}

	// 队列尾部插入元素
	void push(const T& data)
	{
		_con.push_back(data);
	}

	// 队列头部出元素
	void pop()
	{
		_con.pop_front();
	}

	// 返回队列当前元素个数
	size_t size()
	{
		return _con.size();
	}

	// 判断队列是否为空
	bool empty()
	{
		return _con.empty();
	}

	// 普通对象直接返回对头元素的引用(可修改)
	T& front()
	{
		return _con.front();
	}

	// const对象返回对偷的元素不能修改
	const T& front()const
	{
		return _con.front();
	}

	// 普通对象直接返回对尾元素的引用(可修改)
	T& back()
	{
		return _con.back();
	}

	// const对象返回对尾的元素不能修改
	const T& back()const
	{
		return _con.back();
	}

private:
	Container _con;
};

三. priority_queue

3.1 priority_queue介绍

  1. 优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆。所有需要用到堆的场景,都可以考虑使用priority_queue(注意默认情况下priority_queue是大堆
  2. 它的接口和栈的接口类似
    push():数据进堆
    pop():删除堆顶数据
    top():拿到堆顶数据
    empty():堆是否为空?为空的话返回true,不空返回false
    size():当前堆元素的数量

3.2 priority_queue的模拟实现

PS:容器vector的数据位置要用到堆结构处理,堆的知识和相关堆算法,这些堆的内容可以参考下面文章:二叉树和堆

仿函数
定义一个类专门重载operator(),使该类实例化出来的对象可以像调用函数一样的调用,从而实现特定功能,你如比较两个数据的大小。
在这里插入图片描述
priority的基本框架

  • 模板参数,除了数据类型和容器,还加了仿函数类Compare,为了使用户可以自主选择大堆和小堆才引入的,当然默认情况是大堆,根STL里一致,大堆传less的仿函数,小堆传greater
    在这里插入图片描述

  • 仿函数我们在priority_queue外单独定义

// 仿函数less
template<class T>
struct less
{
	bool operator()(const T& x1, const T& x2)
	{
		return x1 < x2;
	}
};

// 仿函数greater
template<class T>
struct greater
{
	bool operator()(const T& x1, const T& x2)
	{
		return x1 > x2;
	}
};

// priority_queue
template<class T, class Container = vector<T>, class Compare = less<T>>
class priority_queue
{
public:
	// 无参的默认构造函数
	priority_queue()
		:_con()
	{}

private:
	Container _con;
};

堆的向上和向下调整算法
我们尾部插入元素要利用向上调整算法调堆,删除堆顶元素要用向下调整算法调堆。只是在插入和删除操作时调用,一般外部不用直接调用(因为没意义),所以我们把这两个算法设置成私有。

但不能把他们设置为内联,因为他们内部有循环操作,且代码较长。

private:
// 向下调整算法
void AdjustDown(int parent)
{
	int n = _con.size();
	int child = parent * 2 + 1;
	while(child < n)
	{
		if (child + 1 < n&&Compare()(_con[child], _con[child + 1]))
		{
			++child;
		}
		if (Compare()(_con[parent], _con[child]))
		{
			swap(_con[parent], _con[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			return;
		}
	}
}

// 向上调整算法
void AdjustUp(int child)
{
	int n = _con.size();
	while (child > 0)
	{
		int parent = (child - 1) / 2;
		if (Compare()(_con[parent], _con[child]))
		{
			swap(_con[parent], _con[child]);
			child = parent;
		}
		else
		{
			return;
		}
	}
}

Container _con;

用迭代器区间构造一个priority_queue对象
像下图一样构造一个优先级队列的对象
在这里插入图片描述

template<class Iterator>
priority_queue(Iterator first, Iterator last)
    // 先把数据存入容器对象
	: _con(first, last)
{
	// 向下调整法建堆
	int n = _con.size();
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(i);
	}
}

入队列
就是vector的尾插,再把插入的元素向上调整,保持堆的结构

// 入队列
void push(const T& data)
{
	_con.push_back(data);
	AdjustUp(_con.size()-1);
}

出队列
删除堆顶元素。交换一下堆顶和堆底元素,不用重新建堆,vector尾删,最后在给堆顶元素进行向下调整算法。

// 出队列
void pop()
{
	swap(_con[0], _con[_con.size() - 1]);
	_con.pop_back();
	AdjustDown(0);
}

获取堆顶元素
堆顶元素不允许修改,因为:堆顶元素修改可以会破坏堆的特性。

// 获取栈顶(堆顶)元素
const T& top()const
{
	return _con.front();
}

在这里插入图片描述
完整代码

#include<iostream>
#include<vector>
using namespace std;

namespace MyPriorityQueue
{
	// 仿函数less
	template<class T>
	struct less
	{
		bool operator()(const T& x1, const T& x2)
		{
			return x1 < x2;
		}
	};

	// 仿函数greater
	template<class T>
	struct greater
	{
		bool operator()(const T& x1, const T& x2)
		{
			return x1 > x2;
		}
	};

	// priority_queue
	template<class T, class Container = vector<T>, class Compare = less<T>>
	class priority_queue
	{
	public:
		// 无参的默认构造函数
		priority_queue()
			:_con()
		{}

		// 传迭代对应容器类型对象的迭代器来构造
		template<class Iterator>
		priority_queue(Iterator first, Iterator last)
			: _con(first, last)
		{
			// 向下调整法建堆
			int n = _con.size();
			for (int i = (n - 1 - 1) / 2; i >= 0; --i)
			{
				AdjustDown(i);
			}
		}

		// 入队列
		void push(const T& data)
		{
			_con.push_back(data);
			AdjustUp(_con.size()-1);
		}

		// 出队列
		void pop()
		{
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			AdjustDown(0);
		}

		// 长度
		int size()const
		{
			return _con.size();
		}

		// 判空
		bool empty()const
		{
			return _con.empty();
		}

		// 获取栈顶(堆顶)元素
		const T& top()const
		{
			return _con.front();
		}

	private:
		// 向下调整算法
		void AdjustDown(int parent)
		{
			int n = _con.size();
			int child = parent * 2 + 1;
			while(child < n)
			{
				if (child + 1 < n&&Compare()(_con[child], _con[child + 1]))
				{
					++child;
				}
				if (Compare()(_con[parent], _con[child]))
				{
					swap(_con[parent], _con[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					return;
				}
			}
		}

		// 向上调整算法
		void AdjustUp(int child)
		{
			int n = _con.size();
			while (child > 0)
			{
				int parent = (child - 1) / 2;
				if (Compare()(_con[parent], _con[child]))
				{
					swap(_con[parent], _con[child]);
					child = parent;
				}
				else
				{
					return;
				}
			}
		}

	private:
		Container _con;
	};

	void test()
	{
		vector<int> v = { 2,1,3,3,2,0,6 };
		priority_queue<int> p(v.begin(), v.end());
	}
}
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-09-22 15:02:37  更:2021-09-22 15:03:14 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 15:59:42-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码