c++ - 单链列表C++的Quickselect算法

标签 c++ list median quickselect nth-element

我需要一种算法,可以找到线性时间复杂度O(n)和恒定空间复杂度O(1)中单链表的中位数。

编辑:单链接列表是C样式的单链接列表。不允许STL(没有容器,没有函数,禁止所有STL,例如没有std::forward_list)。不允许将数字移动到任何其他容器(如数组)中。
O(logn)的空间复杂度是可以接受的,因为对于我的列表来说,实际上甚至小于100。另外我也不能使用像nth_element这样的STL函数

基本上我已经用3 * 10 ^ 6个元素链接了列表,并且我需要在3秒内获得中值,所以我不能使用排序算法对列表进行排序(这将是O(nlogn),并且需要类似可能需要10-14秒)。

我在网上做了一些搜索,发现可以通过 quickselect 在O(n)和O(1)空间中找到std::vector的中位数(最坏的情况是在O(n ^ 2),但很少见),例如:https://www.geeksforgeeks.org/quickselect-a-simple-iterative-implementation/

但是我找不到用于链表的算法。问题是我可以使用数组索引随机访问 vector 。如果我想修改该算法,则复杂度会更大,因为。例如,当我将数据透视索引更改为左侧时,我实际上需要遍历列表以获取该新元素并走得更远(这将使我至少获得O(kn)且列表中有一个大k,甚至 push O(n ^ 2)...)。

编辑2:

我知道我有太多的变量,但是我一直在测试不同的东西,而我仍在处理我的代码...
我当前的代码:

#include <bits/stdc++.h>

using namespace std;

template <class T> class Node {
    public:
    T data;
    Node<T> *next;
};

template <class T> class List {
    public:
    Node<T> *first;
};

template <class T> T getMedianValue(List<T> & l) {
    Node<T> *crt,*pivot,*incpivot;
    int left, right, lung, idx, lungrel,lungrel2, left2, right2, aux, offset;
    pivot = l.first;
    crt = pivot->next;
    lung = 1;
//lung is the lenght of the linked list (yeah it's lenght in romanian...)
//lungrel and lungrel2 are the relative lenghts of the part of 
//the list I am processing, e.g: 2 3 4 in a list with 1 2 3 4 5
    right = left = 0;
    while (crt != NULL) { 
        if(crt->data < pivot->data){
            aux = pivot->data;
            pivot->data = crt->data;
            crt->data = pivot->next->data;
            pivot->next->data = aux;
            pivot = pivot->next;
            left++;
        }
        else right++;
       // cout<<crt->data<<endl;
        crt = crt->next; 
        lung++; 
    }
    if(right > left) offset = left;
//  cout<<endl;
//  cout<<pivot->data<<" "<<left<<" "<<right<<endl;
//  printList(l);
//  cout<<endl;
    lungrel = lung;
    incpivot = l.first;
   // offset = 0;
    while(left != right){
        //cout<<"parcurgere"<<endl;
        if(left > right){
            //cout<<endl;
            //printList(l);
            //cout<<endl;
            //cout<<"testleft "<<incpivot->data<<" "<<left<<" "<<right<<endl;
            crt = incpivot->next;
            pivot = incpivot;
            idx = offset;left2 = right2 = lungrel = 0;
            //cout<<idx<<endl;
            while(idx < left && crt!=NULL){
                 if(pivot->data > crt->data){
                   //  cout<<"1crt "<<crt->data<<endl;
                     aux = pivot->data;
                     pivot->data = crt->data;
                     crt->data = pivot->next->data;
                     pivot->next->data = aux;
                     pivot = pivot->next;
                     left2++;lungrel++;
                  }
                  else {
                      right2++;lungrel++;
                      //cout<<crt->data<<" "<<right2<<endl;
                  }
                  //cout<<crt->data<<endl;
                  crt = crt->next;
                  idx++;
             }
             left = left2 + offset;
             right = lung - left - 1;
             if(right > left) offset = left;
             //if(pivot->data == 18) return 18;
             //cout<<endl;
             //cout<<"l "<<pivot->data<<" "<<left<<" "<<right<<" "<<right2<<endl;
           //  printList(l);
        }
        else if(left < right && pivot->next!=NULL){
            idx = left;left2 = right2 = 0;
            incpivot = pivot->next;offset++;left++;
            //cout<<endl;
            //printList(l);
            //cout<<endl;
            //cout<<"testright "<<incpivot->data<<" "<<left<<" "<<right<<endl;
            pivot = pivot->next;
            crt = pivot->next;
            lungrel2 = lungrel;
            lungrel = 0;
           // cout<<"p right"<<pivot->data<<" "<<left<<" "<<right<<endl;
            while((idx < lungrel2 + offset - 1) && crt!=NULL){
                 if(crt->data < pivot->data){
                //     cout<<"crt "<<crt->data<<endl;
                     aux = pivot->data;
                     pivot->data = crt->data;
                     crt->data = (pivot->next)->data;
                     (pivot->next)->data = aux;
                     pivot = pivot->next;
                 //    cout<<"crt2 "<<crt->data<<endl;
                     left2++;lungrel++;
                  }
                  else right2++;lungrel++;
                  //cout<<crt->data<<endl;
                  crt = crt->next;
                  idx++;
             }
             left = left2 + left;
             right = lung - left - 1;
                 if(right > left) offset = left;
            // cout<<"r "<<pivot->data<<" "<<left<<" "<<right<<endl;
           //  printList(l);
        }
        else{
            //cout<<cmx<<endl;
            return pivot->data;
        }
    }
    //cout<<cmx<<endl;
    return pivot->data;
}
template <class T> void printList(List<T> const & l) {
    Node<T> *tmp;
    if(l.first != NULL){
        tmp = l.first;
        while(tmp != NULL){
            cout<<tmp->data<<" ";
            tmp = tmp->next;
        }
    }
}
template <class T> void push_front(List<T> & l, int x)
{
    Node<T>* tmp = new Node<T>;

    tmp->data = x;

    tmp->next = l.first;
    l.first = tmp;
}

int main(){
    List<int> l;
    int n = 0;
    push_front(l, 19);
    push_front(l, 12);
    push_front(l, 11);
    push_front(l, 101);
    push_front(l, 91);
    push_front(l, 21);
    push_front(l, 9);
    push_front(l, 6);
    push_front(l, 25);
    push_front(l, 4);
    push_front(l, 18);
    push_front(l, 2);
    push_front(l, 8);
    push_front(l, 10);
    push_front(l, 200);
    push_front(l, 225);
    push_front(l, 170);
    printList(l);
    n=getMedianValue(l);
    cout<<endl;
    cout<<n;

    return 0;
}

您是否对如何使quickselect适应可能会解决我问题的单个列出的链接或其他算法有任何建议?

最佳答案

在您的问题中,您提到您在选择不在列表开头的枢轴时遇到了麻烦,因为这将需要遍历列表。如果正确执行,则只需遍历整个列表两次:

  • 一次,用于查找列表的中间和结尾,以便选择一个好的枢轴(例如,使用"median-of-three"规则)
  • 一次用于实际排序

  • 如果您不太在乎选择一个好的枢轴,并且只选择列表的第一个元素作为枢轴(如果数据为O,则最坏情况为O(n ^ 2)time complexity),那么第一步就没有必要了。已经排序)。
    如果您通过维护指向末尾的指针来第一次遍历列表的末尾,那么您就不必再遍历该列表来查找末尾。另外,如果您使用的是标准Lomuto partition scheme(出于以下原因我未使用),则还必须在列表中维护两个指针,它们代表标准Lomuto分区方案的ij索引。通过使用这些指针,永远不必遍历列表以访问单个元素。
    同样,如果您维护了指向每个分区的中间和结尾的指针,那么当您以后必须对这些分区之一进行排序时,就不必再次遍历该分区来找到中间和结尾。
    现在,我为链表创建了自己的QuickSelect算法实现,已在下面发布。
    既然您说过链表是单链表,并且不能升级到双链表,所以我不能使用Hoare partition scheme,因为向后迭代单链表非常昂贵。因此,我通常使用效率较低的Lomuto partition scheme代替。
    使用Lomuto分区方案时,通常会选择第一个元素或最后一个元素作为枢轴。但是,选择其中任何一个都有缺点,即排序的数据将导致算法的最坏情况下的时间复杂度为O(n ^ 2)。可以通过根据"median-of-three" rule选择枢轴来防止这种情况,即从第一个元素,中间元素和最后一个元素的中值中选择一个枢轴。因此,在我的实现中,我使用的是“三个中位数”规则。
    同样,Lomuto分区方案通常会创建两个分区,一个分区用于小于枢轴的值,一个分区用于大于或等于枢轴的值。但是,如果所有值都相同,这将导致O(n ^ 2)的最坏情况下的时间复杂度。因此,在我的实现中,我将创建三个分区,一个分区用于小于枢轴的值,一个分区用于大于枢轴的值,一个分区用于等于枢轴的值。
    尽管这些措施并没有完全消除O(n ^ 2)的最坏情况下时间复杂度的可能性,但它们至少使其极不可能(除非输入是由恶意攻击者提供的)。为了保证O(n)的时间复杂度,必须使用更复杂的数据透视选择算法,例如median of medians
    我遇到的一个重要问题是,对于偶数个元素,median定义为两个“中间”或“中间”元素的arithmetic mean。因此,我不能简单地编写类似于 std::nth_element 的函数,因为例如,如果元素总数为14,那么我将寻找第7位和第8位最大的元素。这意味着我将不得不两次调用这样的函数,这样效率低下。因此,我改写了一个可以立即搜索两个“中位数”元素的函数。尽管这使代码更加复杂,但是与不必两次调用同一函数的优点相比,由于附加的代码复杂性而导致的性能损失应最小。
    请注意,尽管我的实现可以在C++编译器上完美编译,但我不会将其称为教科书C++代码,因为该问题表明我不允许使用C++标准模板库中的任何内容。因此,我的代码是C代码和C++代码的混合体。
    在以下代码中,我仅使用标准模板库(特别是函数 std::nth_element )来测试算法和验证结果。我的实际算法中没有使用任何这些功能。
    #include <iostream>
    #include <iomanip>
    #include <cassert>
    
    // The following two headers are only required for testing the algorithm and verifying
    // the correctness of its results. They are not used in the algorithm itself.
    #include <random>
    #include <algorithm>
    
    // The following setting can be changed to print extra debugging information
    // possible settings:
    // 0: no extra debugging information
    // 1: print the state and length of all partitions in every loop iteraton
    // 2: additionally print the contents of all partitions (if they are not too big)
    #define PRINT_DEBUG_LEVEL 0
    
    template <typename T>
    struct Node
    {
        T data;
        Node<T> *next;
    };
    
    // NOTE:
    // The return type is not necessarily the same as the data type. The reason for this is
    // that, for example, the data type "int" requires a "double" as a return type, so that 
    // the arithmetic mean of "3" and "6" returns "4.5".
    // This function may require template specializations to handle overflow or wrapping.
    template<typename T, typename U>
    U arithmetic_mean( const T &first, const T &second )
    {
        return ( static_cast<U>(first) + static_cast<U>(second) ) / 2;
    }
    
    //the main loop of the function find_median can be in one of the following three states
    enum LoopState
    {
        //we are looking for one median value
        LOOPSTATE_LOOKINGFORONE,
    
        //we are looking for two median values, and the returned median
        //will be the arithmetic mean of the two
        LOOPSTATE_LOOKINGFORTWO,
    
        //one of the median values has been found, but we are still searching for
        //the second one
        LOOPSTATE_FOUNDONE
    };
    
    template <
        typename T, //type of the data
        typename U  //type of the return value
    >
    U find_median( Node<T> *list )
    {
        //This variable points to the pointer to the first element of the current partition.
        //During the partition phase, the linked list will be broken and reassembled afterwards, so
        //the pointer this pointer points to will be nullptr until it is reassembled.
        Node<T> **pp_start = &list;
    
        //This pointer represents nothing more than the cached value of *pp_start and it is
        //not always valid
        Node<T> *p_start = *pp_start;
    
        //These pointers are maintained for accessing the middle of the list for selecting a pivot
        //using the "median-of-three" rule.
        Node<T> *p_middle;
        Node<T> *p_end;
    
        //result is not defined if list is empty
        assert( p_start != nullptr );
    
        //in the main loop, this variable always holds the number of elements in the current partition
        int num_total = 1;
    
        // First, we must traverse the entire linked list in order to determine the number of elements,
        // in order to calculate k1 and k2. If it is odd, then the median is defined as the k'th smallest
        // element where k = n / 2. If the number of elements is even, then the median is defined as the
        // arithmetic mean of the k'th element and the (k+1)'th element.
        // We also set a pointer to the nodes in the middle and at the end, which will be required later
        // for selecting a pivot according to the "median-of-three" rule.
        p_middle = p_start;
        for ( p_end = p_start; p_end->next != nullptr; p_end = p_end->next )
        {
            num_total++;
            if ( num_total % 2 == 0 ) p_middle = p_middle->next;
        }   
    
        // find out whether we are looking for only one or two median values
        enum LoopState loop_state = num_total % 2 == 0 ? LOOPSTATE_LOOKINGFORTWO : LOOPSTATE_LOOKINGFORONE;
    
        //set k to the index of the middle element, or if there are two middle elements, to the left one
        int k = ( num_total - 1 ) / 2;
    
        // If we are looking for two median values, but we have only found one, then this variable will
        // hold the value of the one we found. Whether we have found one can be determined by the state of
        // the variable loop_state.
        T val_found;
    
        for (;;)
        {
            //make p_start cache the value of *pp_start again, because a previous iteration of the loop
            //may have changed the value of pp_start
            p_start = *pp_start;
    
            assert( p_start   != nullptr );
            assert( p_middle  != nullptr );
            assert( p_end     != nullptr );
            assert( num_total != 0 );
    
            if ( num_total == 1 )
            {
                switch ( loop_state )
                {
                case LOOPSTATE_LOOKINGFORONE:
                    return p_start->data;
                case LOOPSTATE_FOUNDONE:
                    return arithmetic_mean<T,U>( val_found, p_start->data );
                default:
                    assert( false ); //this should be unreachable
                }
            }
    
            //select the pivot according to the "median-of-three" rule
            T pivot;
            if ( p_start->data < p_middle->data )
            {
                if ( p_middle->data < p_end->data )
                    pivot = p_middle->data;
                else if ( p_start->data < p_end->data )
                    pivot = p_end->data;
                else
                    pivot = p_start->data;
            }
            else
            {
                if ( p_start->data < p_end->data )
                    pivot = p_start->data;
                else if ( p_middle->data < p_end->data )
                    pivot = p_end->data;
                else
                    pivot = p_middle->data;
            }
    
    #if PRINT_DEBUG_LEVEL >= 1
            //this line is conditionally compiled for extra debugging information
            std::cout << "\nmedian of three: " << (*pp_start)->data << " " << p_middle->data << " " << p_end->data << " ->" << pivot << std::endl;
    #endif
    
            // We will be dividing the current partition into 3 new partitions (less-than,
            // equal-to and greater-than) each represented as a linked list. Each list
            // requires a pointer to the start of the list and a pointer to the pointer at
            // the end of the list to write the address of new elements to. Also, when
            // traversing the lists, we need to keep a pointer to the middle of the list,
            // as this information will be required for selecting a new pivot in the next
            // iteration of the loop. The latter is not required for the equal-to partition,
            // as it would never be used.
            Node<T> *p_less    = nullptr, **pp_less_end    = &p_less,    **pp_less_middle    = &p_less;
            Node<T> *p_equal   = nullptr, **pp_equal_end   = &p_equal;
            Node<T> *p_greater = nullptr, **pp_greater_end = &p_greater, **pp_greater_middle = &p_greater;
    
            // These pointers are only used as a cache to the location of the end node.
            // Despite their similar name, their function is quite different to pp_less_end
            // and pp_greater_end.
            Node<T> *p_less_end    = nullptr;
            Node<T> *p_greater_end = nullptr;
    
            // counter for the number of elements in each partition
            int num_less = 0;
            int num_equal = 0;
            int num_greater = 0;
    
            // NOTE:
            // The following loop will temporarily split the linked list. It will be merged later.
    
            Node<T> *p_next_node = p_start;
    
            //the following line isn't necessary; it is only used to clarify that the pointers no
            //longer point to anything meaningful
            *pp_start = p_start = nullptr;
    
            for ( int i = 0; i < num_total; i++ )
            {
                assert( p_next_node != nullptr );
    
                Node<T> *p_current_node = p_next_node;
                p_next_node = p_next_node->next;
    
                if ( p_current_node->data < pivot )
                {
                    //link node to pp_less
                    assert( *pp_less_end == nullptr );
                    *pp_less_end = p_less_end = p_current_node;
                    pp_less_end = &p_current_node->next;
                    p_current_node->next = nullptr;
    
                    num_less++;
                    if ( num_less % 2 == 0 )
                    {
                        pp_less_middle = &(*pp_less_middle)->next;
                    }
                }
                else if ( p_current_node->data == pivot )
                {
                    //link node to pp_equal
                    assert( *pp_equal_end == nullptr );
                    *pp_equal_end = p_current_node;
                    pp_equal_end = &p_current_node->next;
                    p_current_node->next = nullptr;
    
                    num_equal++;
                }
                else
                {
                    //link node to pp_greater
                    assert( *pp_greater_end == nullptr );
                    *pp_greater_end = p_greater_end = p_current_node;
                    pp_greater_end = &p_current_node->next;
                    p_current_node->next = nullptr;
    
                    num_greater++;
                    if ( num_greater % 2 == 0 )
                    {
                        pp_greater_middle = &(*pp_greater_middle)->next;
                    }
                }
            }
    
            assert( num_total == num_less + num_equal + num_greater );
            assert( num_equal >= 1 );
    
    #if PRINT_DEBUG_LEVEL >= 1
            //this section is conditionally compiled for extra debugging information
            {
                std::cout << std::setfill( '0' );
                switch ( loop_state )
                {
                case LOOPSTATE_LOOKINGFORONE:
                    std::cout << "LOOPSTATE_LOOKINGFORONE k = " << k << "\n";
                    break;
                case LOOPSTATE_LOOKINGFORTWO:
                    std::cout << "LOOPSTATE_LOOKINGFORTWO k = " << k << "\n";
                    break;
                case LOOPSTATE_FOUNDONE:
                    std::cout << "LOOPSTATE_FOUNDONE k = " << k << " val_found = " << val_found << "\n";
                }
                std::cout << "partition lengths: ";
                std::cout <<
                    std::setw( 2 ) << num_less    << " " <<
                    std::setw( 2 ) << num_equal   << " " <<
                    std::setw( 2 ) << num_greater << " " <<
                    std::setw( 2 ) << num_total   << "\n";
    #if PRINT_DEBUG_LEVEL >= 2
                Node<T> *p;
                std::cout << "less: ";
                if ( num_less > 10 )
                    std::cout << "too many to print";
                else
                    for ( p = p_less; p != nullptr; p = p->next ) std::cout << p->data << " ";
                std::cout << "\nequal: ";
                if ( num_equal > 10 )
                    std::cout << "too many to print";
                else
                    for ( p = p_equal; p != nullptr; p = p->next ) std::cout << p->data << " ";
                std::cout << "\ngreater: ";
                if ( num_greater > 10 )
                    std::cout << "too many to print";
                else
                    for ( p = p_greater; p != nullptr; p = p->next ) std::cout << p->data << " ";
                std::cout << "\n\n" << std::flush;
    #endif
                std::cout << std::flush;
            }
    #endif
    
            //insert less-than partition into list
            assert( *pp_start == nullptr );
            *pp_start = p_less;
    
            //insert equal-to partition into list
            assert( *pp_less_end == nullptr );
            *pp_less_end = p_equal;
    
            //insert greater-than partition into list
            assert( *pp_equal_end == nullptr );
            *pp_equal_end = p_greater;
    
            //link list to previously cut off part
            assert( *pp_greater_end == nullptr );
            *pp_greater_end = p_next_node;
    
            //if less-than partition is large enough to hold both possible median values
            if ( k + 2 <= num_less )
            {
                //set the next iteration of the loop to process the less-than partition
                //pp_start is already set to the desired value
                p_middle = *pp_less_middle;
                p_end = p_less_end;
                num_total = num_less;
            }
    
            //else if less-than partition holds one of both possible median values
            else if ( k + 1 == num_less )
            {
                if ( loop_state == LOOPSTATE_LOOKINGFORTWO )
                {
                    //the equal_to partition never needs sorting, because all members are already equal
                    val_found = p_equal->data;
                    loop_state = LOOPSTATE_FOUNDONE;
                }
                //set the next iteration of the loop to process the less-than partition
                //pp_start is already set to the desired value
                p_middle = *pp_less_middle;
                p_end = p_less_end;
                num_total = num_less;
            }
    
            //else if equal-to partition holds both possible median values
            else if ( k + 2 <= num_less + num_equal )
            {
                //the equal_to partition never needs sorting, because all members are already equal
                if ( loop_state == LOOPSTATE_FOUNDONE )
                    return arithmetic_mean<T,U>( val_found, p_equal->data );
                return p_equal->data;
            }
    
            //else if equal-to partition holds one of both possible median values
            else if ( k + 1 == num_less + num_equal )
            {
                switch ( loop_state )
                {
                case LOOPSTATE_LOOKINGFORONE:
                    return p_equal->data;
                case LOOPSTATE_LOOKINGFORTWO:
                    val_found = p_equal->data;
                    loop_state = LOOPSTATE_FOUNDONE;
                    k = 0;
                    //set the next iteration of the loop to process the greater-than partition
                    pp_start = pp_equal_end;
                    p_middle = *pp_greater_middle;
                    p_end = p_greater_end;
                    num_total = num_greater;
                    break;
                case LOOPSTATE_FOUNDONE:
                    return arithmetic_mean<T,U>( val_found, p_equal->data );
                }
            }
    
            //else both possible median values must be in the greater-than partition
            else
            {
                k = k - num_less - num_equal;
    
                //set the next iteration of the loop to process the greater-than partition
                pp_start = pp_equal_end;
                p_middle = *pp_greater_middle;
                p_end = p_greater_end;
                num_total = num_greater;
            }
        }
    }
    
    
    // NOTE:
    // The following code is not part of the algorithm, but is only intended to test the algorithm
    
    // This simple class is designed to contain a singly-linked list
    template <typename T>
    class List
    {
    public:
        List() : first( nullptr ) {}
    
        // the following is required to abide by the rule of three/five/zero
        // see: https://en.cppreference.com/w/cpp/language/rule_of_three
        List( const List<T> & ) = delete;
        List( const List<T> && ) = delete;
        List<T>& operator=( List<T> & ) = delete;
        List<T>& operator=( List<T> && ) = delete;
    
        ~List()
        {
            Node<T> *p = first;
    
            while ( p != nullptr )
            {
                Node<T> *temp = p;
                p = p->next;
                delete temp;
            }
        }
    
        void push_front( int data )
        {
            Node<T> *temp = new Node<T>;
    
            temp->data = data;
    
            temp->next = first;
            first = temp;
        }
    
        //member variables
        Node<T> *first;
    };
    
    int main()
    {
        //generated random numbers will be between 0 and 2 billion (fits in 32-bit signed int)
        constexpr int min_val = 0;
        constexpr int max_val = 2*1000*1000*1000;
    
        //will allocate array for 1 million ints and fill with random numbers
        constexpr int num_values = 1*1000*1000;
    
        //this class contains the singly-linked list and is empty for now
        List<int> l;
        double result;
    
        //These variables are used for random number generation
        std::random_device rd;
        std::mt19937 gen( rd() );
        std::uniform_int_distribution<> dis( min_val, max_val );
    
        try
        {
            //fill array with random data
            std::cout << "Filling array with random data..." << std::flush;
            auto unsorted_data = std::make_unique<int[]>( num_values );
            for ( int i = 0; i < num_values; i++ ) unsorted_data[i] = dis( gen );
    
            //fill the singly-linked list
            std::cout << "done\nFilling linked list..." << std::flush;
            for ( int i = 0; i < num_values; i++ ) l.push_front( unsorted_data[i] );
    
            std::cout << "done\nCalculating median using STL function..." << std::flush;
    
            //calculate the median using the functions provided by the C++ standard template library.
            //Note: this is only done to compare the results with the algorithm provided in this file
            if ( num_values % 2 == 0 )
            {
                int median1, median2;
    
                std::nth_element( &unsorted_data[0], &unsorted_data[(num_values - 1) / 2], &unsorted_data[num_values] );
                median1 = unsorted_data[(num_values - 1) / 2];
                std::nth_element( &unsorted_data[0], &unsorted_data[(num_values - 0) / 2], &unsorted_data[num_values] );
                median2 = unsorted_data[(num_values - 0) / 2];
    
                result = arithmetic_mean<int,double>( median1, median2 );
            }
            else
            {
                int median;
    
                std::nth_element( &unsorted_data[0], &unsorted_data[(num_values - 0) / 2], &unsorted_data[num_values] );
                median = unsorted_data[(num_values - 0) / 2];
    
                result = static_cast<int>(median);
            }
    
            std::cout << "done\nMedian according to STL function: " << std::setprecision( 12 ) << result << std::endl;
    
            // NOTE: Since the STL functions only sorted the array, but not the linked list, the 
            //       order of the linked list is still random and not pre-sorted.
    
            //calculate the median using the algorithm provided in this file
            std::cout << "Starting algorithm" << std::endl;
            result = find_median<int,double>( l.first );
            std::cout << "The calculated median is: " << std::setprecision( 12 ) << result << std::endl;
    
            std::cout << "Cleaning up\n\n" << std::flush;
        }
        catch ( std::bad_alloc )
        {
            std::cerr << "Error: Unable to allocate sufficient memory!" << std::endl;
            return -1;
        }
    
        return 0;
    }
    
    我已经使用一百万个随机生成的元素成功地测试了我的代码,并且它几乎在瞬间找到了正确的中位数。

    关于c++ - 单链列表C++的Quickselect算法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60776300/

    相关文章:

    c++ - 使用类成员函数/变量初始化默认参数

    c++ - 区分 cl.exe (Visual Studio C++) 生成的 32 位和 64 位 PE 目标文件

    java - 在Java中如何在jframe组件之间传递变量和列表

    python - 当模块分配给变量时如何使用模块中的方法

    algorithm - 在 Scala 中计算最多 5 的中位数

    python - 我如何使用类别中值进行 df.fillna

    c++ - invoke_result获取模板成员函数的返回类型

    Android Quote 应用程序,下一步和后退按钮

    algorithm - 在二叉搜索树中查找中位数

    c++ - 来自 Windows 原始输入的水平鼠标滚轮消息