Android自定义控件和二分搜索树貌似八竿子打不着啊,最近在看数据结构,感觉还好,但是就是有点枯燥
咱也是会玩安卓的人,搞一个View模拟一下二分搜索树呗,寓学于乐。
绘图部分使用我的LogicCanvas库,使用详见Github:
当然你也可以使用安卓原生的canvas绘制,这都不是重点,思路最重要。
本项目源码在此,点击查看
功能:
1.将数据以二分搜索树的树状结构展现
2.数据添加操作,此处上滑添加随机元素
3.数据移除操作,此处下滑移除随机元素
4.不止支持数字,也支持泛型(T extends Comparable)
/**
* 数据
*/
private List mData = new ArrayList<>();
/**
* 根节点
*/
private Node root = new Node(null, v2(0, 0));
/**
* 绘画者
*/
private Painter mPainter;
/**
* 度量标尺=网格宽度=小球直径 也决定文字大小、连线长度
*/
private int STEP = 50;
比起常规的二分搜索树,为了方便绘制,增加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;//左空右非空
}
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;
}
/**
* 删除掉以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;
}
@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);
}
val treeView = TreeView(this)
val data = ArrayList()
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(this)
val data = ArrayList()
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(this)
val data = ArrayList()
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)
就酱紫 本项目源码在此,点击查看
[1]本文由张风捷特烈原创,转载请注明
[2]欢迎广大编程爱好者共同交流
[3]个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正
[4]你的喜欢与支持将是我最大的动力
更多安卓技术欢迎访问:安卓技术栈
我的github地址:欢迎star
张风捷特烈个人网站,编程笔记请访问:http://www.toly1994.com
QQ:1981462002
邮箱:[email protected]
微信:zdl1994328