使用二进制搜索查找数字第N次出现的索引

时间:2018-01-11 01:08:39

标签: c++ algorithm binary-search

我有一个有限数组,其元素只有-1,0或1.我想找到第n个出现的索引(比如说0)。

我可以遍历整个数组,但我正在寻找更快的方法。我可以考虑使用二进制搜索,但无法对算法进行建模。在这种情况下如何进行二进制搜索?

3 个答案:

答案 0 :(得分:8)

如果没有至少一次 O(N)预处理,则无法执行此操作。仅从信息理论的角度来看,必须知道元素[0:k-1]才能知道元素[k]是否是你想要的元素。

如果您要多次进行此搜索,则可以对数组进行简单的线性传递,随时计算每个元素。将索引存储在二维数组中,这样您就可以直接索引所需的任何内容。

例如,给定[-1 0 1 1 -1 -1 0 0 0 -1 1],您可以将其转换为3xN数组,idx

- todos
  |- cleanRoom
      |- createdBy
      |    |- name: Alica
      |    |- age: 27
      |- title: "Clean the room up"
      |- doerList
           |- roomCleanerMichael
           |     |- user
           |     |    |- name: Michael
           |     |    |- age: 22
           |     |- status: done
           |- roomCleanerAlica
                 |- user
                 |    |- name: Alica
                 |    |- age: 27
                 |- status: done

元素I的第N次出现是[[0 4 5 9]] [[1 6 7 8]] [[2 3 10]]

在初始O(N)传递之后,使用O(N)空格查找O(1)时间。

答案 1 :(得分:0)

由于您希望搜索arrayvector或某些container,其中有问题的搜索与某个元素{{1}的索引位置有关根据其容器中的T次出现,这篇文章可能对您有所帮助:

根据您的问题以及与您有关的一些评论,当您在考虑使用Nth并且过程遇到问题时明确声明您的容器为Unsorted时建模算法:

  • 这篇文章是一个开发过程的例子,它可以帮助您实现所需的算法:

  • 此处的搜索算法是线性的,其中二进制搜索不适合您当前的需求:

  • 构建算法的同一过程可以应用于其他类型的算法,包括二进制搜索,哈希表等。


- 第一次构建

binary search
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;
}

值10是正确的答案,因为值// output: 10 的3 rd 出现在原始向量中的位置10,因为向量是基于1的。索引向量仅用作0以便更快地搜索。

如果您注意到我甚至将此类模板设为接受book keeping的任何基本类型T,只要std::vector<T>具有可比性,或者有运算符为它定义。

AFAIK我认为对于您正在努力争取的搜索类型,没有任何其他搜索方法比这更快,但请不要引用我。但是我想我可以再多一点优化这个代码......只需要一些时间来仔细研究它。

这可能看起来有点疯狂,但这确实有效:只是玩一下代码有点乐趣

T

它可以在一条线上完成。打印到控制台而不创建类的实例。


- 第二次构建

现在这可能不一定能使算法更快,但为了便于阅读,这会清理代码。在这里我删除了typedef,只是在3 if语句中使用单个版本的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; } 结构,你会看到Index代码,所以我决定为它创建一个私有帮助函数,这就是如何简单的算法寻找清晰的可读性。

duplicate


- 3rd Build

为了使这个完全通用以找到任何元素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 ); } }; 中的任何Nth occurrence,上面的内容可以简化并简化为:我还从T移除了静态计数器并移动了它是Index的私人部分,将它放在那里更有意义。

RepititionSearch


- 第4版

我也在上面做了同样的算法,而不需要或依赖于只需要一个向量来保存索引信息。这个版本根本不需要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

因为我们能够删除辅助向量的依赖性并且不需要辅助函数;我们根本不需要一个班级来容纳容器;我们可以编写一个带矢量并应用相同算法的函数模板。此版本也不需要静态计数器。


- 5th Build

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;

是的,这需要很多东西;但这些是编写和设计算法并将其简化为更简单代码的过程中涉及的步骤。正如您所看到的,我已经将此代码改进了大约5次。我使用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; } structclasstypedefs与多个存储容器,以删除static member并将可重复的代码放入辅助功能,用于消除辅助容器的依赖关系。帮助函数,甚至根本不需要一个类,只是创建一个完成它应该做的事情的函数。

您可以对这些步骤应用类似的方法来构建一个能够执行您想要或需要它执行的操作的函数。您可以使用相同的过程编写将执行二进制搜索,哈希表等的函数。

答案 2 :(得分:0)

OP表示有序结构很重要,vectorarrayunsorted。据我所知,对于未排序的数据,没有比线性更快的搜索算法。以下是一些参考链接:

以上链接供参考;这应该足以证明,如果arrayvector中的数据未排序并且必须保持其结构,那么除了使用线性迭代之外没有选择,可以使用散列技术,但仍然很棘手,使用二进制搜索只能在大多数情况下对sorted数据起作用。


- 这是一个很好的线性算法,用于在Nth中查找T data次出现。

要解决您在给定Nth Tunsortedarray中找到vector元素container出现的问题,您可以使用这个简单的函数模板:

  • 需要3个参数:
    • 对使用数据
    • 填充的容器的const引用
    • const无符号值N其中NNth次出现。
    • 和您要搜索的const模板类型T
  • 它返回容器内索引位置的无符号值 元素Nth
  • T出现次数
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表示当前索引位置
    •   
    • 1表示元素T
    • 的出现次数   
  •   
  • 然后我们使用c++11或更高版本的简化for循环      
        
    • 我们会浏览e
    • 中的每个data   
    • 我们检查传递给该函数的element是否为equal to   e
    • 中的当前data   
    • 如果支票通过或为真,我们会pre-increment counter和   post-increment i我们只想post-increment i
    •   
    • 递增计数器后,我们检查当前是否有   counter等于传递给函数
    • Nth值   
    • 如果检查通过,我们会返回i-1的值,因为容器基于0
    •   
    • 如果检查失败,我们继续循环的下一次迭代并重复该过程
    •   
  •   
  • 如果检查了e中的所有data并且没有出现    T == eN != counter然后我们离开for循环和函数    返回-1;在生产代码中,这可能会引发异常或返回错误。
  •   

这里最糟糕的情况是要么没有找到,要么Nth出现T恰好是edata的最后一个O(N) array indexing这是线性的,对于基本容器,这应该足够有效。如果容器具有O(1)功能,则如果您知道所需的索引位置,则其项目访问权应为reference answer不变。

注意:这是我认为应该解决问题的答案,如果您对如何设计或建模此类算法的设计过程的细分感兴趣,您可以参考我的better {{ 3}}

AFAIK我不认为unsorted array data有{{1}}方法可以使用{{1}},但请不要引用我。