zoukankan      html  css  js  c++  java
  • 【Java】 大话数据结构(11) 查找算法(2)(二叉排序树/二叉搜索树)

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

    二叉排序树介绍

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

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

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

      (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,如下图所示。

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

    	/*
    	 * 删除操作,先找到删除结点位置及其父结点
    	 * 因为需要有父结点,所以暂时没想到递归的方法(除了令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();
    	}
    }
    

      

    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的情况。

  • 相关阅读:
    tomcat启动startup.bat一闪而过
    shell简介
    hbase总结,值得一看
    hive的 安装和配置
    存储器管理
    银行家算法
    洛谷 2590 树的统计
    树链剖分 洛谷 3384
    2.3最大公约数与最小公倍数
    2.2 素数与合数
  • 原文地址:https://www.cnblogs.com/yongh/p/9240852.html
Copyright © 2011-2022 走看看