C++ STL(第二十一篇:算法-- 应用于有序区间的算法)

本文介绍了C++ STL中应用于有序区间的算法,包括includes、merge、lower_bound、upper_bound、binary_search、equal_range和inplace_merge。详细阐述了每个算法的功能、应用场景和实现原理,帮助理解如何在有序序列中进行高效操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、概述

有序区间,顾名思义就是区间内的元素都是经过排序之后的。对于这种类型的区间,有一系列的算法。今天就对这种区间的算法进行整理。

2、includes

判断序列 S2 是否 “涵盖于” 序列 S1,所谓涵盖,意思是 “S2 的每一个元素都出现于 S1”。代码如下:

template<class InputIterator1, class InputIterator2>
bool includes( InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2)
{
	while( first1 != last1)
	{
		if( *first1 < *first2)
			++first1;
		else if( *first2 > *first1)
			return false;
		else
		{
			++first1;
			++first2;
		}
	}
	return first2 == last2;
}

3、merge

将两个经过排序的集合 S1 和 S2,合并起来置于另一段空间中。所得结果也是一个有序序列,返回此序列的last迭代器。merge 是一个稳定操作。代码如下:

template<class InputIterator1, class InputIterator2, class OutputIterator>
OutputIterator merge(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2, OutputIterator result)
{
	while( first1 != last1 && first2 != last2)
	{
		if( *first1 < *first2)
		{
			*result = *first1;    			
			++first1;
		}
		else if( *first2 < *first1)
		{
			*result = *first2;
			++first2;
		}
		else
		{
			*result = *first1;
			++first1;
			++first2;
		}
		
		++result;
	}
	copy(first1, last1, copy(first2, last2, result));
}

4、lower_bound

这是二分查找的一种版本,试图在已排序的 [first, last) 中寻找元素 value。如果 [first, last) 具有与 value 相等的元素,便返回一个迭代器,指向其中第一个元素。如果没有这样的元素存在,便返回 “假设这样的元素存在时应该出现的位置”。也就是说,它会返回一个迭代器,指向第一个 “不小于value” 的元素。如下图所示:
在这里插入图片描述
upper_bound 在下面整理了。

lower_bound 的代码如下:

template<class ForwardIterator, class T>
ForwardIterator lower_bound( ForwardIterator first, ForwardIterator last, const T& value)
{
	return __lower_bound(first, last, value, distance_type(first), inerator_category(first));
}

template<class ForwardIterator, class T, class Distance>
inline ForwardIterator __lower_bound( ForwardIterator first, ForwardIterator last, const T& value, Distance*, forward_iterator_tag)
{
	Distance len = 0;
	distance( first, last, len);	//求取整个区间的长度 len
	
	Distance half;
	ForwardIterator middle;
	while( len > 0 )
	{
		half = len >> 1; 			//除以2,位操作比较快
 		middle = first;				//这一行和下一行是调整middle 节点
 		advance(middle, half);	
 		if( *middle < value )		//如果中间位置的元素值 < 目标值
 		{
 			first = middle;			//令 first 指向 middle 的下一位置
 			++first;
 			len = len - half -1;	//修正 len,回头测试循环的结束条件
 		}
 		else
 			len = half;
	}
	
	return first;
}

//前面版本的重载函数,这叫模板多态
template<class RandomAccessIterator, class T, class Distance>
RandomAccessIterator __lower_bound(  RandomAccessIteratorfirst,  RandomAccessIterator last, const T& value, Distance*, random_access_iterator_tag)
{
	Distance len = last - first;
	Distance half;
 	RandomAccessIterator middle;
 	while( len > 0)
 	{
 		half = len >> 1; 			//除以2,位操作比较快
 		middle = first + half;
 		if( *middle < value )		//如果中间位置的元素值 < 目标值
 		{
 			first = middle+1;		//令 first 指向 middle 的下一位置
 			len = len - half -1;	//修正 len,回头测试循环的结束条件
 		}
 		else
 			len = half;
 	}
 	
	return first;
}

5、upper_bound

upper_bound 和 lower_bound 差不多。它会返回 “在不破坏顺序的情况下,可插入value的最后一个合适位置”。

由于STL 规范“区间圈定” 时的起头和结尾并不对称,左闭右开,所以upper_bound 与 lower_bound 的返回值意义大有不同。如果你查找某值,而它的确出现在区间之内,则 lower_bound 返回的是一个指向该元素的迭代器。然而 upper_bound 不这么做。因为 upper_bound 返回的是在不破坏排序状态的情况下,value可被插入的 “最后一个” 合适位置。如果 value 存在,那么它返回的迭代器将指向value 的下一个位置,而非指向 value 本身。
template<class ForwardIterator, class T>
inline ForwardIterator upper_bound(ForwardIterator first,ForwardIterator last, const T& value)
{
return __upper_bound(first, last, value, distance_type(first), iterator_category(first));
}

template<class ForwardIterator, class T, class Distance>
inline ForwardIterator __upper_bound( ForwardIterator first, ForwardIterator last, const T& value, Distance*, forward_iterator_tag)
{
	Distance len = 0;
	distance( first, last, len);	//求取整个区间的长度 len
	
	Distance half;
	ForwardIterator middle;
	while( len > 0 )
	{
		half = len >> 1; 			//除以2,位操作比较快
 		middle = first;				//这一行和下一行是调整middle 节点
 		advance(middle, half);	
 		if( value < *middle)		//如果中间位置的元素值 > 目标值
     		len = half;				//修正 len,回头测试循环的结束条件
     	else
 		{
 			first = middle;			//令 first 指向 middle 的下一位置
 			++first;
 			len = len - half -1;	//修正 len,回头测试循环的结束条件
 		}     			
	}
	
	return first;
}

//前面版本的重载函数,这叫模板多态
template<class RandomAccessIterator, class T, class Distance>
RandomAccessIterator __lower_bound(  RandomAccessIteratorfirst,  RandomAccessIterator last, const T& value, Distance*, random_access_iterator_tag)
{
	Distance len = last - first;
	Distance half;
 	RandomAccessIterator middle;
 	while( len > 0)
 	{
 		half = len >> 1; 			//除以2,位操作比较快
 		middle = first + half;
 		if( value < *middle )		//如果中间位置的元素值 > 目标值
 			len = half;				//修正 len,回头测试循环的结束条件
 		else
 		{
 			first = middle+1;		//令 first 指向 middle 的下一位置
 			len = len - half -1;	//修正 len,回头测试循环的结束条件
 		}
 	}
 	
	return first;
}

6、binary_search

binary_search 是一种二分查找法,试图在以排序的 [first, last) 中寻找元素 value。如果 [first, last) 内有等用于 value 的元素,便返回 true,否则返回 false。

binary_search 利用了 lower_bound 的特性,所以底层调用 lower_bound.

tempalte<class ForwardIterator, class T>
bool binary_search(ForwardIterator first, ForwardIterator last, const T& value)
{
ForwardIterator i = lower_bound(first, last, value);
return i != last && !(value < *i);
}

7、equal_range

equal_range 也是二分查找法的一个版本, 试图在已排序的 [first, last) 中寻找 value。它返回一对迭代器 i 和 j,其中 i 是在不破坏次序的前提下,value 可插入的第一个位置(即 lower_bound),j 则是在不破坏次序的前提下, value 可插入的最后一个位置(即 upper_bound )。因此,[i,j) 内的每个元素都等同于 value,而且 [i,j) 是 [first, last) 之中符合此特性的最大子区间。

equal_range 的代码如下:

template<class ForwardIterator, class T>
inline pair<ForwardIterator, ForwardIterator>
equal_range( ForwarIterator first, ForwarIterator last, const T& value)
{
	return __equal_range(first, last, value, distance_type(first), iterator_category(first));
}

//Random_access_iterator版本
tempalte<class RandomAccessIterator, class T, class Distance>
pair<RandomAccessIterator, RandomAccessIterator>    
__equal_range(RandomAccessIterator first, RandomAccessIterator last,
			const T& value, Distance*, random_access_iterator_tag)
{
	Distance len = last - first;
	Distance half;
	RandomAccessIterator middle, left, right;
	while( len > 0 )
	{
		half = len >> 1;
		middle = first + half;
		if( *middle < value)
		{
			first = middle + 1;
			len = len - half - 1;
		}
		else if( value < *middle )
		 	len = half;
		 else
		 {
		 	left = lower_bound(first, middle, value);
		 	right = upper_bound(++middle, first + len, value);
		 	return pair<...>(left, right);
		 }			
	}
	
	return pair<...>( first, first );
}

//forward_iterator 版本
tempalte<class ForwardIterator, class T, class Distance>
pair<ForwardIterator, ForwardIterator>    
__equal_range(ForwardIterator first, ForwardIterator last,
			const T& value, Distance*, forward_iterator_tag)
{
	Distance len = 0;
	distance( first, last, len);
	
	Distance half;
	RandomAccessIterator middle, left, right;
	
	while( len > 0 )
	{
		half = len >> 1;
		middle = first;
		advance(middle, half);
		if( *middle < value)
		{
			first = middle;
			++first;
			len = len - half - 1;
		}
		else if( value < *middle )
		 	len = half;
		 else
		 {
		 	left = lower_bound(first, middle, value);
		 	advance(first, len);
		 	right = upper_bound(++middle, first, value);
		 	return pair<...>(left, right);
		 }			
	}
	
	return pair<...>( first, first );
}

8、inplace_merge

如果两个连接在一起的序列 [first, middle) 和 [middle, last) 都已排序,那么 inplace_merge 可将它们结合成单一一个序列,并扔保持有序,如果原先两个序列式递增排序,执行结果也会是递增排序,如果原先两个序列式递减排序,执行结果也会是递减排序。

inplace_merge 是一种稳定操作。代码如下:

template<class BidirectionalIterator>
inline void inplace_merge(BidirectionalIterator first, BidirectionalIterator middle, BidirectionalIterator last)
{
	//如果任何一个序列为空,就什么都不必做
	if( first == middle || middle == last) 
		return;
	
	__inplace_merge_aux(first, middle, last, value_type(first), distance_type(first));
}

  template<class BidirectionalIterator, class T, class Distance>
  inline void __inplace_merge_aux(BidirectionalIterator first,
  									BidirectionalIterator middle,
  									BidirectionalIterator last,
  									T* , Distance*)
  {
  	Distance len1 = 0;
  	distance(first, middle, len1);
  	Distance len2 = 0;
  	distance(middle, last, len2);   
	
	//注意,会使用额外的内存空间
	temporary_buffer<BidirectionalIterator, T> buf(first, last);
	if( buf.begin() == 0 )	//内存配置失败
		__merge_without_buffer(first, middle, last, len1, len2);
	else
		__merge_adaptive(first, middle, last, len1, len2, buf.begin(), Distance(buf.size())));
  }

这个算法如果有额外的内存辅助,效率会好很多。但是在没有缓冲区或缓冲区不足的情况下,也可以运作。但这里对内存配置失败的就不整理了。

//辅助函数。有缓冲区的情况下
template<class BidirectionalIterator, class Distance, class Pointer>
void __merge_adaptive(BidirectionalIterator first, 
						BidirectionalIterator middle, 
						BidirectionalIterator last, 
						Distance len1, 
						Distance len2, 
						Pointer buffer, 
						Distance buffer_size)
{
	//缓冲区足够安置序列一
	if( len1 <= len2 && len1 <= buffer_size)
	{
		Pointer end_buffer = copy(first, middle, buffer);
		merge(buffer, end_buffer, middle, last, first);
	}
	else if( len2 <= buffer_size)
	{
		//缓冲区空间足够安置序列二
		Pointer end_buffer = copy(middle, last, buffer);
		__merge_backward(first, middle, buffer, end_buffer, last);
	}
	else
	{
		//缓冲区空间不足安置任何一个序列
		BidirectionalIterator first_cut = first;
		BidirectionalIterator second_cut = middle;
		Distance len11 = 0;
		Distance len22 = 0;
		if( len1 > len2)
		{
			//序列一比较长
			len11 = len1/2;
			advance(first_cut, len11);
			second_cut = lower_bound(middle, last, *frist_cut);
			distance(middle, second_cut, len22);
		}
		else
		{
			len22 = len2 / 2;
			advance(second_cut, len22);
			first_cut = upper_bound(first, middle, *second_cut);
			distance(first, first_cut, len11);
		}
		
		BidirectionalIterator new_middle = __rotate_adaptive(first_cut, middle, second_cut, len1 - len11, len22, buffer, buffer_size);

		//针对左端,递归调用
		__merge_adaptive(first, first_cut, new_middle, len11, len22, buffer, buffer_size);

		//针对右端,递归调用
		__merge_adaptive(new_middle, second_cut, last, len1-len11, len2-len22, buffer, buffer_size);
	}
}

上述辅助函数首先判断缓冲区是否足以容纳 inplace_merge 所接受的两个序列中的任何一个。如果空间充裕,逻辑就很简单:把两个序列中的某一个 copy 到缓冲区中,在使用 merge 完成其余工作。
在这里插入图片描述
但当缓冲区不足以容纳任何一个序列时,我们以递归分割的方式,让处理长度减半,看看能否容纳于缓冲区中。如果能,就切割较长序列,然后通过算法,从较短序列中,查找比切割序列最后一个元素大的第一个元素,两个区间进行旋转操作。如下图:
在这里插入图片描述
在这里插入图片描述
这样就变成了两段段较小的 序列进行合并,然后递归调用处理左半部分,递归调用处理右半部分。

如果缓冲区的空间还是不足,则调用rotate函数执行,还是在 __rotate_adaptive 函数中。代码如下:

template<class BidirectionalIterator1, class BidirectionalIterator2, class Distance>
BidirectionalIterator1 __rotate_adaptive(BidirectionalIterator1 first, 
										BidirectionalIterator1 middle, 
										BidirectionalIterator1 last,
										Distance len1,
										Distance len2,
										BidirectionalIterator2 buffer,
										Distance buffer_size )
{
	BidirectionalIterator2 buffer_end;
	if( len1 > len2 && len2 <= buffer_size)
	{
		//缓冲区足够安置序列二(较短)
		buffer_end = copy(middle, last, buffer);
		copy_backward( first, middle, last);
		return copy(buffer, buffer_end, first);
	}
	else if( len1 <= buffer_size)
	{
		//缓冲区足够安置序列一
		buffer_end = copy(first, middle, buffer);
		copy(middle, last, first);
		return copy_backward(buffer, buffer_end, last);
	}
	else
	{
		//缓冲区仍然不足,改用rotate算法,不需要缓冲区
		rotate(first, middle, last);
		advance(first, len2);
		return first;
	}
}

感谢大家,我是假装很努力的YoungYangD(小羊)

参考资料:
《STL源码剖析》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值