C++ STL(第二十三篇:算法-- 单纯的数据处理算法)

本文深入解析STL中的数据处理算法,包括rotate、rotate_copy、search、search_n、unique、unique_copy、nth_element等函数的实现原理及应用。同时,详细介绍了排列组合算法next_permutation、prev_permutation和random_shuffle的工作机制。

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

1、概述

前面整理了 STL 单纯的数据处理算法,因为太多,分为两个部分,今天就把剩余的部分给整理完毕,这样 STL 的内容就告一段落了。

rotate:将 [first, middle) 内的元素和 [middle, last) 内的元素互换。 middle 所指的元素会成为容器的第一个元素。如果有个数字序列 {1,2,3,4,5,6,7},对元素 3 做旋转操作,会形成{3,4,5,6,7,1,2}。迭代器的移动能力,决定了这个算法的效率,所以有很多个版本。代码如下所示:

template<class ForwardIterator>
inline void rotate( ForwardIteartor first, 
					ForwardIteartor middle, 
					ForwardIteartor last)
{
	if( first == middle || middle == last )
		return;
	
	__rotate( first, middle, last, distance_type(first), iterator_category(first));
}

template<class ForwardIterator, class Distance>
void __rotate(	ForwardIterator first,
				ForwardIterator middle,
				ForwardIterator last, 
				Distance*, 
    			forward_iterator_tag)
{
	for(ForwardIterator  i = middle; )
	{
		iter_swap(first,middle);		//前段、后段的元素一一交换
		++first;						//双双前进1
		++i;
		
		//以下判断是前段先结束  还是  后段先结束
		if( first == middle )			//前段结束
		{
			if( i == last ) 			//如果后段也结束,整个就结束了
				 return;
			middle = i;					//否则调整,对新的前、后段再作交换
		}
		else if( i == last )			//后段先结束
			i = middle;					//调整,准备对新的前、后段再作交换
	}
}

template<class BidirectionalIterator, class Distance>
void __rotate(	BidirectionalIterator first, 
				BidirectionalIterator middle,
				BidirectionalIterator last, 
				Distance*,
				bidirectional_iterator_tag)
{
	reverse(first,middle);
	reverse(middle, last);
	reverse(first, last);
}

template<class RandomAccessIterator, class Distance>
void __rotate(	RandomAccessIterator first, 
				RandomAccessIterator middle, 
				RandomAccessIterator last,
				Distance*, 
				random_access_iterator_tag)
{
	//以下迭代器相减的操作,只适用于 random_access_iterator
	Distance n = __gcd( last - first, middle - first);
	while( n-- )
		__rotate_cycle(first, last, first+n, middle - first, value_type(first));
}

//最大公因数,利用辗转相除法
template<class EuclideanRingElement>
 EuclideanRingElement __gcd(  EuclideanRingElement m,  EuclideanRingElement n)
 {
 	while( n != 0 )
 	{
 		 EuclideanRingElement t = m%n;
 		 m = n;
 		 n = t;
 	}
 	return m;
 }

template<class RandomAccessIterator, class Distance, class T>
void __rotate_cycle(RandomAccessIterator first, 
					RandomAccessIterator last, 
					RandomAccessIterator initial, 
					Distance shift, T*)
{
	T value = *initial;
	RandomAccessIterator  ptr1 = initial;
	RandomAccessIterator  ptr2 = ptr1 + shift;
	while( ptr2 != initial)
	{
		*ptr1 = *ptr2;
		ptr1 = ptr2;
		if( last - ptr2 > shift )
			ptr2 += shift;
		else
			ptr2 = first + (shift - (last - ptr2));
	}
	*ptr1 = value;
}

对于前两种迭代器的算法,很好理解,但是对于随机访问迭代器的就比较麻烦,这里我就不展开整理了,附上链接一份,大家可以仔细的研究一下 __rotate_cycle 详解

rotate_copy:行为类似 rotate,但产生出来的新序列会被置于 result 所指出的容器总。返回值 OutputIterator 指向新产生的最后元素的下一位置。

template< class ForwardIterator, class OutputIterator>
OutputIterator rotate_copy( ForwardIterator first,
							ForwardIterator middle, 
							ForwardIterator last, 
							OutputIterator result)
{
	return copy(first,middle, copy(middle, last, result));
}

search:在序列一 [first1, last1) 所涵盖的区间中,查找序列二 [first2, last2) 的首次出现点。如果序列一内不存在与序列二完全匹配的子序列,便返回迭代器 last1。

template<class ForwardIterator1, class ForwardIterator2>
inline ForwardIterator1 serch(	ForwardIterator1 first1, 
								ForwardIterator1 last1, 
								ForwardIterator2 first1, 
								ForwardIterator2 last2)
{
	return __search(first1, last1, first2, last2, distance_type(first1), distance_type(first2));
}

template<class ForwardIterator1, class ForwardIterator2, class Distance1, class Distance2>
ForwardIterator1 __serch(	ForwardIterator1 first1, 
							ForwardIterator1 last1, 
							ForwardIterator2 first1, 
							ForwardIterator2 last2, 
							Distance1*, 
							Distance2* )
{
	Distance1 d1 = 0;
	distance(first1, last1, d1);
	Distance2 d2 = 0;
	distance(first2, last2, d2);
	if( d1 < d2 )
		return last1;
	
	ForwardIterator1  current1 = first1;
	ForwardIterator1  current2 = first2;
	
	while( current2 != last2)
	{
		if( *current1 == *current2)
		{
			++current1;
			++current2;
		}
		else
		{
			if( d1 == d2 )
				return last1;
			else
			{
				++current1;
				current2 = first2;
				--d1;
			}
		}
	}
	return first1;
}

search_n:在序列 [first, last) 所涵盖的区间内,查找 "连续 count 个符合条件之元素” 所形成的子序列,并返回一个迭代器指向该子序列起始处。如果找不到这样的子序列,就返回迭代器 last。

template<class ForwardIterator, class Integer, class T>
ForwardIterator search_n( ForwardIterator first, ForwardIterator last, Integer count, const T& value)
{
	if( n <= 0 )
		return first;
	
	first = find(first, last, value);
	while( first != last)
	{
		Integer n = count -1;
		
		ForwardIterator i = first;
		++i;
		while( i != last && n != 0 && *i == value )
		{
			++i;
			--n;
		}
	
		if( n == 0)
			return first;
		else
			first = find(i, last, value);
	}

	return last;
}

unique:移除 [first, last) 相邻元素的重复元素。如果想移除所有的重复元素,必须先将序列排序。

template< class ForwardIterator>
ForwardIterator unique( ForwardIterator first, ForwardIterator last)
{
	first = adjacent_find( first, last);
	return unique_copy(first, last, first);
}

unique_copy:和unique差不多,只是将元素复制到以 result 开头的区间上。

template<class InputIterator, class OutputIterator>
inline OutputIterator unique_copy(	InputIterator first, 
									InputIterator last, 
									OutputIterator result)
{
	if( first == last )
		return result;
	
	return __unique_copy( first, last, result, iterator_category(result));
}

//不同的迭代器,不同的版本
template<class InputIterator, class ForwardIterator>
ForwardIterator __unique_copy(	InputIterator first, 
								InputIterator last, 
								ForwardIterator result, 
								forward_iterator_tag)
{
	*result = *first;
	while( ++first != last )
	{
		if( *result != *first)
			*++result = *first;
	}
	
	return ++result;
}
//因为这个比较简单,其它版本就不写了

nth_element:重新排列 [first, last),使迭代器 nth 所指的元素,与“整个 [first, last) 完整排序后,同一位置的元素” 同值。
此外并保证 [nth, last) 内没有任何一个元素小于 [first, nth) 内的元素,但对于 [first, nth) 和 [nth, last) 两个子区间内的元素次序则无任何保证。 nth_element 比较类似 partition。

由于 nth_element 比 partial_sort(局部排序) 保证的更少,所以绝大数情况下比 partial_sort 更快。

nth_element 的原理是,不断的以 median-of-3 partitioning(三点中值为枢轴之分割法)将整个序列分割为更小的左、右子序列。如果 nth 迭代器落于左子序列,就再对左子序列进行分割,否则就再对右子序列进行分割。以此类推,直到分割后的子序列长度不大于3,便对最后这个待分割的子序列做 Insertion Sort,大功告成。

template<class RandomAccessIterator>
inline void nth_element( RandomAccessIterator first, RandomAccessIterator nth, RandomAccessIterator last)
{
	__nth_element(first, nth, last, value_type(first));
}

template<class RandomAccessIterator, class T>
void __nth_element( RandomAccessIterator first, RandomAccessIterator nth, RandomAccessIterator last, T*)
{
	//长度超过3,不断的调用
	while( last - first > 3)
	{
		//采用三点中值分割法,返回一个迭代器,指向分割后的右段第一个元素
		RandomAccessIterator cut = __unguarded_partition(first, last, 
						T(__median(*first, *(first + (last - first)/2), *(last-1))));
		
		if( cut <= nth)
			first = cut;
		else
			last = cut;
	}
	__insertion_sort(first, last);
}

上面用到的__unguarded_partition 在整理 排序中的 sort 时整理过了,就不重复整理了。

2、排列组合算法

STL 提供了两个用来计算排列组合关系的算法,分别是 next_permucation 和 prev_permutation。

对于三个字符所组成的序列 {a,b,c}。这个序列有六个可能的排列组合: abc,acb,bac,bca,cab,cba。这些排列组合根据 less-than 操作符做字典顺序的排序。abc 名列第一,因为每一个元素都小于其后的元素。acb是次一个排列组合,它是固定了a之后所做的新组合。

2.1、next_permucation

next_permucation() 会取得 [first, last) 所标示之序列的下一个排列组合。如果没有下一个排序组合,便返回 false;否则返回 true。

首先,从最尾端开始往前寻找两个相邻元素,令第一元素为 *i,第二元素为 *ii,且满足 *i < *ii。找到这样一组相邻元素后,再从最尾端开始往前检查,找出第一个大于 *i 的元素,令 i 和 j 的元素对调,再将 ii 之后的所有元素颠倒排序。此即所求之 “下一个” 排列组合。

template<class BidirectionalIterator>
bool next_permutation(BidirectionalIterator first, BidirectionalIterator last)
{
	if( first == last )
		return false;
	
	BidirectionalIterator  i = first;
	++i;
	if( i == last )		//只有一个元素
		return false;
	
	i = last;			//指向尾端
	--i;

	for(;;)
	{
		BidirectionalIterator  ii = i;
		--i;

		//锁定一组相邻元素
		if( *i < *ii )
		{
			//从尾端往前找,知道遇上比 *i 大的元素
			BidirectionalIterator  j = last;
			while( !(*i < *--j));
			
			//交换 i,j 将 ii 之后的元素进行逆向重排
			iter_swap(i,j);
			reverse(ii, last);
			return true;
		}
		
		//进行至最前面了
		if( i == first )
		{
			reverse( first, last);	//全部逆向重排
			return false;
		}
	}
}

2.2、prev_permutation

prev_permutation 会取得 “前一个” 排列组合。首先,从最尾端开始往前寻找两个相邻元素,令第一元素为 i,第二元素为ii,且满足 *i > *ii。找到这样一组相邻元素后,再从尾端开始往前检验,找出第一个小于 *i 的元素,将 i,j元素对调,再将 ii 之后的所有元素颠倒排序。代码如下:

  template<class BidirectionalIterator>
  bool prev_permutation(BidirectionalIterator first, BidirectionalIterator last)
  {
  	if( first == last )
   		return false;
   	
   	BidirectionalIterator  i = first;
   	++i;
   	if( i == last )		//只有一个元素
   		return false;
   	
   	i = last;			//指向尾端
   	--i;
   
   	for(;;)
   	{
   		BidirectionalIterator  ii = i;
   		--i;
   
   		//锁定一组相邻元素
   		if( *i > *ii )
   		{
   			//从尾端往前找,知道遇上比 *i 大的元素
   			BidirectionalIterator  j = last;
   			while( !(*i > *--j));
   			
   			//交换 i,j 将 ii 之后的元素进行逆向重排
   			iter_swap(i,j);
   			reverse(ii, last);
   			return true;
   		}
   		
   		//进行至最前面了
   		if( i == first )
   		{
   			reverse( first, last);	//全部逆向重排
   			return false;
   		}
   	}
  }

2.3、random_shuffle

random_shuffle 是将 [first, last) 元素次序随机重排。如果 N = last - first,在 N! 种可能的元素排列顺序中随机选出一种。random_shuffle 有两个版本,差别在于随机数的取得。版本一使用内部随机数产生器,版本二使用一个会产生随机数的仿函数。代码如下:

template<class RandomAccessIterator>
inline void random_shuffle(RandomAccessIterator first, RandomAccessIterator last)
{
	__random_shuffle(first, last, distance_type(first));
}

template<class RandomAccessIterator, class Distance>
void __random_shuffle(RandomAccessIterator first, RandomAccessIterator last, Distance*)
{
	if( first == last)
		return;

	for( RandomAccessIterator  i = first + 1; i != last; ++i)
		iter_swap(i, first + Distance(rand() % ((i - first) + 1)));
}

OK,STL 的内容整理完毕,继续努力整理归纳其它的内容。

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值