C++变量和STL容器的交换
变量交换
现在有两个int
型变量x
和y
,需要将x
和y
的值进行交换
临时变量版
这样显然是不行的:
x=y;
y=x;
因为当x
被赋值为y
,那么x
和y
所表示的均为原来y
的值,原来x
表示的数据将丢失
不难想到使用一个临时变量tmp
暂存其中一方的数据:
int tmp=x;
x=y;
y=tmp;
算术运算版
之前的方法需要使用额外的变量来实现,那么有没有不使用额外变量的方法呢?当然是有的:
x=x+y;
y=x-y;
x=x-y;
语句非常对称也非常精妙,第一句将x
赋值为x
与y
的和,这样原来的x
仍然可以通过x-y
表示出来,而交换后的y
就是原来的x
,所以第二句赋值语句就将y
赋值成了原来的x
,此时x
仍然表示原来两个变量的和,所以交换后的x
也即原来的y
的值就是x-y
位运算版
x^=y;
y^=x;
x^=y;
这个可以说是 xor 艺术了,原理与上一种类似,但是由于是位运算,运算速度要优于前面一种
汇编版
__asm
{
mov eax,[x]
xchg eax,[y]
mov [x],eax
}
同样也是三行,不过之前的如果编译成汇编语言肯定不止三行,所以这个版本在性能和占用空间上碾压前三者
不过应该不会有人这么用的,仅供观赏,要慎用
函数版
C++中提供了用于变量交换的swap
函数,只需要把两个变量传入即可:
swap(x,y)
swap
函数的内部实现:
template <class _Ty>
_NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept { // forward _Arg as movable
return static_cast<remove_reference_t<_Ty>&&>(_Arg);
}
#if _HAS_CXX17
template <class _Ty, enable_if_t<is_move_constructible_v<_Ty> && is_move_assignable_v<_Ty>, int> _Enabled>
#else // ^^^ _HAS_CXX17 / !_HAS_CXX17 vvv
template <class _Ty, int _Enabled>
#endif // _HAS_CXX17
_CONSTEXPR20 void swap(_Ty& _Left, _Ty& _Right) noexcept(
is_nothrow_move_constructible_v<_Ty>&& is_nothrow_move_assignable_v<_Ty>) {
_Ty _Tmp = _STD move(_Left);
_Left = _STD move(_Right);
_Right = _STD move(_Tmp);
}
可以发现其内部机理与第一种方法类似,只是用了泛型并且增加了一些修饰
STL容器交换
两个对象之间的交换,我们就没必要考虑时间复杂度的常数问题,而应该考虑与数据量的相关性
首先先考虑两个普通的int
数组x[1048576]
,y[1048576]
的交换,朴素的做法是对其中的每一个元素做一次交换:
for(int i=0;i<1048576;i++) swap(x[i],y[i]);
swap
函数也提供了两个数组交换的接口,与上面的实现过程一致
swap(x,y)
这样的时间复杂度是 \(O(n)\) ,换个角度思考,两个数组事实上只是两块内存中的连续单元,不像同一个数组中两个元素进行交换会影响结果,它们交换与否丝毫不影响算法的正确性,内容的交换等效于对它们的变量名进行交换,但是C++又不支持动态更改变量名,所以我们需要使用指针或引用,交换时也仅需交换指针或引用:
int *p1=x,*p2=y;
swap(p1,p2);
正是基于这个思想,stl的许多容器进行swap
操作的时间复杂度都是 \(O(1)\),包括:vector
,list
,map
,set
,deque
,
而priority_queue
,queue
,stack
在开C++11时为 \(O(1)\),否则为 \(O(n)\)