以恒定的时间复杂度查找双链表的中间元素

时间:2015-06-14 03:23:42

标签: linked-list

我试图在恒定的时间复杂度中找到双链表的中间元素。 我遇到了以下http://www.geeksforgeeks.org/design-a-stack-with-find-middle-operation/解决方案。 但我不明白如何使用中间指针。 任何人都可以帮助我理解这一点或给我一个更好的解决方案。

2 个答案:

答案 0 :(得分:1)

诀窍在于,您不会通过搜索找到,而是将其作为列表的属性进行持续维护。在链接中,它们定义了一个包含头节点,中间节点和节点数的结构;由于中间节点是结构的属性,因此您可以随时直接访问它来返回它。从那里开始,诀窍是维护它:所以pushpop函数必须调整中间节点,这也显示在代码中。

更深入:维护中间节点:我们知道给定计数对于奇数个节点(比如9),中间节点是“节点数除以2向上舍入”,因此9/2 = 4.5舍入up =第5个节点。因此,如果您从一个包含8个节点的列表开始,并添加一个节点,则新计数为9,您需要将中间节点移至“下一个”节点。这就是他们在检查新计数是否均匀时所做的事情。

答案 1 :(得分:1)

为了便于说明,我用C ++重写了这段代码:

#include <iostream>

typedef class Node* PNode;
class Node{
public:
    PNode next;
    PNode prev;
    int data;
    Node(){
        next = nullptr;
        prev = nullptr;
        data = 0;
    }
};

class List{
private:
    //Attributes
    PNode head;
    PNode mid;
    int count;
    //Methods
    void UpdateMiddle( bool _add );

public:
    //Constructors 
    List(){
        head = nullptr;
        mid = nullptr;
        count = 0;
    }
    ~List(){
        while( head != nullptr ){
            this->delmiddle();
            std::cout << count << std::endl;
        }
    }
    //Methods
    void push( int _data );
    void pop();
    int findmiddle();
    void delmiddle();
};

void List::UpdateMiddle( bool _add ){
    if( count == 0 ){
        mid = nullptr;
    }
    else if( count == 1 ){
        mid = head;
    }
    else{
        int remainder = count%2;
        if(_add){
            if( remainder == 0 ){
                mid = mid->prev;
            }
        }
        else{
            if( remainder == 1 ){
                mid = mid->next;
            }
        }
    }
}

void List::push( int _data ){
    PNode new_node = new Node();

    new_node->data = _data;
    new_node->prev = nullptr;
    new_node->next = head;

    if( head != nullptr ) head->prev = new_node;
    head = new_node;
    count++;

    UpdateMiddle( true );
}

void List::pop(){
    if( head != nullptr ){
        PNode del_node = head;
        head = head->next; 

        if( head != nullptr ) head->prev = nullptr;

        delete del_node;
        count--;
        UpdateMiddle(false);
    }
    else if( count != 0 ){
        std::cout << "ERROR";
        return;
    }
}

int List::findmiddle(){
    if( count > 0 ) return mid->data;
    else return -1;
}

void List::delmiddle(){
    if( mid != nullptr ){
        if( count == 1 || count == 2){
            this->pop();
        }
        else{
            PNode del_mid = mid;
            int remainder = count%2;

            if( remainder == 0 ){
                mid = del_mid->next;
                mid->prev = del_mid->prev;
                del_mid->prev->next = mid;
                delete del_mid;
                count--;
            }
            else{
                mid = del_mid->prev;
                mid->next = del_mid->next;
                del_mid->next->prev = mid;
                delete del_mid;
                count--;
            }
        }
    }
}

推送和弹出功能是不言自明的,它们在堆栈顶部添加节点并删除顶部的节点。在此代码中,只要添加或删除节点,函数UpdateMiddle就负责管理mid指针。其参数_add告诉它是否已添加或删除节点。当有两个以上的节点时,此信息很重要。

请注意,在UpdateMiddlepush内调用pop时,计数器已分别增加或减少。让我们从基本情况开始,其中有0个节点。 mid只是nullptr。当有一个节点时,mid将是该节点。

现在让我们来看一下数字列表&#34; 5,4,3,2,1&#34;。目前中间是3和count,节点数量是5个奇数。让我们添加一个6.它现在将是&#34; 6,5,4,3,2,1&#34;而count现在将是一个偶数。 mid现在也应该是4,因为它是中间的第一个,但它仍然没有更新。但是,现在如果我们添加7将是&#34; 7,6,5,4,3,2,1和#34;,count将是7,一个奇数,但请注意{ {1}}不会改变,它应该仍然是4.

可以从中观察到一种模式。添加节点时,mid从偶数更改为奇数,count保持不变,但从奇数到偶数mid更改位置。更具体地说,它向左移动一个位置。这基本上是mid的作用。通过检查UpdateMiddle当前是奇数还是偶数添加或删除节点后,它会决定是否应重新定位count。判断是添加还是删除节点也很重要,因为逻辑与删除时的添加相反。这基本上是您链接的代码中应用的逻辑。

此算法有效,因为mid的位置在添加或删除之前应始终正确,而函数mid假定唯一的更改是添加或删除节点,以及对于这个添加或删除,mid的位置是正确的。但是,我们通过将属性和函数UpdateMiddle设为私有来确保这一点,并通过公共函数使其可修改。