更多精彩內容,請關注微信公衆號:後端技術小屋c++
STL中實現了一些跟容器相關的一些算法。這裏介紹algorithm
頭文件中一些有意思的算法實現。算法
stl_algo.h stl_algobase.h stl_numeric.h
algorithm
頭文件中定義的find
函數可適用於全部定義了迭代器的STL容器。可是一些經常使用的容器如map/unordered_map/set/unordered_set也定義了類內方法find
。當須要搜索元素時,咱們應當選擇類內find
方法仍是類外find
函數呢?後端
當須要搜索容器內某個元素時,應當優先使用類內find
方法, 由於其性能不低於類外函數find
。舉例來在unordered_map
中類內find
的時間複雜度爲O(1)
,而類外函數find
的時間複雜度爲O(n)
。微信
如下爲類外find
的實現。首先經過iterator_traits
獲取迭代器的類型(可參考STL源碼分析--iterator)。判斷其爲通常的input iterator仍是random access iterator。而後針對這兩種狀況對find進行了重載。dom
template <class _InputIter, class _Tp> inline _InputIter find(_InputIter __first, _InputIter __last, const _Tp& __val) { __STL_REQUIRES(_InputIter, _InputIterator); __STL_REQUIRES_BINARY_OP(_OP_EQUAL, bool, typename iterator_traits<_InputIter>::value_type, _Tp); return find(__first, __last, __val, __ITERATOR_CATEGORY(__first)); } template <class _InputIter, class _Tp> inline _InputIter find(_InputIter __first, _InputIter __last, const _Tp& __val, input_iterator_tag) { while (__first != __last && !(*__first == __val)) ++__first; return __first; } template <class _RandomAccessIter, class _Tp> _RandomAccessIter find(_RandomAccessIter __first, _RandomAccessIter __last, const _Tp& __val, random_access_iterator_tag) { typename iterator_traits<_RandomAccessIter>::difference_type __trip_count = (__last - __first) >> 2; for ( ; __trip_count > 0 ; --__trip_count) { if (*__first == __val) return __first; ++__first; if (*__first == __val) return __first; ++__first; if (*__first == __val) return __first; ++__first; if (*__first == __val) return __first; ++__first; } switch(__last - __first) { case 3: if (*__first == __val) return __first; ++__first; case 2: if (*__first == __val) return __first; ++__first; case 1: if (*__first == __val) return __first; ++__first; case 0: default: return __last; } }
find_if與find類似,只不過遍歷區間時,判斷條件變了,判斷元素值與目標值相等 --> 判斷元素值知足給定的判斷條件ide
template <class _InputIter, class _Predicate> inline _InputIter find_if(_InputIter __first, _InputIter __last, _Predicate __pred, input_iterator_tag) { while (__first != __last && !__pred(*__first)) ++__first; return __first; }
power用於計算一個數的指數,在實現中使用了快速冪算法,時間複雜度爲O(log n)
,其中n
爲power的冪次。函數
舉例說明快速冪算法:
計算: 3^7 = 3^(1+2+4) = (3^1) * (3^2) * (3*4) = 3 * (3^2) * (3^2)^2
源碼分析
很容易看出規律,用僞代碼表示:性能
result = 1 curr = x while (x) { if (x%2) { result *= curr } curr *= curr x >>= 1 } return result
在STL中實現:__opr
爲函數對象,用它可定義兩個_Tp
對象的乘法
操做,缺省爲multiplies<_Tp>
,對應的identity_element
結果爲1,由於任何數字乘1都等於其自己。指針
template <class _Tp, class _Integer> inline _Tp __power(_Tp __x, _Integer __n) { return __power(__x, __n, multiplies<_Tp>()); } template <class _Tp> inline _Tp identity_element(multiplies<_Tp>) { return _Tp(1); } template <class _Tp, class _Integer, class _MonoidOperation> _Tp __power(_Tp __x, _Integer __n, _MonoidOperation __opr) { if (__n == 0) return identity_element(__opr); else { while ((__n & 1) == 0) { __n >>= 1; __x = __opr(__x, __x); } _Tp __result = __x; __n >>= 1; while (__n != 0) { __x = __opr(__x, __x); if ((__n & 1) != 0) __result = __opr(__result, __x); __n >>= 1; } return __result; } }
交換兩個同類型對象的值。
注意,當對兩個容器進行交換時,應當儘可能使用容器類內swap
方法,由於algorithm中的swap
函數會生成臨時變量,執行額外的複製構造函數和析構函數,開銷較大。而類內swap
方法會盡可能避免這些開銷。兩者之因此會有這種差異是由於類外swap
函數沒法訪問容器的private/protect成員,沒法以最小代價實現交換。
template <class _Tp> inline void swap(_Tp& __a, _Tp& __b) { __STL_REQUIRES(_Tp, _Assignable); _Tp __tmp = __a; __a = __b; __b = __tmp; }
以vector
爲例,其類內swap以下:
void swap(vector<_Tp, _Alloc>& __x) { __STD::swap(_M_start, __x._M_start); __STD::swap(_M_finish, __x._M_finish); __STD::swap(_M_end_of_storage, __x._M_end_of_storage); }
若是想交換兩個vector
的值,使用algorithm中的swap
函數意味着生成一個臨時vector
,以及由此帶來的內存分配釋放、容器內元素構造/析構的開銷。而使用vector
類內swap
方法只須要交換三個指針便可,開銷相比類外swap可忽略不計。
rotate操做:對於容器區間[first, last), 給定旋轉點mid, rotate以後,區間將會變成[mid, last)+[first, mid)。
相信刷過leetcode這道題的同窗對roate必定不會陌生:https://leetcode.com/problems/rotate-array/
STL中針對正向迭代器(forward iterator),雙向迭代器(bidirectional iterator)和隨機迭代器(random access iterator)的特性重載了rotate算法。雖然實現不一樣,可是對用戶暴露了一樣的接口,用戶不用care迭代器類型,由於STL中會經過萃取技術作類型推導。所以在性能和通用性獲得了很好的均衡。
假設容器區間左右兩個部分爲A B
, 且len(A) < len(B)
。又假設B
可拆分紅B1 B2 B3 B4
, 且len(B1) = len(B2) = len(B3) = len(A), len(B4) < len(A)
所以A B = A B1 B2 B3 B4
, rotate以後的結果: B A = B1 B2 B3 B4 A
算法描述:
第一步:
從作到右不斷交換相鄰的相同長度區間的值
A B1 B2 B3 B4
-->
B1 A B2 B3 B4
-->
B1 B2 A B3 B4
-->
B1 B2 B3 A B4
第二步:
注意,第一步的最終結果只須要對A B4 進行roate操做, 便可變成B1 B2 B3 B4 A
,也就是rotate最終要達到的效果。
len(A) > len(B4)
, 設A = A1 A2, len(A1) = len(B4)
, 所以A B4 = A1 A2 B4
, B4 A = B4 A1 A2
。交換A1和B4, 獲得B4 A2 A1
, 此時問題轉化爲交換A1和A2算法時間複雜度:n次swap
操做,也就是3n次賦值操做
template <class _ForwardIter, class _Distance> _ForwardIter __rotate(_ForwardIter __first, _ForwardIter __middle, _ForwardIter __last, _Distance*, forward_iterator_tag) { if (__first == __middle) return __last; if (__last == __middle) return __first; _ForwardIter __first2 = __middle; do { swap(*__first++, *__first2++); if (__first == __middle) __middle = __first2; } while (__first2 != __last); _ForwardIter __new_middle = __first; __first2 = __middle; while (__first2 != __last) { swap (*__first++, *__first2++); if (__first == __middle) __middle = __first2; else if (__first2 == __last) __first2 = __middle; } return __new_middle; }
原理:
由於雙向迭代器既可前進,有可後退,所以rotate的實現比正向迭代器簡單的多。設A B, roate以後爲B A
第一輪:
翻轉A => reverse(A) B
第二輪
翻轉B => reverse(A) reverse(B)
第三輪
翻轉整個區間 => reverse(reverse(A) reverse(B))
=> B A
時間複雜度:交換n次,也就是賦值3n次
template <class _BidirectionalIter, class _Distance> _BidirectionalIter __rotate(_BidirectionalIter __first, _BidirectionalIter __middle, _BidirectionalIter __last, _Distance*, bidirectional_iterator_tag) { __STL_REQUIRES(_BidirectionalIter, _Mutable_BidirectionalIterator); if (__first == __middle) return __last; if (__last == __middle) return __first; __reverse(__first, __middle, bidirectional_iterator_tag()); __reverse(__middle, __last, bidirectional_iterator_tag()); while (__first != __middle && __middle != __last) swap (*__first++, *--__last); if (__first == __middle) { __reverse(__middle, __last, bidirectional_iterator_tag()); return __last; } else { __reverse(__first, __middle, bidirectional_iterator_tag()); return __first; } }
這個實現其實涉及到一個羣論中定理:
設區間由A B 組成,len(A) = m, len(B) = n, gcd(m, m+n)= s
(gcd表示兩個整數的最大公約數)
那麼分別以0, 1, 2, ..., s-1位置爲初始點,經過x(n) = [x(n-1) + m] % (m+n)
的規則迭代(終止條件:再次回到初始點),正好能夠惟一遍歷區間上的全部位置
證實:
首先證實以不一樣初始點迭代時,遍歷的點中沒有交集,使用反證法:
假設0<= a < s, 0<= b < s, a < b
, 以a, b爲初始點迭代時,遍歷的點中有交集。
=> 存在k1, k2, 使得 (a + k1 * m) % (m+n) = (b + k2 * m) % (m+n)
=> (b - a) % (m+n) = [(k2 - k1) * m] % (m+n)
=> b-a = (m * k3) % (m+n)
由於s = gcd(m, m+n)
=> b-a = k4 * s, k4 * s < m + n
由於0 < b-a < s
=> k4 = 0 => b = a
獲得矛盾,所以得證。
再證實以a, 0 <= a <s
爲起始點迭代,可以剛好遍歷(m+n)/s
個點
=> 若是不考慮終止條件,迭代必然陷入死循環,死循環的週期(單位:迭代次數)爲(m+n)/s
=> 對於任意a, 存在0 <= k1 < k2
, 使得(a + k1 * m) % (m+n) = (a + k2 * m) % (m+n)
=> (k2 - k1)m = k3 * (m+n)
=> k2 - k1 = k3 * (m+n) / m
=> k2 - k1 = k3 * x / y
, 其中x與y互質
=> 爲使k2-k1
最小,k3 = y = m/s
=> k2 - k1 = m/s * (m+n)/m = (m+n)/s
=> 迭代週期爲(m+n)/s
=> 得證
綜合第一和第二結論,上述定理得證。
在證實了上述結論以後,再回到rotate
話題:
s = gcd(m, m+n)
x(n) = [x(n-1) + m] % (m+n)
的規則計算下一個點,直到回到起始點,這些點組成一個環,對這個環內全部位置的元素逆時針移動時間複雜度:賦值n次,說明在容器可隨機訪問的狀況下,時間複雜度是其餘狀況的1/3
template <class _RandomAccessIter, class _Distance, class _Tp> _RandomAccessIter __rotate(_RandomAccessIter __first, _RandomAccessIter __middle, _RandomAccessIter __last, _Distance *, _Tp *) { __STL_REQUIRES(_RandomAccessIter, _Mutable_RandomAccessIterator); _Distance __n = __last - __first; _Distance __k = __middle - __first; _Distance __l = __n - __k; _RandomAccessIter __result = __first + (__last - __middle); if (__k == 0) return __last; else if (__k == __l) { swap_ranges(__first, __middle, __middle); return __result; } _Distance __d = __gcd(__n, __k); for (_Distance __i = 0; __i < __d; __i++) { _Tp __tmp = *__first; _RandomAccessIter __p = __first; if (__k < __l) { for (_Distance __j = 0; __j < __l/__d; __j++) { if (__p > __first + __l) { *__p = *(__p - __l); __p -= __l; } *__p = *(__p + __k); __p += __k; } } else { for (_Distance __j = 0; __j < __k/__d - 1; __j ++) { if (__p < __last - __k) { *__p = *(__p + __k); __p += __k; } *__p = * (__p - __l); __p -= __l; } } *__p = __tmp; ++__first; } return __result; }
對於區間內每一個元素執行__f
。注意,這裏__f
既能夠是函數對象,也能夠是函數指針或std::function
類型,__first
和__last
指定了執行__f
的容器區間。
// for_each. Apply a function to every element of a range. template <class _InputIter, class _Function> _Function for_each(_InputIter __first, _InputIter __last, _Function __f) { __STL_REQUIRES(_InputIter, _InputIterator); for ( ; __first != __last; ++__first) __f(*__first); return __f; }
推薦閱讀
更多精彩內容,請掃碼關注微信公衆號:後端技術小屋。若是以爲文章對你有幫助的話,請多多分享、轉發、在看。