web-dev-qa-db-ja.com

ソートされたリンクリストからバランスのとれたバイナリ検索ツリーを作成する

並べ替えられた単一リンクリストからバランスのとれたバイナリ検索ツリーを作成する最良の方法は何ですか?

20
Timothy Chen

ノードをボトムアップで作成するのはどうですか?

このソリューションの時間の複雑さはO(N)です。私のブログ投稿の詳細な説明:

http://www.leetcode.com/2010/11/convert-sorted-list-to-balanced-binary.html

リンクされたリストを2回トラバースするだけで十分です。まず、リストの長さを取得するためのトラバーサル(これは、パラメーターnとして関数に渡されます)、次にリストの順序でノードを作成します。

BinaryTree* sortedListToBST(ListNode *& list, int start, int end) {
  if (start > end) return NULL;
  // same as (start+end)/2, avoids overflow
  int mid = start + (end - start) / 2;
  BinaryTree *leftChild = sortedListToBST(list, start, mid-1);
  BinaryTree *parent = new BinaryTree(list->data);
  parent->left = leftChild;
  list = list->next;
  parent->right = sortedListToBST(list, mid+1, end);
  return parent;
}

BinaryTree* sortedListToBST(ListNode *head, int n) {
  return sortedListToBST(head, 0, n-1);
}
26
1337c0d3r

少なくともリストのすべての要素を読み取る必要があるため、線形時間よりも優れた方法はありません。そのため、リストを配列(線形時間)にコピーし、通常の方法で効率的にツリーを構築することもできます。つまり、リスト[9,12,18,23,24,51,84]がある場合、23をルートにして、12と51の子を作成し、9と18を12と24の子にします。と84は51の子になります。全体として、O(n)である場合、正しく実行する必要があります。

実際のアルゴリズムは、その価値があるため、「リストの中央の要素をルートとして取得し、サブリストのBSTを再帰的に作成して中央の要素の左右に配置し、ルートの下に添付する」ことです。

3
Stuart Golodetz

これはpython実装です:

def sll_to_bbst(sll, start, end):
    """Build a balanced binary search tree from sorted linked list.

    This assumes that you have a class BinarySearchTree, with properties
    'l_child' and 'r_child'.

    Params:
        sll: sorted linked list, any data structure with 'popleft()' method,
            which removes and returns the leftmost element of the list. The
            easiest thing to do is to use 'collections.deque' for the sorted
            list.
        start: int, start index, on initial call set to 0
        end: int, on initial call should be set to len(sll)

    Returns:
        A balanced instance of BinarySearchTree

    This is a python implementation of solution found here: 
    http://leetcode.com/2010/11/convert-sorted-list-to-balanced-binary.html

    """

    if start >= end:
        return None

    middle = (start + end) // 2
    l_child = sll_to_bbst(sll, start, middle)
    root = BinarySearchTree(sll.popleft())
    root.l_child = l_child
    root.r_child = sll_to_bbst(sll, middle+1, end)

    return root
2
m.kocikowski

最適なのは、非同期トピックのランタイムだけではありません。ソートされたリンクリストには、バイナリツリーを直接作成するために必要なすべての情報が含まれており、おそらくこれが彼らが探しているものだと思います

最初と3番目のエントリは2番目のエントリの子になり、4番目のノードには2番目と6番目の子(5番目と7番目の子を持っています)が続きます。

疑似コードで

read three elements, make a node from them, mark as level 1, Push on stack
loop
    read three elemeents and make a node of them
    mark as level 1
    Push on stack
    loop while top two enties on stack have same level (n)
         make node of top two entries, mark as level n + 1, Push on stack
while elements remain in list

(残りの要素が3つ未満の場合や、いずれかの時点でツリーのバランスが取れていない場合の調整が必要)

編集:

どの時点でも、スタックには高さNの左ノードがあります。次のステップでは、1つの要素を読み取ってから、スタック上に高さNの別のノードを読み取って作成します。高さNのノードを作成するには、スタックに高さN -1のノードを作成してプッシュし、要素を読み取り、スタックに高さN-1の別のノードを作成します。これは再帰呼び出しです。

実際、これは、アルゴリズムが(変更されたとしても)バランスの取れたツリーを生成しないことを意味します。 2N + 1個のノードがある場合、左側に2N-1個の値、右側に1個の値を持つツリーが生成されます。

ですから、@ sgolodetzの答えの方が良いと思います。ただし、ツリーが構築されたときにツリーのバランスを再調整する方法を考えることができない場合を除きます。

2

トリックの質問!

最良の方法はSTLを使用することであり、セットが実装されているソートされた連想コンテナADTがソートされた範囲の挿入に線形時間の償却を要求するという事実を利用します。任意の言語のコアデータ構造のパス可能なセットは、同様の保証を提供する必要があります。実際の答えについては、他の人が提供した非常に巧妙なソリューションを参照してください。


それは何ですか?何か役立つものを提供する必要がありますか?

ハム...

これはどうですか?

バランスのとれた二分木で可能な最小の意味のある木は3つのノードです。親と2人の子供。このようなツリーの最初のインスタンスは、最初の3つの要素です。子親子。これを単一のノードとして想像してみましょう。さて、まあ、もう木はありません。しかし、私たちが望む形はChild-parent-Childであることはわかっています。
私たちの想像力で少しの間、私たちは最初の勝利で親へのポインタを保持したいと考えています。しかし、それは単一にリンクされています!
A、B、C、およびDと呼ぶ4つのポインタが必要です。したがって、Aを1に移動し、BをAに設定して1つ進めます。 CをBに等しく設定し、2つ進めます。 Bの下のノードは、すでにその正しい子を指しています。最初のツリーを作成します。ツリー1の親にBを残します。 Cは、2つの最小ツリーを子として持つノードに座っています。 AをCに等しく設定し、1つ進めます。 DをAに設定し、1つ進めます。これで、次の最小限のツリーを構築できます。 Dはそのツリーのルートを指し、Bはもう一方のルートを指し、Cは...を指します。新しいルートから2つの最小ツリーを吊るします。

いくつかの写真はどうですか?

[A][B][-][C]  

ノードとしての最小ツリーのイメージを使用して...

[B = Tree][C][A][D][-]

その後

[Tree A][C][Tree B]

問題がある以外は。 Dの2つ後のノードが次のルートです。

[B = Tree A][C][A][D][-][Roooooot?!]  

それへのポインターをCの代わりに維持することができれば、はるかに簡単になります。結局、Cを指すことがわかっているので、先に進んで、バイナリツリーでノードの構築を開始できます。これを保持し、その一部として、Cを左ノードとして入力できます。これをエレガントにするにはどうすればよいですか?

Cの下のNode=のポインターをBの下のノードに設定します。
それは言葉のあらゆる意味で不正行為ですが、このトリックを使用することにより、Bを解放します。
あるいは、あなたは正気で、実際にノード構造を構築し始めることができます。結局のところ、SLLのノードを実際に再利用することはできません。それらはおそらくPOD構造体です。

だから今...

[TreeA]<-[C][A][D][-][B]  
[TreeA]<-[C]->[TreeB][B] 

そして...ちょっと待って。これをツリーではなく単一のノードと考えれば、同じトリックを使用してCを解放できます。結局のところ、それは本当に単一のノードだからです。

[TreeC]<-[B][A][D][-][C]  

トリックをさらに一般化することができます。

[TreeC]<-[B][TreeD]<-[C][-]<-[D][-][A]    
[TreeC]<-[B][TreeD]<-[C]->[TreeE][A]  
[TreeC]<-[B]->[TreeF][A]  
[TreeG]<-[A][B][C][-][D]
[TreeG]<-[A][-]<-[C][-][D]  
[TreeG]<-[A][TreeH]<-[D][B][C][-]  
[TreeG]<-[A][TreeH]<-[D][-]<-[C][-][B]  
[TreeG]<-[A][TreeJ]<-[B][-]<-[C][-][D]  
[TreeG]<-[A][TreeJ]<-[B][TreeK]<-[D][-]<-[C][-]      
[TreeG]<-[A][TreeJ]<-[B][TreeK]<-[D][-]<-[C][-]  

重要なステップが欠けています!

[TreeG]<-[A]->([TreeJ]<-[B]->([TreeK]<-[D][-]<-[C][-]))  

になる:

[TreeG]<-[A]->[TreeL->([TreeK]<-[D][-]<-[C][-])][B]    
[TreeG]<-[A]->[TreeL->([TreeK]<-[D]->[TreeM])][B]  
[TreeG]<-[A]->[TreeL->[TreeN]][B]  
[TreeG]<-[A]->[TreeO][B]  
[TreeP]<-[B]  

アルゴリズムはかなりクリーンアップできることは明らかですが、アルゴリズムを繰り返し設計することで、最適化がどのように行われるかを示すのは興味深いと思いました。この種のプロセスは、優れた雇用主が何よりも求めているべきものだと思います。

トリックは、基本的に、次の中間点に到達するたびに親になることであり、その左側のサブツリーがすでに終了していることです。他のトリックは、すべてのサブツリーが完了していなくても、2つの子ノードとそのノードを指すノードが1つあると、ノードの処理が完了することです。これを使用すると、各要素が最大で4回しか操作されないため、線形時間ソリューションであると私が確信しているものを取得できます。問題は、これが本当にバランスのとれた二分探索木を形成するリストを与えられることに依存していることです。言い換えると、このソリューションを適用するのがはるかに困難になるか、不可能になるいくつかの隠れた制約があります。たとえば、要素の数が奇数の場合、または一意でない値が多数ある場合、これはかなり愚かなツリーを生成し始めます。

考慮事項:

  • 要素を一意にレンダリングします。
  • ノード数が奇数の場合は、末尾にダミー要素を挿入してください。
  • より素朴な実装のために熱心に歌ってください。
  • 2番目のトリックをいじる代わりに、両端キューを使用して、完成したサブツリーのルートと中点を保持します。
2
Jake Kurzer

並べ替えられたリンクリストの代わりに、並べ替えられた配列(論理的には関係ありませんが、実行時間は異なります)で最小の高さのBSTを作成するように求められました。以下は、取得できるコードです。

typedef struct Node{
     struct Node *left;
     int info;
     struct Node  *right;
}Node_t;

Node_t* Bin(int low, int high) {

     Node_t* node = NULL;
     int mid = 0;

     if(low <= high) {
         mid = (low+high)/2;
         node = CreateNode(a[mid]);
         printf("DEBUG: creating node for %d\n", a[mid]);

        if(node->left == NULL) {
            node->left = Bin(low, mid-1);
        }

        if(node->right == NULL) {
            node->right = Bin(mid+1, high);
        }

        return node;
    }//if(low <=high)
    else {
        return NULL;
    }
}//Bin(low,high)


Node_t* CreateNode(int info) {

    Node_t* node = malloc(sizeof(Node_t));
    memset(node, 0, sizeof(Node_t));
    node->info = info;
    node->left = NULL;
    node->right = NULL;

    return node;

}//CreateNode(info)

// call function for an array example: 6 7 8 9 10 11 12, it gets you desired 
// result

 Bin(0,6); 

HTH誰か。

1
Cleonjoys

@ 1337c0d3rから少し改善された実装 私のブログ

// create a balanced BST using @len elements starting from @head & move @head forward by @len
TreeNode *sortedListToBSTHelper(ListNode *&head, int len) {
    if (0 == len)   return NULL;

    auto left = sortedListToBSTHelper(head, len / 2);
    auto root = new TreeNode(head->val);
    root->left = left;
    head = head->next;
    root->right = sortedListToBSTHelper(head, (len - 1) / 2);
    return root;
}

TreeNode *sortedListToBST(ListNode *head) {
    int n = length(head);
    return sortedListToBSTHelper(head, n);
}
0
sinoTrinity

これは、私が提案する疑似再帰アルゴリズムです。


createTree(treenode *root, linknode *start, linknode *end)
{
   if(start == end or start = end->next)
   {
      return; 
   } 
   ptrsingle=start;
   ptrdouble=start;
   while(ptrdouble != end and ptrdouble->next !=end)
   {
    ptrsignle=ptrsingle->next;
    ptrdouble=ptrdouble->next->next;
   }
   //ptrsignle will now be at the middle element. 
   treenode cur_node=Allocatememory;
   cur_node->data = ptrsingle->data;
   if(root = null)
       {
           root = cur_node; 
       }
   else
      {
         if(cur_node->data (less than) root->data)
          root->left=cur_node
         else
           root->right=cur_node
      }
   createTree(cur_node, start, ptrSingle);
   createTree(cur_node, ptrSingle, End); 
}

Root = null;最初の呼び出しはcreatetree(Root、list、null);になります。

ツリーの再帰的な構築を行っていますが、中間配列は使用していません。中央の要素に到達するために、2つのポインタを1つずつ、2つずつ進めます。 2番目のポインターが最後に来るまでに、最初のポインターは中央にあります。

実行時間はo(nlogn)になります。余分なスペースはo(logn)になります。 nlognの挿入を保証するR-Bツリーを使用できる実際の状況では効率的なソリューションではありません。しかし、インタビューには十分です。

0
Manoj R

この投稿に関する詳細な説明がお役に立てば幸いです http://preparefortechinterview.blogspot.com/2013/10/planting-trees_1.html

0
Furquan

@Stuart Golodetzや@Jake Kurzerと同様に、重要なことはリストがすでにソートされていることです。

@Stuartの回答では、彼が提示した配列はBSTのバッキングデータ構造です。たとえば、検索操作では、ツリーを走査するためにインデックス配列計算を実行するだけで済みます。配列を大きくして要素を削除するのは難しい部分なので、ベクトルまたは他の一定時間のルックアップデータ構造を使用します。

@Jakeの回答でもこの事実を使用していますが、残念ながら、get(index)操作を実行するたびにリストを走査して毎回見つける必要があります。ただし、追加のメモリ使用量は必要ありません。

インタビュアーがツリーのオブジェクト構造表現を望んでいることが特に言及されていない限り、@ Stuartの答えを使用します。

このような質問では、トレードオフとすべてのオプションについて議論するための追加のポイントが与えられます。

0
StevenWilkins

リンクされたリストにあるノードの数がわかっている場合は、次のように実行できます。

// Gives path to subtree being built.  If branch[N] is false, branch
// less from the node at depth N, if true branch greater.
bool branch[max depth];

// If rem[N] is true, then for the current subtree at depth N, it's
// greater subtree has one more node than it's less subtree.
bool rem[max depth];

// Depth of root node of current subtree.
unsigned depth = 0;

// Number of nodes in current subtree.
unsigned num_sub = Number of nodes in linked list;

// The algorithm relies on a stack of nodes whose less subtree has
// been built, but whose right subtree has not yet been built.  The
// stack is implemented as linked list.  The nodes are linked
// together by having the "greater" handle of a node set to the
// next node in the list.  "less_parent" is the handle of the first
// node in the list.
Node *less_parent = nullptr;

// h is root of current subtree, child is one of its children.
Node *h, *child;

Node *p = head of the sorted linked list of nodes;

LOOP // loop unconditionally

    LOOP WHILE (num_sub > 2)
        // Subtract one for root of subtree.
        num_sub = num_sub - 1;

        rem[depth] = !!(num_sub & 1); // true if num_sub is an odd number
        branch[depth] = false;
        depth = depth + 1;
        num_sub = num_sub / 2;
    END LOOP

    IF (num_sub == 2)
        // Build a subtree with two nodes, slanting to greater.
        // I arbitrarily chose to always have the extra node in the
        // greater subtree when there is an odd number of nodes to
        // split between the two subtrees.

        h = p;
        p = the node after p in the linked list;
        child = p;
        p = the node after p in the linked list;
        make h and p into a two-element AVL tree;
    ELSE  // num_sub == 1

        // Build a subtree with one node.

        h = p;
        p = the next node in the linked list;
        make h into a leaf node;
    END IF

    LOOP WHILE (depth > 0)
        depth = depth - 1;
        IF (not branch[depth])
            // We've completed a less subtree, exit while loop.
            EXIT LOOP;
        END IF

        // We've completed a greater subtree, so attach it to
        // its parent (that is less than it).  We pop the parent
        // off the stack of less parents.
        child = h;
        h = less_parent;
        less_parent = h->greater_child;
        h->greater_child = child;
        num_sub = 2 * (num_sub - rem[depth]) + rem[depth] + 1;
        IF (num_sub & (num_sub - 1))
          // num_sub is not a power of 2
          h->balance_factor = 0;
        ELSE
          // num_sub is a power of 2
          h->balance_factor = 1;
        END IF
    END LOOP

    IF (num_sub == number of node in original linked list)
        // We've completed the full tree, exit outer unconditional loop
        EXIT LOOP;
    END IF

    // The subtree we've completed is the less subtree of the
    // next node in the sequence.

    child = h;
    h = p;
    p = the next node in the linked list;
    h->less_child = child;

    // Put h onto the stack of less parents.
    h->greater_child = less_parent;
    less_parent = h;

    // Proceed to creating greater than subtree of h.
    branch[depth] = true;
    num_sub = num_sub + rem[depth];
    depth = depth + 1;

END LOOP

// h now points to the root of the completed AVL tree.

C++でのこれのエンコーディングについては、ビルドメンバー関数(現在は361行目)の https://github.com/wkaras/C-plus-plus-intrusive-container-templates/blob/master/)を参照してください。 avl_tree.h 。これは実際にはより一般的なものであり、具体的にはリンクリストではなく、任意の順方向反復子を使用するテンプレートです。

0
WaltK