2020-11-25

1、二叉树的定义

二叉树(Binary Tree)是有限个节点的集合,这个集合可以是空集,也可以是一个根节点和两颗不相交的子二叉树组成的集合,其中一颗树叫根的左子树,另一颗树叫右子树。所以二叉树是一个递归的概念

2、两种特殊的二叉树

满二叉树和完全二叉树。
1)、满二叉树(Full Binary Tree)就是高度为k,且拥有(2^k)-1个节点的二叉树。一棵满二叉树每个节点,要么都有两棵子树,要么都没有子树;而且每一层所有的节点之间必须要么都有两棵子树,要么都没子树。
2)、完全二叉树(Complete Binary Tree)
假设完全二叉树高度为k,则完全二叉树需要符合以下两点:
(1)、所有叶子节点都出现在k层或k-1层,并且从1~k-1层必须达到最大节点数。
(2)、第k层可以是不满的,但是第k层的所有节点必须集中在最左边。

3、二叉树的性质

1)在非空二叉树的第i层上,至多有2^(i-1)个结点
2)深度为k的二叉树至多有2^k-1个结点
3)对任何一颗二叉树T,如果其终端结点数为n0,度为2的结点数为n2,则n0 = n2+1
4)n个结点的完全二叉树深度为⌈log2(n+1)⌉,对以2为底n+1对数进行向上取整(⌈⌉是向上取整符号)

4、二叉树的存储方式

a、顺序存储:

二叉树结点从上到下、从左往右依次存储,n层的二叉树需要2^(n-1)个连续存储空间。特别适合存储完全二叉树和满二叉树,否则浪费大量空间。如下图:
在这里插入图片描述
对于完全二叉树用顺序存储方式,若父节点下标为i,则左孩子为2i,右孩子为2i+1(从下标1开始存储节点)。

b、链式存储:

使用二叉链表表示法,如下代码:

二叉链表表示:
public class BinaryTree {
	Object data;
	BinaryTree leftChild;// 左孩子
	BinaryTree rightChild;// 右孩子

	public BinaryTree(Object data) {
		this.data = data;
	}
	//还可以其他构造方法
}

5、二叉查找树(二叉搜索树、二叉排序树)

在这里插入图片描述
(1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
(2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
(3)左、右子树也分别为二叉排序树;
(4)没有键值相等的结点。
空树也是二叉排序树!!!

二叉查找树删除的三种情况:

a、删除节点为叶子结点时直接删除

如如上图,删除叶子结点1、3、8,可以直接删除。

b、删除节点只有一个孩子结点

在这里插入图片描述如上图,删除节点4(它只有一个左孩子),直接令祖父结点2指向孙子结点3即可。

c、删除节点有两个孩子结点

在这里插入图片描述如上图,删除节点2(它有两个孩子结点),用其右子树的最小结点代替待删除结点的数据,然后递归删除那个右子树最小结点。或者用其左子树的最大结点代替待删除结点的数据,然后递归删除那个右子树最小结点。

// 定义二叉树结构
	static class BSTree {
		private int value;
		private BSTree leftChild;
		private BSTree rightChild;

		public BSTree(int data) {
			this.value = data;
		}

		public BSTree(int value, BSTree leftChild, BSTree rightChild) {
			this.value = value;
			this.leftChild = leftChild;
			this.rightChild = rightChild;
		}

		public int getValue() {
			return value;
		}

		public void setValue(int value) {
			this.value = value;
		}

		public BSTree getLeftChild() {
			return leftChild;
		}

		public void setLeftChild(BSTree leftChild) {
			this.leftChild = leftChild;
		}

		public BSTree getRightChild() {
			return rightChild;
		}

		public void setRightChild(BSTree rightChild) {
			this.rightChild = rightChild;
		}

	}

// 二叉查找树root中插入节点data
	public static boolean insert(BSTree root, int data) {
		BSTree newNode = new BSTree(data, null, null);
		if (root == null) {// root为空树,新插入节点为根
			root = newNode;
			return true;
		}
		BSTree tree = root;
		while (tree != null) {
			if (tree.value == data) {
				System.out.println("二叉查找树没有键值相等的结点");
				return false;
			}
			if (tree.value > data) {
				if (tree.leftChild == null) {
					tree.leftChild = newNode;
					return true;
				}
				tree = tree.leftChild;
			} else {
				if (tree.rightChild == null) {
					tree.rightChild = newNode;
					return true;
				}
				tree = tree.rightChild;
			}
		}
		return false;
	}

/* 二叉查找树的删除(三种情况)
 * 1、删除节点为叶子结点时直接删除
 * 2、删除节点只有一个孩子结点
 * 3、删除节点有两个孩子结点
*/
	public static boolean delete(BSTree root, int data) {
		// 1、先找到要删除元素data节点

		BSTree treeParent = null;// 为了删除有一个孩子的节点或者叶子节点
		BSTree tree = root;
		while (tree != null) {
			if (tree.value > data) {
				treeParent = tree;
				tree = tree.leftChild;
			} else if (tree.value < data) {
				treeParent = tree;
				tree = tree.rightChild;
			} else {// 相等,找到删除结点tree
				break;
			}
		}

		// 2、判断待删除结点tree的情况
		if (tree == null) {
			System.out.println("二叉查找树中没有该节点,无法删除");
			return false;
		}

		// 删除节点有两个孩子结点【这里用右子树上最小结点替换】
		if (tree.leftChild != null && tree.rightChild != null) {
			BSTree rightParent = tree;
			BSTree rightTree = tree.rightChild;
			while (rightTree.leftChild != null) {
				// 右子树的最小结点一定在最左边
				rightParent = rightTree;
				rightTree = rightTree.leftChild;
			}
			// 右子树最小结点rightTree要替换删除结点tree
			tree.value = rightTree.value;
			// 删除rightTree
			if (rightTree.rightChild != null) {
				rightParent.leftChild = rightTree.rightChild;
			} else {
				rightParent.leftChild = null;
			}

			return true;
		}

		// 删除节点为叶子结点/只有一个孩子结点
		if (tree.rightChild != null) {
			BSTree rightParent = tree;
			BSTree rightTree = tree.rightChild;
			treeParent.leftChild = rightTree;
			return true;
		} else if (tree.leftChild != null) {
			BSTree rightParent = tree;
			BSTree rightTree = tree.leftChild;
			treeParent.rightChild = rightTree;
			return true;
		} else {// tree为叶子节点,直接删除
			if (treeParent.value > tree.value) {// tree为左边的叶子节点
				treeParent.leftChild = null;
			} else {
				treeParent.rightChild = null;
			}
			return true;
		}

	}

	// 中序遍历二叉树,因为二叉排序树的中序结果为有序序列
	public static void search(BSTree root) {
		if (root == null) {
			return;
		}
		search(root.leftChild);
		System.out.print(root.value + "   ");
		search(root.rightChild);
	}

	public static void main(String[] args) {
		BSTree root = new BSTree(20, null, null);
		insert(root, 15);
		insert(root, 30);
		insert(root, 12);
		insert(root, 18);
		insert(root, 10);
		insert(root, 14);
		insert(root, 16);
		insert(root, 19);
		insert(root, 11);
		insert(root, 13);
		insert(root, 17);
		search(root);
		System.out.println("\n-------------------------------------");

		// 删除15结点(有两个孩子结点)
		// delete(root, 15);

		// 删除10节点(有一个右孩子11)
		// delete(root, 10);

		// 删除14节点(有一个左孩子13)
		// delete(root, 14);

		// 删除叶子节点11
		delete(root, 11);

		search(root);
	}
示意图如下:

在这里插入图片描述

二叉查找树的时间复杂度分析:

一般情况下:插入、删除、查找时间复杂度为O(log n),取决于树的高度!!!

最坏情况下:二叉查找树退化为线性结构(例如:以1、2、3、4等有序序列插入而形成的查找树),它的插入、删除、查找时间复杂度为O(n)。为了防止这种情况,设计了平衡二叉树(AVL树)。

6、平衡二叉树(AVL树、平衡二叉查找树)

平衡二叉树的定义:

(1)、可以是一 棵空树
(2)、如果不是空树,则它的左右两个子树的高度差的绝对值不超过1,并且左右子树都是一棵平衡二叉树。

平衡二叉树的优缺点

很好地解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在O(logN)。但是 频繁旋转会使插入和删除牺牲掉O(logN)左右的时间

平衡二叉树失衡的四种情况

当插入、删除节点时可能会破坏平衡二叉树的结构,可能有如下四种情形:LL型、RR型、LR型、RL型。参考链接:失衡四种情况解决

(1)、LL型:如下图,当插入结点24,破坏了AVL树的平衡性。规定从插入结点开始,向上寻找第一个不平衡起始结点,发现为结点35。
在这里插入图片描述

在这里插入图片描述

其一般规律如下图:数字只是标记作用!!!
在这里插入图片描述

(2)、RR型:如下图,当插入结点70后破坏了AVL树的平衡。发现60为不平衡起始点。

在这里插入图片描述
其一般规律如下图:数字只是标记作用!!!
在这里插入图片描述

(3)、LR型:
在这里插入图片描述
其一般规律如下图:数字只是标记作用!!!
在这里插入图片描述

(4)、RL型:

其一般规律如下图:数字只是标记作用!!!
在这里插入图片描述

// 定义平衡二叉树的结构
	static class AVLNode {
		private int value;
		private AVLNode leftChild;
		private AVLNode rightChild;
		private int height;// 判断结点的高度

		public AVLNode(int data) {
			this.value = data;
		}

		public AVLNode(int value, AVLNode leftChild, AVLNode rightChild) {
			this.value = value;
			this.leftChild = leftChild;
			this.rightChild = rightChild;
		}

		public int getValue() {
			return value;
		}

		public void setValue(int value) {
			this.value = value;
		}

		public AVLNode getLeftChild() {
			return leftChild;
		}

		public void setLeftChild(AVLNode leftChild) {
			this.leftChild = leftChild;
		}

		public AVLNode getRightChild() {
			return rightChild;
		}

		public void setRightChild(AVLNode rightChild) {
			this.rightChild = rightChild;
		}

		public int getHeight() {
			return height;
		}

		public void setHeight(int height) {
			this.height = height;
		}
	}

// 计算某个结点高度
	private static int getNodeHeight(AVLNode node) {
		if (node == null) {
			return 0;
		}
		return node.getHeight();
	}

// 计算结点node左右两个高度中的最大值
	private static int getMaxHeight(AVLNode node, int left, int right) {
		return Math.max(left, right);
	}

	/*
	 * LL旋转步骤: 
	 * 		1、让不平衡起始点元素的左孩子成为  新的起始节点 
	 * 		2、原来的不平衡起始点作为  新起始点的右孩子
	 * 		3、原来的不平衡起始点的左孩子上的右子树作为 原来的不平衡起始点的左孩子
	 * 
	 * @param node:失衡结点
	 * @return :旋转后的根节点
	 */
	public static AVLNode LL(AVLNode node) {
		// 不平衡起始点node的左子树
		AVLNode node_left = node.leftChild;
		// 原来的不平衡起始点的左孩子上的右子树【可能null】
		AVLNode newLeft = node_left.rightChild;

		// 开始旋转
		node_left.rightChild = node;
		if (newLeft != null) {
			node.leftChild = newLeft;
		}

		// 重新计算高度失衡结点和旋转后根节点的高度
		node.height = getMaxHeight(node, getNodeHeight(node.leftChild), getNodeHeight(node.rightChild)) + 1;
		node_left.height = getMaxHeight(node_left, getNodeHeight(node_left.leftChild),
				getNodeHeight(node_left.rightChild)) + 1;

		return node_left;// 返回旋转后的根节点
	}

	/*
	 * RR
	 */
	public static AVLNode RR(AVLNode node) {
		AVLNode node_right = node.rightChild;
		AVLNode newRight = node_right.leftChild;// 可能null

		// 开始旋转
		node_right.leftChild = node;
		node.rightChild = newRight;

		// 重新计算高度
		node.height = getMaxHeight(node, getNodeHeight(node.leftChild), getNodeHeight(node.rightChild)) + 1;
		node_right.height = getMaxHeight(node_right, getNodeHeight(node_right.leftChild),
				getNodeHeight(node_right.rightChild)) + 1;

		return node_right;
	}

	/*
	 * LR:先左旋转(RR),再右旋转(LL)
	 */
	public static AVLNode LR(AVLNode node) {
		// RR
		node.leftChild = RR(node.leftChild);
		// LL
		AVLNode ll = LL(node);

		return ll;
	}

	/*
	 * RL:先右旋转(LL),再左旋转(RR)
	 */
	public static AVLNode RL(AVLNode node) {
		// LL
		node.rightChild = LL(node.rightChild);
		// RR
		AVLNode rr = RR(node);

		return rr;
	}

	/*
	 * 插入操作:向树root中插入结点data。在写代码时可以用如下实例思考代码:
	 * 以(1、2、3)的插入顺序观察RR型---->root = RR(root);
	 * 以(3、5、4)的插入顺序观察RL型---->root = RL(root);
	 * 以(3、2、1)的插入顺序观察LL型---->root = LL(root);
	 * 以(5、3、4)的插入顺序观察LR型---->root = LR(root);
	 */
	public static AVLNode insert(AVLNode root, int data) {
		AVLNode newNode = new AVLNode(data, null, null);
		if (root == null) {// 空树,新插入结点即为根
			root = newNode;
			return root;
		}
		if (data == root.value) {
			System.out.println("AVL树中不存在值相等的节点");
			return null;
		}
		if (data > root.value) {// 走根root的右子树
			root.rightChild = insert(root.rightChild, data);
			// 判断插入后是否会引起失衡
			if (getNodeHeight(root.rightChild) - getNodeHeight(root.leftChild) == 2) {
				if (data > root.rightChild.value) {// RR型
					root = RR(root);
				} else {// RL型
					root = RL(root);
				}
			}

		} else {
			root.leftChild = insert(root.leftChild, data);
			// 判断插入后是否会引起失衡
			if (getNodeHeight(root.leftChild) - getNodeHeight(root.rightChild) == 2) {
				if (data > root.leftChild.value) {// LR型
					root = LR(root);
				} else {// LL型
					root = LL(root);
				}
			}
		}

		root.height = getMaxHeight(root, getNodeHeight(root.leftChild), getNodeHeight(root.rightChild)) + 1;
		return root;
	}

	// 中序遍历AVL树【有序结果】
	public static void search(AVLNode root) {
		if (root == null) {
			return;
		}
		search(root.leftChild);
		System.out.print(root.value + "   ");
		search(root.rightChild);
	}

	public static void main(String[] args) {
		AVLNode root = new AVLNode(3);// 树根
		insert(root, 2);
		insert(root, 1);
		insert(root, 4);
		insert(root, 5);
// 特别注意:因为在插入、遍历时都使用了递归,小心栈溢出问题,可以手动调大栈大小!
		search(root);
	}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值