向内螺旋树遍历

时间:2015-12-14 01:33:55

标签: java algorithm tree traversal tree-traversal

我遇到了一个有趣的问题:

  

给定二进制树以向内螺旋顺序打印它,即第一个打印级别1,然后是级别n,然后是级别2,然后是n-1,依此类推。

For Ex:
1
2 3
4 5 6 7
8 9 10 11 12 13 14 15

Should Output:
1 15 14 13 12 11 10 9 8 2 3 7 6 5 4

我想到了一个解决方案:

  • 将每个级别元素存储在列表中 列表[0] = [1]
    列表[1] = [2,3] 列表[2] = [4,5,6,7]
    清单[3] = [8,9,10,11,12,13,14,15]

  • 按所需顺序(0,n-1,1,n-2,...)循环列表列表并打印。这里n是在上述情况下为4的级别数。

它的空间复杂度为O(n)。我相信可能有一个更好的解决方案,它有更好的空间复杂性,但我想不出它。任何人有任何指针?

5 个答案:

答案 0 :(得分:3)

您不需要逐级存储节点,您可以通过将所有节点存储在deque structure中来解决任务,并且还可以在节点中维护节点的级别。

Deque <Integer> deque = new LinkedList();
Queue <Integer> q = new LinkedList();
int[]level = new int[n];//we need to store the level of each node, n is number of node
q.add(0);//Adding the root node
while(!q.isEmpty()){
    int node = q.deque();
    for(int child : getNodeChildren(node)){
         level[child] = level[node] + 1;//Keep track the level of child node
         q.add(child);
         deque.add(child);//Store the child node into the queue.
    }
}
// Print output
boolean printHead = true;
while(!deque.isEmpty()){
   if(printHead){
      int node = deque.pollFirst();
      print node;
      //Check if we already printed every node in this current level, 
      //if yes, we need to switch to print from the end of the queue
      if(!deque.isEmpty() && level[deque.getFirst()] != level[node]){
          printHead = false;
      }
   }else{
      int node = deque.pollLast();
      print node;
      if(!deque.isEmpty() && level[deque.getLast()] != level[node]){
          printHead = true;
      }
   } 
}

代码的逻辑是:

我们从deque的开头继续打印,如果下一个节点与最后一个打印节点不在同一级别,这也意味着我们只打印当前级别的所有元素,我们需要从deque的末尾切换到打印,反之亦然。我们继续此过程,直到打印完所有节点。

空间复杂度为O(n),时间复杂度为O(n)。

答案 1 :(得分:2)

这是一个O(1)空格和O(n * h)时间的简单解决方案,其中n是节点数,h是树高。保持两个变量指向要打印的当前级别,然后根据二进制字符串遍历树后打印每个节点,二进制字符串的长度对应于适当的树深度;左转为&#34; 0&#34;,右转为&#34; 1&#34; (例如,示例中最左侧的节点由&#34; 000&#34;到达,其邻居位于&#34; 001&#34;)。由于要打印一个节点,我们必须进行最多h次迭代,时间复杂度为O(n * h)

以下是稍微更详细的算法:

down = 0
up = tree height

while:
  if down > up:
    break
  else:
    set exp to down then up (or only one if up == down):
      for i = 0 to 2^exp - 1:
        s = binary string i, padded to exp digits
        traverse tree according to s
        if a node exists by the end of s:
          print node
    down = down + 1
    up = up - 1

我不确定它是否会快一个数量级,但你可以在树中使用相同的原则,而不是从每个节点的根开始:回到直到#34 ; 0&#34;遇到,将其翻转到&#34; 1&#34;,然后一直向下保持到目标水平并递归重复,直到字符串全部为&#34; 1&#34; s。例如,目标级别4:

0000 => (000)1 => (00)10 => (001)1 => (0)100...etc.

答案 2 :(得分:1)

首先,我要感谢海报上的问题,并提出时间复杂性和空间复杂性如何相互对立的实际例子。给定大量空间,您的算法可以非常快(因为您可以创建'状态')。相反,如果你的空间非常有限,你会发现有必要进行额外的迭代以弥补缺乏状态。

事实证明,这个问题很容易在空间复杂度O(n)中得到解决。如果允许为结果创建空间(O(n)),则有几种算法可以创建时间复杂度为O(n)的解决方案。发布了一个解决方案(我喜欢),并且我还提供了一种不同的,也许是优雅的方法来解决O(n)和时间复杂度O(n);参考方法solveOrderN()。

挑战在于如何找到空间复杂度小于O(n)的解决方案。为此,不得允许您创建结果空间,因此您必须在问题中给出的树空间内操作。你被迫交换元素。

我提供的解决方案 - solveSubOrderN() - 不会创建任何结果空间;答案将在与问题相同的内存空间中返回。

我非常乐观我可以在O(log base 2(n))中解决这个问题,甚至在接近O(n)的时间复杂度中也是如此。但经过多次分析,我无法做到这一点。

当你开始交换元素时,当你回到一个不可处理的元素时,你最终会遇到“停止条件”。如果此暂停不存在,则可以达到O(log base 2 n)。但你无法避免它。所以为了弥补这一点,我不得不创建一些空间(一个布尔数组)来表示正在处理的元素的状态。

我想讨论一下这个布尔状态与为结果/解决方案创建空间有很大不同。当您创建一个解决方案(n个元素,大小为s)时,您正在创建space = n * s。在这个问题中,s是一个整数。一般来说,s可能非常大,这使得它成为一个“昂贵”的过程(以及海报造成这个问题的原因)。布尔数组的空间要小得多,甚至可以说随着s的增大而可以忽略不计(即随着s的增长,n / s接近0)。

当空间复杂度小于O(n)时,事实证明你无法实现时间复杂度O(n)。当你达到停止状态时,你被迫再次迭代。但事实证明,对于二叉树,额外迭代的数量很小(每次迭代都只是找到下一个起始点)。

总之,请在下面找到两个解决方案;一个具有空间复杂度O(n)和时间复杂度O(n),但更重要的是具有小于O(n)的空间复杂度,时间复杂度非常接近O(n)。

public class BinaryTree {

public static void main(String[] args) {

    int treeN2[] = {1, 2, 3};
    int treeN3[] = {1, 2, 3, 4, 5, 6, 7};
    int treeN4[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
    int treeN5[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};

    int answer[] = solveOrderN(treeN5);
    System.out.println(Arrays.toString(answer));
    solveSubOrderN(treeN5);
    System.out.println(Arrays.toString(treeN5));

}

/**
 * Given a binary tree, Perform inward spiral tree traversal.<br>
 * With this approach, there is no space created for the result.
 * All manipulation is done within the original tree<br>
 * Some space is created (boolean[n]) to add necessary state of processing.
 * Space Complexity:  Less than O(n), greater than log(base2)(n)
 * Time Complexity:  Slightly greater than O(n)
 * @param tree Input tree
 */
public static void solveSubOrderN(int tree[]) {

    boolean complete[] = new boolean[tree.length];
    Arrays.fill(complete, false);

    System.out.println("Solving Sub O(n); tree size="+tree.length);
    int n = log2Round(tree.length+1);

    if (n == 1)
        return;

    int o[] = getLevelOffsets(n);
    System.out.println("Number of levels="+n);

    int pos = 0;
    complete[0] = true;
    int currentValue = 0;
    int moves=1;

    while (moves < tree.length) {
        pos = getStartingPos(complete);
        currentValue = tree[pos];
        tree[pos] = 0;
        while (true) {
            int nextPos = getTargetPosition(pos, o, n);
            int nextValue = tree[nextPos];
            tree[nextPos] = currentValue;
            complete[nextPos] = true;
            currentValue = nextValue;
            pos = nextPos;
            moves++;
            if (currentValue == 0)
                break;
        }
    }
}

/**
 * Given a binary tree, Perform inward spiral tree traversal.
 * Space Complexity: O(n)
 * Time Complexity: O(n)
 * @param tree Input tree
 * @return The solution
 */
public static int[] solveOrderN(int tree[]) {
    int answer[] = new int[tree.length];
    int n = log2Round(tree.length+1);
    int o[] = getLevelOffsets(n);
    System.out.println("Solving O(n); tree size="+tree.length);
    System.out.println("Number of levels="+n);

    for (int i = 0; i < tree.length; i++) {
        answer[getTargetPosition(i, o, n)] = tree[i];
    }
    return answer;
}

/**
 * Search for the first unprocessed element
 * @param complete An array of boolean (true = processed)
 * @return
 */
public static int getStartingPos(boolean[] complete) {
    for (int i=0; i<complete.length; i++) {
        if (!complete[i])
            return i;
    }
    return 1;
}

public static int getTargetPosition(int pos, int o[], int n) {
    int row = getRow(pos);
    int rowOrder = getRowOrder(row, n);
    boolean isReversed = isBottom(row, n);
    int posInRow = getPosInRow(pos, n);
    int rowOffset = getRowOffset(rowSize(row), posInRow, isReversed);
    return o[rowOrder]+rowOffset;
}

public static int getRowOffset(int rowSize, int posInRow, boolean isReversed) {
    if (!isReversed)
        return posInRow;
    else
        return rowSize-posInRow-1;
}

public static int rowSize(int row) {
    return exp(row, 2);
}

public static int getPosInRow(int pos, int n) {
    int row = getRow(pos);
    return pos-(exp(row,2)-1);
}

/**
 * The top n/2 rows print forward, the bottom n/2 rows print reversed
 * @param row Zero based row [0 to n-1]
 * @param n Number of levels to the tree
 * @return true if line should be printed forward, false if reversed
 */
public static boolean isBottom(int row, int n) {
    int halfRounded = n/2;
    return (row <= n-halfRounded-1) ? false : true;
}

public static int exp(int n, int pow) {
    return (int)Math.pow(pow, n);
}

public static double log2(int n) {
    return (Math.log(n) / Math.log(2));
}

public static int log2Round(int n) {
    return (int)log2(n);
}

/**
 * For a given position [0 to N-1], find the level on the binary tree [0 to n-1]
 * @param pos Zero based position in the tree (0 to N-1)
 * @return Zero based level (0 to n-1)
 */
public static int getRow(int pos) {
    return log2Round(pos+1);
}

/**
 * For a given row [0 to n-1], find the order in which that line would be processed [1 to log base 2 n]
 * @param row The row in the tree [0 to n-1]
 * @return The order that row would be processed [0 to n-1]
 */
public static int getRowOrder(int row, int n) {
    return isBottom(row, n) ? (n-row-1)*2+1 : row*2;
}

public static int getRowForOffset(int row, int n) {
    boolean isOdd = row%2 == 1;
    return isOdd ? n-(row-1)/2 - 1 : row/2;
}

/**
 * Compute the offset for a given ordered row
 * @param n The number of levels in the tree
 * @return Generated offsets for each row (to keep time complexity at O(n))
 */
public static int[] getLevelOffsets(int n) {
    int o[] = new int[n];
    Arrays.fill(o, 0);
    o[0] = 0;
    int offset = 0;
    for (int i=0; i<n; i++) {
        int nextRow = getRowForOffset(i, n);
        o[i] = offset;
        offset += exp(nextRow, 2);
    }
    return o;
}

}

答案 3 :(得分:1)

你可以在没有有意义的额外空间的情况下解决它,但它需要O(n)。时间复杂度取决于二叉树的表示。 如果你有一个数组,时间复杂度将是O(n),例如:

public static void main(String[] args) {
    int[] binaryTree = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
    int nodes = binaryTree.length;
    int levels = (int) Math.ceil(Math.log(nodes) / Math.log(2));
    int[] increment = {1, -1};
    int index = 0;
    for (int i = 0; i < levels; i++) {
        int level = (index) * levels + increment[index] * (i / 2) - index;
        int begin = (int) Math.round(Math.pow(2, level));
        int[] range = {begin, 2 * begin - 1};
        for (int j = range[index]; j != range[1 - index]; j += increment[index]) {
            System.out.print(" " + binaryTree[j-1]);
        }
        System.out.print(" " + binaryTree[range[1 - index]-1]);
        index = 1 - index;
    }
    System.out.println();
}

如果您的二进制树的节点定义为:

publi class Node {
    Node[] children;
    int value;
}

时间复杂度(没有额外空间)将为O(n * log(n))。 例如:

public class BinaryNode {
    BinaryNode[] children = {null, null};
    int value;

    public BinaryNode(int value) {
        this.value = value;
    }

    private int getRecursive(int reference) {
        int result = value;
        if (reference > 1) {
            result = children[reference%2].getRecursive(reference/2);
        }
        return result;
    }

    public int get(int p) {
        int reference = 1;
        int p2 = p;
        while (p2 > 1){
            reference = (reference << 1) + (p2 & 1);
            p2 >>= 1;
        }
        return getRecursive(reference);
    }

    private void generateLevels(int levels){
        if (levels > 0){
            children[0] = new BinaryNode(value*2);
            children[0].generateLevel(levels -1);
            children[1] = new BinaryNode(value*2+1);
            children[1].generateLevel(levels -1);
        }
    }

    public static BinaryNode generate(int levels){
        BinaryNode result = null;
        if (levels > 0){
            result = new BinaryNode(1);
            result.generateLevels(levels - 1);
        }
        return result;
    }

    public static void main(String[] args) {

        int levels = 5;
        BinaryNode btreeRoot = BinaryNode.generate(levels);
        int[] increment = {1, -1};
        int index = 0;
        for (int i = 0; i < levels; i++) {
            int level = (index) * levels + increment[index] * (i / 2) - index;
            int begin = (int) Math.round(Math.pow(2, level));
            int[] range = {begin, 2 * begin - 1};
            for (int j = range[index]; j != range[1 - index]; j += increment[index]) {
                System.out.print(" " + btreeRoot.get(j));
            }
            System.out.print(" " + btreeRoot.get(range[1 - index]));
            index = 1 - index;
        }
        System.out.println();
    }
}

修改

你如何用n计算h? 容易:

h = log2(n)

因此,时间复杂度:O(n * log n)== O(n * h)。

算法在O(log(n))中获得一个元素,你需要获得所有(n个元素),结果时间复杂度为O(n * log(n))。 并且算法在没有额外空间的情况下完成。

BtreeRoot是原始二叉树,算法以正确的顺序获取所有节点,计算level中的下一个级别。 level转到O(1)中的值(1, levels -1, 2, levels - 2)(log2(n)次) 在每个级别中,算法都获得其m个节点:从2^level2^(level +1) - 1。所有级别的总和是n(节点数), 取决于电平(奇数或对),它从开始到结束或反向计算当前节点位置。然后你知道当前节点要打印的位置,需要取值,在一个完美的二叉树中,它需要Log(n)。

答案 4 :(得分:1)

说你的空间复杂度是&lt; O(n),看起来你正在假设树的表示方式,然后你不能用解决方案中的任何附加数据和/或结构来增加该表示(即O(n))。

对于我的回答,我将假设树是完整的(或至少是平衡的),其中平衡意味着树的高度为O(logN)。

在这种假设下,我们可以将树存储在堆数据结构中。在这种结构中,节点数据存储在一个数组中,其中,级别被安排:

 [0][1][1][2][2][2][2][3][3][3][3][3][3][3][3]...

如果树中不存在该节点,则数组中的给定位置将具有指向节点数据的指针或指向NULL的指针。

此情况下的空间量为O(2^L),其中L是树中的级别数。在平衡树L = O(log(N))中,因此此表示的大小为O(N)

另外假设我们知道这个数组的大小,那么树的级别数是ceiling(log_2(size))

假设级别来自0...K-1,则数组中任何级别的起始位置为:2^L - 1

这是算法的伪代码。

PrintLevel( Array<Node> heap, level) {
    int levelStart = power( 2, level ) - 1;
    int nextLevelStart = (levelStart + 1) * 2 - 1; // same as power( 2, level+1 ) - 1
    for( int i = levelStart; i < nextLevelStart; i++ ) {
        if( heap[i] != NULL ) {
            print( heap[i] );
        }
    }
}

SpiralPrint( Array<Node> heap ) {
      int lowLevel = Ceil(Log(heap.size())) - 1
      int highLevel = 0
      while( lowLevel >= highLevel ){
           PrintLevel( heap, highLevel );
           if (lowLevel > highLevel) 
                PrintLevel( heap, lowLevel );
           highLevel += 1;
           lowLevel -= 1;
      }
  }