用C++求最长递增子序列

    技术2022-05-11  139

    给定一个无序整数序列,怎样求其最长递增子序列?#include <deque>#include <cassert>void MaxIncreaseQueue(const std::deque<int> & source,std::deque<int> & result){    typedef std::deque<int> IntArray;    //怎样从source中得到最长递增子序列放到result中?}经过分析我们发现,首先,我们要得到这个序列的最小值,所以我们建立了我们的第一个类GetMinValue: class GetMinValue{    int minvalue_;public:    explicit GetMinValue(const IntArray & in){        assert(in.size() != 0);        IntArray::const_iterator i = in.begin();        for(minvalue_ = *i,++i;i != in.end();++i){            if(minvalue_ > *i)                minvalue_ = *i;        }    }    ~GetMinValue(){}    operator int() const{        return minvalue_;    }};可以看到,GetMinValue以一个IntArray 为参数构造,并由operator int() const来返回其最小值。这样,我们可以这样来得到source的最小值:int min = GetMinValue(source);然后我们开始考虑算法的问题。我想很多人都知道计算机里的堆的概念,一个堆就是一个类似于树的结构,只是它的节点满足一定的排列规则,于是比一般的树更有规律,也更复

    杂。我们这里构造这样一个堆(同时也是一棵树,所以我用了Tree这个名字),其父节点的值总比子节点小(或相等),其右节点的值总比左节点小,如图:                   2   ——父节点值比子节点小                   / /                                                                  6   3 ——右节点值比左节点小于是,当我们遍历整个序列的时候,就可以根据以上规则建立这个堆,然后根据深度最大的节点,回溯出整个子序列。第一步我们建立节点的定义:class Node{    int value_,length_;    Node *parent_,*rightchild_,*leftbrother_;public:    Node(Node * parent,int value):value_(value),length_(1),parent_(parent),rightchild_(0),leftbrother_(0){        if(parent_ != 0){            length_ = parent->length_ + 1;            leftbrother_ = parent->rightchild_;            parent->rightchild_ = this;        }    }    ~Node(){        if(rightchild_)            delete rightchild_;        if(leftbrother_)            delete leftbrother_;    }    //Functions for member access    int Value() const{        return value_;    }    int Length() const{        return length_;    }    Node * Parent() const{        return parent_;    }    Node * RightChild() const{        return rightchild_;    }    Node * LeftBrother() const{        return leftbrother_;    }};首先看到Node的数据成员,有value_——节点的值,length_——节点的深度(也就是子序列的长度),parent_——父节点的指针(用于回溯),rightchild_——最右的子节点的

    指针,leftbrother_——左兄弟的指针。为什么要用rightchild_和leftbrother_而不用几个children_呢?显然大家都能猜到,因为这个树并不一定是二叉树,所以子节点数是不

    确定的。然后大家也许会注意到Node的构造函数,必须要提供父节点的指针,这只是一个设计上的问题。我想把所有关于Node指针的操作全放在Node里面,这样就不用把parent_,

    rightchild_等定义为public,或提供一个修改的接口。随后是Node的析构函数,2个delete可能会吓坏许多有良好C++编程风格的高手。我只能说,当我把this赋值给parent->rightchild_(Node构造函数里)的时候,就注定了“这样的

    结局”(^_^)最后是成员的访问函数,我想应该不会有多少异议。当构造好了节点,我们开始正式构造我们的树(堆)了。树的根root_的value_总是序列里面最小的值(这就是我们定义GetMinValue的原因),这是在树的构造函数里保证的,为

    的是让所有可能的序列都能在这个根下生长。而当我们每向树里加入一个值为val的节点时,都要调用AddValue(int val)函数,这个函数进一步调用AddValue(Node * parent,int

    val)函数的AddValue(&root,val)版本。AddValue(Node * parent,int val)函数是递规调用的,它的逻辑是这样:如果parent不是一个节点或者val比parent的value_值小,那么返回false,告诉调用者不能插入节点;否则对parent的每一个子节点,如果其value_值比val小,那么应该对其每一

    个都调用AddValue;如果没有一个子节点能调用AddValue成功的话,就把val当成parent的子节点插进来。这个规则看似复杂,其实如果有一个流程图来表示,也许会明了得多,可惜我不会在这里画图。现在我想我们应该来看看代码了: class Tree{    Node root_,*max_length_node_;    void AddValue(int val){        AddValue(&root_,val);    }    bool AddValue(Node * parent,int val){        if(!parent || val < parent->Value())            return false;        int count = 0;        for(Node * p = parent->RightChild();p != 0;p = p->LeftBrother(),++count){            if(!AddValue(p,val))                break;        }        if(!count)            AppendNode(parent,val);        return true;    }    void AppendNode(Node * parent,int val){        Node * p = new Node(parent,val);        if(p->Length() > max_length_node_->Length()){            max_length_node_ = p;        }    }public:    explicit Tree(int value):root_(0,value),max_length_node_(&root_){}    ~Tree(){}    void InputIntArray(const IntArray & in){        for(IntArray::const_iterator i = in.begin();i != in.end();++i){            AddValue(*i);        }    }    void OutputIntArray(IntArray & out){        out.erase(out.begin(),out.end());        Node * p = max_length_node_;        while(p != &root_){            out.push_front(p->Value());            p = p->Parent();        }    }};大家也许会发现成员变量里的max_length_node_,这是一个追踪深度最大的节点的变量,初始化为&root_,方便最后的回溯。函数AppendNode是真正插入节点的函数,通过一个简单的Node(parent,val)就完成了所有的动作,这就是我前面对Node的构造函数满意的地方。然后是检查深度最大的节点的变化

    情况,这一点应该是显而易见的。Tree的构造函数主要用来初始化root_,要使得root_.value_的值是最小的,理由我已经说过了。析构函数更是简单得没有明显的代码。最后是2个接口InputIntArray和OutputIntArray分别用来输入源序列和输出结果序列。注意out.push_front(p->Value())这句,因为我是从最深的子节点向上回溯的,也就是说按

    照相反的顺序得到最大子序列,所以用了push_front函数,这也是我使用deque而不用vector的原因。 所有的辅助工具都介绍了,现在你也许要不耐烦的问:“说了这么多,到底怎么得到最大子序列的呢?”那么现在让我们回到最初的MaxIncreaseQueue函数:void MaxIncreaseQueue(const std::deque<int> & source,std::deque<int> & result){    Tree t = Tree(GetMinValue(source));    t.InputIntArray(source);    t.OutputIntArray(result);}一切就OK了!如果你对上面的4行代码还有疑问的话,请参阅我前面的一大堆“口水”。 int main(){    typedef deque<int> IntArray;    const int size = 30;    int array[size] = {5,4,9,4,6,1,7,4,3,5,7,5,3,2,3,4,5,8,1,6,5,4,0,0,1,0,0,0,0,0};    IntArray source(array,array + size),result;    MaxIncreaseQueue(source,result);    cout<<"Max Queue:";    for(IntArray::const_iterator i = result.begin();i != result.end();++i)        cout<<*i<<' ';    cout<<'/n';    return 0;}好了,先说到这里,我也累了,下次再聊吧!


    最新回复(0)