D12-Android自定義控制元件之--二分搜尋樹
Android自定義控制元件和二分搜尋樹貌似八竿子打不著啊,最近在看資料結構,感覺還好,但是就是有點枯燥
咱也是會玩安卓的人,搞一個View模擬一下二分搜尋樹唄,寓學於樂。
繪圖部分使用我的LogicCanvas庫, ofollow,noindex">使用詳見Github:
當然你也可以使用安卓原生的canvas繪製,這都不是重點,思路最重要。
本專案原始碼在此,點選檢視功能:
1.將資料以二分搜尋樹的樹狀結構展現
2.資料新增操作,此處上滑新增隨機元素
3.資料移除操作,此處下滑移除隨機元素
4.不止支援數字,也支援泛型(T extends Comparable<T>)
效果:

二分搜尋字串.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)

二分搜尋字串.png
就醬紫 本專案原始碼在此,點選檢視
後記、
1.宣告:
[1]本文由張風捷特烈原創,轉載請註明
[2]歡迎廣大程式設計愛好者共同交流
[3]個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
[4]你的喜歡與支援將是我最大的動力
2.連線傳送門:
更多安卓技術歡迎訪問:安卓技術棧 我的github地址:歡迎star 張風捷特烈個人網站,程式設計筆記請訪問: http://www.toly1994.com
3.聯絡我
QQ:1981462002
微信:zdl1994328
4.歡迎關注我的微信公眾號,最新精彩文章,及時送達:

公眾號.jpg