簡體   English   中英

使用二進制搜索查找數字的第N次出現索引

[英]Find index of Nth occurrence of a number using Binary Search

我有一個有限數組,其元素僅為-1,0或1。我想找到一個數字的第N個出現的索引(例如0)。

我可以遍歷整個數組,但是我正在尋找一種更快的方法。 我可以考慮使用Binary Search,但是在對算法進行建模時遇到了麻煩。 在這種情況下,如何進行二進制搜索?

如果沒有至少一次O(N)預處理,則無法執行此操作。 僅從信息論的角度來看,您必須具有元素[0:k-1]的知識才能知道元素[k]是否是您想要的元素。

如果您打算多次進行此搜索,則可以對數組進行簡單的線性遍歷,並隨即對每個元素進行計數。 將索引存儲在2-D數組中,因此您可以直接為任何需要的索引創建索引。

例如,給定[-1 0 1 1 -1 -1 0 0 0 -1 1],您可以將其轉換為3xN數組idx

[[0 4 5 9]]
[[1 6 7 8]]
[[2 3 10]]

元素I的第N次出現是idx[I+1][N-1]

在初始O(N)通過之后,您將使用O(N)空間進行O(1)次查找。

由於您要搜索arrayvector或某個container ,其中根據元素T在容器中的Nth出現情況,該搜索與某個元素T的索引位置有關,因此該文章可能對您有幫助:


根據您的問題以及有關該問題的一些評論,當您考慮使用binary search並且在建模算法的過程中遇到麻煩時,您明確聲明容器Unsorted排序。

  • 此處的這篇文章作為算法設計開發過程的示例,可以幫助您實現所需的目標:

  • 這里的搜索算法是線性的,其中二進制搜索將不適合您的當前需求:

  • 構建算法的相同過程可以應用於其他類型的算法,包括二進制搜索,哈希表等。


-第一個版本

 struct Index { static unsigned counter; // Static Counter unsigned location; // index location of Nth element unsigned count; // How many of this element up to this point Index() : location( 0 ), count( 0 ) {} }; unsigned Index::counter = 0; // These typedefs are not necessarily needed; // just used to make reading of code easier. typedef Index IndexZero; typedef Index IndexPos1; typedef Index IndexNeg1; template<class T> class RepititionSearch { public: // Some Constants to compare against: don't like "magic numbers" const T NEG { -1 }; const T ZERO { 0 }; const T POS { 1 }; private: std::vector<T> data_; // The actual array or vector of data to be searched std::vector<Index> indices_; // A vector of Indexes - record keeping to prevent multiple full searches. public: // Instantiating a search object requires an already populated container explicit RepititionSearch ( const std::vector<T>& data ) : data_( data ) { // make sure indices_ is empty upon construction. indices_.clear(); } // method to find the Nth occurrence of object A unsigned getNthOccurrence( unsigned NthOccurrence, T element ) { // Simple bounds checking if ( NthOccurrence < 0 || NthOccurrence >= data.size() ) { // Can throw error or print message...; return -1; } IndexZero zeros; IndexPos1 ones; IndexNeg1 negOnes; // Clear out the indices_ so that each consecutive call is correct indices_.clear(); unsigned idx = 0; for ( auto e : data_ ) { if ( element == e && element == NEG ) { ++negOnes.counter; negOnes.location = idx; negOnes.count = negOnes.counter; indices_.push_back( negOnes ); } if ( element == e && element == ZERO ) { ++zeros.counter; zeros.location = idx; zeros.count = zeros.counter; indices_.push_back( zeros ); } if ( element == e && element == POS ) { ++ones.counter; ones.location = idx; ones.count = ones.counter; indices_.push_back( ones ); } idx++; } // for each T in data_ // Reset static counters negOnes.counter = 0; zeros.counter = 0; ones.counter = 0; // Now that we saved a record: find the nth occurance // This will not search the full vector unless it is last element // This has early termination. Also this vector should only be // a percentage of the original data vector's size in elements. for ( auto index : indices_ ) { if ( index.count == NthOccurrence) { // We found a match return index.location; } } // Not Found return -1; } }; 

 int main() { // using the sample array or vector from User: Prune's answer! std::vector<char> vec{ -1, 0, 1, 1, -1, -1, 0, 0, 0, -1, 1 }; RepititionSearch <char> search( vec ); unsigned idx = search.getNthOccurrence( 3, 1 ); std::cout << idx << std::endl; std::cout << "\\nPress any key and enter to quit." << std::endl; char q; std::cin >> q; return 0; } 

 // output: 10 

值10是正確答案,因為值1第3 出現在原始向量中的位置10處,因為向量是基於0的。 索引向量僅用作book keeping以加快搜索速度。

如果您注意到了,我什至把它做成一個類模板,以接受任何基本類型T ,只要T是可比較的,或者為它定義了運算符,它將存儲在std::vector<T>中。

AFAIK我認為您正在尋求的搜索類型沒有其他比這種搜索方法更快的搜索方法了,但是請不要在上面引用我。 但是我認為我可以進一步優化此代碼...只需要一些時間仔細研究一下。


這可能看起來有點瘋狂,但這確實可行:在代碼中玩一點樂趣

 int main() { std::cout << RepititionSearch<char>( std::vector<char>( { -1, 0, 1, 1, -1, -1, 0, 0, 0, -1, 1 } ) ).getNthOccurrence( 3, 1 ) << std::endl; } 

它可以單行完成並打印到控制台,而無需創建類的實例。


-第二版

現在,這可能不一定會使算法更快,但這將使代碼有些可讀性。 在這里,我刪除了typedef,僅在3個if語句中使用Index結構的單個版本,您將看到duplicate代碼,因此我決定為此創建一個私有幫助器函數,這就是算法看起來清晰易讀的簡單程度。


 struct Index { unsigned location; unsigned count; static unsigned counter; Index() : location(0), count(0) {} }; unsigned Index::counter = 0; template<class T> class RepitiionSearch { public: const T NEG { -1 }; const T ZERO { 0 }; const T POS { 1 }; private: std::vector<T> data_; std::vector<Index> indices_; public: explicit RepititionSearch( const std::vector<T>& data ) : data_( data ) indices_.clear(); } unsigned getNthOccurrence( unsigned NthOccurrence, T element ) { if ( NthOccurrence < 0 || NthOccurrence >= data.size() ) { return -1; } indices_.clear(); Index index; unsigned i = 0; for ( auto e : data_ ) { if ( element == e && element == NEG ) { addIndex( index, i ); } if ( element == e && element == ZERO ) { addIndex( index, i ); } if ( element == e && element == POS ) { addIndex( index, i ); } i++; } index.counter = 0; for ( auto idx : indices_ ) { if ( idx.count == NthOccurrence ) { return idx.location; } } return -1; } private: void addIndex( Index& index, unsigned inc ) { ++index.counter; index.location = inc; index.count = index.counter; indices_.push_back( index ); } }; 


-第三版

為了使它完全通用,可以找到任何元素TNth occurrence ,可以簡化上述步驟並將其簡化為:我還從Index刪除了靜態計數器,並將其移至RepititionSearch的私有部分,這對於放在那里。

 struct Index { unsigned location; unsigned count; Index() : location(0), count(0) {} }; template<class T> class RepititionSearch { private: static unsigned counter_; std::vector<T> data_; std::vector<Index> indices_; public: explicit RepititionSearch( const std::vector<T>& data ) : data_( data ) { indices_.clear(); } unsigned getNthOccurrence( unsigned NthOccurrence, T element ) { if ( NthOccurrence < 0 || NthOccurrence >= data_.size() ) { return -1; } indices_.clear(); Index index; unsigned i = 0; for ( auto e : data_ ) { if ( element == e ) { addIndex( index, i ); } i++; } counter_ = 0; for ( auto idx : indices_ ) { if ( idx.count == NthOccurrence ) { return idx.location; } } return -1; } private: void addIndex( Index& index, unsigned inc ) { ++counter_; index.location = inc; index.count = counter_; indices_.push_back( index ); } }; template<class T> unsigned RepititionSearch<T>::counter_ = 0; 


-第四版

我也已經在上面完成了相同的算法,而無需或不需要僅保存索引信息的向量。 此版本完全不需要Index結構,也不需要輔助函數。 看起來像這樣:

 template<class T> class RepititionSearch { private: static unsigned counter_; std::vector<T> data_; public: explicit RepititionSearch( const std::vector<T>& data ) : data_( data ) {} unsigned getNthOcc( unsigned N, T element ) { if ( N < 0 || N >= data_.size() ) { return -1; } unsigned i = 0; for ( auto e : data_ ) { if ( element == e ) { ++counter_; i++; } else { i++; } if ( counter_ == N ) { counter_ = 0; return i-1; } } counter_ = 0; return -1; } }; template<class T> unsigned RepititionSearch<T>::counter_ = 0; 

由於我們能夠消除輔助向量的依賴性並消除對輔助函數的需要; 我們甚至不需要一個類來容納容器; 我們可以編寫一個帶有矢量並應用相同算法的函數模板。 同樣,此版本也不需要靜態計數器。


-第五版

 template<class T> unsigned RepititionSearch( const std::vector<T>& data, unsigned N, T element ) { if ( data.empty() || N < 0 || N >= data.size() ) { return -1; } unsigned counter = 0; unsigned i = 0; for ( auto e : data ) { if ( element == e ) { ++counter; i++; } else { i++; } if ( counter == N ) { return i - 1; } } return -1; } 

是的,這需要很多。 但是這些是編寫和設計算法並將其完善為更簡單的代碼的過程中涉及的步驟。 如您所見,我將這段代碼優化了大約5次。 從使用structclasstypedefs和具有多個存儲容器的static member ,到刪除typedefs並將可重復的代碼放入helper函數中,再到刪除輔助容器和helper函數的依賴關系,直到甚至根本不需要一個類,而只是創建一個可以實現其應有功能的函數。

您可以對這些步驟應用類似的方法,以構建執行所需功能的函數。 您可以使用相同的過程來編寫將執行二進制搜索,哈希表等的功能。

OP指出有序結構很重要, vectorarray unsorted 據我所知,沒有比未分類數據更快的搜索算法。 以下是一些參考鏈接:

具有以上鏈接供參考; 這應該有足夠的證據可以得出結論,如果arrayvector的數據未排序並且必須保持其結構,那么除了使用線性迭代,別無選擇,可以使用散列技術,但是仍然可以棘手的是,在大多數情況下,使用二進制搜索將僅對sorted數據起作用。


-這是找到dataTNth出現的一種很好的線性算法。

要解決在給定的unsorted arrayvectorcontainer中找到元素TNth次出現的問題,可以使用以下簡單函數模板:

  • 它包含3個參數:
    • 對填充有數據的容器的const引用
    • const無符號值N ,其中N是第Nth次出現。
    • 以及要搜索的const模板類型T
  • 它返回元素TNth次出現在容器內的索引位置的無符號值

 template<class T> unsigned RepititionSearch( const std::vector<T>& data, const unsigned N, const T element ) { if ( data.empty() || N < 0 || N >= data.size() ) { return -1; } unsigned counter = 0; unsigned i = 0; for ( auto e : data ) { if ( element == e ) { ++counter; i++; } else { i++; } if ( counter == N ) { return i - 1; } } return -1; } 

算法分解

  • 它首先進行一些完整性檢查:
    • 它檢查容器是否為空
    • 它檢查值N以查看其是否在[0,container.size())范圍內。
    • 如果其中任何一個失敗,則返回-1 ; 在生產代碼中,這可能會引發異常或錯誤
  • 然后,我們需要2個遞增計數器:
    • 1為當前索引位置
    • 元素T的出現次數為1
  • 然后,我們使用c++11或更高版本的簡化的for循環
    • 我們瀏覽data每個e
    • 我們檢查傳遞給函數的element是否equal to data的當前e
    • 如果檢查通過或成立,那么我們pre-increment counter ,再post-increment i否則我們只想post-increment i
    • 遞增計數器后,我們將檢查當前counter是否等於傳遞給函數的Nth
    • 如果檢查通過,我們將返回i-1的值,因為容器基於0
    • 如果此處的檢查失敗,則我們繼續循環的下一個迭代並重復該過程
  • 如果在檢查完所有data中的e后沒有出現T == eN != counter則我們離開for循環,該函數返回-1 ; 在生產代碼中,這可能會引發異常或返回錯誤。

這里最壞的情況是沒有發現,或者TNth次發生恰好是data中的最后一個e ,這將產生線性的O(N) ,對於基本容器,這應該足夠有效。 如果容器具有array indexing功能,則如果知道所需的索引位置,則它們的項訪問應為O(1)常量。


注意:這就是我認為應該解決的問題的答案,如果您對設計或建模這種算法的設計過程的工作方式感興趣,可以在這里參考我的reference answer

AFAIK我認為沒有better方法可以對unsorted array data執行此操作,但是請不要在上面引用我。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM