算法系列(八)数据结构之二叉查找树

算法系列(七)数据结构之树的基本结构和二叉树的遍历 中介绍了基本的树结构,二叉树的实现和遍历。

这篇文章重点学习一下二叉查找树。

概述

二叉排序树(Binary Sort Tree)又称二叉查找树(Binary Search Tree)二叉搜索树。

二叉查找树(BST)是二叉树的一个重要的应用,它在二叉树的基础上加上了这样的一个性质:对于树中的每一个节点来说,如果有左儿子的话,它的左儿子的值一定小于它本身的值,如果有右儿子的话,它的右儿子的值一定大于它本身的值。二叉查找树的操作一般有插入、删除和查找,这几个操作的平均时间复杂度都为O(logn),插入和查找操作很简单,删除操作会复杂一点。

由于树的递归定义,通常用递归实现一些操作,二叉查找树平均深度是O(logN),不必担心栈溢出。

定义

二叉查找树是一棵空树,或者是具有下列性质的二叉树:

(1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值;

(2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值;

(3)左、右子树也分别为二叉查找树;

(4)没有键值相等的结点。

代码实现基础

继承了上一篇文章中的树结构,增加了比较器。必须有明确的节点比较方法,否则无法进行基本操作。

package com.algorithm.tree;

import java.util.Comparator;

/**
 * 左子树小于右子树的二叉查找树
 *
 * @author chao
 *
 * @param <T>
 */
public class BinarySeachTree<T> extends BinaryTree<T> {
	private Comparator<T> comparator;

	public BinarySeachTree(BinarySeachTree.BinaryTreeNode<T> root) {
		this(root, null);
	}

	public BinarySeachTree(BinarySeachTree.BinaryTreeNode<T> root, Comparator<T> comparator) {
		super(root);
		this.comparator = comparator;
	}

	private int compare(BinarySeachTree.BinaryTreeNode<T> a, BinarySeachTree.BinaryTreeNode<T> b) {
		if (comparator != null) {
			return comparator.compare(a.element, b.element);
		} else if (a.element instanceof Comparable && b.element instanceof Comparable) {
			return ((Comparable) a.element).compareTo(b.element);
		} else {
			throw new RuntimeException("can't compare " + a.element.getClass());
		}

	}
}

插入操作

步骤:将X节点插入到树T中,当前比较节点为cur,插入节点为x,r。默认从根节点为初始节点开始比较,cur初始值为root,如果根节点为空,将x直接作为根节点。否则比较cur与x的大小,如果x比cur大,说明x在cur的右子树上,以cur.right为初始节点,继续做相同的操作。如果cur比x大,说明x在cur的左子树上,以cur.left为初始节点,继续做相同操作。

代码实现

	/**
	 * 从根节点插入一个新数据
	 *
	 * @param x
	 * @return
	 */
	public BinaryTreeNode<T> insert(BinaryTreeNode<T> x) {
		return insert(root, x);
	}

	/**
	 * 从某一个节点插入新数据
	 *
	 * @param cur
	 * @param x
	 * @return
	 */
	protected BinaryTreeNode<T> insert(BinaryTreeNode<T> cur, BinaryTreeNode<T> x) {
		if (cur == null) {
			return x;
		}
		int compareresult = compare(cur, x);
		if (compareresult < 0) {
			cur.right = insert(cur.right, x);
		} else if (compareresult > 0) {
			cur.left = insert(cur.left, x);
		}
		return cur;
	}

查找最大值与最小值

根据定义可以知道,最小值位于最左侧的叶子节点。最大值位于最右侧的叶子节点。

代码实现

/**
	 * 查找最小节点
	 *
	 * @return
	 */
	public BinaryTreeNode<T> findMin() {
		return findMin(root);
	}

	/**
	 * 以node为根节点的最小节点
	 *
	 * @param node
	 * @return
	 */
	public BinaryTreeNode<T> findMin(BinaryTreeNode<T> node) {
		if (node == null) {
			return null;
		} else if (node.left == null) {
			return node;
		}
		return findMin(node.left);
	}

	/**
	 * 查找最大节点
	 *
	 * @return
	 */
	public BinaryTreeNode<T> findMax() {
		return findMin(root);
	}

	/**
	 * 以node为根节点的最大节点
	 *
	 * @param node
	 * @return
	 */
	public BinaryTreeNode<T> findMax(BinaryTreeNode<T> node) {
		if (node == null) {
			return null;
		} else if (node.right == null) {
			return node;
		}
		return findMin(node.right);
	}

查找是否包含一个节点的操作

有点二分查找的意思。只是二分查找是线性的,查找树的查找是树形结构上的查找。

代码实现:

	/**
	 * 判断是否包含x
	 *
	 * @param x
	 * @return
	 */
	public boolean contains(BinaryTreeNode<T> x) {
		return contains(root, x);
	}

	/**
	 * 以某个节点为根节点判断是否包含x
	 *
	 * @param cur
	 * @param x
	 * @return
	 */
	public boolean contains(BinaryTreeNode<T> cur, BinaryTreeNode<T> x) {
		if (x == null || cur == null) {
			return false;
		}
		int compareresult = compare(cur, x);
		if (compareresult == 0) {
			return true;
		} else if (compareresult < 0) {
			return contains(cur.right, x);
		} else {
			return contains(cur.left, x);
		}
	}

删除操作

删除操作需要分情况讨论:

如果是叶子节点,可以直接删除。

如果只有一个孩子的话,就让它的父亲指向它的儿子,然后删除这个节点。

如果有两个孩子的话,一般的删除策略是用右子树最小的数据代替该节点的数据,递归删除那个节点。

代码实现

/**
	 * 删除节点x
	 *
	 * @param x
	 * @return
	 */
	public BinaryTreeNode<T> remove(BinaryTreeNode<T> x) {
		return remove(root, x);
	}

	/**
	 * 删除子树中的节点x
	 *
	 * @param cur
	 * @param x
	 * @return
	 */
	public BinaryTreeNode<T> remove(BinaryTreeNode<T> cur, BinaryTreeNode<T> x) {
		if (cur == null || x == null) {
			return cur;
		}
		int compareresult = compare(cur, x);
		if (compareresult > 0) {
			cur.left = remove(cur.left, x);
		} else if (compareresult < 0) {
			cur.right = remove(cur.right, x);
		} else if (cur.left != null && cur.right != null) {
			cur.right = remove(cur.right, findMin(cur.right));

		} else {
			cur = (cur.left == null) ? cur.right : cur.left;
		}

		return cur;
	}

删除的过程比较复杂,如果删除次数不多,通常使用的策略是惰性删除,即对删除的节点进行标记。特别是有重复项时非常有用,删除只是项的频率减1。暂时不做

代码实现可以看github,地址https://github.com/robertjc/simplealgorithm

github代码也在不断完善中,有些地方可能有问题,还请多指教

欢迎扫描二维码,关注公众账号

时间: 2024-12-07 18:53:05

算法系列(八)数据结构之二叉查找树的相关文章

算法系列——八皇后问题

public class Queen { private final int size; private int[] location; private int[] columnOccupied; private int[] lineOccupied; //对角线 private int[] reverseLineOccupied; //反对角线 private static int solveCount; private static final int locationOccupied =

算法系列笔记5(扩展数据结构-动态顺序统计和区间树)

在编程中,我们往往使用已有的数据结构无法解决问题,这是不必要急着创建新的数据结构,而是在已有数据结构的基础上添加新的字段.本节在上一次笔记红黑树这一基础数据结构上进行扩展,得出两个重要的应用-动态顺序统计和区间树. 动态顺序统计 在算法系列笔记2中我们在线性时间内完成了静态表的顺序统计,而这里我们在红黑树上进行扩展,在O(lgn)时间内完成该操作,主要包括返回第i 排名的元素os_select(i)和给定一个元素x,返回其排名(os_rank(x)). 思想:添加新项:在红黑树的结点上记录下该结

数据结构与算法系列 目录

最近抽空整理了"数据结构和算法"的相关文章.在整理过程中,对于每种数据结构和算法分别给出"C"."C++"和"Java"这三种语言的实现:实现语言虽不同,但原理如出一辙.因此,读者在了解和学习的过程中,择其一即可! 下面是整理数据数据和算法的目录表,对于每一种按照C/C++/Java进行了划分,方便查阅.若文章有错误或纰漏,请不吝指正.谢谢! 数据结构和算法目录表   C C++ Java 线性结构 1. 数组.单链表和双链表

深度解析(一)数据结构与算法系列目录

数据结构与算法系列 目录 最近抽空整理了"数据结构和算法"的相关文章.在整理过程中,对于每种数据结构和算法分别给出"C"."C++"和"Java"这三种语言的实现:实现语言虽不同,但原理如出一辙.因此,读者在了解和学习的过程中,择其一即可! 下面是整理数据数据和算法的目录表,对于每一种按照C/C++/Java进行了划分,方便查阅.若文章有错误或纰漏,请不吝指正.谢谢! 数据结构和算法目录表   C C++ Java 线性结构

【数据结构&amp;&amp;算法系列】KMP算法介绍及实现(c++ &amp;&amp; java)

KMP算法如果理解原理的话,其实很简单. KMP算法简介 这里根据自己的理解简单介绍下. KMP算法的名称由三位发明者(Knuth.Morris.Pratt)的首字母组成,又称字符串查找算法. 个人觉得可以理解为最小回溯算法,即匹配失效的时候,尽量少回溯,从而缩短时间复杂度. KMP算法有两个关键的地方,1)求解next数组,2)利用next数组进行最小回溯. 1)求解next数组 next数组的取值只与模式串有关,next数组用于失配时回溯使用. 在简单版本的KMP算法中,每个位置 j 的 n

数据结构与算法系列四(单链表)

1.引子 1.1.为什么要学习数据结构与算法? 有人说,数据结构与算法,计算机网络,与操作系统都一样,脱离日常开发,除了面试这辈子可能都用不到呀! 有人说,我是做业务开发的,只要熟练API,熟练框架,熟练各种中间件,写的代码不也能“飞”起来吗? 于是问题来了:为什么还要学习数据结构与算法呢? #理由一: 面试的时候,千万不要被数据结构与算法拖了后腿 #理由二: 你真的愿意做一辈子CRUD Boy吗 #理由三: 不想写出开源框架,中间件的工程师,不是好厨子 1.2.如何系统化学习数据结构与算法?

数据结构与算法系列七(队列)

1.引子 1.1.为什么要学习数据结构与算法? 有人说,数据结构与算法,计算机网络,与操作系统都一样,脱离日常开发,除了面试这辈子可能都用不到呀! 有人说,我是做业务开发的,只要熟练API,熟练框架,熟练各种中间件,写的代码不也能“飞”起来吗? 于是问题来了:为什么还要学习数据结构与算法呢? #理由一: 面试的时候,千万不要被数据结构与算法拖了后腿 #理由二: 你真的愿意做一辈子CRUD Boy吗 #理由三: 不想写出开源框架,中间件的工程师,不是好厨子 1.2.如何系统化学习数据结构与算法?

数据结构与算法系列十三(选择排序)

1.引子 1.1.为什么要学习数据结构与算法? 有人说,数据结构与算法,计算机网络,与操作系统都一样,脱离日常开发,除了面试这辈子可能都用不到呀! 有人说,我是做业务开发的,只要熟练API,熟练框架,熟练各种中间件,写的代码不也能“飞”起来吗? 于是问题来了:为什么还要学习数据结构与算法呢? #理由一: 面试的时候,千万不要被数据结构与算法拖了后腿 #理由二: 你真的愿意做一辈子CRUD Boy吗 #理由三: 不想写出开源框架,中间件的工程师,不是好厨子 1.2.如何系统化学习数据结构与算法?

[算法系列之十八]海量数据处理之BitMap

一:简介 所谓的BitMap就是用一个bit位来标记某个元素对应的Value, 而Key即是该元素.由于采用了bit为单位来存储数据,因此在存储空间方面,可以大大节省. 二:基本思想 我们用一个具体的例子来讲解,假设我们要对0-7内的5个元素(4,7,2,5,3)排序(这里假设这些元素没有重复).那么我们就可以采用BitMap的方法来达到排序的目的.要表示8个数,我们就只需要8个bit(1Bytes). (1)首先我们开辟1字节(8bit)的空间,将这些空间的所有bit位都置为0,如下图: (2