C++ 容器的两把利器:优先级队列与反向迭代器

目录

-------------反向迭代器------------

1、适配器模式

2、反向迭代器原理

3、反向迭代器的实现

[3.1 模板参数解析](#3.1 模板参数解析)

[3.2 类型别名与成员变量](#3.2 类型别名与成员变量)

[3.3 构造函数](#3.3 构造函数)

[3.4 取值运算符 operator*](#3.4 取值运算符 operator*)

[3.5 箭头运算符 operator->](#3.5 箭头运算符 operator->)

[3.6 前置 ++ 运算符 operator++](#3.6 前置 ++ 运算符 operator++)

[3.7 后置 ++ 运算符 operator++(int)](#3.7 后置 ++ 运算符 operator++(int))

[3.8 前置 -- 运算符 operator--](#3.8 前置 -- 运算符 operator--)

[3.9 后置 -- 运算符 operator--(int)](#3.9 后置 -- 运算符 operator--(int))

[3.10 不等比较运算符 operator!=](#3.10 不等比较运算符 operator!=)

-------------优先级队列------------

1、仿函数

2、优先级队列介绍

3、优先级队列的实现

[3.1 类模板定义](#3.1 类模板定义)

[3.2 AdjustDown 向下调整算法](#3.2 AdjustDown 向下调整算法)

[3.3 AdjustUp 向上调整算法](#3.3 AdjustUp 向上调整算法)

[3.4 默认构造函数](#3.4 默认构造函数)

[3.5 范围构造函数](#3.5 范围构造函数)

[3.6 pop 弹出堆顶元素](#3.6 pop 弹出堆顶元素)

[3.7 push 插入新元素](#3.7 push 插入新元素)

[3.8 top 获取堆顶元素](#3.8 top 获取堆顶元素)

[3.9 empty 判断队列是否为空](#3.9 empty 判断队列是否为空)

[3.10 size 获取队列元素个数](#3.10 size 获取队列元素个数)


-------------反向迭代器------------

1、适配器模式

要实现反向迭代器,就不得不提到适配器模式

在上一篇内容中,我们学习的 stackqueue 就是典型的容器适配器 ;而今天要讲的反向迭代器,则是一种迭代器适配器

2、反向迭代器原理

反向迭代器是一个迭代器适配器,它包装了一个正向迭代器,把 ++ 操作映射成原迭代器的 --,把 -- 操作映射成原迭代器的 ++,从而实现反向遍历的效果

反向迭代器在容器中的指向:

3、反向迭代器的实现

cpp 复制代码
namespace ljh
{
	//typedef iterator<iterator, T& , T*> iterator;
	//typedef iterator<iterator, const T& , const T*> iterator;

	template<class Iterator, class Ref, class Ptr>
	struct ReverseIterator
	{
		typedef ReverseIterator<Iterator, Ref, Ptr> Self;
		Iterator _it;

		ReverseIterator(Iterator it)
			:_it(it)
		{
		}

		Ref operator*()
		{
			Iterator tmp = _it;
			return *(--tmp);
		}

		Ptr operator->()
		{
			return &(operator*());
		}

		Self& operator++()
		{
			--_it;
			return *this;
		}

		//由于没写析构函数,所以不用担心拷贝构造是浅拷贝
		Self operator++(int)
		{
			Self tmp(*this); 
			--_it;//正向迭代器--,反向迭代器++

			return tmp;
		}

		Self& operator--()
		{
			++_it;
			return *this;
		}

		Self operator--(int)
		{
			Self tmp(*this);

			++_it;

			return tmp;
		}

		bool operator!=(const Self& s) const
		{
			return _it != s._it;
		}
	};



    typedef ReverseIterator<iterator, T&, T*> reverse_iterator;
	typedef ReverseIterator<const_iterator, const T&, const T*const_reverse_iterator;

    /*===================反向迭代器=====================*/
	reverse_iterator rbegin()
	{
		return reverse_iterator(end());
	}

	reverse_iterator rend()
	{
		return reverse_iterator(begin());
	}

	const_reverse_iterator rbegin()const
	{
		return reverse_iterator(end());
	}

	const_reverse_iterator rend() const
	{
		return reverse_iterator(begin());
	}


}
3.1 模板参数解析
cpp 复制代码
template<class Iterator, class Ref, class Ptr>
struct ReverseIterator

Iterator 被适配的正向迭代器类型(比如 vector::iterator

Ref:迭代器取值(*it)时返回的引用类型(比如 T&const T&

Ptr:迭代器箭头访问(it->)时返回的指针类型(比如 T*const T*


3.2 类型别名与成员变量
cpp 复制代码
typedef ReverseIterator<Iterator, Ref, Ptr> Self;
Iterator _it;

Self:简化自身类型的书写,避免重复写长模板名

_it:内部持有的正向迭代器,是反向迭代器的 "核心数据"


3.3 构造函数
cpp 复制代码
ReverseIterator(Iterator it) : _it(it) {}

用一个正向迭代器来初始化反向迭代器,把传入的迭代器保存到 _it 中。


3.4 取值运算符 operator*
cpp 复制代码
Ref operator*()
{
    Iterator tmp = _it;
    return *(--tmp);
}

这是反向迭代器的核心适配逻辑。

它先拷贝一份内部迭代器 _it,对拷贝做 -- 移动到前一个位置,再取值返回。

这样保证了 rbegin() 能正确指向容器的最后一个元素。


3.5 箭头运算符 operator->
cpp 复制代码
Ptr operator->()
{
    return &(operator*());
}

复用 operator* 的结果,取其地址返回,支持 it->member 这样的指针访问语法。


3.6 前置 ++ 运算符 operator++
cpp 复制代码
Self& operator++()
{
    --_it;
    return *this;
}

反向迭代器的 ++ 对应内部正向迭代器的 --,实现 "向后移动"(在反向遍历中是向前走)。


3.7 后置 ++ 运算符 operator++(int)
cpp 复制代码
Self operator++(int)
{
    Self tmp(*this);
    --_it;
    return tmp;
}

先创建一个当前对象的副本,再移动内部迭代器,最后返回副本。

这是后置 ++ 的标准实现,保证返回的是 "移动前" 的迭代器。


3.8 前置 -- 运算符 operator--
cpp 复制代码
Self& operator--()
{
    ++_it;
    return *this;
}

反向迭代器的 -- 对应内部正向迭代器的 ++,实现 "向前移动"(在反向遍历中是向后退)。


3.9 后置 -- 运算符 operator--(int)
cpp 复制代码
Self operator--(int)
{
    Self tmp(*this);
    ++_it;
    return tmp;
}

逻辑同后置 ++,先拷贝再移动,返回移动前的迭代器。


3.10 不等比较运算符 operator!=
cpp 复制代码
bool operator!=(const Self& s) const
{
    return _it != s._it;
}

bool operator==(const Self& s) const
{

    return _it == s._it; 
}

直接比较两个反向迭代器内部持有的正向迭代器,判断它们是否指向不同位置。


cpp 复制代码
// 先定义反向迭代器的类型别名(依赖之前的 ReverseIterator 适配器)
typedef ReverseIterator<iterator, T&, T*> reverse_iterator;
typedef ReverseIterator<const_iterator, const T&, const T*> const_reverse_iterator;

// 1. 普通版 rbegin():可读写反向迭代器起点
reverse_iterator rbegin()
{
    // 核心:用正向迭代器的 end() 初始化反向迭代器,指向容器最后一个元素
    return reverse_iterator(end());
}

// 2. 普通版 rend():可读写反向迭代器终点
reverse_iterator rend()
{
    // 核心:用正向迭代器的 begin() 初始化反向迭代器,指向第一个元素之前
    return reverse_iterator(begin());
}

// 3. const版 rbegin() const:只读反向迭代器起点
const_reverse_iterator rbegin() const
{
    // 核心:逻辑同普通版,但返回 const 版本,仅支持读操作
    return const_reverse_iterator(end());
}

// 4. const版 rend() const:只读反向迭代器终点
const_reverse_iterator rend() const
{
    // 核心:逻辑同普通版,但返回 const 版本,仅支持读操作
    return const_reverse_iterator(begin());
}

-------------优先级队列------------

1、仿函数

仿函数(Functor)是 C++ 中一种特殊的类对象,核心特点是重载(重载)了 operator() 运算符,使得对象可以像函数一样被调用(用 对象名(参数) 的形式)。

简单说:仿函数是 "像函数的对象",本质是带 operator() 的类实例。

cpp 复制代码
template<class T>
class small
{
public:
	bool operator()(const T& x,const T& y)
	{
         return x < y;
	}
};

template<class T>
class big
{
public:
	bool operator()(const T& x,const T& y)
	{
		return x > y;
	}
};


int main()
{ 
    small<int> s;
    big<int> b;
    cout << s(1,2) << endl;
	cout << b(1, 2) << endl;

	return 0;
}

对初次接触的读者来说,仿函数的调用方式(比如 s(1,2))确实显得有些陌生,和我们熟悉的 += 等运算符重载的写法很不一样。你可能暂时会疑惑它的实际价值,这很正常 ------ 当我们后续用它来定制优先级队列的比较规则时,这种设计的灵活性和必要性就会清晰地展现出来。


2、优先级队列介绍

priority_queue 是 C++ 标准库中的一个容器适配器 ,底层默认用 vector 存储数据,内部维护一个堆结构,确保队首元素始终是优先级最高的(默认是最大值)。

核心特点

只能访问队首的最大元素,不能遍历或随机访问其他元素。

插入新元素时,会自动调整堆结构以维持优先级顺序。

弹出队首元素后,剩余元素也会自动重新调整。

底层原理 :通过调用 make_heappush_heappop_heap 等算法函数来维护堆的特性。

默认行为 :默认是大顶堆(最大元素优先),可以通过传入仿函数(如 greater<T>)改为小顶堆。


3、优先级队列的实现

cpp 复制代码
namespace ljh
{

    // 仿函数/函数对象
    template<class T>
    class Less
    {
    public:
	    bool operator()(const T& x, const T& y)
	    {
		    return x < y;
	    }
    };


    template<class T>
    class Greater
    {
    public:
	    bool operator()(const T& x, const T& y)
	    {
	    	return x > y;
	    }
    };



	template<class T,class Container = vector<T> , class Compare = Less<T> >
	class priority_queue
	{
	private:
		//默认建大堆
		void AdjustDown(int parent)
		{

			Compare com;//仿函数对象

			int child = parent * 2 + 1;

			while (child < _con.size())
			{
				if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
					//if (child + 1 < _con.size() && _con[child] < _con[child + 1])
				{
					++child;
				}

				if (com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}
 
		
		//向上调整算法
		void AdjustUp(int child)
		{

			Compare com;//仿函数对象
			
			int 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;
				}
			}
		}


	public:
        //默认构造
		priority_queue()
		{
		}

		//范围构造
		template<class InputIterator>
		priority_queue(InputIterator first,InputIterator last)
		{
			while (first!=last)
			{
				_con.push_back(*first);
				first++;
			}


			//建堆-向下调整建堆(默认建立大堆)
            //N
			for (int i = (_con.size() - 2) / 2; i >= 0; i--)
			{
				//向下调整算法
				AdjustDown(i);
			}
		}


		void pop()
		{
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();

			AdjustDown(0);
		}

		void push(const T& x)
		{
			_con.push_back(x);

			AdjustUp(_con.size() - 1);
		}

		const T& top()
		{
			return _con[0];
		}

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

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

	private:
		Container _con;
	};

}
3.1 类模板定义
cpp 复制代码
template<class T, class Container = vector<T>, class Compare = Less<T>>
class priority_queue

这是整个优先级队列的模板定义,有三个模板参数:

**T:**队列中存储的元素类型。

**Container:**底层存储数据的容器,默认用 vector,也可以换成 deque 等支持随机访问的容器。

**Compare:**比较规则的仿函数,默认是 Less<T>(大顶堆),可以换成 Greater<T> 实现小顶堆。


3.2 AdjustDown 向下调整算法
cpp 复制代码
void AdjustDown(int parent)
{
    Compare com;
    int child = parent * 2 + 1;
    while (child < _con.size())
    {
        if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
        {
            ++child;
        }
        if (com(_con[parent], _con[child]))
        {
            swap(_con[child], _con[parent]);
            parent = child;
            child = parent * 2 + 1;
        }
        else
        {
            break;
        }
    }
}

这是维护堆结构的核心函数,用来在堆顶元素被移除后,把新的堆顶向下调整到正确位置:

先创建一个比较规则的仿函数对象 con

parent 的左孩子 child = parent*2+1 开始。

先在左右孩子中,用 com 比较出优先级更高的那个,作为真正要交换的 child

然后用 com 比较父节点和这个孩子节点,如果父节点优先级更低,就交换它们,并继续向下调整。

如果父节点优先级已经更高,说明调整完成,直接跳出循环。


3.3 AdjustUp 向上调整算法
cpp 复制代码
void AdjustUp(int child)
{
    Compare com;
    int parent = (child - 1) / 2;
    while (child > 0)
    {
        if (com(_con[parent], _con[child]))
        {
            swap(_con[child], _con[parent]);
            child = parent;
            parent = (child - 1) / 2;
        }
        else
        {
            break;
        }
    }
}

这个函数用来在新元素插入堆尾后,把它向上调整到正确位置:

创建比较规则的仿函数对象 con

计算当前 child 节点的父节点 parent = (child-1)/2

con 比较父节点和孩子节点,如果父节点优先级更低,就交换它们,并继续向上调整。

如果父节点优先级已经更高,说明调整完成,跳出循环。

3.4 默认构造函数
cpp 复制代码
priority_queue()
{}

空的默认构造函数,底层容器 _con 会自己调用默认构造,不需要额外操作。


3.5 范围构造函数
cpp 复制代码
template<class InputIterator>
priority_queue(InputIterator first, InputIterator last)
{
    while (first != last)
    {
        _con.push_back(*first);
        first++;
    }
    for (int i = (_con.size() - 2) / 2; i >= 0; i--)
    {
        AdjustDown(i);
    }
}

这个构造函数可以用一段迭代器区间来初始化队列:

先把区间里的所有元素都插入到底层容器 _con 中。

然后从最后一个非叶子节点开始,依次调用 AdjustDown,把整个容器调整成一个合法的堆结构。

3.6 pop 弹出堆顶元素
cpp 复制代码
void pop()
{
    swap(_con[0], _con[_con.size() - 1]);
    _con.pop_back();
    AdjustDown(0);
}

弹出堆顶元素的步骤:

先把堆顶元素(_con[0])和堆尾元素交换。

然后删除堆尾元素(也就是原来的堆顶)。

最后对新的堆顶元素调用 AdjustDown,重新维护堆的结构。


3.7 push 插入新元素
cpp 复制代码
void push(const T& x)
{
    _con.push_back(x);
    AdjustUp(_con.size() - 1);
}

插入新元素的步骤:

先把新元素插入到底层容器的尾部。

然后对这个新插入的元素调用 AdjustUp,把它向上调整到正确的位置,以维持堆的性质。


3.8 top 获取堆顶元素
cpp 复制代码
const T& top()
{
    return _con[0];
}

直接返回底层容器的第一个元素,也就是堆顶元素。因为堆顶始终是优先级最高的元素。


3.9 empty 判断队列是否为空
cpp 复制代码
bool empty()
{
    return _con.empty();
}

直接调用底层容器的 empty() 方法,判断队列是否为空。


3.10 size 获取队列元素个数
cpp 复制代码
size_t size()
{
    return _con.size();
}

直接返回底层容器的大小,也就是队列中元素的个数。

相关推荐
张张努力变强2 小时前
C++ 类和对象(三):拷贝构造函数与赋值运算符重载之核心实现
开发语言·c++
_OP_CHEN2 小时前
【算法基础篇】(五十)扩展中国剩余定理(EXCRT)深度精讲:突破模数互质限制
c++·算法·蓝桥杯·数论·同余方程·扩展欧几里得算法·acm/icpc
福楠2 小时前
C++ STL | set、multiset
c语言·开发语言·数据结构·c++·算法
enfpZZ小狗2 小时前
基于C++的反射机制探索
开发语言·c++·算法
王老师青少年编程2 小时前
2023年12月GESP真题及题解(C++七级): 纸牌游戏
c++·题解·真题·gesp·csp·七级·纸牌游戏
Trouvaille ~2 小时前
【Linux】进程间通信(一):IPC基础与管道机制深度剖析
linux·运维·c++·管道·进程间通信·匿名管道·半双工
REDcker2 小时前
libwebsockets完整文档
c++·后端·websocket·后端开发·libwebsockets
Sheep Shaun2 小时前
深入理解红黑树:从概念到完整C++实现详解
java·开发语言·数据结构·c++·b树·算法
楼田莉子2 小时前
CMake学习:入门及其下载配置
开发语言·c++·vscode·后端·学习