我们希望能够在屏幕上打印出树形的二叉树。二叉树以数组的形式存在。就像下面这样


我们为Array类增加了一个 heap_string 函数,它返回数组的二叉树结构。下面将描述这个函数的实现方法。

实现方法

我们的想法是,先在数组A的每个元素的前后增加若干个空格,然后再在适当的地方换行,就可以得到一棵树了。如下图所示
 

其规律就是,A[2]的宽度=A[4]的宽度+A[5]的宽度;A[1]的宽度=A[2]的宽度+A[3]的宽度。A[3]的宽度不太好办,因为它的下方没有孩子节点。这个问题可以通过向数组A增加若干个值为空格的节点,将数组变成一棵满二叉树来解决。

构造一棵满二叉树

我们将创建数组A的一个副本“out_heap”,它的元素都是String,并且是一棵满二叉树,ruby代码如下

 1class Array
 2  def heap_string(space='  ')
 3    # Prepares out_heap
 4    # The out_heap is a full tree and every node is a String
 5    out_heap = Array.new
 6    out_heap.base_index = 1
 7    
 8    self.each do |v|
 9      out_heap << v.to_s
10    end
11    
12    full_tree_size = 2**(self.heap_height+1)-1
13    out_heap += Array.new(full_tree_size-out_heap.length, ' ')
14    out_heap.heap_size = self.heap_size
15
16    
17
18  end
19end

此后我们的所有操作都将针对out_heap。接下来,我们将为out_heap中的每个元素增加若干个空格。

追加空格

我们将以“左-右-中”的顺序遍历out_heap,即以out_heap[4]->out_heap[5]->out_heap[2]->out_heap[3]->out_heap[1]的顺序访问out_heap,依次计算出每个元素的宽度并插入空格。详细描述如下:
1. 如果是叶子,则在叶子的左右添加space。space默认等于两个空格。
2. 如果不是叶子,则节点的宽度等于两个孩子的宽度之和,并且居中显示。
对应的ruby代码如下
 1 class Array
 2   def heap_string(space='  ')
 3     # Prepares out_heap
 4     # The out_heap is a full tree and every node is a String
 5     
 6     
 7 
 8     # Adds spaces to each node in out_heap
 9     out_heap.extend(HeapLRMEnumerator)
10     out_heap.each_index(1, out_heap.length) do |i|
11       l = left(i)
12       r = right(i)
13       
14       if l > out_heap.length # is leaf
15         out_heap[i] = space + out_heap[i] + space
16       elsif l <= out_heap.length # is not leaf
17         out_heap[i] = out_heap[i].center(out_heap[l].length+out_heap[r].length)
18       end
19     end
20   
21     
22 
23   end
24 end

到此out_heap中的值为 ["           A[1]           ", "      A[2]      ", "   A[3]   ", "  A[4]  ", "  A[5]  ", "     ", "     "]
使用“左-右-中”的顺序遍历数组是通过上面代码的第9行“out_heap.extend(HeapLRMEnumerator)”来实现的,HeapLRMEnumerator 是我们编写的一个以“左-右-中”的顺序遍历数组的迭代器,代码将在本文的最后给出。

生成连接线“/”和“\”

我们还需要生成节点之间的连接线。注意到连接线的宽度总是和父节点的宽度相同,但是我们不能象上面那样使用"/\".center(宽度)这样的语句,因为我们需要的不仅仅是居中,而是“分散剧中”---我们需要的是
“   /   \   ”而不是“   /\   ”。为此我们为String类追加了一个distribute_center()函数,可以实现这个效果(源代码在最后)。我们为out_heap中每个有孩子的节点生成连接线,保存到conn_lines数组中,ruby代码如下
 1 class Array
 2   def heap_string(space='  ')
 3     # Prepares out_heap
 4     # The out_heap is a full tree and every node is a String
 5     
 6     
 7 
 8     # Adds spaces to each node in out_heap
 9    
10     
11 
12     # Generates "/" and "\"
13     out_heap = out_heap.dup  # back to ordered enumerator
14     out_heap.base_index = 1
15     
16     conn_lines = Array.new
17     (1..out_heap.length).each do |i|
18       l = left(i)
19       r = right(i)
20       conn_line_string = ""
21       if l <= out_heap.heap_size  # has left child
22         conn_line_string << "/"
23       end
24       if r <= out_heap.heap_size  # has right chile
25         conn_line_string << "\\"
26       end
27       conn_lines[i] = conn_line_string.distribute_center(out_heap[i].length)
28     end
29 
30     
31   end
32 end

到此 conn_lines 中的内容为[nil, "        /        \\        ", "     /     \\    ", "          ", "        ", "        ", "     ", "     "]

合并

接下来我们把上面生成的所有的东西合并在一起,并加上换行符。
 1class Array
 2  def heap_string(space='  ')
 3    # Prepares out_heap
 4    # The out_heap is a full tree and every node is a String
 5    
 6    
 7    
 8    # Adds spaces to each node in out_heap
 9   
10    
11
12    # Generates "/" and "\"
13   
14    
15
16    # Combine out_heap and conn_lines
17    out_heap.each_index do |i|
18      parent = parent(i)
19      
20      if parent >= 1 && leftest?(i)  # is leftest and is not root
21        conn_lines_string = ""
22        (parenti).each do |j|
23          conn_lines_string << (conn_lines[j] || "")
24        end
25      
26        out_heap[i] = "\n" + conn_lines_string + "\n" + out_heap[i]
27      end
28    end
29    
30    return out_heap.to_s
31  end


程序到此结束。

缺陷

由于我们是从下到上计算每个元素的宽度,所以如果A[2]超长的话,就会变成下面这样

如果是你,会怎么解决这个问题呢?

全部源代码

全部源代码和单元测试代码

posted on 2007-08-27 15:13  1-2-3  阅读(9120)  评论(3编辑  收藏  举报