一、树的基本知识
树是一种数据结构,它是由n(n>=1)个有限结点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:每个结点有零个或多个子结点;没有父结点的结点称为根结点;每一个非根结点有且只有一个父结点;除了根结点外,每个子结点可以分为多个不相交的子树;
1 import java.util.List; 2 3 public class TreeNode<E> { 4 public E key;// data 5 public TreeNode<E> parent;// parent 6 public List<TreeNode<E>> children;// children 7 8 public TreeNode(E key, TreeNode<E> parent) { 9 this.key = key; 10 this.parent = parent; 11 } 12 13 public TreeNode(E key) { 14 this.key = key; 15 } 16 17 @Override 18 public String toString() { 19 return "BSTNode [key=" + key + "]"; 20 } 21 22 }
2、树的接口类:
1 import java.util.List; 2 3 public interface ITree<E> { 4 /** 5 * 节点数 6 * @return 7 */ 8 int getSize(); 9 10 /** 11 * 获取根节点 12 * @return 13 */ 14 TreeNode<E> getRoot(); 15 16 /** 17 * 获取x的父节点 18 * @param x 19 * @return 20 */ 21 TreeNode<E> getParent(TreeNode<E> x); 22 23 /** 24 * 获取第一个儿子 25 * @param x 26 * @return 27 */ 28 TreeNode<E> getFirstChild(TreeNode<E> x); 29 30 /** 31 * 获取x的下一个兄弟 32 * @param x 33 * @return 34 */ 35 TreeNode<E> getNextSibling(TreeNode<E> x); 36 37 /** 38 * 子树高度 39 * @param x 40 * @return 41 */ 42 int getHeight(TreeNode<E> x); 43 44 /** 45 * 插入子节点 46 * @param x 47 * @param child 48 */ 49 void insertChild(TreeNode<E> x, TreeNode<E> child); 50 51 /** 52 * 删除第i个子节点 53 * @param x 54 * @param i 55 */ 56 void deleteChild(TreeNode<E> x, int i); 57 58 /** 59 * 先序遍历 60 * @param x 61 * @return 62 */ 63 List<TreeNode<E>> preOrder(TreeNode<E> x); 64 65 /** 66 * 后续遍历 67 * @param x 68 * @return 69 */ 70 List<TreeNode<E>> postOrder(TreeNode<E> x); 71 72 /** 73 * 层次遍历 74 * @param x 75 * @return 76 */ 77 List<List<TreeNode<E>>> levelOrder(TreeNode<E> x); 78 79 List<List<TreeNode<E>>> levelOrder(); 80 }
3、MyTree类:
注意这儿层次遍历这个函数,用到的bfs(宽度优先搜索),顾名思义就是遍历完每一层再接着遍历下一层。相比于DFS用的递归而言,那么bfs用到的技巧就是队列,口诀:(队)弹一个,加N个(队)。如果某行有n个节点,从1访问到n时,需要保存1的左右节点,2的左右节点,,,n的左右节点,遍历下行时的顺序是1的左右,2的左右,,,n的左右。这不就是先进(先保存)先出(先访问)吗?算法伪码:1.root进队(每个元素都要进队,别直接访问)2.开始循环:队首出队,其左右节点进队。(当然你也可以左右节点先进队,接着队首出队) 循环条件是队非空。
但若是需要换行打印的话就比较麻烦了,第一行的换行很简单啊,root后直接换行。那么你已经知道了第一行的换行,你就知道了第二行的换行位置啊,毕竟第二行都是第一行的“映射”。所以呢,跟刚才一样,在访问第n行的时候就要保存第n+1行的最右信息,反正n+1行的最右就是从第n行的左节点(第n+1行的最左)开始迭代。那么就需要用到双指针:增加两个TreeNode:last和nlast。last:表示当前遍历层最右结点。nlast:表示下一层最右结点。遍历时,每次将nlast指向插入队列元素,最后一个插入结点时即最右结点。插入左右孩子之后,检测last是否为当前输出结点,若是,则表示需要进行换行,并将last指向下一层的nlast。
1 import java.util.ArrayList; 2 import java.util.LinkedList; 3 import java.util.List; 4 import java.util.Queue; 5 6 public class MyTree<E> implements ITree<E> { 7 private int size = 0; 8 private TreeNode root; 9 10 public MyTree(TreeNode root) { 11 this.root = root; 12 size++; 13 } 14 15 @Override 16 public int getSize() { 17 return size; 18 } 19 20 @Override 21 public TreeNode<E> getRoot() { 22 return root; 23 } 24 25 @Override 26 public TreeNode<E> getParent(TreeNode<E> x) { 27 return x.parent; 28 } 29 30 @Override 31 public TreeNode<E> getFirstChild(TreeNode<E> x) { 32 return x.children.get(0); 33 } 34 35 @Override 36 public TreeNode<E> getNextSibling(TreeNode<E> x) { 37 List<TreeNode<E>> children = x.parent.children; 38 int i = children.indexOf(x); 39 // try { 40 // return children.get(i + 1); 41 // } catch (Exception e) { 42 // return null; 43 // } 44 if (i == children.size() - 1) { 45 return null; 46 } else { 47 return children.get(i + 1); 48 } 49 } 50 51 /** 52 * 获得整棵树的高度 53 */ 54 public int getHeight() { 55 return getHeight(root); 56 } 57 58 // dfs 深度优先遍历求高度 59 @Override 60 public int getHeight(TreeNode<E> x) { 61 if (x.children == null) { 62 return 0; 63 } else { 64 int h = 0; 65 for (int i = 0; i < x.children.size(); i++) { 66 h = Math.max(h, getHeight(x.children.get(i))); 67 } 68 return h + 1; 69 } 70 } 71 72 @Override 73 public void insertChild(TreeNode<E> p, TreeNode<E> child) { 74 if (p.children == null) { 75 p.children = new ArrayList<>(); 76 } 77 p.children.add(child); 78 child.parent = p; 79 size++; 80 } 81 82 @Override 83 public void deleteChild(TreeNode<E> p, int i) { 84 p.children.remove(i); 85 size--; 86 } 87 88 // 因为这不是二叉树,所以先序等遍历就先不写 89 @Override 90 public List<TreeNode<E>> preOrder(TreeNode<E> x) { 91 return null; 92 } 93 94 @Override 95 public List<TreeNode<E>> postOrder(TreeNode<E> x) { 96 return null; 97 } 98 99 // 层次遍历 bfs 宽度优先搜索 队列 口诀:(队)弹一个,加N个(队) 100 // 深搜 递归 101 // 双指针 换行 102 @Override 103 public List<List<TreeNode<E>>> levelOrder(TreeNode<E> x) { 104 List<List<TreeNode<E>>> res = new ArrayList<>();// list的list 105 Queue<TreeNode<E>> q = new LinkedList<>(); 106 q.add(x);// 初始化 107 TreeNode<E> last = x;// 标记上一行的最末节点 108 TreeNode<E> nLast = null;// 标记最新加入队列的节点 109 List<TreeNode<E>> l = new ArrayList<>();// 第一行的list 110 res.add(l); 111 112 while (!q.isEmpty()) { 113 TreeNode<E> peek = q.peek(); 114 // 把即将弹出的节点的子节点加入队列 115 if (peek.children != null) { 116 for (TreeNode<E> n : peek.children) { 117 q.add(n); 118 nLast = n; 119 } 120 } 121 l.add(q.poll());// 弹出,加入到当前层列表 122 123 if (peek == last && !q.isEmpty()) {// 如果现在弹出的节点是之前标记的最后节点,就要换列表 124 l = new ArrayList<>(); 125 res.add(l); 126 last = nLast; 127 } 128 } 129 return res; 130 } 131 132 @Override 133 public List<List<TreeNode<E>>> levelOrder() { 134 return levelOrder(root); 135 } 136 }