树及其基本操作(Java语言版)

一、树

树(英语:tree)是一种抽象数据类型(ADT)或是实现这种抽象数据类型的数据结构,用来模拟具有树状结构性质的数据集合。它是由n(n>0)个有限节点组成一个具有层次关系的集合。把它叫做“树”是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:

  • 每个节点都只有有限个子节点或无子节点;
  • 没有父节点的节点称为根节点;
  • 每一个非根节点有且只有一个父节点;
  • 除了根节点外,每个子节点可以分为多个不相交的子树;
  • 树里面没有环路(cycle)
  1. 二叉树:每个节点最多含有两个子树的树称为二叉树;
    树及其基本操作(Java语言版)_第1张图片

  2. 完全二叉树:对于一棵二叉树,假设其深度为d(d>1)。除了第d层外,其它各层的节点数目均已达最大值,且第d层所有节点从左向右连续地紧密排列,这样的二叉树被称为完全二叉树;
    树及其基本操作(Java语言版)_第2张图片

  3. 满二叉树:所有叶节点都在最底层的完全二叉树;
    树及其基本操作(Java语言版)_第3张图片

  4. 平衡二叉树(AVL树):当且仅当任何节点的两棵子树的高度差不大于1的二叉树;
    树及其基本操作(Java语言版)_第4张图片

  5. 排序二叉树(二叉查找树(英语:Binary Search Tree)):也称二叉搜索树、有序二叉树;
    树及其基本操作(Java语言版)_第5张图片

二、树的基本操作(二叉搜索树为例)

创建一个BinarySortTree类

0.创建一个TreeNode类代表节点

TreeNode代表节点,每个TreeNode对象表示一个节点,left存放的是左子树,right存放的是右子树,val存放的是该节点的值。

public class BinarySortTree {	
	// Definition for a binary tree node.
	public class TreeNode {
		int val;
		TreeNode left;
		TreeNode right;
		TreeNode() {}
		TreeNode(int val) {
			this.val = val;
		}
		TreeNode(int val, TreeNode left, TreeNode right) {
			this.val = val;
			this.left = left;
			this.right = right;
		}
	}
	TreeNode root; // 新建根节点
}

1.根据数组的元素直接创建二叉树

创建二叉树并将数组元素插入到二叉树中。

    public void createTree(int[] nums) {
    	root = new TreeNode(nums[0]); // 数组第一个为根节点
        for (int i = 1; i < nums.length; i++) { // 从第二个元素开始迭代
            TreeNode treeNode = new TreeNode();
            treeNode.val = nums[i];            
            TreeNode currentNode = root; // 要插入位置的节点,初始为root,从根节点依次寻找
            TreeNode parentNode = null; // 要插入位置的父节点
            while (currentNode != null) {
            	parentNode = currentNode;
            	if (currentNode.val > nums[i]) { // 当前节点比将要插入的值大
            		currentNode = currentNode.left; // 与根节点左子树比较
            		if (currentNode == null) { // 若左节点为空 则直接插入即可
            			parentNode.left = treeNode;
            			System.out.println(nums[i] + " has been inserted into the binary tree!");
            			break;
            		}
            	} else if (currentNode.val < nums[i]) { // 当前节点比将要插入的值小
            		currentNode = currentNode.right; // 与根节点右子树比较
            		if (currentNode == null) { // 若右节点为空 则直接插入即可
            			parentNode.right = treeNode;
            			System.out.println(nums[i] + " has been inserted into the binary tree!");
            			break;
                    }
            	} else {
            		System.out.println(nums[i]+" repeats!");
            		break;
                }
            }            
        }
    }

2.在二叉搜索树中插入节点

要插入节点,必须先找到插入的位置。由于二叉搜索树的特殊性,待插入的节点需要从根节点开始进行比较。小于根节点则与根节点左子树比较,反之则与右子树比较,直到左子树为空或右子树为空,则插入到相应为空的位置。
在比较的过程中要注意保存父节点的信息及待插入的位置是父节点的左子树还是右子树

    public void insert(int val) {
    	TreeNode treeNode = new TreeNode();
    	treeNode.val = val;
    	if (root == null) { // 如果根节点为空,说明是个空树
    		root = treeNode;
    		System.out.println(val + " has been inserted into the binary tree!");
    	} else {
    		TreeNode currentNode = root; // 要插入位置的节点,初始为root,从根节点依次寻找
			TreeNode parentNode = null; // 要插入位置的父节点
    		while (currentNode != null) {
    			parentNode = currentNode;
    			if (currentNode.val > val) { // 当前节点比将要插入的值大
    				currentNode = currentNode.left; // 与根节点左子树比较
    				if (currentNode == null) { // 若左节点为空 则直接插入即可
    					parentNode.left = treeNode;
    					System.out.println(val + " has been inserted into the binary tree!");
    					break;
    				}	
    			} else if (currentNode.val < val) { // 当前节点比将要插入的值小
    				currentNode = currentNode.right; // 与根节点右子树比较
    				if (currentNode == null) { // 若右节点为空 则直接插入即可
    					parentNode.right = treeNode;
    					System.out.println(val + " has been inserted into the binary tree!");
    					break;
    				}
    			} else {
    				System.out.println(val + " repeats!");
    				break;
    			}
    		}
    	}
    }

3. 查找与val数值相等的节点

根据二叉搜索树的特性依次比较val与各个节点值的大小,从而查找与val相等的节点。

    public void get(int val) {
    	TreeNode currentNode = root;
    	TreeNode findTreeNode = new TreeNode();
    	while (currentNode != null) {
			if (currentNode.val > val) { // 当前节点比将要插入的值大
				currentNode = currentNode.left; // 去根节点左子树中继续寻找
			} else if (currentNode.val < val) { // 当前节点比将要插入的值小
				currentNode = currentNode.right; // 去根节点右子树中继续寻找
			} else if (currentNode.val == val)  {
				findTreeNode = currentNode; //相等,则currentNode是要寻找的节点。
				System.out.println(findTreeNode.val + " has been found!");
				return;
			}
    	}
    	System.out.println("Error! " + val + " does not exist!");
    }    

4.删除与val值相同的节点

由于是二叉搜索树,删除共三种情况
1 该节点是叶子节点
2 该节点有一个叶子节点
3 该节点有两个叶子节点

    public void remove(int val) {
    	TreeNode currentNode = root;
    	TreeNode parentNode = null; // 要插入位置的父节点
		boolean isLeftChild = false; // 判断是父节点的左子树还是右子树
		TreeNode findTreeNode = new TreeNode();
		//寻找删除的节点
    	while (currentNode != null && currentNode.val != val) {
    		parentNode = currentNode;
			if (currentNode.val > val) { // 当前节点比将要插入的值大
				currentNode = currentNode.left; // 去根节点左子树中继续寻找
				isLeftChild = true;
			} else if (currentNode.val < val) { // 当前节点比将要插入的值小
				currentNode = currentNode.right; // 去根节点右子树中继续寻找
				isLeftChild = false;
			}
    	}
    	findTreeNode = currentNode;
    	
//    	System.out.println(currentNode.val + " has been find!");
    	// 1 如果该节点是叶子节点,只用将其叶子节点删掉即可。
    	if (currentNode.left == null && currentNode.right == null) {
    		if (currentNode == root) { // 是根节点,即该树只有一个节点
    			currentNode = null;
    		} else if (isLeftChild) { // 是其父节点的左子树节点
    			parentNode.left = null;
    		} else if (!isLeftChild) { // 是其父节点的右子树节点
    			parentNode.right = null;
    		}
    	} else if(currentNode.left != null && currentNode.right == null) {
    	// 2 该节点有一个叶子节点,叶子结点为左节点
    		if (currentNode == root) { // 是根节点,即该树只有一个节点
    			currentNode = currentNode.left;
    		} else if (isLeftChild) { // 是其父节点的左子树节点
    			parentNode.left = currentNode.left;
    		} else if (!isLeftChild) { // 是其父节点的右子树节点
    			parentNode.right = currentNode.left;
    		}
    	} else if(currentNode.left == null && currentNode.right != null) {	
    	// 2 该节点有一个叶子节点,叶子结点为右节点
    		if (currentNode == root) { // 是根节点,即该树只有一个节点
    			currentNode = currentNode.right;
    		} else if (isLeftChild) { // 是其父节点的左子树节点
    			parentNode.left = currentNode.right;
    		} else if (!isLeftChild) { // 是其父节点的右子树节点
    			parentNode.right = currentNode.right;
    		}
    	} else {
    	// 3 该节点有两个叶子节点
    	//删除节点用左子树中最大值结点(前驱节点)代替,或右子树最小值节点(后继节点)代替
    		// 寻找右子树最小值节点,即后继节点
    		TreeNode successorNode = currentNode; // 后继节点
    		TreeNode successorParent  = currentNode; // 后继节点的父节点
    		TreeNode rightCurrentNode  = currentNode.right; // 先进入当前节点的右子树
    		while (rightCurrentNode != null) {
    			successorParent = successorNode;
    			successorNode = rightCurrentNode;
    			rightCurrentNode = rightCurrentNode.left; // 寻找右子树的左子树(寻找最小值)
    		}
    		//successorNode的左子树成为其父节点的右子树
    		// 然后其右子树更新指向currentNode的右子树
    		if(successorNode != currentNode.right) {
    			successorParent.left = successorNode.right;
    			successorNode.right = currentNode.right;
    		}
    		
    		// 开始删除
    		if (currentNode == root) { // 是根节点,即该树只有一个节点
    			currentNode = successorNode;
    		} else if (isLeftChild) { // 是其父节点的左子树节点
    			parentNode.left = successorNode;
    		} else if (!isLeftChild) { // 是其父节点的右子树节点
    			parentNode.right = successorNode;
    		}
    		successorNode.left = currentNode.left; // 更新左节点
    	}
    	System.out.println(findTreeNode.val + " has been removed!");
    	
    }

5.二叉树的先序遍历

先序遍历为根节点、左子树节点、右子树节点的顺序遍历,采用递归的方式。
如第一节图中的二叉搜索树所示的二叉树先序遍历为:7 1 0 5 3 2 4 6 9 8

	public void preOrderTraversal (TreeNode root) {
		if (root == null) {
			return;
		}
		System.out.print(root.val + " "); //先输出当前节点(初始的时候是root节点)
		preOrderTraversal(root.left); // 如果左子节点不为空,则递归继续前序遍历
		preOrderTraversal(root.right); // 如果右子节点不为空,则递归继续前序遍历
	}

6.二叉树的中序遍历

中序遍历为左子树节点、根节点、右子树节点的顺序遍历,采用递归的方式。
如第一节图中的二叉搜索树所示的二叉树中序遍历为:0 1 2 3 4 5 6 7 8 9

	public void inOrderTraversa (TreeNode root) {
		if (root == null) {
			return;
		}
		inOrderTraversa(root.left); // 如果当前节点的左子节点不为空,则递归中序遍历
		System.out.print(root.val + " "); // 输出当前节点
		inOrderTraversa(root.right); // 如果当前的右子节点不为空,则递归中序遍历		
	}
	

7.二叉树的后序遍历

后序遍历为左子树节点、右子树节点、根节点的顺序遍历,采用递归的方式。
如第一节图中的二叉搜索树所示的二叉树后序遍历为:0 2 4 3 6 5 1 8 9 7

	public void postOrderTraversal (TreeNode root) {
		if (root == null) {
			return;
		}
		postOrderTraversal(root.left); // 如果当前节点的左子节点不为空,则递归后序遍历
		postOrderTraversal(root.right); // 如果当前节点的右子节点不为空,则递归后序遍历
		System.out.print(root.val + " "); // 输出当前节点		
	}

8.二叉树的层次遍历

层次遍历,即广度优先遍历,采用队列的方式实现。
如第一节图中的二叉搜索树所示的二叉树层次遍历为:7 1 9 0 5 8 3 6 2 4

    public void levelOrderTraversal(TreeNode root) {
    	if(root == null) {
    		return;
    	}    	
    	Queue<TreeNode> queue = new LinkedList<TreeNode>(); // 存放每层操作的根节点
        queue.offer(root);        
        while (!queue.isEmpty()) {
            int queueSize = queue.size();
            for (int i = 0; i < queueSize; i++) { // 用for循换可以隔离开每一层的遍历
            	TreeNode rootNode = queue.poll(); // 开始操作后将其从队列移除
            	System.out.print(rootNode.val + " ");
                if (rootNode.left != null) {
    	            TreeNode leftNode = rootNode.left; // 左节点存入队列,下一层遍历它就成了新根节点	            
    	            queue.offer(leftNode);
                }
                if (rootNode.right != null) {
                	TreeNode rightNode = rootNode.right; // 右节点存入队列,下一层遍历它就成了新根节点
                	queue.offer(rightNode);
                }
            }
        }
    }

三、完整代码(包含测试)

import java.util.LinkedList;
import java.util.Queue;

public class BinarySortTree {
	
	// Definition for a binary tree node.
	public class TreeNode {
		int val;
		TreeNode left;
		TreeNode right;
		TreeNode() {}
		TreeNode(int val) {
			this.val = val;
		}
		TreeNode(int val, TreeNode left, TreeNode right) {
			this.val = val;
			this.left = left;
			this.right = right;
		}
	}
	
	
    TreeNode root; // 新建根节点
    /*
     * 根据数组的元素直接创建二叉树
     */
    public void createTree(int[] nums) {
    	root = new TreeNode(nums[0]); // 数组第一个为根节点
        for (int i = 1; i < nums.length; i++) { // 从第二个元素开始迭代
            TreeNode treeNode = new TreeNode();
            treeNode.val = nums[i];            
            TreeNode currentNode = root; // 要插入位置的节点,初始为root,从根节点依次寻找
            TreeNode parentNode = null; // 要插入位置的父节点
            while (currentNode != null) {
            	parentNode = currentNode;
            	if (currentNode.val > nums[i]) { // 当前节点比将要插入的值大
            		currentNode = currentNode.left; // 与根节点左子树比较
            		if (currentNode == null) { // 若左节点为空 则直接插入即可
            			parentNode.left = treeNode;
            			System.out.println(nums[i] + " has been inserted into the binary tree!");
            			break;
            		}
            	} else if (currentNode.val < nums[i]) { // 当前节点比将要插入的值小
            		currentNode = currentNode.right; // 与根节点右子树比较
            		if (currentNode == null) { // 若右节点为空 则直接插入即可
            			parentNode.right = treeNode;
            			System.out.println(nums[i] + " has been inserted into the binary tree!");
            			break;
                    }
            	} else {
            		System.out.println(nums[i]+" repeats!");
            		break;
                }
            }            
        }
    }
       
    /**
     * 在二叉树中插入节点
     * 要插入节点,必须先找到插入的位置。
     * 由于二叉搜索树的特殊性,待插入的节点需要从根节点开始进行比较
     * 小于根节点则与根节点左子树比较,反之则与右子树比较,直到左子树为空或右子树为空
     * 则插入到相应为空的位置
     * 在比较的过程中要注意保存父节点的信息及待插入的位置是父节点的左子树还是右子树
     * @param val
     */
    public void insert(int val) {
    	TreeNode treeNode = new TreeNode();
    	treeNode.val = val;
    	if (root == null) { // 如果根节点为空,说明是个空树
    		root = treeNode;
    		System.out.println(val + " has been inserted into the binary tree!");
    	} else {
    		TreeNode currentNode = root; // 要插入位置的节点,初始为root,从根节点依次寻找
			TreeNode parentNode = null; // 要插入位置的父节点
    		while (currentNode != null) {
    			parentNode = currentNode;
    			if (currentNode.val > val) { // 当前节点比将要插入的值大
    				currentNode = currentNode.left; // 与根节点左子树比较
    				if (currentNode == null) { // 若左节点为空 则直接插入即可
    					parentNode.left = treeNode;
    					System.out.println(val + " has been inserted into the binary tree!");
    					break;
    				}	
    			} else if (currentNode.val < val) { // 当前节点比将要插入的值小
    				currentNode = currentNode.right; // 与根节点右子树比较
    				if (currentNode == null) { // 若右节点为空 则直接插入即可
    					parentNode.right = treeNode;
    					System.out.println(val + " has been inserted into the binary tree!");
    					break;
    				}
    			} else {
    				System.out.println(val + " repeats!");
    				break;
    			}
    		}
    	}
    }
	
    // 查找与val数值相等的节点
    public void get(int val) {
    	TreeNode currentNode = root;
    	TreeNode findTreeNode = new TreeNode();
    	while (currentNode != null) {
			if (currentNode.val > val) { // 当前节点比将要插入的值大
				currentNode = currentNode.left; // 去根节点左子树中继续寻找
			} else if (currentNode.val < val) { // 当前节点比将要插入的值小
				currentNode = currentNode.right; // 去根节点右子树中继续寻找
			} else if (currentNode.val == val)  {
				findTreeNode = currentNode; //相等,则currentNode是要寻找的节点。
				System.out.println(findTreeNode.val + " has been found!");
				return;
			}
    	}
    	System.out.println("Error! " + val + " does not exist!");
    }
    
    
    /**
     * 删除与val值相同的节点
     * 删除共三种情况
     * 1 该节点是叶子节点
     * 2 该节点有一个叶子节点
     * 3 该节点有两个叶子节点
     * @param val
     */
    public void remove(int val) {
    	TreeNode currentNode = root;
    	TreeNode parentNode = null; // 要插入位置的父节点
		boolean isLeftChild = false; // 判断是父节点的左子树还是右子树
		TreeNode findTreeNode = new TreeNode();
		//寻找删除的节点
    	while (currentNode != null && currentNode.val != val) {
    		parentNode = currentNode;
			if (currentNode.val > val) { // 当前节点比将要插入的值大
				currentNode = currentNode.left; // 去根节点左子树中继续寻找
				isLeftChild = true;
			} else if (currentNode.val < val) { // 当前节点比将要插入的值小
				currentNode = currentNode.right; // 去根节点右子树中继续寻找
				isLeftChild = false;
			}
    	}
    	findTreeNode = currentNode;
    	
//    	System.out.println(currentNode.val + " has been find!");
    	// 1 如果该节点是叶子节点,只用将其叶子节点删掉即可。
    	if (currentNode.left == null && currentNode.right == null) {
    		if (currentNode == root) { // 是根节点,即该树只有一个节点
    			currentNode = null;
    		} else if (isLeftChild) { // 是其父节点的左子树节点
    			parentNode.left = null;
    		} else if (!isLeftChild) { // 是其父节点的右子树节点
    			parentNode.right = null;
    		}
    	} else if(currentNode.left != null && currentNode.right == null) {
    	// 2 该节点有一个叶子节点,叶子结点为左节点
    		if (currentNode == root) { // 是根节点,即该树只有一个节点
    			currentNode = currentNode.left;
    		} else if (isLeftChild) { // 是其父节点的左子树节点
    			parentNode.left = currentNode.left;
    		} else if (!isLeftChild) { // 是其父节点的右子树节点
    			parentNode.right = currentNode.left;
    		}
    	} else if(currentNode.left == null && currentNode.right != null) {	
    	// 2 该节点有一个叶子节点,叶子结点为右节点
    		if (currentNode == root) { // 是根节点,即该树只有一个节点
    			currentNode = currentNode.right;
    		} else if (isLeftChild) { // 是其父节点的左子树节点
    			parentNode.left = currentNode.right;
    		} else if (!isLeftChild) { // 是其父节点的右子树节点
    			parentNode.right = currentNode.right;
    		}
    	} else {
    	// 3 该节点有两个叶子节点
    	//删除节点用左子树中最大值结点(前驱节点)代替,或右子树最小值节点(后继节点)代替
    		// 寻找右子树最小值节点,即后继节点
    		TreeNode successorNode = currentNode; // 后继节点
    		TreeNode successorParent  = currentNode; // 后继节点的父节点
    		TreeNode rightCurrentNode  = currentNode.right; // 先进入当前节点的右子树
    		while (rightCurrentNode != null) {
    			successorParent = successorNode;
    			successorNode = rightCurrentNode;
    			rightCurrentNode = rightCurrentNode.left; // 寻找右子树的左子树(寻找最小值)
    		}
    		//successorNode的左子树成为其父节点的右子树
    		// 然后其右子树更新指向currentNode的右子树
    		if(successorNode != currentNode.right) {
    			successorParent.left = successorNode.right;
    			successorNode.right = currentNode.right;
    		}
    		
    		// 开始删除
    		if (currentNode == root) { // 是根节点,即该树只有一个节点
    			currentNode = successorNode;
    		} else if (isLeftChild) { // 是其父节点的左子树节点
    			parentNode.left = successorNode;
    		} else if (!isLeftChild) { // 是其父节点的右子树节点
    			parentNode.right = successorNode;
    		}
    		successorNode.left = currentNode.left; // 更新左节点
    	}
    	System.out.println(findTreeNode.val + " has been removed!");
    	
    }
    
	// 树的先序遍历
	public void preOrderTraversal (TreeNode root) {
		if (root == null) {
			return;
		}
		System.out.print(root.val + " "); //先输出当前节点(初始的时候是root节点)
		preOrderTraversal(root.left); // 如果左子节点不为空,则递归继续前序遍历
		preOrderTraversal(root.right); // 如果右子节点不为空,则递归继续前序遍历
	}
	
	// 树的中序遍历
	public void inOrderTraversa (TreeNode root) {
		if (root == null) {
			return;
		}
		inOrderTraversa(root.left); // 如果当前节点的左子节点不为空,则递归中序遍历
		System.out.print(root.val + " "); // 输出当前节点
		inOrderTraversa(root.right); // 如果当前的右子节点不为空,则递归中序遍历		
	}
	
	// 树的后序遍历
	public void postOrderTraversal (TreeNode root) {
		if (root == null) {
			return;
		}
		postOrderTraversal(root.left); // 如果当前节点的左子节点不为空,则递归后序遍历
		postOrderTraversal(root.right); // 如果当前节点的右子节点不为空,则递归后序遍历
		System.out.print(root.val + " "); // 输出当前节点		
	}

    // 广度优先遍历,即树的层次遍历,借用队列实现
    public void levelOrderTraversal(TreeNode root) {
    	if(root == null) {
    		return;
    	}    	
    	Queue<TreeNode> queue = new LinkedList<TreeNode>(); // 存放每层操作的根节点
        queue.offer(root);        
        while (!queue.isEmpty()) {
            int queueSize = queue.size();
            for (int i = 0; i < queueSize; i++) { // 用for循换可以隔离开每一层的遍历
            	TreeNode rootNode = queue.poll(); // 开始操作后将其从队列移除
            	System.out.print(rootNode.val + " ");
                if (rootNode.left != null) {
    	            TreeNode leftNode = rootNode.left; // 左节点存入队列,下一层遍历它就成了新根节点	            
    	            queue.offer(leftNode);
                }
                if (rootNode.right != null) {
                	TreeNode rightNode = rootNode.right; // 右节点存入队列,下一层遍历它就成了新根节点
                	queue.offer(rightNode);
                }
            }
        }
    }
    
    public static void main(String[] args) {
		BinarySortTree binarySortTree = new BinarySortTree();
		
		binarySortTree.createTree(new int[]{7, 1, 5, 9, 3, 0, 2, 6, 8});

		binarySortTree.insert(4);
		
		binarySortTree.get(5);
		
		System.out.print("先序遍历:");
		binarySortTree.preOrderTraversal(binarySortTree.root);
		System.out.println();
		System.out.print("中序遍历:");
		binarySortTree.inOrderTraversa(binarySortTree.root);
		System.out.println();
		System.out.print("后序遍历:");
		binarySortTree.postOrderTraversal(binarySortTree.root);
		System.out.println();
		System.out.print("层次遍历:");
		binarySortTree.levelOrderTraversal(binarySortTree.root);
		System.out.println();
		
		binarySortTree.remove(3);
		
		System.out.print("先序遍历:");
		binarySortTree.preOrderTraversal(binarySortTree.root);
		System.out.println();
		System.out.print("中序遍历:");
		binarySortTree.inOrderTraversa(binarySortTree.root);
		System.out.println();
		System.out.print("后序遍历:");
		binarySortTree.postOrderTraversal(binarySortTree.root);
		System.out.println();
		System.out.print("层次遍历:");
		binarySortTree.levelOrderTraversal(binarySortTree.root);
		System.out.println();
	}
}

你可能感兴趣的:(数据结构,java,数据结构)