簡體   English   中英

算法 - O(n)中二進制搜索樹的每兩個節點之間的距離之和?

[英]Algorithm- Sum of distances between every two nodes of a Binary Search Tree in O(n)?

問題是找出BinarySearchTree的每兩個節點之間的距離之和,假設每個父子對由單位距離分開。 每次插入后都要計算。

例如:

 ->first node is inserted..

      (root)

   total sum=0;

->left and right node are inserted

      (root)
      /    \
  (left)   (right)

   total sum = distance(root,left)+distance(root,right)+distance(left,right);
             =        1           +         1          +         2
             =     4

and so on.....

我提出的解決方案:

  1. 蠻力。 腳步:

    1. 執行DFS並跟蹤所有節點: O(n)
    2. 選擇每兩個節點並使用最低公共祖先方法計算它們之間的O(nC2)_times_O(log(n))=O(n2log(n))距離並將它們相加。

    總體復雜性: -O(n2log(n))

  2. O(nlog(n)) 腳步:-

    1. 在插入之前執行DFS並跟蹤所有節點: O(n)
    2. 計算插入節點與之間的距離: O(nlog(n)) 剩下的節點。
    3. 使用步驟2中計算的總和添加現有總和

    整體復雜性: -O(nlog(n))

現在的問題是“ O(n)的順序是否存在任何解決方案?

我們可以通過兩次遍歷樹來完成此操作。

首先,我們需要三個數組

int []left存儲了左子樹的距離之和。

int []right存儲右子樹的距離之和。

int []up存儲了父樹的距離之和(沒有當前的子樹)。

因此,首先遍歷,對於每個節點,我們計算左右距離。 如果節點是葉子,只需返回0,如果不是,我們可以有這個公式:

int cal(Node node){
    int left = cal(node.left);
    int right = cal(node.right);
    left[node.index] = left;
    right[node.index] = right;
    //Depend on the current node have left or right node, we add 0,1 or 2 to the final result
    int add = (node.left != null && node.right != null)? 2 : node.left != null ? 1 : node.right != null ? 1 : 0;
    return left + right + add;
}

然后對於第二次遍歷,我們需要向每個節點添加與其父節點的總距離。

             1
            / \
           2   3
          / \
         4   5

例如,對於節點1(根),總距離為left[1] + right[1] + 2up[1] = 0 ; (我們添加2,因為根有左右子樹,它的確切公式是:

int add = 0; 
if (node.left != null) 
    add++;
if(node.right != null)
    add++;

對於節點2,總距離為left[2] + right[2] + add + up[1] + right[1] + 1 + addRightup[2] = up[1] + right[1] + addRight 公式末尾有1的原因是因為當前節點到其父節點有一條邊,所以我們需要加1 現在,我代表當前節點的額外距離add ,額外的距離如果在父節點的左子樹是addLeft ,同樣addRight右子樹。

對於節點3,總距離為up[1] + left[1] + 1 + addLeftup[3] = up[1] + left[1] + addLeft ;

對於節點4,總距離為up[2] + right[2] + 1 + addRightup[4] = up[2] + right[2] + addRight ;

所以依賴於當前節點是左或右節點,我們相應地更新up

時間復雜度為O(n)

是的,您可以通過DP在O(n)中找到每兩個節點之間的整個樹的總距離。 簡而言之,你應該知道3件事:

cnt[i] is the node count of the ith-node's sub-tree
dis[i] is the sum distance of every ith-node subtree's node to i-th node
ret[i] is the sum distance of the ith-node subtree between every two node

請注意ret[root]是問題的答案,所以只需計算ret[i]並解決問題...如何計算ret[i] 需要cnt[i]dis[i]並遞歸地解決它。 關鍵問題是:

給出ret [left] ret [right] dis [left] dis [right] cnt [left] cnt [right] to cal ret [node] dis [node] cnt [node]。

              (node)
          /             \
    (left-subtree) (right subtree)
      /                   \
...(node x_i) ...   ...(node y_i)...
important:x_i is the any node in left-subtree(not leaf!) 
and y_i is the any node in right-subtree(not leaf either!).

cnt[node]很簡單,只等於cnt[left] + cnt[right] + 1

dis[node]不是那么難,等於dis[left] + dis[right] + cnt[left] + cnt[right] 原因:sigma(x i - > left)是dis[left] ,所以sigma(x i - > node)是dis[left] + cnt[left]

ret[node]等於三部分:

  1. x i - > x j和y i - > y j ,等於ret[left] + ret[right]
  2. x i - > node和y i - > node,equals dis[node]
  3. x i - > y j

等於sigma(x i - > node - > y j ),固定x i ,然后我們得到cnt [left] * distance(x i ,node)+ sigma(node-> y j ),然后cnt [left] * distance (x i ,node)+ sigma(node-> left-> y j ),

它是cnt[left]*distance(x_i,node) + cnt[left] + dis[left]

總結x icnt[left]*(cnt[right]+dis[right]) + cnt[right]*(cnt[left] + dis[left]) ,然后是2*cnt[left]*cnt[right] + dis[left]*cnt[right] + dis[right]*cnt[left]

總結這三個部分,我們得到ret[i] 遞歸地做,我們將得到ret[root]

我的代碼:

import java.util.Arrays;

public class BSTDistance {
    int[] left;
    int[] right;
    int[] cnt;
    int[] ret;
    int[] dis;
    int nNode;
    public BSTDistance(int n) {// n is the number of node
        left = new int[n];
        right = new int[n];
        cnt = new int[n];
        ret = new int[n];
        dis = new int[n];
        Arrays.fill(left,-1);
        Arrays.fill(right,-1);
        nNode = n;
    }
    void add(int a, int b)
    {
        if (left[b] == -1)
        {
            left[b] = a;
        }
        else
        {
            right[b] = a;
        }
    }
    int cal()
    {
        _cal(0);//assume root's idx is 0
        return ret[0];
    }
    void _cal(int idx)
    {
        if (left[idx] == -1 && right[idx] == -1)
        {
            cnt[idx] = 1;
            dis[idx] = 0;
            ret[idx] = 0;
        }
        else if (left[idx] != -1  && right[idx] == -1)
        {
            _cal(left[idx]);
            cnt[idx] = cnt[left[idx]] + 1;
            dis[idx] = dis[left[idx]] + cnt[left[idx]];
            ret[idx] = ret[left[idx]] + dis[idx];
        }//left[idx] == -1 and right[idx] != -1 is impossible, guarranted by add(int,int)  
        else 
        {
            _cal(left[idx]);
            _cal(right[idx]);
            cnt[idx] = cnt[left[idx]] + 1 + cnt[right[idx]];
            dis[idx] = dis[left[idx]] + dis[right[idx]] + cnt[left[idx]] + cnt[right[idx]];
            ret[idx] = dis[idx] + ret[left[idx]] + ret[right[idx]] + 2*cnt[left[idx]]*cnt[right[idx]] + dis[left[idx]]*cnt[right[idx]] + dis[right[idx]]*cnt[left[idx]];
        }
    }
    public static void main(String[] args)
    {
        BSTDistance bst1 = new BSTDistance(3);
        bst1.add(1, 0);
        bst1.add(2, 0);
        //   (0)
        //  /   \
        //(1)   (2)
        System.out.println(bst1.cal());
        BSTDistance bst2 = new BSTDistance(5);
        bst2.add(1, 0);
        bst2.add(2, 0);
        bst2.add(3, 1);
        bst2.add(4, 1);
        //       (0)
        //      /   \
        //    (1)   (2)
        //   /   \
        // (3)   (4)
        //0 -> 1:1
        //0 -> 2:1
        //0 -> 3:2
        //0 -> 4:2
        //1 -> 2:2
        //1 -> 3:1
        //1 -> 4:1
        //2 -> 3:3
        //2 -> 4:3
        //3 -> 4:2
        //2*4+3*2+1*4=18
        System.out.println(bst2.cal());
    }
}

輸出:

4
18

為了方便(讀者理解我的解決方案),我在bst2.cal()之后粘貼cnt[],dis[] and ret[]值:

cnt[] 5 3 1 1 1 
dis[] 6 2 0 0 0
ret[] 18 4 0 0 0 

PS:這是來自UESTC_elfness的解決方案,對他來說這是一個簡單的問題,而且我說Sayakiss ,問題對我來說並不那么難......

所以你可以相信我們......

首先,向每個節點添加四個變量。 四個變量是到左后代的距離,到右后代的距離之和,左后代的節點數和右后代中的節點數之和。 將它們表示為l,r,nl和nr。

其次,將總變量添加到根節點以記錄每次插入后的總和。

這個想法是,如果你有當前樹的總數,插入新節點后的新總數是(舊總數+新節點到所有其他節點的距離之和)。 每次插入時需要計算的是新節點到所有其他節點的距離之和。

1- Insert the new node with four variable set to zero.
2- Create two temp counter "node travel" and "subtotal" with value zero.
3- Back trace the route from new node to root. 
   a- go up to parent node
   b- add one to node travel 
   c- add node travel to subtotal
   d- add (nr * node travel) + r to subtotal if the new node is on left offspring
   e- add node travel to l
   f- add one to nl
4- Add subtotal to total

1 - O(n)

2 - O(1)

3 - O(log n),a到f取O(1)

4 - O(1)

如果你的意思是每次插入都是O(n),那么可以這樣做,假設你是在每次插入后從root開始的。

0- Record the current sum of the distances. Call it s1: O(1).
1- Insert the new node: O(n).
2- Perform a BFS, starting at this new node.
   For each new node you discover, record its distance to the start (new) node, as BFS always does: O(n).
   This gives you an array of the distances from the start node to all other nodes.
3- Sum these distances up. Call this s2: O(n).
4- New_sum = s1 + s2: O(1).

因此該算法是O(n)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM