Chp6二分搜索树(Binary Search Tree) 一、树结构 使用树结构的原因:
1.树结构是一种天然的组织结构
2.数据使用树结构存储后,查找高效
二叉树:是动态数据结构【不需要在创建数据结构的时候就决定好要存储多少数据,要添加元素就 new 一个新的空间加入到其中即可,删除二、基础概念同理】
节点 Node 中,除了要存放元素 e 之外,还要存储两个指向其他节点的引用 left【左孩子】 和 right 【右孩子】
二叉树要点: 1.二叉树具有唯一的根节点;
2.二叉树中每个节点最多有两个孩子,每个节点最多有一个父亲节点;一个孩子也没有的叫做叶子节点
3.二叉树具有天然的递归结构【每个节点的左子树也是二叉树、每个节点的右子树也是二叉树】
4.二叉树不一定都是“满”的【满二叉树:除叶子节点外,每个节点都有两个孩子】
5.一个节点、甚至 NULL 也是二叉树
二、构建二分搜索树: 二分搜索树的性质: 1.二分搜索树是二叉树
2.二分搜索树的每个节点的值:大于其左子树的所有节点的值且小于其右子树的所有节点的值
3.二分搜索树的每个子树也是二分搜索树
4.存储的元素必须具有可比较性【存储自定义数据类型,必须自定义好数据的比较方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class BST <Key extends Comparable <Key >,Value > { private class Node { private Key key; private Value value; private Node left, right; public Node (Key key,Value value) { this .key = key; this .value = value; left = right = null ; } } private Node root; private int count; public BST () { root = null ; count = 0 ; } public int size () { return count; } public boolean isEmpty () { return count == 0 ; } }
三、向二分搜索树中添加元素 图解步骤
1,要插入元素60,先从根root节点进行比较,由于60比41大,所以要把60插入到41的右子树。
2,而41的右子树存在元素,因此61不能直接插入。于是60与41的右子树58进行比较,由于60比58大,因此要插入到58的右子树
3,而58的右子树没有元素,则直接将60插入58的右子树。
特殊情况(有重复的话,该元素就相当于已经存在于树中,不做任何改变)
如果想要包含重复元素,只需定义:左子树 <= 节点;右子树 >= 节点【只需将 = 关系放到定义中即可】
代码实现:
insert–在以node为根的二分搜索树中插入节点(key,value) 使用递归算法
递归出口:1,当前节点为空;2,插入的key比node节点中的key小,且左子树为空;3、插入的key比Node节点中的key大且右子树为空;4、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public void insert (Key key,Value value) { if (root == null ){ root = new Node(e); count ++; } else insert(root, key ,value); } private void insert (Node node, Key key ,Value value) { if (key.compareTo(node.key)==0 ) node.value = value; return ; else if (key.compareTo(node.key) < 0 && node.left == null ){ node.left = new Node(key,value); count ++; return ; } else if (key.compareTo(node.key) > 0 && node.right == null ){ node.right = new Node(key,value); count ++; return ; } if (key.compareTo(node.key) < 0 ) insert(node.left, key,value); else insert(node.right, key,value); }
精简代码: 递归出口改为一个:即,只要node为null则必须插入新节点。将节点与二叉树挂起来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 private Node insert (Node node,Key key,Value value) { if (node == null ){ count ++; return new Node(key,value); } if (key.compareTo(node.key)==0 ) node.value = value; else if (e.compareTo(node.e) < 0 ) node.left = insert(node.left, e); else node.right = insert(node.right, e); return node; } }
四、 二分搜索树的查询操作 contain–查看二分搜索树是否存在键key 使用递归算法
递归出口:1,传入的node为null,返回false;2,传入的key与node的key相等则找到,返回true
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public boolean contain (Key key) { return contain(root,key); } private boolean contain (Node node,Key key) { if (node == null ) return false ; if (key.compareTo(node.key) == 0 ) return ture; else if (key.compareTo(node.key) < key) return contain(node.left,key); else return contain(node.right,key); }
五、search–二分搜索树中搜索键key所对应的值value。 使用
递归出口:1,当前节点为空,返回null;2,传入的key与当前节点node的key相等,则直接返回node.value
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public Value search (key key) { return search(root,key); } private Value search (Node node,Key key) { if (node == null ) return null ; if (key.compareTo(node.key)==0 ) return node.value; else if (key.compareTo(node.key)<0 ) return search(node.left,key); else return search(node.right,key); }
六、遍历二叉搜索树
前序遍历 只要node非null。首先打印当前node自己的key值;再通过递归函数遍历node的左子节点;最后通过递归函数遍历node的右子节点。
1 2 3 4 5 6 7 8 9 10 11 public void preOrder () { preorder(root); } private void preOrder (Node node) { if (node !=null ){ System.out.println(node.key); preOrder(node.left); preOrder(node.right); } }
中序遍历(遍历后是从小到大排好序的) 只要node非null。首先用递归函数遍历node的左子节点;再打印当前node的key值;最后用递归函数遍历node的右子节点。
1 2 3 4 5 6 7 8 9 10 public void inOrder () { inOrder(root); } private void inOrder (Node node) { if (node != null ){ inOrder(node.left); System.out.println(node.key); inOrder(node.right); } }
后续遍历 只要node非null。首先用递归函数遍历node的左子节点;再用递归函数遍历node的右子节点;最后打印当前node的key值。
1 2 3 4 5 6 7 8 public void postOrder () { postOrder(); } private void postOrder (Node node) { postOrder(node.left); postOrder(node.right); System.out.println(node.key); }
七、二分搜索树的层序遍历
思路: 需要借助队列实现。先把根节点root入队。进行循环条件是队列不为空,队首元素出列,打印该节点node的key值,随后该节点的左子节点和右子节点入队。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public void levelOrder () { ListedList<Node> q = new LinkedList<Node>(); q.add(root); while (! q.isEmpty()){ Node node = q.remove(); System.out.println(node.key); if (node.left != null ) q.add(node.left); if (node.right != null ) q.add(node.right); } }
八、寻找最小和最大的key的节点 思路 :定义递归函数。递归出口:若该节点左(右)子节点为空,则直接返回其自身;其左(右)子节点不为空,则递归调用其左(右)子节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public Key minimum () { assert count != 0 ; Node minNode = minimum( root ); return minNode.key; } private Node minimum (Node node) { if ( node.left == null ) return node; return minimum(node.left); } public Key maximum () { assert count != 0 ; Node maxNode = maximum(root); return maxNode.key; } private Node maximum (Node node) { if ( node.right == null ) return node; return maximum(node.right); }
九、删除最小和最大key的节点 思路: 定义递归函数。递归出口:当前节点node的左孩子为空(即证明当前节点为最小节点),用当前节点的右子节点node.right,代替当前节点返回给当前节点的母节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public void removeMin () { if ( root != null ) root = removeMin( root ); } public void removeMax () { if ( root != null ) root = removeMax( root ); } private Node removeMin (Node node) { if ( node.left == null ){ Node rightNode = node.right; node.right = null ; count --; return rightNode; } node.left = removeMin(node.left); return node; } private Node removeMax (Node node) { if ( node.right == null ){ Node leftNode = node.left; node.left = null ; count --; return leftNode; } node.right = removeMax(node.right); return node; }
十、删除二分搜索树中的任意节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 public void remove (Key key) { root = remove(root, key); } private Node remove (Node node, Key key) { if ( node == null ) return null ; if ( key.compareTo(node.key) < 0 ){ node.left = remove( node.left , key ); return node; } else if ( key.compareTo(node.key) > 0 ){ node.right = remove( node.right, key ); return node; } else { if ( node.left == null ){ Node rightNode = node.right; node.right = null ; count --; return rightNode; } if ( node.right == null ){ Node leftNode = node.left; node.left = null ; count--; return leftNode; } Node successor = new Node(minimum(node.right)); count ++; successor.right = removeMin(node.right); successor.left = node.left; node.left = node.right = null ; count --; return successor; } }
十一、测试二分搜索树 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public static void main (String[] args) { int N = 1000000 ; Integer[] arr = new Integer[N]; for (int i = 0 ; i < N ; i ++) arr[i] = new Integer(i); for (int i = 0 ; i < N ; i ++){ int pos = (int ) (Math.random() * (i+1 )); Integer t = arr[pos]; arr[pos] = arr[i]; arr[i] = t; } BST<Integer,String> bst = new BST<Integer,String>(); for (int i = 0 ; i < N ; i ++) bst.insert(new Integer(arr[i]), Integer.toString(arr[i])); for (int i = 0 ; i < 2 *N ; i ++){ String res = bst.search(new Integer(i)); if ( i < N ) assert res.equals(Integer.toString(i)); else assert res == null ; } }
注意事项: 由于我们实现的二分搜索树不是平衡二叉树,所以如果按照顺序插入一组数据,我们的二分搜索树会退化成为一个链表。