zoukankan      html  css  js  c++  java
  • Java与算法之(13)

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. int key = 15;  
    2. int[] datas = new int[] { 8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15 };  
    3. for(int i = 0; i < datas.length; i++) {  
    4.     if(datas[i] == key) {  
    5.         System.out.println("找到了, 共查找" + (i + 1) + "次");  
    6.         break;  
    7.     }  
    8. }  
    但是查找效率并不稳定,如果查找8,只需要比较一次,查找15则需要比较15次。如果数组扩大到1亿个数,而查找的数字恰好排在最后,查找则变得非常低效。

    更好的查找方式是使用二叉搜索树,首先用数组构建二叉树,如下图:

    二叉树的相关基础知识可参考:Java与算法之(7) - 完全二叉树

    注意上面这棵二叉树的特点,每个左子节点的值都比父节点小,每个右子节点的值都比父节点大。满足这个条件的二叉树称为二叉搜索树(Binary Search Tree),也叫二叉排序树(Binary Sorting Tree)。

    根据这个特性,可以得出查找的规律。以查找15为例,从根节点8开始比较,因15>8,(如果存在)则一定在8的右子树内;与右子树12比较,因15>12,(如果存在)则一定在12的右子树内;与14比较。。。与15比较,找到目标。

    如果查找5,则依次比较8、4、6、5。

    在这棵树中,查找任何一个数字,最多需要比较4次(约log2(15))。介绍查找方法之前,先看如何构建这棵树。

    1 插入节点

    用数据描述这棵树,首选需要描述节点。用一个类来表示,每个节点包括本身的值及左右两个子节点的指针。

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. private static class Node {  
    2.     Node leftChild;  
    3.     Node rightChild;  
    4.     int data;  
    5.   
    6.     public Node(int data) {  
    7.         this.data = data;  
    8.     }  
    9. }  
    树由节点组成,一个一个节点加进去,树叶逐渐变得枝繁叶茂。构建树的过程可以分解成不断重复的插入节点行为。

    第一个加入的节点做为根节点,以后加入节点的操作和前面所述的查询过程一样,从根开始比较,如果小于则和左子节点比较,如果大于则和右子节点比较,不断重复这个过程直到到达叶子节点。比叶子节点小则做为叶子节点的左子节点,大则做为右子节点。

    构建过程如下:

    这个过程的逻辑是一直向下寻找,直到没有子节点为止。整个过程适合用递归的方式,主要代码如下:

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. public void add(int key) {  
    2.     if(root == null) {  
    3.         root = new Node(key);  
    4.         return;  
    5.     }  
    6.     addNode(root, new Node(key));  
    7. }  
    8.   
    9. private void addNode(Node parent, Node child) {  
    10.     if(child.data == parent.data) {  
    11.         return;  
    12.     }  
    13.     if(child.data < parent.data) {  
    14.         if(parent.leftChild != null) {  
    15.             addNode(parent.leftChild, child);  
    16.         } else {  
    17.             parent.leftChild = child;  
    18.         }  
    19.     } else {  
    20.         if(parent.rightChild != null) {  
    21.             addNode(parent.rightChild, child);  
    22.         } else {  
    23.             parent.rightChild = child;  
    24.         }  
    25.     }  
    26. }  

    2 查找节点

    查找一个节点和插入一个节点的流程很相似,但是结果相反。插入节点是一直向下寻找,找到则插入失败,找不到则做为叶子节点加入树中。查找节点是一直向下寻找,找到则成功返回,找不到则查找失败。

    代码如下:

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. public void search(int key) {  
    2.     this.steps = 0;  
    3.     Node node = searchNode(root, key);  
    4.     if(node == null) {  
    5.         System.out.println("共查找" + this.steps + "次, 未找到" + key);  
    6.     } else {  
    7.         System.out.println("共查找" + this.steps + "次, 搜索到" + key);  
    8.     }  
    9. }  
    10.   
    11. private Node searchNode(Node from, int key) {  
    12.     this.steps++;  
    13.     if(from == null || key == from.data) {  
    14.         return from;  
    15.     } else if(key > from.data) {  
    16.         return searchNode(from.rightChild, key);  
    17.     } else {  
    18.         return searchNode(from.leftChild, key);  
    19.     }  
    20. }  

    3 删除节点

    在二叉搜索树中删除一个节点后,需要调整二叉树的结构,使其仍然保持二叉搜索树的特点。以被删除节点拥有子节点的情况,分三种情况考虑。见下图:

    • 15节点左右子节点都没有,删除时直接把父节点14的右子节点设置为null即可
    • 2节点没有右子节点,删除时需要把左子树连接回树中,即把4的左子节点指向1;6节点没有左子节点,删除时需要把右子树连接回书中,即把4的右子节点指向7
    • 8节点同时拥有左右子节点,删除规则是先找到右子节点即12,然后递归12节点的左子节点,直到叶子节点,这张图中将找到9。设置8节点的值为9,并删除9节点。

    按这个规则推导其他数字删除的步骤:

    删除4,先找到6,6没有左子节点,查找结束,将4节点的值设置为6,按规则2删除6节点。

    删除12,先找到14,递归左子节点找到13,设置12节点的值为13,删除13。

    二叉搜索数完整代码如下:

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. public class BinarySearchTree {  
    2.   
    3.     private Node root;  
    4.     private int steps;  
    5.   
    6.     /** 
    7.      * 插入节点 
    8.      * @param key 
    9.      */  
    10.     public void add(int key) {  
    11.         if(root == null) {  
    12.             root = new Node(key);  
    13.             return;  
    14.         }  
    15.         addNode(root, new Node(key));  
    16.     }  
    17.   
    18.     private void addNode(Node parent, Node child) {  
    19.         if(child.data == parent.data) {  
    20.             return;  
    21.         }  
    22.         if(child.data < parent.data) {  
    23.             if(parent.leftChild != null) {  
    24.                 addNode(parent.leftChild, child);  
    25.             } else {  
    26.                 parent.leftChild = child;  
    27.             }  
    28.         } else {  
    29.             if(parent.rightChild != null) {  
    30.                 addNode(parent.rightChild, child);  
    31.             } else {  
    32.                 parent.rightChild = child;  
    33.             }  
    34.         }  
    35.     }  
    36.   
    37.     /** 
    38.      * 查找节点 
    39.      * @param key 
    40.      */  
    41.     public void search(int key) {  
    42.         this.steps = 0;  
    43.         Node node = searchNode(root, key);  
    44.         if(node == null) {  
    45.             System.out.println("共查找" + this.steps + "次, 未找到" + key);  
    46.         } else {  
    47.             System.out.println("共查找" + this.steps + "次, 搜索到" + key);  
    48.         }  
    49.     }  
    50.   
    51.     private Node searchNode(Node from, int key) {  
    52.         this.steps++;  
    53.         if(from == null || key == from.data) {  
    54.             return from;  
    55.         } else if(key > from.data) {  
    56.             return searchNode(from.rightChild, key);  
    57.         } else {  
    58.             return searchNode(from.leftChild, key);  
    59.         }  
    60.     }  
    61.   
    62.     /** 
    63.      * 删除节点 
    64.      * @param key 
    65.      */  
    66.     public void delete(int key) {  
    67.         Node child = root;  
    68.         Node parent = child;  
    69.         boolean isLeftChild = true;  
    70.         while(child != null) {  
    71.             if(child.data == key) {  
    72.                 deleteNode(parent, child, isLeftChild);  
    73.                 child = null;  
    74.             } else if(key < child.data) {  
    75.                 isLeftChild = true;  
    76.                 parent = child;  
    77.                 child = child.leftChild;  
    78.             } else {  
    79.                 isLeftChild = false;  
    80.                 parent = child;  
    81.                 child = child.rightChild;  
    82.             }  
    83.         }  
    84.     }  
    85.   
    86.     private void deleteNode(Node parent, Node child, boolean isLeftChild) {  
    87.         if(child.leftChild == null && child.rightChild == null) {  
    88.             if(isLeftChild) {  
    89.                 parent.leftChild = null;  
    90.             } else {  
    91.                 parent.rightChild = null;  
    92.             }  
    93.         } else if(child.leftChild == null) {  
    94.             if(isLeftChild) {  
    95.                 parent.leftChild = child.rightChild;  
    96.             } else {  
    97.                 parent.rightChild = child.rightChild;  
    98.             }  
    99.         } else if(child.rightChild == null) {  
    100.             if(isLeftChild) {  
    101.                 parent.leftChild = child.leftChild;  
    102.             } else {  
    103.                 parent.rightChild = child.leftChild;  
    104.             }  
    105.         } else {  
    106.             Node leaf = child.rightChild;  
    107.             parent = child;  
    108.             while(leaf.leftChild != null) {  
    109.                 parent = leaf;  
    110.                 leaf = leaf.leftChild;  
    111.             }  
    112.             child.data = leaf.data;  
    113.             if(parent != child)  
    114.                 parent.leftChild = leaf.leftChild;  
    115.             else  
    116.                 parent.rightChild = leaf.rightChild;  
    117.         }  
    118.     }  
    119.   
    120.     /** 
    121.      * 中序遍历二叉搜索树, 结果是从小到大排列的 
    122.      * @param node 
    123.      */  
    124.     public void inOrder(Node node) {  
    125.         if(node == null) {  
    126.             return;  
    127.         }  
    128.         inOrder(node.leftChild);  
    129.         System.out.print(node.data + " ");  
    130.         inOrder(node.rightChild);  
    131.     }  
    132.   
    133.     private static class Node {  
    134.         Node leftChild;  
    135.         Node rightChild;  
    136.         int data;  
    137.   
    138.         public Node(int data) {  
    139.             this.data = data;  
    140.         }  
    141.     }  
    142.   
    143.     public static void main(String[] args) {  
    144.         int[] datas = new int[] { 8, 4, 12, 2, 6, 10, 14, 1, 3, 5, 7, 9, 11, 13, 15 };  
    145.         BinarySearchTree bsTree = new BinarySearchTree();  
    146.         for(int i = 0; i < datas.length; i++) {  
    147.             bsTree.add(datas[i]);  
    148.         }  
    149.         System.out.print("中序遍历");  
    150.         bsTree.inOrder(bsTree.root);  
    151.         System.out.println();  
    152.   
    153.         bsTree.search(8);  
    154.         bsTree.search(12);  
    155.         bsTree.search(15);  
    156.   
    157.         System.out.println("删除节点8");  
    158.         bsTree.delete(8);  
    159.   
    160.         System.out.print("中序遍历");  
    161.         bsTree.inOrder(bsTree.root);  
    162.         System.out.println();  
    163.   
    164.         bsTree.search(8);  
    165.     }  
    166. }  

    运行结果:

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. 中序遍历10 11 12 13 14 15   
    2. 共查找1次, 搜索到8  
    3. 共查找2次, 搜索到12  
    4. 共查找4次, 搜索到15  
    5. 删除节点8  
    6. 中序遍历10 11 12 13 14 15   
    7. 共查找5次, 未找到8  
    本例中的二叉树结构是一种理想情况,如果对数组{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}使用上面的方法构建二叉搜索树,动手画一下就可以发现最终得到的仍然是一个链表,查找15需要比较15次。

    这棵树根的左右严重失衡,左侧一个子节点都没有,而右侧的深度为15。为了保证查找的效率,需要对这棵树做优化,让整棵树保持一定的平衡,这就是下一篇的主角:平衡二叉搜索树。

  • 相关阅读:
    转】Apache解决高并发和高可用
    Kafka学习(一)配置及简单命令使用
    unity3d教程动态创建简单平面地形
    LeetCode: Unique Binary Search Trees [095]
    德惠也有星巴克
    一个css和js结合的下拉菜单,支持主流浏览器
    【图像处理】人类视觉成像原理
    windows使用nginx+memcached实现负载均衡和session或者缓存共享
    OpenCV基础篇之画图及RNG随机数对象
    在阿里云上布置git server
  • 原文地址:https://www.cnblogs.com/sa-dan/p/6837109.html
Copyright © 2011-2022 走看看