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)\)