leetcode二叉树及三种遍历方式理解(动画演示说明)JavaScript

本文详细介绍了二叉树的概念,特别是二叉查找树(BST)的特性,包括其快速查找、插入和删除操作。二叉查找树的高度和深度区别、节点的遍历(前序、中序、后序)及其递归实现方式也被详尽阐述。此外,文中还讲解了在BST中查找特定值、最小值和最大值的方法,并提供了删除节点的复杂处理逻辑。
摘要由CSDN通过智能技术生成

二叉树

树是一种非线性的数据结构,以分层的方式存储数据。树被用来存储具有层级关系的数据,比如文件系统中的文件;树还被用来存储有序列表。本章将研究一种特殊的树:二叉树。选择树而不是那些基本的数据结构,是因为在二叉树上进行查找非常快(而在链表上查找则不是这样),为二叉树添加或删除元素也非常快(而对数组执行添加或删除操作则不是这样)。

​ 二叉树是一种特殊的树,它的子节点个数不超过两个。二叉树具有一些特殊的计算性质,使得在它们之上的一些操作异常高效。

二叉树的高度和深度的区别

​ 高度和深度是相反的表示,深度是从上到下数的,而**高度是从下往上数**。

(对某个节点来说)

​ 深度是指从根节点到该节点的最长简单路径边的条数;
​ 高度是指从最下面的叶子节点到该节点的最长简单路径边的条数;
(对二叉树)
​ 深度是从根节点数到它的叶节点;
​ 高度是从叶节点数到它的根节点;
​ 注意: 树的深度和高度一样,但是具体到树的某个节点,其深度和高度不一样。

img

​ 二叉查找树由节点组成,所以我们要定义的第一个对象就是Node,该对象和前面介绍链表时的对象类似。Node 类的定义如下:

function Node(data, left, right) {
	this.data = data;
	this.left = left;
	this.right = right;
	this.show = show;
}
function show() {
	return this.data;
}

// Node 对象既保存数据,也保存和其他节点的链接(left 和right),show() 方法用来显示保存在节点中的数据

查找正确插入点的算法如下

(1) 设根节点为当前节点。
(2) 如果待插入节点保存的数据小于当前节点,则设新的当前节点为原节点的左节点;反之,执行第4 步。
(3) 如果当前节点的左节点为null,就将新的节点插入这个位置,退出循环;反之,继续执行下一次循环。
(4) 设新的当前节点为原节点的右节点。
(5) 如果当前节点的右节点为null,就将新的节点插入这个位置,退出循环;反之,继续执行下一次循环。
// BST 二叉查找树
function BST() {
	this.root = null;
	this.insert = insert;
	this.inOrder = inOrder;
}
function insert(data) {
	var n = new Node(data, null, null);
	if (this.root == null) {
		this.root = n;
	}
	else {
		var current = this.root;
		var parent;
	while (true) {
		parent = current;
		if (data < current.data) {
			current = current.left;
			if (current == null) {
			parent.left = n;
			break;
			}
		}
		else {
			current = current.right;
			if (current == null) {
				parent.right = n;
				break;
				}
			}
		}
	}
}
三种遍历BST 的方式:中序、先序和后序

​ 中序遍历按照节点上的键值,以升序访问BST 上的所有节点。先序遍历先访问根节点,然后以同样方式访问左子树和右子树。后序遍历先访问叶子节点,从左子树到右子树,再到根节点。

​ 如图所示是二叉树中的一个节点,这个节点有左子树与右子树,通过两根绿色的连接线,将此节点划分成了三个区域,我们分别用前、中、后这三个辅助点来表示。这三个点表明在二叉树的遍历中什么时候要取出此节点的值。任何一个节点去遍历都是 前-左绿线-中-右绿线-后这样的顺序遍历的。

先序遍历

使用递归方式实现前序遍历的具体过程为:

  • 先访问根节点
  • 再序遍历左子树
  • 最后序遍历右子树

【图解数据结构】 一组动画彻底理解二叉树三种遍历

我们来对上面的动画进行一个充分的说明来理解前序遍历的递归实现方式。

  • 首先访问了 28 这个节点,我们看 前辅助点 ,由于是前序遍历,那么此刻我们取出该节点的值 28
  • 而后通过左绿线访问 28 的左子树
  • 16 这个节点中,我们看 前辅助点 ,由于是前序遍历,取出该节点的值 16
  • 通过左绿线访问 16 的左子树
  • 13 这个节点中,我们看 前辅助点 ,由于是前序遍历,取出该节点的值 13
  • 13 这个节点左子树为空,那么我们左绿线就没有,然后看 中辅助点 ,由于是前序遍历,因此不做处理
  • 13 这个节点右子树为空,那么我们右绿线就没有,然后看 后辅助点 ,由于是前序遍历,因此不做处理
  • 而后回到 16 这个节点中,看 中辅助点 ,由于是前序遍历,因此不做处理
  • 而后看 16 这个节点的右子树 22 这个节点,看 前辅助点 ,由于是前序遍历,取出该节点的值 22
中序遍历

使用递归方式实现中序遍历的具体过程为:

  • 先中序遍历左子树
  • 再访问根节点
  • 最后中序遍历右子树

【图解数据结构】 一组动画彻底理解二叉树三种遍历

我们来对上面的动画进行一个充分的说明来理解中序遍历的递归实现方式。

  • 首先访问了 28 这个节点,我们看 前辅助点 ,由于是中序遍历,因此不做处理
  • 而后通过左绿线访问 28 的左子树
  • 16 这个节点中,我们看 前辅助点 ,由于是中序遍历,因此不做处理
  • 通过左绿线访问 16 的左子树
  • 13 这个节点中,我们看 前辅助点 ,由于是中序遍历,因此不做处理
  • 13 这个节点左子树为空,那么我们左绿线就没有,然后看 中辅助点 ,由于是中序遍历,取出该节点的值 13
  • 13 这个节点右子树为空,那么我们右绿线就没有,然后看 后辅助点 ,由于是中序遍历,因此不做处理
  • 而后回到 16 这个节点中,看 中辅助点 ,由于是中序遍历,取出该节点的值 16
  • 而后看 16 这个节点的右子树 22 这个节点,看 前辅助点 ,由于是中序遍历,因此不做处理
  • 中辅助点 ,由于是中序遍历,取出该节点的值 22
后序遍历

使用递归方式实现后序遍历的具体过程为:

  • 先后序遍历左子树
  • 再后序遍历右子树
  • 最后访问根节点

【图解数据结构】 一组动画彻底理解二叉树三种遍历

我们来对上面的动画进行一个充分的说明来理解后序遍历的递归实现方式。

  • 首先访问了 28 这个节点,我们看 前辅助点 ,由于是后序遍历,因此不做处理
  • 而后通过左绿线访问 28 的左子树
  • 16 这个节点中,我们看 前辅助点 ,由于是后序遍历,因此不做处理
  • 通过左绿线访问 16 的左子树
  • 13 这个节点中,我们看 前辅助点 ,由于是后序遍历,因此不做处理
  • 13 这个节点左子树为空,那么我们左绿线就没有,然后看 中辅助点 ,由于是后序遍历,因此不做处理
  • 13 这个节点右子树为空,那么我们右绿线就没有,然后看 后辅助点 ,由于是后序遍历,取出该节点的值 13
  • 而后回到 16 这个节点中,看 中辅助点 ,由于是后序遍历,因此不做处理
  • 而后看 16 这个节点的右子树 22 这个节点,看 前辅助点 ,由于是中序遍历,因此不做处理
  • 中辅助点 ,由于是后序遍历,因此不做处理
  • 后辅助点 ,由于是后序遍历,取出该节点的值 16

二叉查找树上查找

对BST 通常有下列三种类型的查找:
(1) 查找给定值;
(2) 查找最小值;
(3) 查找最大值。

查找最小值和最大值

getMin() 方法查找BST 上的最小值,该方法的定义如下:
function getMin() {
    var current = this.root;
    while (!(current.left == null)) {
    	current = current.left;
    }
    return current.data;
}
getMax() 方法的定义如下:
function getMax() {
    var current = this.root;
    while (!(current.right == null)) {
    	current = current.right;
    }
    return current.data;
}

查找给定值

// find() 方法用来在BST 上查找给定值,定义如下:
// 利用二叉树的性质,如果data小于该数据,就往左寻找,data大于该数据,就往右寻找
function find(data) {
    var current = this.root;
    while (current != null) {
    	if (current.data == data) {
    	return current;
    	}
    	else if (data < current.data) {
   	 	current = current.left;
    	}
    	else {
    	current = current.right;
    	}
    }
    return null;
}

二叉查找树上删除节点

​ 为了管理删除操作的复杂度,我们使用递归操作,同时定义两个方法:remove()removeNode()

​ 从BST 中删除节点的第一步是判断当前节点是否包含待删除的数据,如果包含,则删除该节点;如果不包含,则比较当前节点上的数据和待删除的数据。如果待删除数据小于当前节点上的数据,则移至当前节点的左子节点继续比较;

​ 如果删除数据大于当前节点上的数据,则移至当前节点的右子节点继续比较。
​ 如果待删除节点是叶子节点(没有子节点的节点),那么只需要将从父节点指向它的链接指向null。
​ 如果待删除节点只包含一个子节点,那么原本指向它的节点就得做些调整,使其指向它的子节点

​ 最后,如果待删除节点包含两个子节点,正确的做法有两种:要么查找待删除节点左子树上的最大值,要么查找其右子树上的最小值。这里我们选择后一种方式。

整个删除过程由两个方法完成。remove() 方法只是简单地接受待删除数据,调用removeNode()删除它,后者才是完成主要工作的方法。两个方法的定义如下:

function remove(data) {
	root = removeNode(this.root, data);
}
function removeNode(node, data) {
	if (node == null) {
    	return null;
    }
    if (data == node.data) {
    // 没有子节点的节点
    if (node.left == null && node.right == null) {
    	return null;
    }
    // 没有左子节点的节点
    if (node.left == null) {
    	return node.right;
    }
    // 没有右子节点的节点
    if (node.right == null) {
    	return node.left;
    }
    // 有两个子节点的节点
    var tempNode = getSmallest(node.right);
    node.data = tempNode.data;
    node.right = removeNode(node.right, tempNode.data);
    return node;
    }
    else if (data < node.data) {
    	node.left = removeNode(node.left, data);
    	return node;
    }
    else {
    	node.right = removeNode(node.right, data);
   	 	return node;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值