二叉树旋转

本文我们来学习二叉树的另一种操作——旋转。掌握了这个神技,你将会在平衡树的道路上所向披靡。

1. 什么是旋转

二叉树节点旋转一共有两种操作:左旋和右旋。

如图 1 所示,左边的二叉树通过左旋得到右边的二叉树;反之右旋同理。(a, b, c 表示子树,而不是单独表示一个节点)
二叉树旋转_第1张图片

图1 左旋和右旋
  • 左旋:是以节点的"右分支"为轴,进行逆时针旋转。我们将左旋操作定义为 left_rotate.
  • 右旋:是以节点的“左分支"为轴,进行顺时针旋转。我们将右旋操作定义为 right_rotate.

在图 1 中,左侧二叉树经过 left_rotate(x) 可以得到右侧,右侧执行 right_rotate(y) 可以得到左侧。

2. 为什么要旋转

在解释这个道理之前,我们先看看执行旋转后,二叉树中节点的深度有什么变化。在图 1 中,二叉树执行左旋后,a 分支所有节点的深度比以前多 1,b 分支保持不变,c 分支所有节点比以前少 1.

这就意味着,通过合适的左旋和右旋操作,我们可以调整二叉树的深度。另一方面,通过合适的左旋和右旋,我们可以把二叉树变换成任意的形状!

思考一下:你有没有办法通过左旋和右旋,把二叉树转换成一条只有一个分支的,向右延展的链?

二叉树旋转_第2张图片

图2 二叉树通过若干次左旋和右旋操作变换成链

答案:

left_rotate(4);
right_rotate(10);
right_rotate(8);
right_rotate(5);
right_rotate(4);
right_rotate(2);

当然,你可以尝试更多其它的例子,在你的草稿本上画一画。

3. 旋转算法

有了上面的铺垫后,旋转操作是不是变的特别容易?接下来,我们给出左旋和右旋的伪代码。

下面是我们定义的数据结构:

// 表示二叉树
let tree = {
	root: null
};

// 表示节点
let node = {
	left: null,   // 左孩子
	right: null,  // 右孩子
	parent: null, // 父节点
	key: 0
};

二叉树旋转_第3张图片

  • 左旋
function left_rotate(x) {
	let y = detach(x.right); // 摘下 x 的右分支 y
	transplant(x, y);
	let b = detach(y.left); // 摘下 y 的左分支
	x.right = b; // 挂到 x 的右侧
	b.parent = x;
}
  • 右旋
function left_rotate(y) {
	let x = detach(y.left);
	transplant(y, x);
	let b = detach(x.right);
	y.left = b;
	b.parent = y;
}

注意到上面有一步 transplant 操作,请参考上一篇文章:《二叉搜索树》。另一个 detach 操作,它的目的就是摘下节点,代码如下。

function deatch(z) {
	if (z.parent == null) return z;
	
	if (z.parent.left == z) {
		z.parent.left = null;
	} else {
		z.parent.right = null;
	}
	z.parent = null;
	return z;
}

4. 总结

  • 知道旋转的步骤
  • 知道为什么有旋转
  • 知道怎样通过旋转对二叉树进行变换

你可能感兴趣的:(--,红黑树)