树
树(Tree)是n(n>=0)个结点的有限集。n=0时称为空树。
在任意一颗非空树中:
(1)有且仅有一个特定的称为根(Root)的结点。
(2)当n>1时,其余结点可分为m(m>0)个互不相交的有限集T1、T2、…..、Tm,其中每一个集合本身又是一棵树,并且称为根的子树(SubTree)。
树的常用术语
名称 |
含义 |
节点 |
树中的数据元素 |
树的度 |
树内各节点的度的最大值 |
节点的度 |
节点拥有的子树的数目称为度,记作d(v) |
叶子节点 |
节点的度数为0,称为叶子节点leaf、终端节点、末端节点 |
分支节点 |
节点度数不为0,称为非终端节点或分支节点 |
分支 |
节点之间的关系 |
内部节点 |
除根节点外的分支节点,当然也不包括叶子节点 |
孩子节点 |
节点的子树的根节点成为该节点的孩子 |
双亲节点 |
父节点,即该节点的前驱 |
兄弟节点 |
具有相同双亲节点的节点 |
祖先节点 |
从根节点到该节点所经分支上所有的节点。 |
子孙节点 |
节点的所有子树上的节点都成为该节点的子孙。 |
节点的层次 |
根节点为第一层,根的孩子为第二层,依次类推记作(Lv) |
树的深度 |
节点的层次的最大值 |
堂兄弟 |
双亲在同一层的节点 |
有序树 |
结点的子树是有顺序的(兄弟有大小,有先后次序),不能交换 |
无序数 |
结点的子树是无序的,可以交换 |
路径 |
树中的k个节点n1、n2、…nk,满足ni是n(i+1)的双亲,成为n1到nk的一条路径。就是一条线串下来的,前一个都是后一个父(前驱)节点。 |
森林 |
m(m≥0)课不相交的树的集合,对于节点而言,其子树的集合就是森林。 |
二叉树
二叉树(Binary Tree)是n(n>=0)个节点的有限集合,该集合可以为空集(称为空二叉树),或者由一个根节点和两个互不相交的、分别称为根节点的左子树和右子树的二叉树组成。
二叉树的5种基本形态
- 根节点既有左子树,又有右子树
- 根节点只有左子树
- 根节点只有右子树
- 只有一个根节点
- 空二叉树
二叉树特点
- 每个节点最多有两个子树,所以二叉树不存在度大于2的节点,可以没有子树或者一个子树。
- 左子树和右子树有顺序,次序不能任意颠倒。
- 即使树中某节点只有一颗子树,也要区分是左子树还是右子树。
特殊二叉树
斜树:所有的结点都只有左子树的二叉树叫左斜树。所有结点都是只有右子树的二叉树叫右斜树。这两者统称为斜树。
斜树有很明显的特点,就是每一层都只有一个结点,结点的个数与二叉树的深度相同。线性表结构就可以理解为是树的一种极其特殊的表现形式。
满二叉树:在一棵二叉树中,如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。
满二叉树的特点有:
(1)叶子只能出现在最下一层。
(2)非叶子结点的度一定是2。
(3)在同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多。
完全二叉树:对一棵具有n个结点的二叉树按层序编号,如果编号为i(1<i<n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。
满二叉树一定是一棵完全二叉树,但完全二叉树不一定是满的。
完全二叉树的特点:
(1)叶子结点只能出现在最下两层。
(2)最下层的叶子一定集中在左部连续位置。
(3)倒数二层,若有叶子结点,一定都在右部连续位置。
(4)如果结点度为1,则该结点只有左孩子,即不存在只有右子树的情况。
(5)同样结点数的二叉树,完全二又树的深度最小。
二叉树性质
性质1:在二叉树的第i层上至多有2i-1个结点(i>=1)
性质2:深度为k的二叉树至多有2k-1个结点(k>=1)
性质3:对任何一棵二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0 = n2+1。
性质4:具有n个结点的完全二叉树的深度为[log2n ] + 1 ([X]表示不大于X的最大整数)。
性质5:如果对一颗有n个结点的完全二叉树的结点按层序编号(从第1层到第[log2n ] + 1层,每层从左到右),对任一结点 i (1<=i<=n)有:
- 如果 i=1,则结点i是二叉树的根,无双亲;如果 i>1,则其双亲是结点[i/2]。
- 如果 2i>n,则结点i无左孩子(结点i为叶子结点);否则其左孩子是结点2i。
- 如果 2i+1>n,则结点i无右孩子;否则其右孩子是结点2i+1。
结合下图理解:
二叉树的遍历
二叉树的遍历是指从根结点出发,按照某种次序依次访问二叉树中所有结点,使得每个结点被访问一次且仅被访问一次。
前中后序遍历的介绍
前序遍历:规则是若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。如图所示,遍历的顺序为:ABDGHCEIF。
中序遍历:规则是若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。如图所示,遍历的顺序为:GDHBAEICF。
后序遍历:规则是若树为空,则空操作返回,否则从左到右的方式遍历访问左右子树,最后访问根结点。如图所示,遍历的顺序为:GHDBIEFCA。
前中后序遍历的代码实现
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
| package com.nanzx.tree;
public class BinaryTreeDemo {
public static void main(String[] args) { BinaryTree binaryTree = new BinaryTree(); HeroNode root = new HeroNode(1, "宋江"); HeroNode node2 = new HeroNode(2, "吴用"); HeroNode node3 = new HeroNode(3, "卢俊义"); HeroNode node4 = new HeroNode(4, "林冲"); HeroNode node5 = new HeroNode(5, "关胜");
root.setLeft(node2); root.setRight(node3); node3.setRight(node4); node3.setLeft(node5); binaryTree.setRoot(root);
System.out.println("前序遍历"); binaryTree.preOrder(); System.out.println("中序遍历"); binaryTree.infixOrder(); System.out.println("后序遍历"); binaryTree.postOrder(); } }
class BinaryTree { private HeroNode root;
public void setRoot(HeroNode root) { this.root = root; }
public void preOrder() { if (this.root != null) { this.root.preOrder(); } else { System.out.println("二叉树为空,无法遍历"); } }
public void infixOrder() { if (this.root != null) { this.root.infixOrder(); } else { System.out.println("二叉树为空,无法遍历"); } }
public void postOrder() { if (this.root != null) { this.root.postOrder(); } else { System.out.println("二叉树为空,无法遍历"); } } }
class HeroNode { private int no; private String name; private HeroNode left; private HeroNode right;
public HeroNode(int no, String name) { super(); this.no = no; this.name = name; }
public int getNo() { return no; }
public void setNo(int no) { this.no = no; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public HeroNode getLeft() { return left; }
public void setLeft(HeroNode left) { this.left = left; }
public HeroNode getRight() { return right; }
public void setRight(HeroNode right) { this.right = right; }
@Override public String toString() { return "HeroNode [no=" + no + ", name=" + name + "]"; }
public void preOrder() { System.out.println(this); if (this.left != null) { this.left.preOrder(); } if (this.right != null) { this.right.preOrder(); } }
public void infixOrder() { if (this.left != null) { this.left.infixOrder(); } System.out.println(this); if (this.right != null) { this.right.infixOrder(); } }
public void postOrder() { if (this.left != null) { this.left.postOrder(); } if (this.right != null) { this.right.postOrder(); } System.out.println(this); } }
|
前中后序查找的代码实现
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
| package com.nanzx.tree;
public class BinaryTreeDemo {
public static void main(String[] args) { BinaryTree binaryTree = new BinaryTree(); HeroNode root = new HeroNode(1, "宋江"); HeroNode node2 = new HeroNode(2, "吴用"); HeroNode node3 = new HeroNode(3, "卢俊义"); HeroNode node4 = new HeroNode(4, "林冲"); HeroNode node5 = new HeroNode(5, "关胜");
root.setLeft(node2); root.setRight(node3); node3.setRight(node4); node3.setLeft(node5); binaryTree.setRoot(root); System.out.println("前序遍历方式~~~"); HeroNode resNode = binaryTree.preOrderSearch(5); if (resNode != null) { System.out.printf("找到了,信息为 no=%d name=%s", resNode.getNo(), resNode.getName()); } else { System.out.printf("没有找到 no = %d 的英雄", 5); }
}
}
class BinaryTree { private HeroNode root;
public void setRoot(HeroNode root) { this.root = root; }
public HeroNode preOrderSearch(int no) { if (root != null) { return root.preOrderSearch(no); } else { return null; } }
public HeroNode infixOrderSearch(int no) { if (root != null) { return root.infixOrderSearch(no); } else { return null; } }
public HeroNode postOrderSearch(int no) { if (root != null) { return this.root.postOrderSearch(no); } else { return null; } } }
class HeroNode { private int no; private String name; private HeroNode left; private HeroNode right;
public HeroNode(int no, String name) { super(); this.no = no; this.name = name; }
public int getNo() { return no; }
public void setNo(int no) { this.no = no; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public HeroNode getLeft() { return left; }
public void setLeft(HeroNode left) { this.left = left; }
public HeroNode getRight() { return right; }
public void setRight(HeroNode right) { this.right = right; }
@Override public String toString() { return "HeroNode [no=" + no + ", name=" + name + "]"; }
public HeroNode preOrderSearch(int no) { System.out.println("进入前序遍历"); if (this.no == no) { return this; } HeroNode resNode = null; if (this.left != null) { resNode = this.left.preOrderSearch(no); } if (resNode != null) { return resNode; } if (this.right != null) { resNode = this.right.preOrderSearch(no); } return resNode; }
public HeroNode infixOrderSearch(int no) { HeroNode resNode = null; if (this.left != null) { resNode = this.left.infixOrderSearch(no); } if (resNode != null) { return resNode; } System.out.println("进入中序查找"); if (this.no == no) { return this; } if (this.right != null) { resNode = this.right.infixOrderSearch(no); } return resNode; }
public HeroNode postOrderSearch(int no) { HeroNode resNode = null; if (this.left != null) { resNode = this.left.postOrderSearch(no); } if (resNode != null) { return resNode; } if (this.right != null) { resNode = this.right.postOrderSearch(no); } if (resNode != null) { return resNode; } System.out.println("进入后序查找"); if (this.no == no) { return this; } return resNode; } }
|
删除结点的代码实现
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
| package com.nanzx.tree;
public class BinaryTreeDemo {
public static void main(String[] args) { BinaryTree binaryTree = new BinaryTree(); HeroNode root = new HeroNode(1, "宋江"); HeroNode node2 = new HeroNode(2, "吴用"); HeroNode node3 = new HeroNode(3, "卢俊义"); HeroNode node4 = new HeroNode(4, "林冲"); HeroNode node5 = new HeroNode(5, "关胜");
root.setLeft(node2); root.setRight(node3); node3.setRight(node4); node3.setLeft(node5); binaryTree.setRoot(root);
System.out.println("删除前,前序遍历"); binaryTree.preOrder(); binaryTree.delNode(5); System.out.println("删除后,前序遍历"); binaryTree.preOrder(); } }
class BinaryTree { private HeroNode root;
public void setRoot(HeroNode root) { this.root = root; }
public void delNode(int no) { if (root != null) { if (root.getNo() == no) { root = null; } else { root.delNode(no); } } else { System.out.println("空树,不能删除~"); } }
public void preOrder() { if (this.root != null) { this.root.preOrder(); } else { System.out.println("二叉树为空,无法遍历"); } } }
class HeroNode { private int no; private String name; private HeroNode left; private HeroNode right;
public HeroNode(int no, String name) { super(); this.no = no; this.name = name; }
public int getNo() { return no; }
public void setNo(int no) { this.no = no; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public HeroNode getLeft() { return left; }
public void setLeft(HeroNode left) { this.left = left; }
public HeroNode getRight() { return right; }
public void setRight(HeroNode right) { this.right = right; }
@Override public String toString() { return "HeroNode [no=" + no + ", name=" + name + "]"; }
public void delNode(int no) { if (this.left != null && this.left.no == no) { this.left = null; return; } if (this.right != null && this.right.no == no) { this.right = null; return; } if (this.left != null) { this.left.delNode(no); } if (this.right != null) { this.right.delNode(no); } }
public void preOrder() { System.out.println(this); if (this.left != null) { this.left.preOrder(); } if (this.right != null) { this.right.preOrder(); } } }
|
顺序存储二叉树
从数据存储来看,数组存储方式和树的存储方式可以相互转换,即数组可以转换成树,树也可以转换成数组。
顺序存储二叉树的特点: (i表示二叉树中的第几个元素(从0开始编号))
- 顺序二叉树通常只考虑完全二叉树
- 第i个元素的左子节点为 2 * i + 1
- 第i个元素的右子节点为 2 * i + 2
- 第i个元素的父节点为 (i-1) / 2
需求: 给定数组为{1,2,3,4,5,6,7},要求以二叉树前序遍历的方式进行遍历。 前序遍历的结果应当为 1,2,4,5,3,6,7
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 32 33 34 35 36
| package com.nanzx.tree;
public class ArrBinaryTreeDemo {
public static void main(String[] args) { int[] arr = { 1, 2, 3, 4, 5, 6, 7 }; ArrBinaryTree tree = new ArrBinaryTree(arr); tree.preOrder(); } }
class ArrBinaryTree { private int[] arr;
public ArrBinaryTree(int[] arr) { super(); this.arr = arr; }
public void preOrder() { preOrder(0); }
public void preOrder(int index) { if (arr == null || arr.length < 0) { System.out.println("数组为空,不能按照二叉树前序遍历。"); } System.out.print(arr[index]); if ((2 * index + 1) < arr.length) { preOrder(2 * index + 1); } if ((2 * index + 2) < arr.length) { preOrder(2 * index + 2); } } }
|
线索化二叉树
线索化二叉树是为了解决无法直接找到该结点在某种遍历次序中的前驱结点和后继结点的问题。
n个节点的二叉树中含有n+1个空指针域。利用二叉树中的空指针域来存放在某种遍历次序下的前驱和后继结点的指针,这种指针叫“线索”。这种加上了线索的二叉树称为线索二叉树。线索二叉树可分为前序线索二叉树、中序线索二叉树和后序线索二叉树三种。
为了知道某一结点的left是指向它的左孩子还是指向前驱结点,我们设置了两个标志域:
leftType |
left |
data |
right |
rightType |
0表示left存放左孩子,1表示存放前驱结点 |
存放左孩子或前驱结点 |
存放数据 |
存放右孩子或后继结点 |
0表示right存放右孩子,1表示存放后继结点 |
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
| package com.nanzx.tree.threadedbinarytree;
public class ThreadedBinaryTreeDemo {
public static void main(String[] args) { HeroNode root = new HeroNode(1, "tom"); HeroNode node2 = new HeroNode(3, "jack"); HeroNode node3 = new HeroNode(6, "smith"); HeroNode node4 = new HeroNode(8, "mary"); HeroNode node5 = new HeroNode(10, "king"); HeroNode node6 = new HeroNode(14, "dim");
root.setLeft(node2); root.setRight(node3); node2.setLeft(node4); node2.setRight(node5); node3.setLeft(node6);
ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree(); threadedBinaryTree.setRoot(root); threadedBinaryTree.threadedNodes();
HeroNode leftNode = node5.getLeft(); HeroNode rightNode = node5.getRight(); System.out.println("10号结点的前驱结点是 =" + leftNode); System.out.println("10号结点的后继结点是 =" + rightNode);
System.out.println("使用线索化的方式遍历 线索化二叉树"); threadedBinaryTree.threadedList(); }
}
class ThreadedBinaryTree { private HeroNode root; private HeroNode pre = null;
public void setRoot(HeroNode root) { this.root = root; }
public void threadedNodes() { this.threadedNodes(root); }
public void threadedNodes(HeroNode node) { if (node == null) { return; } threadedNodes(node.getLeft()); if (node.getLeft() == null) { node.setLeft(pre); node.setLeftType(1); }
if (pre != null && pre.getRight() == null) { pre.setRight(node); pre.setRightType(1); } pre = node;
threadedNodes(node.getRight()); }
public void threadedList() { HeroNode node = root; while (node != null) { while (node.getLeftType() == 0) { node = node.getLeft(); }
System.out.println(node); while (node.getRightType() == 1) { node = node.getRight(); System.out.println(node); } node = node.getRight(); } } }
class HeroNode { private int no; private String name; private HeroNode left; private HeroNode right; private int leftType; private int rightType;
public HeroNode(int no, String name) { super(); this.no = no; this.name = name; }
public int getNo() { return no; }
public void setNo(int no) { this.no = no; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public HeroNode getLeft() { return left; }
public void setLeft(HeroNode left) { this.left = left; }
public HeroNode getRight() { return right; }
public void setRight(HeroNode right) { this.right = right; }
public int getLeftType() { return leftType; }
public void setLeftType(int leftType) { this.leftType = leftType; }
public int getRightType() { return rightType; }
public void setRightType(int rightType) { this.rightType = rightType; }
@Override public String toString() { return "HeroNode [no=" + no + ", name=" + name + "]"; } }
|