【问题标题】:how can i create a binary search in a linked List? [closed]如何在链接列表中创建二进制搜索? [关闭]
【发布时间】:2021-06-13 06:47:57
【问题描述】:

这是一个类的作业,对于这个程序,我需要实现链表的二进制搜索,我该怎么做?是否可以确定和访问列表的中间元素,并仅关注左侧或 列表的右半部分? 如何实现链表的二分查找?

搜索.h

#ifndef SEARCHING_H_
#define SEARCHING_H_
#include "Vector.h"
#include "List.h"
using namespace std;
template <typename T>
void print_vector(const Vector<T>& vec)
{
    for (int i = 0; i < vec.size(); i++)
        cout << vec[i] << " ";
    cout << endl;
    return;
}
// LINEAR SEARCH OF VECTOR
// return index at which target found, if not found return -1;
template <typename T>
int linear_search_V(const Vector<T>& vec, const T& target, int& ops)
{
    ops = 0;
    for (int i = 0; i < vec.size(); i++) {
        ops++;
        if (vec[i] == target)
            return i;
    }
    return -1;
}
// LINEAR SEARCH OF LINKED LIST
// return iterator to node at which target
// found in lst; else return iterator at end() of lst;
template <typename T>
typename List<T>::const_iterator linear_search_L(const List<T>& lst, const T& target, int& ops)
{
    ops = 0;
    typename List<T>::const_iterator itr;
    for (itr = lst.begin(); itr != lst.end(); ++itr) {
        ops++;
        if (*itr++ == target)
            return itr;
    }
    return lst.end();
}
// BINARY SEARCH OF VECTOR;
// returns index at which target found, else -1;
template <typename T>
int binary_search_V(const Vector<T>& vec, const T& target, int& ops)
{
    ops = 0;
    int low = 0;
    int high = vec.size() - 1;
    while (low <= high) {
        ops++;
        int mid = (low + high) / 2;
        if (vec[mid] < target)
            low = mid + 1;
        else if (vec[mid] > target)
            high = mid - 1;
        else
            return mid;
    }
    return -1;
}
// BINARY SEARCH OF LINKED LIST
// returns iterator at target value found; iterator end() else;
template <typename T>
typename List<T>::const_iterator binary_search_L(const List<T> lst, const T& target, int& ops)
{
    ops = 0;
    //implement function for binary search in Linked List:
}
#endif


//this is the List.h i need to use for the binary search in a linked list

    #ifndef LIST_H
    
    #define LIST_H
    
    //#include <algorithm>
    
    using namespace std;
    
    template <typename T>
    
    class List
    
    {
    
     private:
    
     // The basic doubly linked list node.
    
     // Nested inside of List, can be public
    
     // because the Node is itself private
    
     struct Node
    
     {
    
     T data;
    
     Node *prev;
    
     Node *next;
    
     Node( const T & d = T{ }, Node * p = nullptr, Node * n = nullptr )
    
     : data{ d }, prev{ p }, next{ n } { }
    
    
    
     Node( T && d, Node * p = nullptr, Node * n = nullptr )
    
     : data{ std::move( d ) }, prev{ p }, next{ n } { }
    
     };
    
     public:
    
     class const_iterator
    
     {
    
     public:
    
    
    
     // Public constructor for const_iterator.
    
     const_iterator( ) : current{ nullptr }
    
     { }
    
     // Return the T stored at the current position.
    
     // For const_iterator, this is an accessor with a
    
     // const reference return type.
    
     const T & operator* ( ) const
    
     { return retrieve( ); }
    
    
    
     const_iterator & operator++ ( )
    
     {
    
     current = current->next;
    
     return *this;
    
     }
    
     const_iterator operator++ ( int )
    
     {
    
     const_iterator old = *this;
    
     ++( *this );
    
     return old;
    
     }
    
     const_iterator & operator-- ( )
    
     {
    
     current = current->prev;
    
     return *this;
    
     }
    
     const_iterator operator-- ( int )
    
     {
    
     const_iterator old = *this;
    
     --( *this );
    
     return old;
    
     }
    
    
    
     bool operator== ( const const_iterator & rhs ) const
    
     { return current == rhs.current; }
    
     bool operator!= ( const const_iterator & rhs ) const
    
     { return !( *this == rhs ); }
    
    // iterators side-by-side
    
    bool operator > (const const_iterator & rhs) const
    
    {
    
    return current->prev == rhs.current;
    
    }
    
     protected:
    
     Node *current;
    
     // Protected helper in const_iterator that returns the T
    
     // stored at the current position. Can be called by all
    
     // three versions of operator* without any type conversions.
    
     T & retrieve( ) const
    
     { return current->data; }
    
     // Protected constructor for const_iterator.
    
     // Expects a pointer that represents the current position.
    
     const_iterator( Node *p ) : current{ p }
    
     { }
    
    
    
     friend class List<T>;
    
     };
    
     class iterator : public const_iterator
    
     {
    
     public:
    
     // Public constructor for iterator.
    
     // Calls the base-class constructor.
    
     // Must be provided because the private constructor
    
     // is written; otherwise zero-parameter constructor
    
     // would be disabled.
    
     iterator( )
    
     { }
    
     T & operator* ( )
    
     { return const_iterator::retrieve( ); }
    
    .
    
     const T & operator* ( ) const
    
     { return const_iterator::operator*( ); }
    
    
    
     iterator & operator++ ( )
    
     {
    
     this->current = this->current->next;
    
     return *this;
    
     }
    
     iterator operator++ ( int )
    
     {
    
     iterator old = *this;
    
     ++( *this );
    
     return old;
    
     }
    
     iterator & operator-- ( )
    
     {
    
     this->current = this->current->prev;
    
     return *this;
    
     }
    
     iterator operator-- ( int )
    
     {
    
     iterator old = *this;
    
     --( *this );
    
     return old;
    
     }
    
     protected:
    
     // Protected constructor for iterator.
    
     // Expects the current position.
    
     iterator( Node *p ) : const_iterator{ p }
    
     { }
    
     friend class List<T>;
    
     };
    
     public:
    
     List( )
    
     { init( ); }
    
     ~List( )
    
     {
    
     clear( );
    
     delete head;
    
     delete tail;
    
     }
    
     List( const List & rhs )
    
     {
    
     init( );
    
     for( auto & x : rhs )
    
     push_back( x );
    
     }
    
     List & operator= ( const List & rhs )
    
     {
    
     List copy = rhs;
    
     std::swap( *this, copy );
    
     return *this;
    
     }
    
    // keep for compiler
    
    List(List&& rhs)
    
    : theSize{ rhs.theSize }, head{ rhs.head }, tail{ rhs.tail }
    
    {
    
    rhs.theSize = 0;
    
    rhs.head = nullptr;
    
    rhs.tail = nullptr;
    
    }
    
    // keep for compiler
    
    List& operator= (List&& rhs)
    
    {
    
    std::swap(theSize, rhs.theSize);
    
    std::swap(head, rhs.head);
    
    std::swap(tail, rhs.tail);
    
    return *this;
    
    }
    
     // Return iterator representing beginning of list.
    
     // Mutator version is first, then accessor version.
    
     iterator begin( )
    
     { return iterator( head->next ); }
    
     const_iterator begin( ) const
    
     { return const_iterator( head->next ); }
    
     // Return iterator representing endmarker of list.
    
     // Mutator version is first, then accessor version.
    
     iterator end( )
    
     { return iterator( tail ); }
    
     const_iterator end( ) const
    
     { return const_iterator( tail ); }
    
     // Return number of elements currently in the list.
    
     int size( ) const
    
     { return theSize; }
    
     // Return true if the list is empty, false otherwise.
    
     bool empty( ) const
    
     { return size( ) == 0; }
    
     void clear( )
    
     {
    
     while( !empty( ) )
    
     pop_front( );
    
     }
    
     // front, back, push_front, push_back, pop_front, and pop_back
    
     // are the basic double-ended queue operations.
    
     T & front( )
    
     { return *begin( ); }
    
     const T & front( ) const
    
     { return *begin( ); }
    
     T & back( )
    
     { return *--end( ); }
    
     const T & back( ) const
    
     { return *--end( ); }
    
     void push_front( const T & x )
    
     { insert( begin( ), x ); }
    
     void push_back( const T & x )
    
     { insert( end( ), x ); }
    
     void pop_front( )
    
     { erase( begin( ) ); }
    
     void pop_back( )
    
     { erase( --end( ) ); }
    
     // Insert x before itr.
    
     iterator insert( iterator itr, const T & x )
    
     {
    
     Node *p = itr.current;
    
     ++theSize;
    
     return iterator( p->prev = p->prev->next = new Node{ x, p->prev, p } );
    
     }
    
    
    
     // Erase item at itr.
    
     iterator erase( iterator itr )
    
     {
    
     Node *p = itr.current;
    
     iterator retVal( p->next );
    
     p->prev->next = p->next;
    
     p->next->prev = p->prev;
    
     delete p;
    
     --theSize;
    
     return retVal;
    
     }
    
     iterator erase( iterator from, iterator to )
    
     {
    
     for( iterator itr = from; itr != to; )
    
     itr = erase( itr );
    
     return to;
    
     }
    
     private:
    
     int theSize;
    
     Node *head;
    
     Node *tail;
    
     void init( )
    
     {
    
     theSize = 0;
    
     head = new Node;
    
     tail = new Node;
    
     head->next = tail;
    
     tail->prev = head;
    
     }
    
    };
    
    #endif

【问题讨论】:

  • 泪流满面。链接列表和排序不是理想的伴侣。
  • 单链表还是双链表?
  • 我建议使用节点数而不是索引。对于单个链表,如果键小于中点的值,则必须从顶部开始。如果key大于中点的值,可以继续迭代。
  • 为什么要这样做?这是c ++类中的问题吗?如果是这样,目的可能是说明二分搜索与链表不兼容。如果不是,您应该根据实际要求使用不同的容器。
  • 在双链表中进行二分搜索更容易,但仍然很痛苦。您只需向前或向后(从当前中点)迭代到下一个中​​点。这里的关键短语是“迭代到中点”。有了支持随机访问的容器,就不需要迭代了。

标签: c++ computer-science


【解决方案1】:

在链表上使用二分搜索没有意义,因为随机访问在链表中具有高得令人望而却步的time complexity。在单链表中,平均而言,每次随机访问都必须遍历一半的列表。平均而言,这与简单的线性搜索执行整个搜索所需的工作量相同。

与其将节点排列在一个链表中,其中每个节点都与另一个节点链接,不如将节点排列在树中更有意义,其中每个节点最多与两个其他节点链接。我建议将它们安排在binary search tree 中,因为这是二分搜索的理想数据结构。

【讨论】:

  • 很好的建议,但恕我直言,没有回答 OP 的问题。
  • @ThomasMatthews:在撰写本文时,OP 还没有回答他们为什么要对链表执行二进制搜索的问题。因此,它可能是XY problem。在这种情况下,直接回答问题是没有意义的。但是,您是对的,它可能不是 XY 问题。目前,我们从 OP 获得的信息太少,无法确定这一点。
【解决方案2】:

链表上的二分查找是一个奇怪的概念,但我们暂时先搁置一下这个奇怪的问题。首先,让我们将二分查找算法适配到链表接口。

要开始二分搜索,您需要列表的中间位置。所以首先你需要知道你的名单有多长。如果列表未存储其大小,则可以通过迭代列表来确定大小,并在您继续进行时计算节点。一旦有了大小,就可以正常除以 2。此外,缓存指向列表头部的指针的副本,原因稍后将变得清楚。

一旦你知道你需要哪个节点,你就可以从缓存的头指针开始,推进那么多节点。如果您找到了所需的节点,那么您就完成了。如果中间节点太低,请将缓存的头指针替换为指向中间节点的指针(这已成为您搜索的新低端)。在任何一种情况下(太低或太高),将您查找的节点的索引除以 2 并重复。

注意:此代码可能看起来很复杂和/或混乱,尤其是与矢量版本相比时。

好吧,上面还需要检查“未找到”。可能还需要处理一些舍入误差。虽然这些很重要,但我更愿意看看为什么人们可能想要这样做。 (此外,处理这些细节可能是有用的做法。你仍然需要为你的任务做一些工作。;))


与对长度为n 的列表的线性搜索相比,这种二分搜索的效果如何?平均而言,线性搜索将比较 n/2 节点并遵循 n/2 链接。另一方面,二分搜索将平均比较 lg n 节点并遵循 n 链接(或者如果您不缓存起点,则更类似于 n lg n 链接)。也许更引人注目的是,二分搜索至少会跟随n/2 链接(到达第一个中点),这是二分搜索的平均。二分搜索怎么可能比线性搜索更好?

一种可能性在于比较的次数。如果比较节点特别昂贵,那么减少的比较次数(理论上)可以弥补遍历列表的增加量。我不知道什么时候发挥作用的任何例子,但我会承认这种可能性。理论上。

我发现更有可能的是,你被分配了这个任务来说明你确实想要对链表进行二分搜索。将这样的想法带回家,即如果您需要进行二分搜索,您将需要一个提供对其元素的随机访问的容器(如向量)。为手头的工作使用正确的工具。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-08-09
    • 2021-03-02
    • 1970-01-01
    • 1970-01-01
    • 2017-06-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多