二叉树基本使用

什么是二叉树?

在计算机科学中,二叉树是每个结点最多有两个子树的树结构。通常子树被称作“左子树”(left subtree)和“右子树”(right subtree)。二叉树常被用于实现二叉查找树和二叉堆。

为什么要学习二叉树?

在树中查找数据项的速度和在有序数组中查找一样快,并且插入数据项和删除数据项的速度和在链表中一样快。
二叉树结合了数组与链表结构的优点,成为了更加快速的高级数据结构。

下面先用一张图整体理解一下二叉树,
在这里插入图片描述
下面是有关二叉树的基本术语,有必要了解一下,
在这里插入图片描述

在这里插入图片描述

二叉树有以下几个性质:

性质1:二叉树第i层上的结点数目最多为 2{i-1} (i≥1)。
性质2:深度为k的二叉树至多有2{k}-1个结点(k≥1)。
性质3:包含n个结点的二叉树的高度至少为log2 (n+1)。(很少用,且实用意义不大)

在这里插入图片描述

满二叉树:
高度为h,并且由2{h} –1个结点的二叉树,被称为满二叉树。
在这里插入图片描述

完全二叉树:
叶子结点只能出现在最下层和次下层,且最下层的叶子结点集中在树的左部。显然,一棵满二叉树必定是一棵完全二叉树,而完全二叉树未必是满二叉树。
在这里插入图片描述

在二叉查找树中:(01) 若任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;(02) 任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;(03) 任意节点的左、右子树也分别为二叉查找树。(04) 没有键值相等的节点(no duplicate nodes)。
在这里插入图片描述

二叉树与二叉搜索树的区别:

  • 二叉树,每一个父节点下最多只能有两个节点,父节点与子节点(或者子树)有着一定的联系规则,这种规则不仅仅限制于排序规则。

  • 二叉搜索树是二叉树的一种特殊二叉树,也是最常用的二叉树。二叉搜索树满足左子节点的关键字小于父节点,父节点的关键字小于或等于右子节点。所有的左子树节点的关键字值小于所有右子树的关键字值。也就是所谓的左小右大。这样有利于进行快速的数据处理。

二叉树的父、子节点之间一定要有某种联系规则吗
答案是肯定的。

试想:如果父节点与子节点之间,没有某种规则进行关联,而是随意对数据进行摆放,那二叉树就是去了它原有的意义。

关联关系都是根据需求而定的,如果需要有序二叉树,则可以选择搜索二叉树。如果需要根据需求设定父子节点之间的联系,则需要从新定义二叉树父子节点之间的联系,如哈夫曼编码是根据字符出现频率和编码设定的父子节点关系。

二叉搜索树-查找

从根节点 8 开始,如果所查询的数字大于根节点,则向右子树寻找。

如:查找 13

根节点8 -> 查询右子节点10 ->
查询右子节点 14 -> 查询左子节点13 -> 返回查询结果

原则:左小右大
在这里插入图片描述

二叉搜索树-插入
从根节点 开始,如果插入的数字小于根节点,则向左子树寻找。
如:插入 13
根节点15 -> 查询左子节点5->
查询右子节点 12 -> 查询右子节点为空 -> 执行插入操作
原则:左小右大

在这里插入图片描述

在这里插入图片描述

二叉搜索树 – 删除

如果没有子节点:
直接删除即可

如果左不空,右空:
用左子树代替当前节点即可

如果左空,右不空:
直接用右子树代替当前节点即可

如果左右子节点均不为空:
需要对左子树进行规则遍历,找到节点继承者。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

以上将二叉树的基本概念,二叉树的插入,遍历以及删除节点大致描述了一下,下面用代码;来说具体实现一下,

1、首先定义一个节点,通过上面的描述我们大概知道,节点包含几部分,数据域,这里用double,索引,左右节点,

class Node{
	public int iData; //数据关键字,key
	public double dData; //数据的值
	public Node leftChild ;
	public Node rightChild;
	
	public void displayNode(){
		System.out.println("{");
		System.out.println(iData);
		System.out.println(",  ");
		System.out.println(dData);
		System.out.println("} ");
	}
}

2、二叉树查找,

private Node root ;
	
	public Tree() { //初始化为一个空树
		root = null;
	}
	
	// 根据给定的key值查询
	public Node find(int key){
		Node current = root; // 从root节点开始查询
		while (current.iData != key) {
			if (key < current.iData) { //向左子树查询
				current = current.leftChild;
			} else {   //向右子树查询
				current = current.rightChild;
			}
			if (current == null) {
				return null;
			}
		}
		return current;
	}

3、二叉树插入,

//插入
	public void insert(int id ,double dd) {
		
		Node newNode = new Node();
		newNode.iData = id;
		newNode.dData = dd;
		if (root == null) {
			root = newNode;
		} else {
			Node current = root ;//从root节点开始
			Node parent ; //定义一个父节点,该父节点与current相关
			while (true) {
				parent = current;
				if (id < current.iData) { //go left
					current = current.leftChild;
					if (current == null) {
						parent.leftChild = newNode;
						return;
					}
				} else { //go right
					current = current.rightChild;
					if (current == null) {
						parent.rightChild = newNode;
						return;
					}
				}
				
			}
		}
	}

4、二叉树删除,删除的情况最复杂,根据上文的描述大家应该可以知道,我们需要分为多种情况来考虑,这也是考察对二叉树的联想能力,

//根据key删除一个节点
	public boolean delete(int key){
		Node current = root ;
		Node parent = root ;
		boolean isLeftChild = true;
		
		while (current.iData != key) {
			parent = current;
			if (key < current.iData) {
				isLeftChild = true;
				current = current.leftChild;
			} else {
				isLeftChild = false ;
				current = current.rightChild;
			}
			if (current == null) {
				return false;
			}
		}
		
		//found node to delete
		//如果没有子节点,直接删除即可
		if (current.leftChild == null && current.rightChild == null) {
			if (current == root) { //如果删除的节点为root
				root = null; //空树
			} 
			else if(isLeftChild){ //如果删除左子节点
				 parent.leftChild = null;
			}
			else { //如果删除右子节点
				parent.rightChild = null;
			}
		}
		
		//如果没有右节点,用左子树代替当前节点即可。
		else if(current.rightChild == null){
			if (current == root) {
				root = current.leftChild;
			} 
			else if (isLeftChild) {
				parent.leftChild = current.leftChild;
			}
			else {
				parent.rightChild = current.leftChild;
			}
		}
		
		//如果没有左节点,直接用右子树代替当前删除的节点
		else if (current.leftChild == null) {
			if (current == root) {
				root = current.rightChild;
			}
			else if (isLeftChild) {
				parent.leftChild = current.rightChild;
			}
			else {
				parent.rightChild = current.rightChild;
			}
		}
		
		// 如果左右子节点均不为空,则需要寻找到节点继承者
		else {
			// 寻找继承者
			Node successor = getSuccessor(current); 
			
			if (current == root) {
				root = successor;
			}
			else if (isLeftChild) {
				parent.leftChild = successor ;
			}
			else {
				parent.rightChild = successor;
			}
			
			successor.leftChild = current.leftChild; //getsuccessort里边定义了successor的右节点,此时需要定义下左节点。
		}
		
		return true;
	}



//获取被删除的节点的额继承节点
	private Node getSuccessor(Node delNode){
		Node successorParent = delNode; //初始化继承者的父节点
		Node successor = delNode;//初始化继承者几点
		Node current = delNode.rightChild; //从当前节点开始寻找继承者,必须从右子树里寻找继承者,
											//因为右子树比当前节点的值大
		
		while (current != null) {  //寻找右子树的最左节点作为继承者。保证右子树大于继承者,最左节点是最小的。
			successorParent = successor;
			successor = current;
			current = current.leftChild;
		}
		
		if (successor != delNode.rightChild) { //如果继承者不是当前删除节点的右子节点。说明右子树不止一层
			successorParent.leftChild = successor.rightChild; // successort的右子树成为了父类的左子树
			successor.rightChild = delNode.rightChild; //successort的右子树指向被删除节点的右子树
		}
		return successor;
	}


为了能够展示出二叉树的结构,下面是一个打印二叉树的方法,

//打印树
	public void displayTree(){
		Stack globalStack = new Stack();
		globalStack.push(root);
		int nBlanks =32;getClass();
		boolean isRowEmpty = false;
		System.out.println("=========================================================================");
		while(isRowEmpty == false) {
			Stack localStack = new Stack();
			isRowEmpty = true;
			for (int  j =0;j<nBlanks;j++) {
				System.out.print(" ");
			}
			
			while (globalStack.isEmpty() == false) {
				Node temp = (Node)globalStack.pop();
				if (temp!=null) {
					System.out.print(temp.iData);
					localStack.push(temp.leftChild);
					localStack.push(temp.rightChild);
					if (temp.leftChild != null || temp.rightChild !=null) {
						isRowEmpty = false;
					}
				}
				else {
					System.out.print("--");
					localStack.push(null);
					localStack.push(null);
				}
				for (int j = 0;j<nBlanks*2-2;j++) {
					System.out.print(" ");
				}
			}
			System.out.println();
			nBlanks /= 2;
			while (localStack.isEmpty() == false) {
				globalStack.push(localStack.pop());
			}
		}
		System.out.println("=========================================================================");
	}	

下面我们写一段测试代码,来验证一下上面的几个方法,

public static void main(String[] args) {
		
		int value;
		
		Tree theTree = new Tree();
		theTree.insert(50, 1.3);
		theTree.insert(25, 1.1);
		theTree.insert(75, 1.7);
		
		theTree.insert(12, 1.3);
		theTree.insert(37, 1.9);
		theTree.insert(43, 1.4);
		
		theTree.insert(87, 1.6);
		theTree.insert(93, 1.2);
		theTree.insert(97, 1.5);
		
		theTree.insert(30, 1.4);
		theTree.insert(19, 1.3);
		
		Node node = theTree.find(93);
		System.out.println(node.dData);
		
		theTree.displayTree();
		System.out.println("删除一个节点 -------------");
		theTree.delete(25);
		theTree.traverse(1);
		
		/*theTree.traverse(1);
		
		System.out.println("----------------");
		theTree.traverse(2);
		System.out.println("----------------");
		theTree.traverse(3);*/
		
		/*theTree.delete(25);
		theTree.displayTree();*/
		
	}

运行一下,可以看到打印结果,
在这里插入图片描述
可以看到,二叉树的删除,插入和查找基本完成,下面说说二叉树的几种遍历方式,

在这里插入图片描述

从这三种遍历方式的描述来看,我们大概可以知道数据节点的访问顺序,下面我们用代码实现一下这三种遍历方式,

    public static List preList = new ArrayList();		//前序遍历结果保存
	public static List afterList = new ArrayList();		//后续遍历结果保存
	public static List inList = new ArrayList();		//中序遍历结果保存
	
	//遍历
	//前序遍历,从根节点开始遍历。
	private void preOrder(Node localRoot){
		if (localRoot != null) {
			preList.add(localRoot.iData);
			System.out.print(localRoot.iData+"  " );
			preOrder(localRoot.leftChild);
			preOrder(localRoot.rightChild);
		}
	}
	//中序
	private void inOrder(Node localRoot){
		if (localRoot !=null) {
			inOrder(localRoot.leftChild);
			inList.add(localRoot.iData);
			System.out.print(localRoot.iData+"  ");
			inOrder(localRoot.rightChild);
		}
	}
	//后序
	private void postOrder(Node localRoot){
		if (localRoot != null) {
			postOrder(localRoot.leftChild);
			postOrder(localRoot.rightChild);
			afterList.add(localRoot.iData);
			System.out.print(localRoot.iData+"  ");
		}
	}
	
	//测试三种遍历方式打印出的结果有何不同之处
	public void traverse(int type){
		switch (type) {
		case 1:
			System.out.print("\npre order :");
			preOrder(root);
			System.out.println(Arrays.toString(preList.toArray()));
			break;
		case 2:
			System.out.print("\nin order :");
			inOrder(root);
			System.out.println(Arrays.toString(inList.toArray()));
			break;
		case 3:
			System.out.print("\npost order :");
			postOrder(root);
			System.out.println(Arrays.toString(afterList.toArray()));
			break;
		default:
			break;
		}
	}

下面继续使用我们的测试代码,对遍历的结果进行打印输出,可以看到,三种遍历方式的结果已经成功输出,
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小码农叔叔

谢谢鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值