JAVA实现数据结构:二叉树

  • 树(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)同样结点数目的二叉树,完全二叉树深度最小。
注:满二叉树一定是完全二叉树,但反过来不一定成立。

发布了41 篇原创文章 · 获赞 1 · 访问量 1465

猜你喜欢

转载自blog.csdn.net/qq_44467578/article/details/104052616