我遇到了一个有趣的问题:
给定二进制树以向内螺旋顺序打印它,即第一个打印级别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)。我相信可能有一个更好的解决方案,它有更好的空间复杂性,但我想不出它。任何人有任何指针?
答案 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^level
到2^(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;
}
}