树的定义
一棵树是一些节点的集合。这个集合可以为空集,若不为空,那么树由根节点A以及0个或多个非空的子树
T
1
T_{1}
T1,
T
2
T_{2}
T2, •••,
T
k
T_{k}
Tk组成,这些子树中每一棵的根,都由来自根A的一条有向边所连接。
- 每一棵子树的根叫做根A的儿子,而根A是每一棵子树的父亲。
- 没有儿子的节点叫树叶。
- 具有相同父亲的节点称为兄弟。
- 从根A节点到 n i n_{i} ni唯一路径长为 n i n_{i} ni的深度。如根A的深度为0,T1的深度为1。
- n i n_{i} ni的高:从 n i n_{i} ni到一片树叶的最长路径。
树的实现
下图为window的C盘文件夹的部分目录结构:
将每个节点的所有儿子都放到树节点的链表中:
//树节点
public class TreeNode<T> {
private T data;//数据域
private TreeNode firstChild;//表示节点的第一个儿子
private TreeNode nearNode;//与自己相邻的兄弟
}
则上图变成:
图中向下的箭头是firstChild(节点的第一个儿子)的链,水平箭头表示nearNode(与自己相邻的兄弟)。
树的遍历
利用上图C盘的树为例子:
创建树
public class Tree {
/**
* 创建树
*/
public TreeNode creatTree() {
//建立各节点
TreeNode<String> nodeA = new TreeNode<>("C盘");
TreeNode<String> nodeB = new TreeNode<>("window");
TreeNode<String> nodeC = new TreeNode<>("bootmgr");
TreeNode<String> nodeD = new TreeNode<>("program file");
TreeNode<String> nodeE = new TreeNode<>("用户");
TreeNode<String> nodeF = new TreeNode<>("addins");
TreeNode<String> nodeG = new TreeNode<>("AppCompat");
TreeNode<String> nodeH = new TreeNode<>("Help");
TreeNode<String> nodeI = new TreeNode<>("Common Files");
TreeNode<String> nodeJ = new TreeNode<>("Intel");
TreeNode<String> nodeK = new TreeNode<>("Administrator");
TreeNode<String> nodeL = new TreeNode<>("Default User");
//各节点的关系
nodeA.setFirstChild(nodeB);
nodeB.setFirstChild(nodeF);
nodeB.setNearNode(nodeC);
nodeC.setNearNode(nodeD);
nodeD.setFirstChild(nodeI);
nodeD.setNearNode(nodeE);
nodeE.setFirstChild(nodeK);
nodeF.setNearNode(nodeG);
nodeG.setNearNode(nodeH);
nodeI.setNearNode(nodeJ);
nodeK.setNearNode(nodeL);
return nodeA;
}
}
迭代法遍历
public void printAll(TreeNode node) {
//节点不为空则打印当前节点数据
if (node != null) {
System.out.println(node.getData());
//如果第一个儿子不为空,则迭代继续找子节点的第一个儿子节点,直到返回null
if (node.getFstChild() != null) {
printAll(node.getFirstChild());
}
//找完第一个儿子,继续找寻找相邻兄弟节点,迭代查找兄弟的兄弟节点。
if (node.getNearNode() != null) {
printAll(node.getNearNode());
}
}
}
广度优先遍历
入队顺序:
- 当头结点A入队。
- 头结点A出队,A的第一个儿子节点B入队。
- 队头B出队。B的第一个儿子节点F入队,B的相邻兄弟节点C入队,此时对列中从队头到队尾排序为:F、C。
- 队头F出队。F的相邻兄弟节点G入队,此时对列中从队头到队尾排序为:C、G。
- 队头C出队。C的相邻兄弟节点D入队,此时对列中从队头到队尾排序为:G、D。
…
最后依次打印:A、B、F、C、G、D、H、I、E、J、K、L。
/**
* 广度优先遍历 (Breadth FirstSearch)
* 用到对列数据结构
*/
public void printBFS(TreeNode root) {
if (root == null) {
return;
}
Queue<TreeNode> queue = new LinkedList<>();
//添加根节点
queue.offer(root);
while (!queue.isEmpty()) {
//出队
TreeNode firstNode = queue.poll();
System.out.println(firstNode.getData());
//第一个儿子节点入队
TreeNode firstChild = firstNode.getFirstChild();
if (firstChild != null) {
queue.offer(firstChild);
}
//相邻兄弟节点入队
TreeNode nearNode = firstNode.getNearNode();
if (nearNode != null) {
queue.offer(nearNode);
}
}
}
深度优先遍历
入栈顺序:
- 当头结点A入栈。
- 头结点A出栈,A的第一个儿子B入栈。
- 队头B出栈。B的第一个儿子F入栈,B的相邻兄弟C入栈。此时栈:C、F。
- 队头C出栈。C的相邻兄弟D入栈,此时栈:D、F。
- 队头D出队。D的第一个儿子I入队,D的相邻兄弟E入栈。此时栈:E、I、B。
…
最后依次打印:A、B、C、D、E、K、L、I、J、F、G、H。
/**
* 深度优先遍历(Depth First Search)
* 用到栈数据结构
*/
public void printDFS(TreeNode root){
if (root == null) {
return;
}
Stack<TreeNode> stack = new Stack<>();
//根节点入栈
stack.push(root);
while (!stack.isEmpty()) {
//出栈
TreeNode firstNode = stack.pop();
System.out.println(firstNode.getData());
//第一个儿子节点入栈
TreeNode firstChild = firstNode.getFirstChild();
if (firstChild != null) {
stack.push(firstChild);
}
//相邻兄弟节点入队
TreeNode nearNode = firstNode.getNearNode();
if (nearNode != null) {
stack.push(nearNode);
}
}
}
数据结构与算法分析
大话数据结构