C++学习记录——십오 仿函数、优先级队列、反向迭代器


仿函数

优先级队列是一个容器适配器,它的默认容器是vector。优先级队列底层是由堆写的,所以它的push和pop也要参考堆来写。优先级队列默认为大堆,库中写的是less,如果要用小堆,要用模板参数greater作为第三个参数,第二个参数必须要写。像greater就是仿函数,也叫函数对象。使用优先级队列库的时候也要引入< functional >头文件来使用仿函数。

priority_queue<int, vector<int>, greater<int>>//小堆
priority_queue<int>//大堆

先写仿函数,再写优先级队列。

假设我们要写一个小于的仿函数,仿函数都有一个特点,重载()函数。

struct Less
{
    bool operator()(int x, int y)
    {
        return x < y;
    }
};

int main()
{
    Less lessFunc;
    cout << lessFunc(1, 2) << endl;
    return 0;
}

使用这个类对象的时候,写出来的代码就和使用函数一样,但实际上它是一个类的使用。当然应该加上模板参数T,要不这个类只能比较int类型。

template<class T>
struct Less
{
    bool operator()(const T& x, const T& y)
    {
        return x < y;
    }
};

int main()
{
    Less<int> lessFunc;
    cout << lessFunc(1, 2) << endl;
    return 0;
}

为什么加引用?之前写过,引用会让代码更高效;const则是为了适应const和普通版本。

仿函数的出现有一部分原因是函数指针使用上的一些不便,而仿函数就可以易于理解。

优先级队列

练习

void test_priority_queue()
{
    //priority_queue<int, vector<int>, greater<int>> pq;
    priority_queue<int> pq;
    //priority_queue<int, deque<int>> pq;
    pq.push(1);
    pq.push(0);
    pq.push(5);
    pq.push(2);
    pq.push(1);
    pq.push(7);

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

知道了优先级队列的部分属性,我们做一个题。

数组的第k个最大元素

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素
你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

在这里插入图片描述

之前通过堆来写,现在就可以优先级队列来做。当然sort一下也可以。

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int> pq(nums.begin(), nums.begin() + k);
        while(--k)
        {
            pq.pop();
        }
        return pq.top();
    }
};

默认大堆,生成优先级队列后,一次次pop,最后停的位置就是第k个大的数字。这个代码的时间复杂度O(N + k * logN),建堆的时候是N,pop向下调整的时候是k * logN。

如果N远大于k,且N很大,这时候就要用到之前文件排序时提到的,我们只建k个数的堆,然后遍历所有数据,和堆顶依次比较入堆,这里就需要小堆,最后取堆顶就行。

    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int, vector<int>, greater<int>> pq(nums.begin(), nums.begin() + k);
        for(size_t i = k; i < nums.size(); ++i)
        {
            if(nums[i] > pq.top())
            {
                pq.pop();
                pq.push(nums[i]);
            }
        }
        return pq.top();
    }

拿前k个数据建堆,拿之后的所有来比较。这时候时间度就变成了O(k + (N - k) * logk)。

模拟实现

先不写仿函数less和greater,先写两个模板参数class T和class Container = vector< T >。优先级队列是用堆实现的,它的插入和删除就需要向上调整和向下调整

	template <class T, class Container = vector<T>>
	class priority_queue
	{
	public:
		void adjust_up(int child)
		{
			//Comapre com;
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				//if (_con[parent] < _con[child])
				if (com(_con[parent], _con[child]))
					//if (Comapre()(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}

		void adjust_down(int parent)
		{
			size_t child = parent * 2 + 1;
			while (child < _con.size())
			{
				Comapre com;

				//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]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}
		void push(const T& x)
		{
			_con.push_back(x);
			adjust_up(_con.size() - 1);
		}

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

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

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

		bool empty()
		{
			return _con.empty();
		}
	private:
		Container _con;
	};

测试用例

void test_priority_queue()
{
	//priority_queue<int> pq;
	priority_queue<int, deque<int>> pq;
	pq.push(1);
	pq.push(0);
	pq.push(5);
	pq.push(2);
	pq.push(1);
	pq.push(7);

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

即使第二个模板参数传了一个双端队列,也可以运行程序,这就是适配器的体现。

现在是大堆,如果要建小堆要怎么写?我们不可能把代码改一下,而是应该很简便地一用就换成小堆,所以这里就要用到仿函数了,新增一个Compare参数,默认 = less< T >。我们跟着库里的写。写好这个后,向下/上调整就可以改一下判断了。

	template<class T>
	struct less
	{
		bool operator()(const T& x, const T& y)
		{
			return x < y;
		}
	};

	template<class T>
	struct greater
	{
		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
	{
	public:
		void adjust_up(int child)
		{
			Comapre com;
			size_t parent = (child - 1) / 2;
			while (child > 0)
			{
				//if (_con[parent] < _con[child])
				if (com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}

		void adjust_down(int parent)
		{
			size_t child = parent * 2 + 1;
			while (child < _con.size())
			{
				Comapre com;
				//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]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

实际上,仿函数是一种泛型编程

优先级队列还可以处理自定义类型的数据。假如用日期类数据,可以排出日期类的数据堆,拿到最大或者最小的那个。如果这样写

priority_queue<Date*> pq;
pq.push(new Date(2023, 3, 17));
pq.push(new Date(2023, 3, 18));
pq.push(new Date(2023, 3, 19));
cout << *(pq.top()) << endl;

建立小堆,会打印出2023,3,17,但是多次运行,下面那行数字会变大,17变成18,变成19。

此时的T是Date*, new出来的数据的大小无法准确地掌握,不一定会返回我们想要的结果,比较的也是指针。

这里的解决办法就是不用优先级队列里用的less和greater类,再定义一个Date数据比较大小的类

class PDateless
{
public:
	bool operator()(constDate* p1, const Date* p2)
	{
		return *p1 < *p2;
	}
};

priority_queue<Date*, vector<Date>, PDateless> pq;

建一个简单的日期类,传给优先级队列:

	class Date
	{
	public:
		Date(int year = 1900, int month = 1, int day = 1)
			: _year(year)
			, _month(month)
			, _day(day)
		{}

		bool operator<(const Date& d)const
		{
			return (_year < d._year) ||
				(_year == d._year && _month < d._month) ||
				(_year == d._year && _month == d._month && _day < d._day);
		}

		bool operator>(const Date& d)const
		{
			return (_year > d._year) ||
				(_year == d._year && _month > d._month) ||
				(_year == d._year && _month == d._month && _day > d._day);
		}

		friend ostream& operator<<(ostream& _cout, const Date& d)
		{
			_cout << d._year << "-" << d._month << "-" << d._day;
			return _cout;
		}

	private:
		int _year;
		int _month;
		int _day;
	};

	class PDateLess
	{
	public:
		bool operator()(const Date* p1, const Date* p2)
		{
			return *p1 < *p2;
		}
	};

	class PDateGreater
	{
	public:
		bool operator()(const Date* p1, const Date* p2)
		{
			return *p1 > *p2;
		}
	};

	void test_priority_queue2()
	{
		// 大堆,需要用户在自定义类型中提供<的重载
		//priority_queue<Date> q1;
		priority_queue<Date, vector<Date>, greater<Date>> q1;
		q1.push(Date(2018, 10, 29));
		q1.push(Date(2018, 10, 30));
		q1.push(Date(2018, 10, 28));
		cout << q1.top() << endl;

		//priority_queue<Date*, vector<Date*>, PDateLess> q2;
		priority_queue<Date*, vector<Date*>, PDateGreater> q2;
		q2.push(new Date(2018, 10, 29));
		q2.push(new Date(2018, 10, 30));
		q2.push(new Date(2018, 10, 28));
		cout << *(q2.top()) << endl;
	}

反向迭代器(重点理解)

List类的反向迭代器

List是带头双向循环链表

反向迭代器的++要倒着走,那是不是把正向迭代器改变几个符号就可以了?begin就是rend,end就是rbegin,++就是- -,- -就是++,typedef一个reverse_iterator,然后照着正向的代码,也传入模板参数T,Ref,Ptr,用的时候传普通和const版本就好了,反向迭代器也就完成了,然而当这样写出来后,才发现了这代码根本不高效。

库中的源代码是怎么实现的?

typedef reverse_iterator<const_iterator> const_reverse_iterator;
typedef reverse_iterator<iterator> reverse_iterator;

源码传了一个正向迭代器作为模板参数,源码中把迭代器实现为一个适配器。C++很不喜欢冗余的代码,既然有相似的正向迭代器,就用相似的来实现。

实际写的话,还是有不同之处。反向迭代器的解引用取的是当前位置的前一个。

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

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


iterator begin() { return (link_type)((*node).next); }
iterator end() { return node; }

上面的代码就显示了,end在begin前面。我们看下面的解析。

在这里插入图片描述

list是带头双向循环链表,end在头节点位置,begin在头节点的下一个位置。rbegin在end位置,rend在begin位置。使用反向迭代器的时候我们会这样写

rit = rbegin();
while(rit != rend())
{
    cout << *rit << " ";
    ++rit;
}

rit取rbegin,也就是头节点位置,判断条件是不等于rend,然后解引用,前置++。其实rit就是一个指针,指向头节点,不过这个指针是正向迭代器类型,它要解引用,它要++,- -就要使用正向迭代器类里的方法,就如同内置类型的指针做法一样,只是内置类型指针的做法库中已经定义,我们直接用即可。所以我们为什么要重载解引用,重载++?自定义类型的数据,我们要自己写对应的方法来实现其功能。

rbegin和rend知道了,那么解引用和++呢?库中这样实现解引用

reference operator*() const//这是源码,我们只看思路,reference不用看
{
    Iterator tmp = current;//取现在的节点
    return *--tmp;//返回前一个节点的值
}

而正向迭代器的–是这样实现的

		self& operator--()
		{
			_node = _node->prev;
			return *this;
		}

解引用时,tmp拿到当前节点,但是当前节点是从头节点开始的,这里不能直接解引用,所以它往前走一步,就用到了前置- -,也就是正向迭代器的- -,可以看到取节点的前一个,然后返回,返回后对此解引用,得到的就是前一个的值,也就是5,打印出了5,但是此时rit没有动,只是tmp在移动,前面tmp拿到rit指向的节点来进行操作;解引用完后++rit,反向迭代器的++就是正向迭代器的–,节点就来到了5这个位置,这时候再次解引用,还是上面的步骤,拿到了4,打印4,再次++,节点来到4。最后节点是2时,打印出1,已经全部打印出来了,++rit,节点来到1,也就是rend的位置,这时候条件不符合,while循环结束。

这里就体现了反向迭代器是如何封装正向迭代器来实现自己的。

我们还是要自己模拟实现。

正向迭代器在物理空间是一个指针,访问内置类型,就是解引用,访问自定义类型,就是调用一个对应的函数,那么这时候它就起到了函数指针的作用,只是这个指针的类型是自定义类型。反向迭代器也一样。

	template<class Iterator>
	struct ReverseIterator
	{
		typedef ReverseIterator<Iterator> Self;
		Iterator _cur;
		ReverseIterator(Iterator it)
			:_cur(it)
		{}

		operator*()
		{
			Iterator tmp = _cur;//为了不改变原有结构,就用tmp
			--tmp;
			return *tmp;
		}

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

		Self operator++(int)
		{
			Iterator tmp = _cur;
			--_cur;
			return tmp;
		}

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

		Self operator--(int)
		{
			Iterator tmp = _cur;
			++_cur;
			return tmp;
		}

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

重载*函数,需要有const和普通版本,因为传过来迭代器模板参数有const和普通版本。加两个模板参数。

	template<class Iterator, class Ref, class Ptr>
	struct ReverseIterator
	{
		typedef ReverseIterator<Iterator, Ref, Ptr> Self;
		Iterator _cur;
		ReverseIterator(Iterator it)
			:_cur(it)
		{}

		Ref operator*()
		{
			Iterator tmp = _cur;//为了不改变原有结构,就用tmp
			--tmp;
			return *tmp;
		}

list中

template<class T>
class list
{
	typedef list_node<T> node;
public:
	typedef _list_iterator<T, T&, T*> iterator;
	typedef _list_iterator<T, const T&, const T*> const_iterator;
	typedef ReverseIterator<iterator, T&, T*> reverse_iterator;
	typedef ReverseIterator<iterator, const T&, const T*> const_reverse_iterator;

    //......
	reverse_iterator rbegin()
	{
		return reverse_iterator(end());
	}

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

测试用例

void test_list()
{
    list<int> lt;
    lt.push_back(1);
    lt.push_back(2);
    lt.push_back(3);
    lt.push_back(4);

    list<int>::iterator it = lt.begin();
    while (it != lt.end())
    {
        (*it) *= 2;
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    list<int>::reverse_iterator rit = lt.rbegin();
    while (rit != lt.rend())
    {
        cout << *rit << " ";
        ++rit;
    }
    cout << endl;
}

整体的迭代器部分代码

	template<class T>
	struct list_node
	{
		list_node<T>* next;
		list_node<T>* prev;
		T data;

		list_node(const T& x = T())
			:_next(nullptr)
			, _prev(nullptr)
			, _data(x)
		{}
	};

	template <class T, class Ref, class Ptr>
	struct _list_iterator
	{
		typedef list_node<T> node;
		typedef _list_iterator<T, Ref, Ptr> self;
		node* _node;

		_list_iterator(node* n)
			:_node(n)
		{}

		Ref operator*()//const &
		{
			return _node->data;
		}

		Ptr operator->()//const T*
		{
			return &_node->data;
		}

		self& operator++()
		{
			_node = _node->next;
			return *this;
		}

		bool operator!=(const self& s)
		{
			return _node != s._node;
		}

		self& operator++()
		{
			_node = _node->next;
			return *this;
		}

		self operator++(int)
		{
			self tmp(*this);
			_node = _node->next;
			return tmp;
		}

		self& operator--()
		{
			_node = _node->prev;
			return *this;
		}

		self operator--(int)
		{
			self tmp(*this);
			_node = _node->prev;
			return tmp;
		}

		bool operator==(const self& s)
		{
			return _node != s._node;
		}

		bool operator!=(const self& s)
		{
			return _node != s._node;
		}
	};

	template<class Iterator, class Ref, class Ptr>
	struct ReverseIterator
	{
		typedef ReverseIterator<Iterator, Ref, Ptr> Self;
		Iterator _cur;
		ReverseIterator(Iterator it)
			:_cur(it)
		{}

		Ref operator*()
		{
			Iterator tmp = _cur;//为了不改变原有结构,就用tmp
			--tmp;
			return *tmp;
		}

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

		Self operator++(int)
		{
			Iterator tmp = _cur;
			--_cur;
			return tmp;
		}

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

		Self operator--(int)
		{
			Iterator tmp = _cur;
			++_cur;
			return tmp;
		}

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

	template<class T>
	class list
	{
		typedef list_node<T> node;
	public:
		typedef _list_iterator<T, T&, T*> iterator;
		typedef _list_iterator<T, const T&, const T*> const_iterator;
		typedef ReverseIterator<iterator, T&, T*> reverse_iterator;
		typedef ReverseIterator<iterator, const T&, const T*> const_reverse_iterator;

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

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

vector反向迭代器

vector如何写反向迭代器?其实刚才的思路就已经写出来了适配器。只要支持++和–,就能通过正向迭代器适配出反向迭代器。如果只是按照正向迭代器去写一个反向迭代器,只能写出一个类型的,但如果把正向迭代器当作一个模板参数,就可以适配出所有的反向迭代器,只要是双向的,也就是支持++和–。

一个例子

void test_vector8()
{
	bit::vector<int> v1;
	v1.push_back(10);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);
	v1.push_back(50);

	bit::vector<int>::reverse_iterator rit = v1.rbegin();
	while (rit != v1.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
}

在这里插入图片描述

再拿出这个图来看,可以仔细想一想这个代码怎么走的。

反向迭代器封装正向迭代器,也就是用正向迭代器作为模板来实现自己,和正向迭代器封装指针,其实是一样的规则。正向迭代器有模板参数T*,const T*,也有引用,当然反向迭代器也有;编译器遇到这个指针是反向迭代器的,它就使用反向迭代器的方法,而这个方法是程序员自己定义的;我们可不可以重载内置类型的指针等的方法?可以,重载后编译器识别到相关类型就用对应的方法。它们都被放进库里,引用头文件后就可以使用,或者用自己重载的方法。

反向迭代器需要好好琢磨,这也是对C++类和泛型编程的一个深层次理解。

结束。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值