c++ stl nth_element 原理解析


  nth_element是stl中的一個庫函數,該函數可以從某個序列中找到第 n 小的元素 K,並將 K 移動到序列中第 n 的位置處。不僅如此,整個序列經過 nth_element() 函數處理后,所有位於 K 之前的元素都比 K 小,所有位於 K 之后的元素都比 K 大。

但這個函數與完整排序的區別在於:

  1.它只關注第n個,只保證小於該值的元素在其左邊,大於等於的在其右邊,但並不保證其完全有序。

  2.它的時間復雜度在O(N),而許多排序所用的復雜度為O(NlogN)。對於特定的找到第k大數的問題,所花時間是好於排序的。

 

它的原理思想如圖

  這個算法的思路很像快排中partation函數。首先有一個first ,nth,last,首先找到這三個所在位置值的中位數,這里22,40,20中位數為22。

  然后對於22進行第一輪,方法同partation,22則作為錨點。

  然后這里找到右端的第一個(如果是數組為0-10裝着上圖的值),這里右端第一個即數組下標為5的數字40,而傳入我們要找的nth也為5(其實即找到0-10中第6大的數),因此其小於等於右端起點,於是進入右段進行下一輪。

  下一輪同理40,30,22取30 進行交換使cut兩端被分割。

  這時候再右端第一個和nth再進入,這時判斷到子序列已經不大於3了,直接用插入排序。最后即得到第nth(這里傳入nth為5,實際即第六大的數)為22.

 

以下是代碼的解析:

下面是nth_element在本人電腦中stl的源碼(可以跳過)

template<typename _RandomAccessIterator, typename _Compare>
    inline void
    nth_element(_RandomAccessIterator __first, _RandomAccessIterator __nth,
        _RandomAccessIterator __last, _Compare __comp)
    {
      // concept requirements
      __glibcxx_function_requires(_Mutable_RandomAccessIteratorConcept<
                  _RandomAccessIterator>)
      __glibcxx_function_requires(_BinaryPredicateConcept<_Compare,
        typename iterator_traits<_RandomAccessIterator>::value_type,
        typename iterator_traits<_RandomAccessIterator>::value_type>)
      __glibcxx_requires_valid_range(__first, __nth);
      __glibcxx_requires_valid_range(__nth, __last);

      if (__first == __last || __nth == __last)
    return;

      std::__introselect(__first, __nth, __last,
             std::__lg(__last - __first) * 2,
             __gnu_cxx::__ops::__iter_comp_iter(__comp));
    }
View Code

 

其中直接講其核心的__introselect函數部分

template<typename _RandomAccessIterator, typename _Size, typename _Compare>
    void
    __introselect(_RandomAccessIterator __first, _RandomAccessIterator __nth,
          _RandomAccessIterator __last, _Size __depth_limit,
          _Compare __comp)
    {
      while (__last - __first > 3)
    {
      if (__depth_limit == 0)
        {
          std::__heap_select(__first, __nth + 1, __last, __comp);
          // Place the nth largest element in its final position.
          std::iter_swap(__first, __nth);
          return;
        }
      --__depth_limit;                                               
      _RandomAccessIterator __cut =      
        std::__unguarded_partition_pivot(__first, __last, __comp); //這里函數返回的是分割后右段的第一個元素。后面會詳細講解一下這個的功能
      if (__cut <= __nth)  //如果右段起點小於等於nth 
        __first = __cut;   //進入右段,對右段進行下次的分割
      else
        __last = __cut;
    }
      std::__insertion_sort(__first, __last, __comp);
    }

這個算法的思路很像快排中partation函數的思路。但對於段長度在3時候,就選擇使用插入排序。

 

這里的關鍵是__unguarded_partition_pivot函數部分

 template<typename _RandomAccessIterator, typename _Compare>
    inline _RandomAccessIterator
    __unguarded_partition_pivot(_RandomAccessIterator __first,
                _RandomAccessIterator __last, _Compare __comp)
    {
      _RandomAccessIterator __mid = __first + (__last - __first) / 2;  
      std::__move_median_to_first(__first, __first + 1, __mid, __last - 1,    //對first last mid排序找到中位值
                  __comp);                                                    //這里的中位值即下面的pivot點
      return std::__unguarded_partition(__first + 1, __last, __first, __comp);  //這里即實現了對於類似快排若左邊大於pivot則與右邊小於pivot的交換
    }                                                                           //並返回的是右端第一個

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM