本文根据《大话数据结构》一书,实现了Java版的二叉排序树/二叉搜索树

二叉排序树介绍

在上篇博客中,顺序表的插入和删除效率还可以,但查找效率很低;而有序线性表中,可以使用折半、插值、斐波那契等查找方法来实现,但因为要保持有序,其插入和删除操作很耗费时间。

二叉排序树(Binary Sort Tree),又称为二叉搜索树,则可以在高效率的查找下,同时保持插入和删除操作也又较高的效率。下图为典型的二叉排序树。

二叉树寻找父节点java 二叉树查找算法java_二叉树寻找父节点java

二叉查找树具有以下性质:

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

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

  (3) 任意节点的左、右子树也分别为二叉查找树。

 

查找操作

思路:查找值与结点数据对比,根据大小确定往左子树还是右子树进行下一步比较。

采用递归的查找算法

/*
	 * 查找
	 */
	public boolean SearchBST(int key) {
		return SearchBST(key, root);
	}

	private boolean SearchBST(int key, Node node) {
		if (node == null)
			return false;
		if (node.data == key) {
			return true;
		} else if (node.data < key) {
			return SearchBST(key, node.rChild);
		} else {
			return SearchBST(key, node.lChild);
		}
	}

 

采用非递归的查找算法

/*
	 * 查找,非递归
	 */
	public boolean SearchBST2(int key) {
		Node p = root;
		while (p != null) {
			if (p.data > key) {
				p = p.lChild;
			} else if (p.data < key) {
				p = p.rChild;
			} else {
				return true;
			}
		}
		return false;
	}

  

插入操作

思路:与查找类似,但需要一个父节点来进行赋值。

采用非递归的插入算法:

/*
	 * 插入,自己想的,非递归
	 */
	public boolean InsertBST(int key) {
		Node newNode = new Node(key);
		if (root == null) {
			root = newNode;
			return true;
		}
		Node f = null; // 指向父结点
		Node p = root; // 当前结点的指针
		while (p != null) {
			if (p.data > key) {
				f = p;
				p = p.lChild;
			} else if (p.data < key) {
				f = p;
				p = p.rChild;
			} else {
				System.out.println("树中已有相同数据,不再插入!");
				return false;
			}
		}
		if (f.data > key) {
			f.lChild = newNode;
		} else if (f.data < key) {
			f.rChild = newNode;
		}
		return true;
	}

  

采用递归的插入算法:

/*
	 * 插入,参考别人博客,递归
	 * 思路:把null情况排除后用递归,否则无法赋值
	 */
	public boolean InsertBST2(int key) {
		if (root == null) {
			root = new Node(key);
			return true;
		}
		return InsertBST2(key, root);
	}

	private boolean InsertBST2(int key, Node node) {
		if (node.data > key) {
			if (node.lChild == null) {
				node.lChild = new Node(key);
				return true;
			} else {
				return InsertBST2(key, node.lChild);
			}
		} else if (node.data < key) {
			if (node.rChild == null) {
				node.rChild = new Node(key);
				return true;
			} else {
				return InsertBST2(key, node.rChild);
			}
		} else {
			System.out.println("树中已有相同数据,不再插入!");
			return false;
		}
	}

 

新补充:在写【Java】 大话数据结构(12) 查找算法(3) (平衡二叉树(AVL树))这篇博客时,发现以下的插入方法比较好(如果没有要求返回值必须为boolean格式的话):(推荐使用此类方法)

/*
	 * 插入操作
	 */
	public void insert(int key) {
		root = insert(root, key);
	}

	private Node insert(Node node, int key) {
		if (node == null) {
			// System.out.println("插入成功!");
			// 也可以定义一个布尔变量来保存插入成功与否
			return new Node(key);
		}
		if (key == node.data) {
			System.out.println("数据重复,无法插入!");
		} else if (key < node.data) {
			node.lChild=insert(node.lChild, key);
		} else {
			node.rChild=insert(node.rChild, key);
		}
		return node;
	}

  

 

删除操作

思路:

(1)删除叶子结点

  直接删除;

(2)删除仅有左或右子树的结点

  子树移动到删除结点的位置即可;

(3)删除左右子树都有的结点

   找到删除结点p的直接前驱(或直接后驱)s,用s来替换结点p,然后删除结点s,如下图所示。

二叉树寻找父节点java 二叉树查找算法java_二叉树寻找父节点java_02

首先找到删除结点位置及其父结点

/*
	 * 删除操作,先找到删除结点位置及其父结点
	 * 因为需要有父结点,所以暂时没想到递归的方法(除了令Node对象带个parent属性)
	 */
	public boolean deleteBST(int key) {
		if (root == null) {
			System.out.println("空表,删除失败");
			return false;
		}
		Node f = null; // 指向父结点
		Node p = root; // 指向当前结点
		while (p != null) {
			if (p.data > key) {
				f = p;
				p = p.lChild;
			} else if (p.data < key) {
				f = p;
				p = p.rChild;
			} else {
				delete(p, f);
				return true;
			}
		}
		System.out.println("该数据不存在");
		return false;
	}

  

再根据上述思路进行结点p的删除:(需注意删除结点为根节点的情况)

/*
	 * 删除结点P的操作
	 * 必须要有父结点,因为Java无法直接取得变量p的地址(无法使用*p=(*p)->lChild)
	 */
	private void delete(Node p, Node f) {// p为删除结点,f为其父结点
		if (p.lChild == null) { // 左子树为空,重接右子树
			if (p == root) { // 被删除结点为根结点时,无法利用f,该情况不能忽略
				root = root.rChild;
				p = null;
			} else {
				if (f.data > p.data) { // 被删结点为父结点的左结点,下同
					f.lChild = p.rChild;
					p = null; // 释放结点别忘了
				} else {// 被删结点为父结点的右结点,下同
					f.rChild = p.rChild;
					p = null;
				}
			}
		} else if (p.rChild == null) { // 右子树为空,重接左子树
			if (p == root) { // 被删除结点为根结点
				root = root.lChild;
				p = null;
			} else {
				if (f.data > p.data) {
					f.lChild = p.lChild;
					p = null;
				} else {
					f.rChild = p.lChild;
					p = null;
				}
			}
		} else { // 左右子树都不为空,删除位置用前驱结点替代
			Node q, s;
			q = p;
			s = p.lChild;
			while (s.rChild != null) { // 找到待删结点的最大前驱s
				q = s;
				s = s.rChild;
			}
			p.data = s.data; // 改变p的data就OK
			if (q != p) {
				q.rChild = s.lChild;
			} else {
				q.lChild = s.lChild;
			}
			s = null;
		}
	}

  

  

 

完整代码(含测试代码)

package BST;

/**
 * 二叉排序树(二叉查找树)
 * 若是泛型,则要求满足T extends Comparable<T> static问题
 * @author Yongh
 *
 */
class Node {
	int data;
	Node lChild, rChild;

	public Node(int data) {
		this.data = data;
		lChild = null;
		rChild = null;
	}
}

public class BSTree {
	private Node root;

	public BSTree() {
		root = null;
	}

	/*
	 * 查找
	 */
	public boolean SearchBST(int key) {
		return SearchBST(key, root);
	}

	private boolean SearchBST(int key, Node node) {
		if (node == null)
			return false;
		if (node.data == key) {
			return true;
		} else if (node.data < key) {
			return SearchBST(key, node.rChild);
		} else {
			return SearchBST(key, node.lChild);
		}
	}

	/*
	 * 查找,非递归
	 */
	public boolean SearchBST2(int key) {
		Node p = root;
		while (p != null) {
			if (p.data > key) {
				p = p.lChild;
			} else if (p.data < key) {
				p = p.rChild;
			} else {
				return true;
			}
		}
		return false;
	}

	/*
	 * 插入,自己想的,非递归
	 */
	public boolean InsertBST(int key) {
		Node newNode = new Node(key);
		if (root == null) {
			root = newNode;
			return true;
		}
		Node f = null; // 指向父结点
		Node p = root; // 当前结点的指针
		while (p != null) {
			if (p.data > key) {
				f = p;
				p = p.lChild;
			} else if (p.data < key) {
				f = p;
				p = p.rChild;
			} else {
				System.out.println("数据重复,无法插入!");
				return false;
			}
		}
		if (f.data > key) {
			f.lChild = newNode;
		} else if (f.data < key) {
			f.rChild = newNode;
		}
		return true;
	}

	/*
	 * 插入,参考别人博客,递归
	 * 思路:类似查找,
	 *       但若方法中的node为null的话,将无法插入新数据,需排除null的情况
	 */
	public boolean InsertBST2(int key) {
		if (root == null) {
			root = new Node(key);
			return true;
		}
		return InsertBST2(key, root);
	}

	private boolean InsertBST2(int key, Node node) {
		if (node.data > key) {
			if (node.lChild == null) { // 有null的情况下,才有父结点
				node.lChild = new Node(key);
				return true;
			} else {
				return InsertBST2(key, node.lChild);
			}
		} else if (node.data < key) {
			if (node.rChild == null) {
				node.rChild = new Node(key);
				return true;
			} else {
				return InsertBST2(key, node.rChild);
			}
		} else {
			System.out.println("数据重复,无法插入!");
			return false;
		}
	}

	/*
	 * 这样的插入是错误的(node无法真正被赋值)
	 */
	/*
	private boolean InsertBST2(int key, Node node) {
		if(node!=null) {
			if (node.data > key) 
				return InsertBST2(key, node.lChild);
			else if (node.data < key) 
				return InsertBST2(key, node.rChild);
			else 
				return false;//重复
		}else {
			node=new Node(key);
			return true;
		}			
	}
	*/

	/*
	 * 删除操作,先找到删除结点位置及其父结点
	 * 因为需要有父结点,所以暂时没想到递归的方法(除了令Node对象带个parent属性)
	 */
	public boolean deleteBST(int key) {
		if (root == null) {
			System.out.println("空表,删除失败");
			return false;
		}
		Node f = null; // 指向父结点
		Node p = root; // 指向当前结点
		while (p != null) {
			if (p.data > key) {
				f = p;
				p = p.lChild;
			} else if (p.data < key) {
				f = p;
				p = p.rChild;
			} else {
				delete(p, f);
				System.out.println("删除成功!");
				return true;
			}
		}
		System.out.println("该数据不存在");
		return false;
	}

	/*
	 * 删除结点P的操作
	 * 必须要有父结点,因为Java无法直接取得变量p的地址(无法使用*p=(*p)->lChild)
	 */
	private void delete(Node p, Node f) {// p为删除结点,f为其父结点
		if (p.lChild == null) { // 左子树为空,重接右子树
			if (p == root) { // 被删除结点为根结点,该情况不能忽略
				root = root.rChild;
				p = null;
			} else {
				if (f.data > p.data) { // 被删结点为父结点的左结点,下同
					f.lChild = p.rChild;
					p = null; // 释放结点别忘了
				} else {// 被删结点为父结点的右结点,下同
					f.rChild = p.rChild;
					p = null;
				}
			}
		} else if (p.rChild == null) { // 右子树为空,重接左子树
			if (p == root) { // 被删除结点为根结点
				root = root.lChild;
				p = null;
			} else {
				if (f.data > p.data) {
					f.lChild = p.lChild;
					p = null;
				} else {
					f.rChild = p.lChild;
					p = null;
				}
			}
		} else { // 左右子树都不为空,删除位置用前驱结点替代
			Node q, s;
			q = p;
			s = p.lChild;
			while (s.rChild != null) { // 找到待删结点的最大前驱s
				q = s;
				s = s.rChild;
			}
			p.data = s.data; // 改变p的data就OK
			if (q != p) {
				q.rChild = s.lChild;
			} else {
				q.lChild = s.lChild;
			}
			s = null;
		}
	}

	/*
	 * 中序遍历
	 */
	public void inOrder() {
		inOrder(root);
		System.out.println();
	}

	public void inOrder(Node node) {
		if (node == null)
			return;
		inOrder(node.lChild);
		System.out.print(node.data + " ");
		inOrder(node.rChild);
	}

	/*
	 * 测试代码
	 */
	public static void main(String[] args) {
		BSTree aTree = new BSTree();
		BSTree bTree = new BSTree();
		int[] arr = { 62, 88, 58, 47, 35, 73, 51, 99, 37, 93 };
		for (int a : arr) {
			aTree.InsertBST(a);
			bTree.InsertBST2(a);
		}
		aTree.inOrder();
		bTree.inOrder();
		System.out.println(aTree.SearchBST(35));
		System.out.println(bTree.SearchBST2(99));
		aTree.deleteBST(47);
		aTree.inOrder();
	}
}

  

二叉树寻找父节点java 二叉树查找算法java_结点_03

二叉树寻找父节点java 二叉树查找算法java_Java_04

35 37 47 51 58 62 73 88 93 99 
35 37 47 51 58 62 73 88 93 99 
true
true
删除成功!
35 37 51 58 62 73 88 93 99

BSTree

 

 小结(自己编写时的注意点):

  查找:操作简单,注意递归的方法没有循环while (p!=null),而是并列的几个判断;

  插入:非递归时,要有父结点;递归时,要注意排除null的情况;

  删除:记住要分两步,第一步找结点位置时也要把父结点带上;第二步删除结点时,要令p=null,还要注意p==root的情况以及q==p的情况。