树
- 树(tree)是包含n(n>=0)个结点的有穷集,其中:
(1)每个元素称为结点(node);
(2)有一个特定的结点被称为根结点或树根(root)。
(3)除根结点之外的其余数据元素被分为m(m≥0)个互不相交的集合T1,T2,……Tm-1,其中每一个集合Ti(1<=i<=m)本身也是一棵树,被称作原树的子树(subtree)。 - 结点
结点拥有的子树数称为结点的度(Degree)。度为0的结点称为叶节点(Leaf)或终端结点;度不为0的结点称为非终端节点或分支结点。除根节点外,分支结点也称为内部节点。树的度是树内各结点的度的最大值
结点的子树的根称为该结点的孩子(Child),该结点称为孩子的双亲(Parent)。同一个双亲的孩子互称兄弟(Sibling)。
结点的层次从根开始定义起,根为第1层,根的子结点为第2层,以此类推;树中结点的最大层次称为树的深度(Depth)。 - 种类
无序树:树中任意节点的子结点之间没有顺序关系,这种树称为无序树,也称为自由树;
有序树:树中任意节点的子结点之间有顺序关系,这种树称为有序树;
二叉树:每个节点最多含有两个子树的树称为二叉树; - 树的存储结构
双亲表示法
孩子表示法(主要关注孩子结点)
孩子兄弟表示法
二叉树
-
二叉树是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树组成。
-
二叉树的特点
1)每个结点最多有两颗子树(没有或有一棵是可以的),所以二叉树中不存在度大于2的结点。
2)左子树和右子树是有顺序的,次序不能任意颠倒。
3)即使树中某结点只有一棵子树,也要区分它是左子树还是右子树。 -
二叉树的性质
1)1)在二叉树的第i层上最多有2i-1 个节点 。(i>=1)
2)二叉树中如果深度为k,那么最多有2k-1个节点。(k>=1)
3)n0=n2+1 n0表示度数为0的节点数,n2表示度数为2的节点数。
4)在完全二叉树中,具有n个节点的完全二叉树的深度为[log2n]+1,其中[log2n]是向下取整。
5)若对含 n 个结点的完全二叉树从上到下且从左至右进行 1 至 n 的编号,则对完全二叉树中任意一个编号为 i 的结点有如下特性:
(1) 若 i=1,则该结点是二叉树的根,无双亲, 否则,编号为 [i/2] 的结点为其双亲结点;
(2) 若 2i>n,则该结点无左孩子, 否则,编号为 2i 的结点为其左孩子结点;
(3) 若 2i+1>n,则该结点无右孩子结点, 否则,编号为2i+1 的结点为其右孩子结点。 -
二叉树的遍历
前序遍历:左右根
中序遍历:左根右(排序)
后序遍历:根左右 -
JAVA实现
package 数据结构;
public class BinaryTree<E extends Comparable<E>> {
private TreeNode root;// 根结点
private int depth;//深度
// 销毁树
public void clear() {
clear(getRoot());
root = null;
}
// 清空指定结点的子系
public void clear(TreeNode node) {
if (node != null) {
clear(node.lchild);
node.lchild = null;
clear(node.rchild);
node.rchild = null;
}
}
// 判空
public boolean isEmpty() {
if (root == null)
return true;
else
return false;
}
//是否含有某元素
public boolean contains(E item) {
if(getNode(item)==null) {
return false;
}
else {
return true;
}
}
// 左小右大添加元素, 按这种方式生成的树中序遍历输出将是升序
//因为要比较的原因,泛型E需要继承Comparable类,利用CompareTo进行比较
public void put(E item) {
// 每次添加数据的时候都是从根结点向下遍历
if (isEmpty()) {
// 当前的叉树树的为空,将新结点设置为根结点,父结点为null
root = new TreeNode<>(item);
return;
}
// 如果传入的数据小于当前结点返回正,大于当前结点返回负,否则返回0
int ret = 0;// 比较值
TreeNode cp = root;// 比较结点
TreeNode father = cp;// 记录父结点
while (cp != null) {
// 与插入结点比较
ret = cp.getDate().compareTo(item);
father = cp;
// 插入大,把当前结点设置为右子结点,然后与右子比较,以此类推找到合适的位置
if (ret < 0)
cp = cp.rchild;
// 插入小
else if (ret > 0)
cp = cp.lchild;
else {// 相等就把旧值覆盖掉
cp.setDate(item);
return;
}
}
// 创建新结点
TreeNode node = new TreeNode<>(item);
// 根据比较结果将新结点放入合适的位置
if (ret < 0)
father.rchild = node;
else
father.lchild = node;
}
// 删除元素
public boolean remove(E item) {
// 获取删除结点
TreeNode delNode = getNode(item);
if (delNode == null)
return false;
// 删除结点的父结点
TreeNode father = getFather(item);
// 情况1:删除结点没有子结点,直接删除本结点
if (delNode.lchild == null && delNode.rchild == null) {
// 如果是根结点
if (delNode == root) {
root = null;
} else {// 非根结点
// 删除的是父结点的左结点或右结点
if (delNode == father.lchild) {
father.lchild = null;
} else {
father.rchild = null;
}
}
// 情况2:删除的结点只有左结点
} else if (delNode.rchild == null) {
TreeNode lc = delNode.lchild;
// 如果是根结点,将删除结点的左结点设置成根结点
if (delNode == root) {
root = lc;
} else {// 非根结点,用删除结点的子结点覆盖他
if (delNode == father.lchild) {
father.lchild = lc;
} else {
father.rchild = lc;
}
}
// 情况3:删除结点只有右结点
} else if (delNode.lchild == null) {
TreeNode rc = delNode.rchild;
// 如果是根结点,删除结点的子结点设置成根结点
if (delNode == root) {
root = rc;
} else {// 非根结点,用删除结点的子结点覆盖他
if (delNode == father.lchild) {
father.lchild = rc;
} else {
father.rchild = rc;
}
}
// 情况4:删除结点有两个子结点
} else {// 找到他的前驱/后继结点替换掉他,删除原位置的继任结点即可
// 这里用了后继替换,左右反一下,after改before就是用前驱替换了
TreeNode heir = Heir(delNode, "after");// 获取到后继结点
delNode.item = heir.item;
// 后继结点为右子结点
if (delNode.rchild == heir) {
// 右子结点有右子结点
if (heir.rchild != null) {
delNode.rchild = heir.rchild;
} else {// 右子结点没有子结点
delNode.rchild = null;
}
} else {// 后继结点必定是左结点
getFather((E) heir.item).lchild = null;
}
}
return true;
}
/*** 对于其中的前驱/后继,就是比该结点小/大的差值最小的结点,也就是中序遍历结果中它的前后一位。
* 比如按终须生成的一个树,12的前驱就是11,后继就是13
*前驱节点的特点:
* 1)删除的结点的左孩子没有右孩子,那么左孩子即为前驱节点
* 2)删除节点的左孩子有右孩子(也就是删除结点的孙子),那么最右边的最后一个节点即为前驱节点
*后继节点的特点:
* 与前驱节点刚好相反,总是右孩子,或者右孩子的最左孩子
*这是因为排序原因,总是左小右大,可以自己推一推理解一下
* 8
* / \
* 4 12
* / \ / \
* 2 6 10 14
* / \ / \ / \ / \
* 1 3 5 7 9 11 13 15
***/
// 获取结点的前驱/后继结点
public TreeNode<E> Heir(TreeNode delNode, String s) {
TreeNode node = null;
if (s == "before") {
node = delNode.lchild;
if (node.rchild != null) {
node = node.rchild;
}
}
if (s == "after") {
node = delNode.rchild;
if (node.lchild != null) {
node = node.lchild;
}
}
return node;
}
// 获取指定元素的结点
private TreeNode<E> getNode(E item) {
TreeNode cp = root;
int ret;
// 从根结点开始遍历
while (cp != null) {
// 与插入结点比较
ret = cp.getDate().compareTo(item);
// 插入大,把当前结点设置为右子结点,然后与右子比较,以此类推找到合适的位置
if (ret < 0)
cp = cp.rchild;
// 大于插入结点
else if (ret > 0)
cp = cp.lchild;
else {
// 相等就返回结点
return cp;
}
}
return null;
}
// 获取指定元素的父结点
private TreeNode getFather(E item) {
int ret = 0;// 比较值
TreeNode cp = root;// 比较结点
TreeNode father = cp;// 记录父结点
while (cp != null) {
// 与插入结点比较
ret = cp.getDate().compareTo(item);
father = cp;
// 当前结点比插入结点小,把当前结点设置为右子结点,然后与右子比较,以此类推找到合适的位置
if (ret < 0)
cp = cp.rchild;
// 大于插入结点
else if (ret > 0)
cp = cp.lchild;
else {
// 相等就返回父结点
return father;
}
}
return null;
}
// 获取根结点
public TreeNode<E> getRoot() {
return root;
}
// 获取深度
public int getDepth(TreeNode node) {
if (node == null) {
return 0;
} else {
return Math.max(getDepth(node.lchild), getDepth(node.rchild)) + 1;
}
}
// 前序遍历
public void prevIterator(TreeNode node) {
if (node != null) {
System.out.print(node.item + " ");
prevIterator(node.lchild);
prevIterator(node.rchild);
}
}
// 中序遍历
public void midIterator(TreeNode node) {
if (node != null) {
midIterator(node.lchild);
System.out.print(node.item + " ");
midIterator(node.rchild);
}
}
// 后序遍历
public void subIterator(TreeNode node) {
if (node != null) {
subIterator(node.lchild);
subIterator(node.rchild);
System.out.print(node.item + " ");
}
}
//结点类以内部类形式使用
private class TreeNode<E extends Comparable<E>> {
E item;
TreeNode<E> lchild;
TreeNode<E> rchild;
public TreeNode() {
this.item = null;
this.lchild = null;
this.rchild = null;
}
public TreeNode(E item) {
this.item = item;
}
public TreeNode(E item, TreeNode<E> lchild, TreeNode<E> rchild) {
this.item = item;
this.lchild = lchild;
this.rchild = rchild;
}
public E getDate() {
return item;
}
public TreeNode<E> getLchild() {
return lchild;
}
public TreeNode<E> getRchild() {
return rchild;
}
public void setDate(E item) {
this.item = item;
}
public void setLchild(TreeNode<E> lchild) {
this.lchild = lchild;
}
public void setRchild(TreeNode<E> rchild) {
this.rchild = rchild;
}
}
}
实例及结果
package 数据结构;
public class Main {
public static void main(String[] args) {
BinaryTree<Integer> binaryTree = new BinaryTree<Integer>();
//放数据
binaryTree.put(73);
binaryTree.put(22);
binaryTree.put(532);
binaryTree.put(62);
binaryTree.put(72);
binaryTree.put(243);
binaryTree.put(42);
binaryTree.put(3);
binaryTree.put(12);
binaryTree.put(52);
System.out.println("深度: " + binaryTree.getDepth(binaryTree.getRoot()));
binaryTree.put(52);
System.out.println("添加相同元素后的深度: " + binaryTree.getDepth(binaryTree.getRoot()));
//判断数据是否存在
System.out.println("数据是否存在:" + binaryTree.contains(12));
//中序遍历
System.out.print("中序遍历结果: ");
binaryTree.midIterator(binaryTree.getRoot());
System.out.println();
//前序遍历
System.out.print("前序遍历结果: ");
binaryTree.prevIterator(binaryTree.getRoot());
System.out.println();
//后序遍历
System.out.print("后序遍历结果: ");
binaryTree.subIterator(binaryTree.getRoot());
//删除数据
System.out.println();
binaryTree.remove(62);
System.out.println("删除数据后判断是否存在:" + binaryTree.contains(62));
//清空二叉树
binaryTree.clear();
System.out.print("清空数据后中序遍历: ");
binaryTree.midIterator(binaryTree.getRoot());
}
}
特殊二叉树
斜树:所有的结点都只有左子树的二叉树叫左斜树。所有结点都是只有右子树的二叉树叫右斜树。这两者统称为斜树。
满二叉树:在一棵二叉树中。如果所有分支结点都存在左子树和右子树,并且所有叶子都在同一层上,这样的二叉树称为满二叉树。
满二叉树的特点有:
1)叶子只能出现在最下一层。出现在其它层就不可能达成平衡。
2)非叶子结点的度一定是2。
3)在同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多。
完全二叉树:对一颗具有n个结点的二叉树按层编号,如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。
特点:
1)叶子结点只能出现在最下层和次下层。
2)最下层的叶子结点集中在树的左部。
3)倒数第二层若存在叶子结点,一定在右部连续位置。
4)如果结点度为1,则该结点只有左孩子,即没有右子树。
5)同样结点数目的二叉树,完全二叉树深度最小。
注:满二叉树一定是完全二叉树,但反过来不一定成立。