加载中

C++变量和STL容器的交换

变量交换

现在有两个int型变量xy,需要将xy的值进行交换

临时变量版

这样显然是不行的:

x=y;
y=x;

因为当x被赋值为y,那么xy所表示的均为原来y的值,原来x表示的数据将丢失

不难想到使用一个临时变量tmp暂存其中一方的数据:

int tmp=x;
x=y;
y=tmp;

算术运算版

之前的方法需要使用额外的变量来实现,那么有没有不使用额外变量的方法呢?当然是有的:

x=x+y;
y=x-y;
x=x-y;

语句非常对称也非常精妙,第一句将x赋值为xy的和,这样原来的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)\),包括:vectorlistmapsetdeque
priority_queuequeuestack 在开C++11时为 \(O(1)\),否则为 \(O(n)\)

参考资料

posted @ 2021-08-19 21:08  fenggwsx  阅读(180)  评论(0编辑  收藏  举报