博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【算法面试】二叉搜索树
阅读量:3556 次
发布时间:2019-05-20

本文共 8700 字,大约阅读时间需要 29 分钟。

每一个内心仰望理想的人,都在低头干活

 摘要

顾名思义,二叉搜索树是由两个孩子节点组成的树状的数据结构,由于其特殊的性质,任意一个节点的左子树的每个节点总比这个节点小,右子树的每个节点总比这个节点大,所以二叉搜索树的查询性能比较好。

本文只讲解二叉搜索树,二叉平衡树不是本文重点

 正文

不得不承认,递归思想在二叉树中展现的淋漓尽致,本文讲解的二叉搜索树主要操作如下:

  • 插入节点

  • 先序遍历

  • 中序遍历

  • 后续遍历

  • 层序遍历

  • 求最小节点

  • 求最大节点

  • 搜索

  • 求子节点的父节点

  • 求最大深度

  • 求树的节点个数

  • 求叶子节点个数

  • 返回深度从1-deepth的节点总个数

  • 返回深度为deepth的节点个数

  • 返回第level层节点个数

  • 返回中序遍历的后继节点

  • 删除节点

其中,删除节点的操作最为复杂,大家一定要多理解。 

节点类型 

class Node{    //值域    public int value;    //左孩子    public Node left;    //右孩子    public Node right;    public Node(int value){        this.value = value;        this.left = null;        this.right = null;    }}

插入节点

根据二叉搜索树的性质,如果node.value<root.value,则向左递归,否则向右递归,所以代码如下

 

public void insertNode(Node root,Node node){    if(root == null){        root = new Node(node.value);        return;   }    Node x = root;    Node p = x; //表示node要插入p后    while(x != null){        p = x;        if(node.value < x.value){            x = x.left; //向左        }else if(node.value > x.value){            x = x.right;    //向右        }else{            return;        }    }    if(node.value < p.value){        p.left = new Node(node.value);    }else{        p.right = new Node(node.value);    }}

前序、中序、后续遍历

先序:根-->左-->右

中序:左-->根-->右

后续:左-->右-->根

/** * 先序遍历 * @param node */public void preOrderTraval(Node node){    if(node == null)        return;    System.out.print(node.value+",");    preOrderTraval(node.left);    preOrderTraval(node.right);}/** * 中序遍历 * @param root */public void inOrderTravel(Node root){    if(root == null)        return;    inOrderTravel(root.left);    System.out.print(root.value+",");    inOrderTravel(root.right);}/** * 后序遍历 * @param root */public void postOrderTraval(Node root){    if(root == null)        return;    postOrderTraval(root.left);    postOrderTraval(root.right);    System.out.print(root.value+",");}

另外说一点:中序遍历可以从小到大输出所有节点,这个性质要牢记。

层序遍历 

层序遍历,与先中后续遍历思想不同,层序遍历利用了队列这个数据结构,思路如下:

  1. 将根节点入队

  2. 一直循环,直到队列为空结束循环

  3. 对头节点出队

  4. 将对头节点的左右孩子分别入队

  5. 返回第2步

代码如下:

/** * 层序遍历 * @param root */public void levelOrderTraval(Node root){    if(root == null)        return;    Queue
queue = new LinkedList<>(); queue.offer(root); while(!queue.isEmpty()){ Node head = queue.poll(); System.out.print(head.value+","); Node left = head.left; Node right = head.right; if(left != null) queue.offer(left); if(right != null) queue.offer(right); }}

求最小、最大节点

由二叉搜索树的性质可知,最小节点是树中最左下角的那个节点,最大节点是树中最右下角的那个节点 

/** * 返回最左(小)节点 * @param node * @return */public Node getMin(Node node){    if(node == null)        return null;    if(node.left==null)        return node;    return getMin(node.left);}/** * 返回最右(大)节点 * @param node * @return */public Node getMax(Node node){    if(node == null)        return null;    if(node.right==null)        return node;    return getMax(node.right);}

 搜索某个节点,返回节点指针

当然用到了递归的性质,如果node.value<root.value,向左递归,否则向右递归

/** * 搜索 * @param root * @param node * @return */public Node search(Node root,Node node){    if(root==null || node==null)        return null;    if(node.value < root.value){        return search(root.left,node);    }else if(node.value > root.value){        return search(root.right,node);    }else{        return root;    }}

 获取父节点

/** * 获取父节点 * @param root * @param node * @return */public Node getParent(Node root,Node node){    if(root==null || node==null)        return null;    if((root.left!=null && root.left.value==node.value)||(root.right!=null && root.right.value==node.value))        return root;    if(node.value

 获取树节点个数和叶子节点个数

/** * 获取树的节点个数 * @param root * @return */public int getTreeNodeCount(Node root){    if(root == null)        return 0;    int leftTreeNodeCount = getTreeNodeCount(root.left);    int rightTreeNodeCount = getTreeNodeCount(root.right);    return leftTreeNodeCount+rightTreeNodeCount+1;}/** * 获取树叶子节点个数 * @param root * @return */public int getTreeLeafNodeCount(Node root){    if(root == null)        return 0;    //该节点是叶子节点    if(root.left==null && root.right==null)        return 1;    return getTreeLeafNodeCount(root.left)+getTreeLeafNodeCount(root.right);}

 获取树的最大深度

这里用图说明一下,树的深度和层数之间的关系

 

/** * 获得树深度 * @param root * @return */public int getMaxDeepth(Node root){    if(root==null)        return 0;    int leftDeepth = getMaxDeepth(root.left)+1;    int rightDeepth = getMaxDeepth(root.right)+1;    return leftDeepth>rightDeepth?leftDeepth:rightDeepth;}

获取深度从1-deepth的节点的总个数

/** * 返回深度从1到deepth的节点总个数 * @param root * @param deepth * @return */public int getDeepthAllNodeCount(Node root, int deepth){    if(deepth <= 0 || root==null)        return 0;    return getDeepthAllNodeCount(root.left,deepth-1)+ getDeepthAllNodeCount(root.right,deepth-1)+1;}

 获取深度为deepth的节点个数

/** * 返回深度为deepth的节点个数 * @param root * @param deepth * @return */public int getDeepthNodeCount(Node root, int deepth){    if(deepth <= 0 || root==null)        return 0;    //这就是要求的那一行    if(deepth == 1){        return 1;    }    return getDeepthNodeCount(root.left,deepth-1)+ getDeepthNodeCount(root.right,deepth-1);}

 返回第level层节点的个数

/** * 返回第level层节点个数 * @param root * @param level * @return */public int getLevelNodeCount(Node root,int level){    //找出深度deepth与层level之间的关系    int maxDeepth = getMaxDeepth(root);    int k = maxDeepth - level;    return getDeepthNodeCount(root,k);}

 返回中序遍历时node节点的后继节点

这一块比较难理解,大家需要完全熟悉中序遍历

/** * 返回中序遍历时node的后继节点 * @param root * @param node * @return */public Node getNext(Node root,Node node){    //判断node节点是否存在    Node exit = search(root,node);    if(exit==null)        return null;    //如果node有右孩子,那么node的后继节点一定是getMin(node.right);    if(exit.right!=null)        return getMin(exit.right);    //如果node是叶子节点,且是父节点的右孩子,那么一直沿着父节点向上找    Node parent = getParent(root,exit);    while(parent!=null && parent.right!=null && parent.right.value==exit.value){        exit = parent;        parent = getParent(root,parent);    }    return parent;}

 好了,二叉搜索树最难的操作到了,删除节点

删除节点

删除的逻辑是这样的

  1. 如果该节点没有孩子节点,那么直接删除该节点,并修改其父节点,使父节点的左右孩子都指向null

  2. 如果该节点只有一个孩子,那么将该节点提升为父节点,当然这里需要判断一下该节点是父节点的左孩子还是右孩子

  3. 如果该节点node有两个孩子,那么找出node的后继节点nextNode,并把nextNode提升为node节点,如果nextNode有右孩子,并把nextNode.right提升为nextNode父节点的左孩子

 

/** * 删除某一节点 * @param root * @param node */public Node deleteNode(Node root,Node node){    Node exit = search(root,node);    //没有这个节点    if(exit == null)        return root;    //是叶子节点    if(exit.left==null && exit.right==null){        Node parent = getParent(root,exit);        if(parent.left!=null && parent.left.value==exit.value){            parent.left = null;        }else{            parent.right = null;        }        return root;    }    //有孩子节点    Node parent = getParent(root,node);    if(parent == null){ //如果是根节点        Node next = getNext(root,root);        if(next == null){            root = root.left;            root.right = null;            return root;        }        next.left = root.left;        Node nextParent = getParent(root,next);        if(nextParent != root){            nextParent.left = next.right;            next.right = root.right;        }        //返回根节点指针        return next;    }    //如果要删除的节点是parent的左孩子    if(parent.left!=null && parent.left.value == exit.value){        if(exit.left!=null && exit.right==null){    //只有左孩子            parent.left = exit.left;        }else if(exit.right!=null &&exit.left==null){   //只有右孩子            parent.left = exit.right;        }else if(exit.left!=null && exit.right!=null){  //有左右孩子            Node next = getNext(root,exit);            if(next!=null){                next.left = exit.left;                Node nextParent = getParent(root,next);                if(exit != nextParent){                    nextParent.left = next.right;                    next.right = exit.right;                }                parent.left = next;            }        }    }else{  //如果要删除的节点是parent的右孩子        if(exit.left!=null && exit.right==null){    //只有左孩子            parent.right = exit.left;        }else if(exit.right!=null &&exit.left==null){   //只有右孩子            parent.right = exit.right;        }else if(exit.left!=null && exit.right!=null){  //有左右孩子            Node next = getNext(root,exit);            if(next!=null) {                next.left = exit.left;                Node nextParent = getParent(root, next);                if (exit != nextParent) {                    nextParent.left = next.right;                    next.right = exit.right;                }                parent.right = next;            }        }    }    exit = null;    return root;}

 删除节点的操作是最复杂的,希望大家画图理解

测试

public static void main(String[] args) {    int arr[] = {50,40,30,45,20,35,43,47,80,70,60,75,90,85,100,55,65,57,53};    BinaryTree binaryTree = new BinaryTree();    Node root = new Node(arr[0]);    for(int i = 1;i

 结束

好了,二叉搜索树基本操作就到这里,如果大家有什么疑问可以加笔者微信或者关注笔者公众号,获取更多后端技术干货。

你可能感兴趣的文章
【问题记录】pytorch自定义数据集 No such file or directory, invalid index of a 0-dim
查看>>
【笔记】spring的注解回顾,springboot-restful项目结构介绍 springboot-freemarker ⼯程配置详解
查看>>
ubuntu上训练yolov3: Caught ValueError in DataLoader worker process 0. string indices must be integers.
查看>>
在集群服务器进行自定义数据集训练记录过程 TensorBoard logging requires TensorBoard with Python summary writer installed.
查看>>
【问题记录】raise IndexError(‘index {} is out of range‘.format(idx)) index 0 is out of range
查看>>
【问题记录】filters = int(module_def[‘filters‘]) ValueError: invalid literal for int() with base 10: ‘‘
查看>>
Document.visibilityState 页面监听 vue中实现离开页面时计时停止: 停止计时后从上一次开始计时
查看>>
【项目实战】vue-springboot-pytorch前后端结合pytorch深度学习 html打开本地摄像头 监控人脸和记录时间
查看>>
vue-springboot项目 mybatis条件查询结果为null时解决方案 @Param @RequestParam 的参数传递
查看>>
【问题记录】解决npm 报错This dependency was not found: A complete log of this run can be found in:
查看>>
【ubuntu】ubuntu18.04:在处理时有错误发生:ufw E: Sub-process /usr/bin/dpkg returned an error code (1)
查看>>
【问题记录】服务器部署项目时启动tomcat后报错 HTTP 错误 404.0- Not Found 您要找的资源已被删除、已更名或暂时不可用 解决方案···
查看>>
【算法练习】 天平( UVa 839) 输入一个树状天平,根据力矩相等原则判断是否平衡 和 小球下落(UVa 679)判断最后一个小球会落到哪里
查看>>
【算法学习笔记】图(三)利用广度优先搜索 求不带权无向图从顶点u到v的最短路径/求距离u最远的顶点
查看>>
【算法学习笔记】 图(四)用优先级队列优化Dijkstra算法求最短路径(邻接矩阵存储)
查看>>
【笔记】docker核心概念和使用 docker命令
查看>>
【学习笔记】spring cloud和微服务(一)介绍
查看>>
【笔记】python爬虫实战1 lxml模块 xPath语法 实例:爬取豆瓣网站
查看>>
【笔记】整合Druid数据源 Druid监控以及属性绑定配置 在浏览器输入端口进入监控页面查看
查看>>
【笔记】springboot+spring security登录流程实现
查看>>