[英]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)次查找。
由於您要搜索array
, vector
或某個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 ); } };
為了使它完全通用,可以找到任何元素T
的Nth 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次。 從使用struct
, class
, typedefs
和具有多個存儲容器的static member
,到刪除typedefs
並將可重復的代碼放入helper函數中,再到刪除輔助容器和helper函數的依賴關系,直到甚至根本不需要一個類,而只是創建一個可以實現其應有功能的函數。
您可以對這些步驟應用類似的方法,以構建執行所需功能的函數。 您可以使用相同的過程來編寫將執行二進制搜索,哈希表等的功能。
OP指出有序結構很重要, vector
或array
unsorted
。 據我所知,沒有比未分類數據更快的搜索算法。 以下是一些參考鏈接:
具有以上鏈接供參考; 這應該有足夠的證據可以得出結論,如果array
或vector
的數據未排序並且必須保持其結構,那么除了使用線性迭代,別無選擇,可以使用散列技術,但是仍然可以棘手的是,在大多數情況下,使用二進制搜索將僅對sorted
數據起作用。
data
中T
的Nth
出現的一種很好的線性算法。
要解決在給定的unsorted
array
, vector
或container
中找到元素T
的Nth
次出現的問題,可以使用以下簡單函數模板:
N
,其中N
是第Nth
次出現。 T
T
的Nth
次出現在容器內的索引位置的無符號值 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 == e
或N != counter
則我們離開for循環,該函數返回-1
; 在生產代碼中,這可能會引發異常或返回錯誤。
這里最壞的情況是沒有發現,或者T
的Nth
次發生恰好是data
中的最后一個e
,這將產生線性的O(N)
,對於基本容器,這應該足夠有效。 如果容器具有array indexing
功能,則如果知道所需的索引位置,則它們的項訪問應為O(1)
常量。
注意:這就是我認為應該解決的問題的答案,如果您對設計或建模這種算法的設計過程的工作方式感興趣,可以在這里參考我的reference answer
AFAIK我認為沒有better
方法可以對unsorted array data
執行此操作,但是請不要在上面引用我。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.