zoukankan      html  css  js  c++  java
  • D12-Android自定义控件之--二分搜索树

    Android自定义控件和二分搜索树貌似八竿子打不着啊,最近在看数据结构,感觉还好,但是就是有点枯燥
    咱也是会玩安卓的人,搞一个View模拟一下二分搜索树呗,寓学于乐。
    绘图部分使用我的LogicCanvas库,使用详见Github:
    当然你也可以使用安卓原生的canvas绘制,这都不是重点,思路最重要。
    本项目源码在此,点击查看

    功能:
    1.将数据以二分搜索树的树状结构展现
    2.数据添加操作,此处上滑添加随机元素
    3.数据移除操作,此处下滑移除随机元素
    4.不止支持数字,也支持泛型(T extends Comparable<T>)

    效果:
    9414344-46b7a7a311c1cefe.png
    二分搜索字符串.png
    9414344-f85423447272e819.png
    数字二叉树.png

    TreeView

    1.成员变量
        /**
         * 数据
         */
        private List<T> mData = new ArrayList<>();
        /**
         * 根节点
         */
        private Node root = new Node(null, v2(0, 0));
    
        /**
         * 绘画者
         */
        private Painter mPainter;
    
        /**
         * 度量标尺=网格宽度=小球直径 也决定文字大小、连线长度
         */
        private int STEP = 50;
    

    2.先看一下节点类

    比起常规的二分搜索树,为了方便绘制,增加pos变量,记录当前节点坐标
    有一个很头疼的问题就是如果节点距离都相同,那么第三层开始就会出现点盖住点的情况
    所以打算维护一个节点的当前深度来让深层的连线变短,为变相获取当前节点的深度,维护father变量

      /////////////////////////////////Node节点类
        private class Node {
            public T el;
            public Node right;
            public Node left;
    
            //为了确定节点的位置,增加了pos变量:即点位
            public Pos pos;
            //为了确定节点所在的层级来决定每层现场,增加了father变量
            public Node father;
    
            /**
             * 构造函数
             *
             * @param left  左子
             * @param right 右子
             * @param el    储存的数据元素
             */
            public Node(Node left, Node right, T el, Pos pos) {
                this.el = el;
                this.left = left;
                this.right = right;
                this.pos = pos.dotC(STEP);
            }
    
            public Node(T el, Pos pos) {
                this(null, null, el, pos);
            }
    
            /**
             * 获取当前节点所在层数
             *
             * @return 当前节点所在层数
             */
            public int getDeep() {
                int deep = 0;
                Node node = this;
                while (node.father != null) {
                    node = node.father;
                    deep++;
                }
                return deep;
            }
    
            /**
             * 树的类型
             *
             * @return 树的类型
             */
            public NodeType getType() {
                if (this.right == null) {
                    if (this.left == null) {
                        return NodeType.LEAF;
                    } else {
                        return NodeType.RIGHT_NULL;
                    }
                }
                if (this.left == null) {
                    return NodeType.LEFT_NULL;
                } else {
                    return NodeType.FULL;
                }
            }
        }
    
        /**
         * 节点类型
         */
        enum NodeType {
            FULL,//左右非空
            LEAF,//左右皆空
            RIGHT_NULL,//右空左非空
            LEFT_NULL;//左空右非空
        }
    

    3.添加节点的方法

    float len = STEP * 5 / ((target.getDeep() + 1));是根据节点深度控制与子节点的连线长度
    这个计算方法也许不是太好,如果你能给出更好的方式,欢迎讨论

        /**
         * 返回插入新节点后的二分搜索树的根
         *
         * @param target 目标节点
         * @param el     插入元素
         * @return 插入新节点后的二分搜索树的根
         */
        private Node addNode(Node target, T el) {
            if (target == null) {
                return new Node(null, null, el, v2(0, 0));
            }
            //根据节点深度控制与子节点的连线长度
            float len = STEP * 5 / ((target.getDeep() + 1));
            
            if (el.compareTo(target.el) < 0) {
                target.left = addNode(target.left, el);
                //维护目标节点左节点坐标,减去len
                target.left.pos = target.pos.minus(v2(len, len));
                //维护父亲节点
                target.left.father = target;
            } else if (el.compareTo(target.el) > 0) {
                target.right = addNode(target.right, el);
                //维护目标节点左节点坐标,X加len,Y减去len,
                target.right.pos = target.pos.add(v2(len, -len));
                //维护父亲节点
                target.right.father = target;
            }
            return target;
        }
    
    4.删除节点
    /**
         * 删除掉以target为根的二分搜索树中值为e的节点, 递归算法 返回删除节点后新的二分搜索树的根
         *
         * @param target 
         * @param el
         * @return
         */
        private Node removeNode(Node target, T el) {
            if (target == null) {
                return null;
            }
    
            if (el.compareTo(target.el) < 0) {
                target.left = removeNode(target.left, el);
            } else if (el.compareTo(target.el) > 0) {
                target.right = removeNode(target.right, el);
                return target;
            } else {//相等时
                switch (target.getType()) {
                    case LEFT_NULL://左残--
                    case LEAF:
                        Node rightNode = target.right;
                        target.right = null;
                        return rightNode;
                    case RIGHT_NULL:
                        Node leftNode = target.left;
                        target.left = null;
                        return leftNode;
                    case FULL:
                        //找后继节点
                        Node successor = getMinNode(target.right);
                        successor.right = removeMinNode(target.right);
                        successor.left = target.left;
                        target.left = target.right = null;
                        return successor;
                }
            }
            return target;
        }
    

    4.绘制
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //绘制网格
            CanvasUtils.drawGrid(getContext(), STEP, canvas);
            //获取绘图者
            mPainter = PainterEnum.INSTANCE.getInstance(canvas);
            //将数据添加入节点
            if (mData != null) {
                for (T data : mData) {
                    root.el = mData.get(0);
                    root = addNode(root, data);
                }
                //绘制以node为根的所有节点
                drawNode(root);
            }
        }
    

    这里使用后续遍历时绘制

    /**
         * 绘制以node为根的所有节点
         *
         * @param node
         */
        public void drawNode(Node node) {
    
            if (node == null) {
                return;
            }
            drawNode(node.left);
            drawNode(node.right);
    
            //坐标系取X中间
            Pos theCoo = v2(STEP * (mWinSize.x / STEP / 2 + 1), STEP / 2);
            //sa绘制弧形命令--360度,直径STEP,偏移node.pos,颜色黑色,坐标系原点mcoo
            mPainter.draw(
                    sa.ang(360).r(STEP / 2)
                            .p(node.pos).fs(Color.BLACK).coo(theCoo));
    
            //绘制文字--根据节点位置确定文字位置
            Pos txtPos = node.pos.add(STEP * (mWinSize.x / STEP / 2 + 1), -STEP / 2 - STEP / 5);
            //st绘制文字命令
            mPainter.drawText(
                    st.str(node.el + "").size(STEP / 3 * 2).fs(Color.WHITE)
                            .p(txtPos.refY()));
    
            //如果有右节点,画右线
            if (node.right != null) {
                //node.pos是节点的中心坐标,这里做了一些偏移,不会压到字
                Pos startPos = node.pos.add(STEP / 4, -STEP / 4);
                //右子的坐标
                Pos endPos = node.right.pos.add(-STEP / 4, STEP / 4);
                //sl绘制直线命令
                mPainter.draw(
                        sl.ps(startPos, endPos)
                                .coo(theCoo).b(3).ss(Color.BLACK));
            }
    
            //如果有左节点,画左线--同上
            if (node.left != null) {
                mPainter.draw(
                        sl.ps(node.pos.add(-STEP / 4, -STEP / 4), node.left.pos.add(STEP / 4, STEP / 4))
                                .coo(theCoo).b(3).ss(Color.BLACK));
            }
    
        }
    
    

    暴漏的方法

        /**
         * 添加节点
         *
         * @param el 节点元素
         */
        public void add(T el) {
            mData.add(el);
        }
    
        /**
         * 移除节点
         *
         * @param el 节点元素
         */
        public void remove(T el) {
            mData.remove(el);
            root = removeNode(root, el);
        }
    

    Activity中测试:

    静态显示测试
    val treeView = TreeView<Int>(this)
    val data = ArrayList<Int>()
    data.add(200)
    data.add(265)
    data.add(855)
    data.add(67)
    data.add(15)
    data.add(48)
    data.add(12)
    data.add(585)
    data.add(45)
    treeView.setData(data)
    
    动态添加、删除测试
    treeView.setOnEventListener(object : BaseView.OnEventListener {
        override fun down(pos: Pos) {
        }
        override fun up(pos: Pos, speed: BaseView.MoveSpeed, orientation: BaseView.Orientation) {
            when (orientation) {
                BaseView.Orientation.TOP -> {
                    val rangeInt = ZRandom.rangeInt(2, 400)
                    treeView.add(rangeInt)
                    treeView.invalidate()
                }
                BaseView.Orientation.BOTTOM -> {
                    val el = treeView.data.get(ZRandom.rangeInt(1, treeView.data.size - 1))
                    treeView.remove(el)
                    treeView.invalidate()
                }
            }
        }
        override fun move(pos: Pos, s: Double, dy: Float, dx: Float, dir: Double, orientation: BaseView.Orientation) {
        }
    })
    

    字符串测试
            val treeView = TreeView<String>(this)
            val data = ArrayList<String>()
            data.add("f")
            data.add("c")
            data.add("a")
            data.add("g")
            data.add("z")
            data.add("d")
            data.add("o")
            data.add("w")
            data.add("g")
            data.add("q")
            treeView.setData(data)
    
            val treeView = TreeView<String>(this)
            val data = ArrayList<String>()
            data.add("赵")
            data.add("钱")
            data.add("孙")
            data.add("李")
            data.add("周")
            data.add("吴")
            data.add("郑")
            data.add("王")
            data.add("冯")
            data.add("陈")
            data.add("楚")
            data.add("卫")
            treeView.setData(data)
    
    9414344-46b7a7a311c1cefe.png
    二分搜索字符串.png

    就酱紫 本项目源码在此,点击查看


    后记、

    1.声明:

    [1]本文由张风捷特烈原创,转载请注明
    [2]欢迎广大编程爱好者共同交流
    [3]个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
    [4]你的喜欢与支持将是我最大的动力

    2.连接传送门:

    更多安卓技术欢迎访问:安卓技术栈
    我的github地址:欢迎star
    张风捷特烈个人网站,编程笔记请访问:http://www.toly1994.com

    3.联系我

    QQ:1981462002
    邮箱:1981462002@qq.com
    微信:zdl1994328

    4.欢迎关注我的微信公众号,最新精彩文章,及时送达:
    9414344-c474349cd3bd4b82.jpg
    公众号.jpg
  • 相关阅读:
    Windows CA 证书服务器
    IE7的web标准之道
    mysql 密码重置
    IIS 与 Apache共存
    php 正则表达式
    sql 记录
    [ Python ] 迭代对象、迭代器、生成器
    [ Python ] 文件的读写操作
    [ Python ] 匿名函数和高阶函数
    [ Python ] set集合及函数的使用
  • 原文地址:https://www.cnblogs.com/toly-top/p/9781861.html
Copyright © 2011-2022 走看看