【C++初阶】优先级队列的模拟实现 && deque类 的理解

为了更好的理解优先级队列priority_queue,这里会同时进行栈和队列的提及

简要概念(栈和队列)

  • 栈:后进先出的结构,通常使用数组栈的形式
  • 队列:先进先出的结构,通常使用链表式的队列

在这里插入图片描述

  • 优先级队列:优先级队列是基于堆实现的一种数据结构。在 C++ STL 中,我们将实现的priority_queue 类就是通过堆来实现的。
  • 这里不再对堆进行详细介绍,具体在这:堆详解

栈和队列的模拟实现与使用

stack(栈)

模拟实现

进行stack的实现前先介绍一下deque<T>

deque的理解和操作

概念(简单看看)

概念简单看看就行

  1. deque 是 C++ STL 中的一种双端队列(double-ended queue),它允许在队列两端高效地添加、删除元素,同时支持随机访问
  2. 与vector的不同:deque 具有良好的内存分配策略,可以避免 vector 扩容时带来的大量元素复制操作。此外,deque 也具有更好的迭代器安全性,因为它不能像 vector 那样通过引起扩容而使得旧迭代器失效。
  3. deque 内部使用了一个中央控制器,管理了一系列固定大小的连续空间块,每个块内部存储多个元素。当 deque 需要增加或删除元素时,中央控制器会根据需要进行块的扩容或收缩。

操作(重要)

这里介绍dequestack && queue的不同处,介绍 为什么要用deque实现栈和队列

  1. 插入和删除方式不同:栈只能在栈顶进行插入和删除元素,而队列只能在队尾插入,在队头删除;而 deque 可以在队列的两端都插入和删除元素。
  2. 访问方式不同:栈只能访问栈顶元素,队列只能访问队头元素;而 deque 可以随机访问任意位置的元素。
  3. 功能上不同:栈的主要功能是实现“后进先出”(LIFO),队列的主要功能是实现“先进先出”(FIFO),而 deque 不仅可以实现这两种功能,还能够满足双向遍历、支持随机插入和删除等操作。

继续对 stack 进行模拟实现:

这里需要注意的:

  1. 对于模板template: T 表示栈中元素的类型,Container 表示底层容器的类型,默认为 deque<T>
  2. 成员变量: _con 为Container类型,如果调用者不给Container类型,_con默认为deque,剩下的增删查改调用_con的操作函数即可
  • 通过push() 将元素依次尾插到 底层容器con 中;
  • pop() 时,容器进行尾删;top()返回容器尾部元素,即此时的栈顶元素
  • 相当于实现了栈的 后进先出 (LIFO)的特性。
namespace aiyimu 
{
	//Container 表示用于存储队列元素的底层容器类型,默认值为 deque<T>
	template<class T, class Container = deque<T>>
	class stack
	{
	public:
		void push(const T& x)
		{
			// 将元素x插入栈顶: 在底层容器尾部插入元素
			_con.push_back(x);
		}

		void pop()
		{
			// 从栈顶弹出元素: 删除容器的最后元素
			_con.pop_back();
		}

		T& top() // 非常量引用: 可修改返回值
		{
			return _con.back();
		}
		
		const T& top() const // 常量引用: 不可修改返回值
		{
			return _con.back();
		}

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

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

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

queue

queue的实现在接口类型上与stack相同,具体在于相关实现的细节上:

  • push()将元素尾插到容器中
  • pop() 时,容器进行头删
  • 先插入的元素通过尾插到容器内部,pop头删将先插入的元素(在容器底部)删除掉。
  • back()front() 返回容器的尾部与头部元素即可
namespace aiyimu
{
	template<class T, class Container = deque<T>>
	class queue
	{
	public:
		void push(const T& x)
		{
			// 将元素x插入队顶: 容器 尾插
			_con.push_back(x);
		}

		void pop()
		{
			// 从队顶弹出元素: 容器 头删
			_con.pop_front();
		}

		T& back()
		{
			// 返回队尾元素: 即容器尾部元素
			return _con.back();
		}

		T& front()
		{
			// 返回队顶元素: 容器头部元素
			return _con.front();
		}

		const T& back() const // 返回队列中最后一个元素的只读引用
		{
			return _con.back();
		}

		const T& front() const // 返回队列中第一个元素的只读引用
		{
			return _con.front();
		}


		bool empty() const // 检查队列是否为空
		{
			return _con.empty();
		}

		size_t size() const // 返回队列中元素个数
		{
			return _con.size();
		}
	private:
		Container _con;
	};
}

priority_queue(优先级队列)

概念

什么是优先级队列?

优先级队列(Priority Queue)是一种特殊的队列,其中的元素具有优先级。在优先级队列中,元素按照优先级的顺序进行插入和删除操作,优先级最高的元素最先被删除(类似于堆的性质)。

优先级队列的常见实现有下面两种:

  1. 基于堆(Heap)的优先级队列:堆是一种特殊的二叉树结构,满足 堆属性,即父节点的优先级高于或等于其子节点。通过维护堆的性质,可以实现高效的插入和删除操作。插入与删除的时间复杂度均为O(log n)。(n为元素个数)

  2. 基于有序数组或链表的优先级队列使用有序数组或链表来存储元素,并保持元素按照优先级有序。插入操作时,根据优先级找到合适的位置插入元素。删除操作时,直接删除优先级最高的元素。插入操作的时间复杂度为O(n),删除时间复杂度为O(1)

下面将实现的为 基于堆的优先级队列


框架

  • Container:用于存储元素的容器类型默认vector<T>:可以通过指定不同的容器类型来实现不同的功能。
  • Compare:用于选择该堆为大堆还是小堆,默认为std::less则为小堆
  • std::less: 是一个函数对象,用于比较两个参数的大小关系。重载了小于运算符,用于比较两个类型为 T 的对象的大小。当 a < b 时,std::less<T>()(a, b) 返回 true,否则返回 false
template<class T, class Container = vector<T>, class Compare = std::less<T>>
	class priority_queue
	{
	public:
		// 默认构造
		priority_queue()
		{}
		
		// 利用迭代器构造函数
		template <class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
		{}

		// O(logN)
		// 向上调整
		void adjust_up(size_t child)
		{}

		//向下调整
		void adjust_down(size_t parent)
		{}
		
		// 其余操作函数
		void push(const T& x)
		{}

		void pop()
		{}

		const T& top()
		{}

		bool empty() const
		{}

		size_t size() const
		{}
	private:
		Container _con;
	};

具体实现

push()

  1. 插入操作即为堆的插入操作,所以执行_con.push_back()后进行向上调整(从尾部_con.size()-1进行调整)
void push(const T& x)
{
	_con.push_back(x);
	adjust_up(_con.size() - 1);
}

adjust_up()(向上调整)

  1. 向上调整即堆heap的操作,具体思路在上文给到的堆详解
  2. 主要对if语句中的内容进行解释:

(小堆)向上调整过程中,当父节点的值小于子节点时,交换父子节点

  • 原始方法直接进行<判断:if (_con[parent] < _con[child])
  • 但这种写法此时就锁定小堆了, 为了使调用者可以根据需要进行大堆小堆的更改
  • 所以这里创建一个Compare对象com,将比较改写为:if(com(_con[parent], _con[child]))
  • 该写法当调用者不做更改时默认为小堆(std::less),当调用者给出std::greater(比较大于),此时的priority_queue就变为大堆
void adjust_up(size_t child)
{
	Compare com;	// 

	size_t parent = (child - 1) / 2;

	while (child > 0)
	{
		//if (_con[parent] < _con[child])
		if(com(_con[parent], _con[child]))// 默认为less,调用者可以自行指定
		{
			std::swap(_con[parent], _con[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

pop()

  • 交换堆顶和堆底的元素,删除堆顶元素,向下调整
void pop()
{
	// 交换堆顶和堆底的元素,删除堆顶元素,向下调整
	std::swap(_con[0], _con[_con.size() - 1]);
	_con.pop_back();

	adjust_down(0);
}

adjust_down()(向下调整)

  • 向下调整需要注意的仍为com(_con[child], _con[child + 1])写法的意义
  • 具体内容在adjust_up中已经提到
void adjust_down(size_t parent)
{
	Compare com;
	size_t child = parent * 2 + 1;	//先假设右孩子大
	while (child < _con.size())
	{
		//if (child + 1 < _con.size() && _con[child] < _con[child + 1])
		if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
		{
			++child;
		}

		//if (_con[parent] < _con[child])
		if(com(_con[parent], _con[child]))
		{
			std::swap(_con[parent], _con[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

top()

  • 返回堆顶元素,即_con[0]
const T& top()
{
	return _con[0];
}

empty()

  • bool类型:判断堆是否为空
bool empty() const
{
	return _con.empty();
}

size()

  • 返回堆大小(堆元素个数)
size_t size() const
{
	return _con.size();
}

priority_queue 的 验证 / 使用

插入元素 && 打印priority_queue

// 头文件

void test_priority_queue1()
{
	aiyimu::priority_queue<int> pq;
	//priority_queue<int> pq;

	// 插入元素
	pq.push(3);
	pq.push(2);
	pq.push(5);
	pq.push(9);
	pq.push(4);
	pq.push(6);
	pq.push(1);

	// 打印pq的所有元素
	while (!pq.empty())
	{
		cout << pq.top() << " ";
		pq.pop();
	}
	cout << endl;
}

输出结果:
在这里插入图片描述

降序 / 升序

int arr[] = { 3,5,6,8,19,7,1,0,9,1,20,4,12 };

	// 不传入Compare类型,默认less类型:小于,即降序
	aiyimu::priority_queue<int> heap_less(arr, arr + sizeof(arr) / sizeof(arr[0]));
	
	// 传入Compare类型,为greater类型:大于,即升序
	aiyimu::priority_queue<int, vector<int>, greater<int>> heap_greater(arr, arr + sizeof(arr) / sizeof(arr[0]));


	while (!heap_less.empty())
	{
		cout << heap_less.top() << " ";
		heap_less.pop();
	}
	cout << endl;


	while (!heap_greater.empty())
	{
		cout << heap_greater.top() << " ";
		heap_greater.pop();
	}
	cout << endl;

输出结果:
在这里插入图片描述

由此可以看出,当使用std::less时堆为降序,std::greater时堆为升序

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值