二叉树指的是每个节点最多只能有两个子树的有序树。通常左边的子树被称为“左子树”,右边的子树被称为“右子树”。由此可见,二叉树仍然是树,只是一种特殊的树。
二叉树的每个节点最多只有两棵子树(不存在大于2的节点)。二叉树有左、右之分,不能颠倒。
树和二叉树的两个重要区别如下:
1.树中节点的最大度数没有限制,而二叉树节点的最大度数为2,也就是说,二叉树的节点最大度数为2。
2.无序树的节点无左右之分,而二叉树的节点有左右之分,也就是说二叉树是有序树。
一棵深度为K的二叉树,如果它包含了2^K - 1个节点,就把这棵二叉树称为满二叉树。满二叉树的特点是,每一层上的节点数都是最大节点数,即各层节点数分别为1,2,4,8,16,32,……,2^(K-1)
一棵有n节点的二叉树,按满二叉树的方式对他进行编号,若树中所有节点和满二叉树1~n编号完全一致,则称该树为完全二叉树。也就是说如果一棵二叉树除最后一层外,其余所有的节点都是满的,并且最后一层或者是满的,或者仅在右边缺少若干连续的节点,则此二叉树就是完全二叉树。
注意:满二叉树是一种特殊的完全二叉树。当完全二叉树最后一层的所有节点都是满的情况下,这棵完全二叉树就变成了满二叉树。
综上所述,二叉树大致有如下几个性质:
(1)、二叉树的第 i 层上的节点至多有 2^(i-1)。 (i > 0)
(2)、深度为 k 的二叉树至多有 2^K - 1 个节点。满二叉树的每层节点的数量依次为1,2,4,8,……,因此深度为 k 的满二叉树包含的节点数为公比为2的等比数列的前k项总合,即2^K - 1。
(3)、在任何一棵二叉树中,如果其叶子节点的数量为N0,度为2的字节点数量为N2,则N0 = N2 + 1。这是因为:如果为任意叶子节点增加一个子节点,则原有叶子节点变成非叶子节点,新增节点变成叶子节点,上述等式不变;如果为任意叶子节点增加两个子节点,则原有叶子节点变成度为2的非叶子节点,新增的两个节点变成叶子节点,上述等式不变。
(4)、具有n个节点的完全二叉树的深度为log2(n+1)。
对于一棵具有 n 个节点的完全二叉树的节点按层自左向右编号,则对任意编号为 i (n >= i >= 1)的节点具有如下性质:
(1)当 i =1时,节点 i 是二叉树的根;若 i > 1,则节点的父节点是 i/2。
(2)若 2i <= n,则节点 i 有左孩子,左孩子的编号为 2i,否则,节点无左孩子,并且是叶子节点。
(3)若 2i + 1 <= n,则节点 i 有右孩子,右孩子的编号是 2i+ 1;否则节点无右孩子。
(4)1 ~ n/2 范围的节点都是有孩子的非叶子节点,其余的节点全部都是叶子节点,编号为 n/2 的节点可能只有左子节点,也极有可能有左子节点,也有右子节点。
二叉树的基本操作:
二叉树记录其节点之间的父子关系更加简单,因为二叉树中的每个节点最多只能保存两个子节点。接下来,就是需要为二叉树实现如下基本操作:
(1)初始化:通常是有个构造器,用于创建一个空树,或者指定节点为根节点来创建二叉树。
(2)为指定节点添加子节点。
(3)判断二叉树是否为空。
(4)返回根节点。
(5)返回指定节点(非根节点)的父节点
(6)返回指定节点(非叶子节点)的左子节点。
(7)返回指定节点(非叶子节点)的右子节点。
(8)返回该二叉树的深度。
(9)返回指定节点的位置。
要实现二叉树的这种数据结构,有如下三种选择:
(1)顺序存储:采用数组来记录二叉树的所有节点。
(2)二叉链表存储:每个节点保留一个left、right域,分别指向其左、右子节点。
(3)三叉链表存储:每个节点保留一个left、right、parent域,分别指向其左、右子节点和父节点。
- 定义一个节点类,使节点与二叉树操作分离
- class Node {
- int value;
- Node leftChild;
- Node rightChild;
- Node(int value) {
- this.value = value;
- }
- public void display() {
- System.out.print(this.value + "\t");
- }
- @Override
- public String toString() {
- // TODO Auto-generated method stub
- return String.valueOf(value);
- }
- }
- 需要实现的二叉树操作
- class BinaryTree {
- private Node root = null;
- BinaryTree(int value) {
- root = new Node(value);
- root.leftChild = null;
- root.rightChild = null;
- }
- public Node findKey(int value) {} //查找
- public String insert(int value) {} //插入
- public void inOrderTraverse() {} //中序遍历递归操作
- public void inOrderByStack() {} //中序遍历非递归操作
- public void preOrderTraverse() {} //前序遍历
- public void preOrderByStack() {} //前序遍历非递归操作
- public void postOrderTraverse() {} //后序遍历
- public void postOrderByStack() {} //后序遍历非递归操作
- public int getMinValue() {} //得到最小(大)值
- public boolean delete(int value) {} //删除
- }
- 查找数据:
- public Node findKey(int value) {
- Node current = root;
- while (true) {
- if (value == current.value) {
- return current;
- } else if (value < current.value) {
- current = current.leftChild;
- } else if (value > current.value) {
- current = current.rightChild;
- }
- if (current == null) {
- return null;
- }
- }
- }
- 插入数据:与查找数据类似,不同点在于当节点为空时,不是返回而是插入
- public String insert(int value) {
- String error = null;
- Node node = new Node(value);
- if (root == null) {
- root = node;
- root.leftChild = null;
- root.rightChild = null;
- } else {
- Node current = root;
- Node parent = null;
- while (true) {
- if (value < current.value) {
- parent = current;
- current = current.leftChild;
- if (current == null) {
- parent.leftChild = node;
- break;
- }
- } else if (value > current.value) {
- parent = current;
- current = current.rightChild;
- if (current == null) {
- parent.rightChild = node;
- break;
- }
- } else {
- error = "having same value in binary tree";
- }
- } // end of while
- }
- return error;
- }
- 遍历数据:
1)中序遍历:最常用的一种遍历方法
- /**
- * //中序遍历(递归):
- * 1、调用自身来遍历节点的左子树
- * 2、访问这个节点
- * 3、调用自身来遍历节点的右子树
- */
- public void inOrderTraverse() {
- System.out.print("中序遍历:");
- inOrderTraverse(root);
- System.out.println();
- }
- private void inOrderTraverse(Node node) {
- if (node == null)
- return ;
- inOrderTraverse(node.leftChild);
- node.display();
- inOrderTraverse(node.rightChild);
- }
- /**
- * 中序非递归遍历:
- * 1)对于任意节点current,若该节点不为空则将该节点压栈,并将左子树节点置为current,重复此操作,直到current为空。
- * 2)若左子树为空,栈顶节点出栈,访问节点后将该节点的右子树置为current
- * 3) 重复1、2步操作,直到current为空且栈内节点为空。
- */
- public void inOrderByStack() {
- System.out.print("中序非递归遍历:");
- Stack<Node> stack = new Stack<Node>();
- Node current = root;
- while (current != null || !stack.isEmpty()) {
- while (current != null) {
- stack.push(current);
- current = current.leftChild;
- }
- if (!stack.isEmpty()) {
- current = stack.pop();
- current.display();
- current = current.rightChild;
- }
- }
- System.out.println();
- }
2)前序遍历:
- /**
- * //前序遍历(递归):
- * 1、访问这个节点
- * 2、调用自身来遍历节点的左子树
- * 3、调用自身来遍历节点的右子树
- */
- public void preOrderTraverse() {
- System.out.print("前序遍历:");
- preOrderTraverse(root);
- System.out.println();
- }
- private void preOrderTraverse(Node node) {
- if (node == null)
- return ;
- node.display();
- preOrderTraverse(node.leftChild);
- preOrderTraverse(node.rightChild);
- }
- /**
- * 前序非递归遍历:
- * 1)对于任意节点current,若该节点不为空则访问该节点后再将节点压栈,并将左子树节点置为current,重复此操作,直到current为空。
- * 2)若左子树为空,栈顶节点出栈,将该节点的右子树置为current
- * 3) 重复1、2步操作,直到current为空且栈内节点为空。
- */
- public void preOrderByStack() {
- System.out.print("前序非递归遍历:");
- Stack<Node> stack = new Stack<Node>();
- Node current = root;
- while (current != null || !stack.isEmpty()) {
- while (current != null) {
- stack.push(current);
- current.display();
- current = current.leftChild;
- }
- if (!stack.isEmpty()) {
- current = stack.pop();
- current = current.rightChild;
- }
- }
- System.out.println();
- }
3)后序遍历:
- /**
- * //后序遍历(递归):
- * 1、调用自身来遍历节点的左子树
- * 2、调用自身来遍历节点的右子树
- * 3、访问这个节点
- */
- public void postOrderTraverse() {
- System.out.print("后序遍历:");
- postOrderTraverse(root);
- System.out.println();
- }
- private void postOrderTraverse(Node node) {
- if (node == null)
- return ;
- postOrderTraverse(node.leftChild);
- postOrderTraverse(node.rightChild);
- node.display();
- }
- /**
- * 后序非递归遍历:
- * 1)对于任意节点current,若该节点不为空则访问该节点后再将节点压栈,并将左子树节点置为current,重复此操作,直到current为空。
- * 2)若左子树为空,取栈顶节点的右子树,如果右子树为空或右子树刚访问过,则访问该节点,并将preNode置为该节点
- * 3) 重复1、2步操作,直到current为空且栈内节点为空。
- */
- public void postOrderByStack() {
- System.out.print("后序非递归遍历:");
- Stack<Node> stack = new Stack<Node>();
- Node current = root;
- Node preNode = null;
- while (current != null || !stack.isEmpty()) {
- while (current != null) {
- stack.push(current);
- current = current.leftChild;
- }
- if (!stack.isEmpty()) {
- current = stack.peek().rightChild;
- if (current == null || current == preNode) {
- current = stack.pop();
- current.display();
- preNode = current;
- current = null;
- }
- }
- }
- System.out.println();
- }
- 得到最小(大)值:依次向左(右)直到空为之
- public int getMinValue() {
- Node current = root;
- while (true) {
- if (current.leftChild == null)
- return current.value;
- current = current.leftChild;
- }
- 删除:删除操作很复杂,删除节点大致分为三种情况:
2)删除节点只有一个子节点:只有一个左子节点和只有一个右子节点
3)删除节点有两个子节点:这种情况比较复杂,需要寻找后继节点,即比要删除的节点的关键值次高的节点是它的后继节点。
说得简单一些,后继节点就是比要删除的节点的关键值要大的节点集合中的最小值。
得到后继节点的代码如下:
- /**
- *
- * 得到后继节点,即删除节点的左后代
- */
- private Node getSuccessor(Node delNode) {
- Node successor = delNode;
- Node successorParent = null;
- Node current = delNode.rightChild;
- while (current != null) {
- successorParent = successor;
- successor = current;
- current = current.leftChild;
- }
- //如果后继节点不是删除节点的右子节点时,
- if (successor != delNode.rightChild) {
- //要将后继节点的右子节点指向后继结点父节点的左子节点,
- successorParent.leftChild = successor.rightChild;
- //并将删除节点的右子节点指向后继结点的右子节点
- successor.rightChild = delNode.rightChild;
- }
- //任何情况下,都需要将删除节点的左子节点指向后继节点的左子节点
- successor.leftChild = delNode.leftChild;
- return successor;
- }
- //删除的节点为父节点的左子节点时:
- parent.leftChild = successor;
- successor.leftChild = delNode.leftChild;
- //删除的节点为父节点的右子节点时:
- parent.rightChild = successor;
- successor.leftChild = delNode.leftChild
b)如果后继节点为要删除节点的右子节点的左后代:
- //删除的节点为父节点的左子节点时:
- successorParent.leftChild = successor.rightChild;
- successor.rightChild = delNode.rightChild;
- parent.leftChild = successor;
- successor.leftChild = delNode.leftChild;
- //删除的节点为父节点的右子节点时:
- successorParent.leftChild = successor.rightChild;
- successor.rightChild = delNode.rightChild;
- parent.rightChild = successor;
- successor.leftChild = delNode.leftChild;
综合以上各种情况,删除代码如下:
- public boolean delete(int value) {
- Node current = root; //需要删除的节点
- Node parent = null; //需要删除的节点的父节点
- boolean isLeftChild = true; //需要删除的节点是否父节点的左子树
- while (true) {
- if (value == current.value) {
- break;
- } else if (value < current.value) {
- isLeftChild = true;
- parent = current;
- current = current.leftChild;
- } else {
- isLeftChild = false;
- parent = current;
- current = current.rightChild;
- }
- //找不到需要删除的节点,直接返回
- if (current == null)
- return false;
- }
- //分情况考虑
- //1、需要删除的节点为叶子节点
- if (current.leftChild == null && current.rightChild == null) {
- //如果该叶节点为根节点,将根节点置为null
- if (current == root) {
- root = null;
- } else {
- //如果该叶节点是父节点的左子节点,将父节点的左子节点置为null
- if (isLeftChild) {
- parent.leftChild = null;
- } else { //如果该叶节点是父节点的右子节点,将父节点的右子节点置为null
- parent.rightChild = null;
- }
- }
- }
- //2、需要删除的节点有一个子节点,且该子节点为左子节点
- else if (current.rightChild == null) {
- //如果该节点为根节点,将根节点的左子节点变为根节点
- if (current == root) {
- root = current.leftChild;
- } else {
- //如果该节点是父节点的左子节点,将该节点的左子节点变为父节点的左子节点
- if (isLeftChild) {
- parent.leftChild = current.leftChild;
- } else { //如果该节点是父节点的右子节点,将该节点的左子节点变为父节点的右子节点
- parent.rightChild = current.leftChild;
- }
- }
- }
- //2、需要删除的节点有一个子节点,且该子节点为右子节点
- else if (current.leftChild == null) {
- //如果该节点为根节点,将根节点的右子节点变为根节点
- if (current == root) {
- root = current.rightChild;
- } else {
- //如果该节点是父节点的左子节点,将该节点的右子节点变为父节点的左子节点
- if (isLeftChild) {
- parent.leftChild = current.rightChild;
- } else { //如果该节点是父节点的右子节点,将该节点的右子节点变为父节点的右子节点
- parent.rightChild = current.rightChild;
- }
- }
- }
- //3、需要删除的节点有两个子节点,需要寻找该节点的后续节点替代删除节点
- else {
- Node successor = getSuccessor(current);
- //如果该节点为根节点,将后继节点变为根节点,并将根节点的左子节点变为后继节点的左子节点
- if (current == root) {
- root = successor;
- } else {
- //如果该节点是父节点的左子节点,将该节点的后继节点变为父节点的左子节点
- if (isLeftChild) {
- parent.leftChild = successor;
- } else { //如果该节点是父节点的右子节点,将该节点的后继节点变为父节点的右子节点
- parent.rightChild = successor;
- }
- }
- }
- current = null;
- return true;
- }
- 测试代码
- public class BinaryTreeDemo {
- public static void main(String[] args) {
- BinaryTree bt = new BinaryTree(52);
- bt.insert(580);
- bt.insert(12);
- bt.insert(50);
- bt.insert(58);
- bt.insert(9);
- bt.insert(888);
- bt.insert(248);
- bt.insert(32);
- bt.insert(666);
- bt.insert(455);
- bt.insert(777);
- bt.insert(999);
- bt.inOrderTraverse();
- bt.preOrderTraverse();
- bt.postOrderTraverse();
- System.out.println(bt.findKey(32));
- System.out.println(bt.findKey(81));
- System.out.println("最小值:" + bt.getMinValue());
- // bt.delete(32); //删除叶子节点
- // bt.delete(50); //删除只有一个左子节点的节点
- // bt.delete(248); //删除只有一个右子节点的节点
- // bt.delete(248); //删除只有一个右子节点的节点
- // bt.delete(580); //删除有两个子节点的节点,且后继节点为删除节点的右子节点的左后代
- // bt.delete(888); //删除有两个子节点的节点,且后继节点为删除节点的右子节点
- bt.delete(52); //删除有两个子节点的节点,且删除节点为根节点
- bt.inOrderTraverse();
- }
- }
中序遍历:9123250 52 58248455580666777 888999
中序非递归遍历:9 12 32 50 5258248455580 666777 888999
前序遍历:52 12 9 50 3258058248 455888 666777 999
前序非递归遍历:52 12 9 50 3258058248 455888 666777 999
后序遍历:9 32 50 12 45524858777 666999 888580 52
后序非递归遍历:9 32 50 12 45524858777 666999 888580 52
32
null
最小值:9
中序遍历:9 12 32 50 58248455580666777 888999
179

被折叠的 条评论
为什么被折叠?



