二叉树旋转

Posted --Allen--

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了二叉树旋转相关的知识,希望对你有一定的参考价值。

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

1. 什么是旋转

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

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

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

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

2. 为什么要旋转

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

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

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

图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
;

  • 左旋
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. 总结

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

以上是关于二叉树旋转的主要内容,如果未能解决你的问题,请参考以下文章

关于二叉树旋转

平衡二叉树的旋转

树总结(二)平衡二叉树

二叉查找树ADT

一篇通俗易懂的平衡二叉树的旋转blog

一篇通俗易懂的平衡二叉树的旋转blog