算法学习系5——树、二叉树、二叉搜索树

算法学习系5——树、二叉树、二叉搜索树

重点:二叉搜索树的运用,递归的使用


前言

  本文主要针对于树这种数据结构进行分析与应用,其中重点分析了二叉搜索树的相关结点操作


  是一个二维的数据结构,是有限结点组成一个具有层次关系的集合。
  特点:二叉树除叶子外每一个结点最多有两个分支,每一个树都有一个根结点,除根结点外每一个结点都有父结点,除叶子外每一个结点都有父结点,二叉树树分为左子树和右子树;
常见的二叉树

  如果各个结点形成了一个闭环,那么就形成了图,链表可以认为是一个特殊的树,虽然他有两个指针;那么树也可以当作特殊的图;

基本代码

public class TreeNode {
	public int val;
	public TreeNode left, right;
	public TreeNode(int val) {
		this.val = val;
		this.left = null;
		this.right = null;
	}
}

二叉树的遍历

遍历就是分别去访问你的根,左子树,右子树,根据不同的顺序分为三种遍历:前序、中序、后序;
前序:根左右
中序:左根右
后序:左右根
二叉树遍历
树的搜索,因为它本身的特殊结构,与线性数据结构不同,遍历常用递归,而且实现相对简单。

二叉搜索树

二叉搜索树,左子树上的所有结点小于根结点;右子树上的结点大于根结点;并且左右子树也同样如此;

复杂度

查询、插入、删除都是O(log n);
因为二叉搜索树类似于二分查找,每次指比较一半的结点,所以复杂度为O(log n)

查询

public Node find(int key) {
    Node currentNode = root;
    while (currentNode != null && currentNode.key != key) {
        if (key < currentNode.key) {
            currentNode = currentNode.leftChild;
        }else{
            currentNode = currentNode.rightChild;
         }
    }
    return currentNode;
}

插入

 待插入的结点从根结点开始进行比较,小于根结点则与根结点左子树比较,反之则与右子树比较,直到左子树为空或右子树为空,则插入到相应为空的位置,在比较的过程中要注意保存父结点的信息 及 待插入的位置是父结点的左子树还是右子树,才能插入到正确的位置

public void insert(int key, int value) {
        if (root == null) {
            root = new Node(key, value);
            return;
        }
        Node currentNode = root;
        Node parentNode = root;
        boolean isLeftChild = true;
        while (currentNode != null) {
            parentNode = currentNode;
            if (key < currentNode.key) {
                currentNode = currentNode.leftChild;
                isLeftChild = true;
            } else {
                currentNode = currentNode.rightChild;
                isLeftChild = false;
            }
        }
        Node newNode = new Node(key, value);
        if (isLeftChild) {
            parentNode.leftChild = newNode;
        } else {
            parentNode.rightChild = newNode;
        }
}

遍历

public void preOrder(Node rootNode) {
        if (rootNode != null) {
            System.out.println(rootNode.key + " " + rootNode.value);
            preOrder(rootNode.leftChild);
            preOrder(rootNode.rightChild);
        }
    }

    public void inOrder(Node rootNode) {
        if (rootNode != null) {
            inOrder(rootNode.leftChild);
            System.out.println(rootNode.key + " " + rootNode.value);
            inOrder(rootNode.rightChild);
        }
    }

    public void postOrder(Node rootNode) {
        if (rootNode != null) {
            postOrder(rootNode.leftChild);
            postOrder(rootNode.rightChild);
            System.out.println(rootNode.key + " " + rootNode.value);
        }
    }

删除

  1.   如果结点为叶子(没有左、右子树),此时删除该结点直接删除即可,并修改其父结点指向它的引用为null;
  2.   结点只包含左子树,或者右子树的话,此时直接删除该结点,并将其左子树或者右子树设置为其父结点的左子树或者右子树即可,此操作不会破坏树结构;
  3.   当结点的左右子树都不空的时候,如果把树结构中的所有节点按顺序排好的话,它的前驱和它的后继两个节点刚好在它左右紧挨着它。当一个节点被删除时,为了保证二叉树的结构不被破坏,要让它的前驱或者后继节点来代替它的位置,然后将它的前驱或者后继节点同样做删除操作,
      小于它的最大值,就是在树中在它左边最靠右的那个节点。同样,大于它的最小值,就是在树中在它右边最靠左的那个节点。当一个节点既有左子节点又有右子节点时,前驱就是它的左子节点的右子节点的右子节点…直到最右子节点;后继就是它的右子节点的左子节点的左子节点…直到最左子节点
/**
	 * 删除节点
	 * 
	 * @param node 待删除节点
	 */
	private void deleteNode(Node node) {
		// 如果按顺序排列好节点,它的前驱和后继就是这个序列上紧挨着它左右两侧的节点.
 
		// 如果节点只有左节点或者只有右节点
 
		if (node.haveLeftChild() && !node.haveRightChild()) {// 只有左节点
			if (node.isLeftChild) {
				node.fatherNode.leftChildNode = node.leftChildNode;
 
			} else if (node.isRightChild) {
				node.fatherNode.rightChildNode = node.leftChildNode;
			} else// 待删除节点是根节点
				treeRoot = node.leftChildNode;
			node.leftChildNode.fatherNode = node.fatherNode;
		} else if (node.haveRightChild() && !node.haveLeftChild()) {// 只有右节点
			if (node.isLeftChild) {
				node.fatherNode.leftChildNode = node.rightChildNode;
 
			} else if (node.isRightChild) {
				node.fatherNode.rightChildNode = node.rightChildNode;
			} else// 待删除节点是根节点
				treeRoot = node.rightChildNode;
			node.rightChildNode.fatherNode = node.fatherNode;
		} else if (node.haveLeftChild() && node.haveRightChild()) {// 有左右子节点
			Node successorNode = getSuccessorNode(node);
			if (successorNode == node.rightChildNode) {// 后继节点是右子节点
				successorNode.fatherNode = node.fatherNode;
				if (node.isLeftChild)
					node.fatherNode.leftChildNode = successorNode;
				else if (node.isRightChild)
					node.fatherNode.rightChildNode = successorNode;
				else {// 是根节点
					successorNode = treeRoot;
				}
 
				successorNode.fatherNode = node.fatherNode;
				successorNode.leftChildNode = node.leftChildNode;
				node.leftChildNode.fatherNode = successorNode;
 
			} else {// 后继节点是右子节点的最左子节点
				if (successorNode.haveRightChild()) {// 左子节点有右子树
					successorNode.fatherNode.leftChildNode = successorNode.rightChildNode;
					successorNode.rightChildNode.fatherNode = successorNode.fatherNode;
 
					replaceNode(node, successorNode);
 
				} else {// 左子节点没有右子树
						// 叶节点,直接删除
					successorNode.fatherNode.leftChildNode = null;
					replaceNode(node, successorNode);
				}
			}
 
		} else {// 没有子节点
			if (node.isLeftChild) {
				node.fatherNode.leftChildNode = null;
			} else if (node.isRightChild) {
				node.fatherNode.rightChildNode = null;
			}
 
		}
 
		node = null;
	}
 
	/**
	 * 非相邻节点的替换逻辑(非相邻加粗!)
	 * @param node 被替换节点
	 * @param replaceNode 替换的节点
	 */
	private void replaceNode(Node node, Node replaceNode) {
		if (node.isLeftChild)
			node.fatherNode.leftChildNode = replaceNode;
		else if (node.isRightChild)
			node.fatherNode.rightChildNode = replaceNode;
		else {// node是根节点
			treeRoot = replaceNode;
		}
 
		node.leftChildNode.fatherNode = node.rightChildNode.fatherNode = replaceNode;
		replaceNode.leftChildNode = node.leftChildNode;
		replaceNode.rightChildNode = node.rightChildNode;
	}
 
	/**
	 * 获取一个节点的后继节点
	 * @param node
	 * @return
	 */
	private Node getSuccessorNode(Node node) {
		if (!node.haveRightChild()) {// 没有右子树
			return null;
		}
 
		Node targetNode = node.rightChildNode;
		while (targetNode.haveLeftChild()) {// 找右子树的最左孩子,保证返回的节点一定没有左子树
			targetNode = targetNode.leftChildNode;
		}
 
		return targetNode;
	}
 
	/**
	 * 删除数中的数据
	 * @param baseData
	 */
	public void deleteData(T baseData) {
		Node node = searchNode(baseData);
		deleteNode(node);
	}

参考博文1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值