1.我的感受這個二叉搜索樹實現的仍是很不錯的,基本操做都涵蓋了
2.在Activity中對view設置監聽函數,能夠動態傳入數據,只要可比較,均可以生成二分搜索樹
3.二分搜索樹的價值:搜索、添加、刪除、更新速度快,最佳狀態複雜度logn,但極端狀況下會退化成單鏈表
4.本例操做演示源碼:但願你能夠和我在Github一同見證:DS4Android的誕生與成長,歡迎starnode
二叉樹特性
1.一個二叉樹必定有且僅有一個根節點
2.一個二叉樹除了數據以外,還有[左子]、[右子]的引用,節點自己稱爲[父]
3.樹形:
|---殘樹:
|---左殘:[左子]爲空,[右子]非空
|---右殘:[右子]爲空,[左子]非空
|---葉:[右子]爲空,[左子]爲空
|---滿樹:[左子]、[右子]非空
4.二叉系:
|---二叉系是自然存在的無限全空二叉樹
|---節點的二叉系座標:(x,y) x:該層的第幾個元素 y:該層層數
5.二叉樹的分類:
|---二分搜索樹:
|---平衡二叉樹:最大樹深-最小樹深<=1
|---徹底二叉樹:按二叉系座標排放元素
|---堆
|---線段樹
複製代碼
二分搜索樹是一種特殊的二叉樹形的數據結構
存儲的數據必須具備可比較性git
特性:對於每一個節點
1.[父]的值都要大於[左子]的值。
2.[父]的值都要小於[右子]的值。
複製代碼
/**
* 做者:張風捷特烈
* 時間:2018/10/7 0007:7:36
* 郵箱:1981462002@qq.com
* 說明:
*/
public class BinarySearchTree<T extends Comparable<T>> {
private Node root;//根節點
private int size;//節點個數
public Node getRoot() {//----!爲方便視圖繪製:暴露此方法
return root;
}
/**
* 獲取節點個數
*
* @return 節點個數
*/
public int size() {
return size;
}
/**
* 二分搜索樹是否爲空
*
* @return 是否爲空
*/
public boolean isEmpty() {
return size == 0;
}
}
複製代碼
/**
* 節點類----!爲方便視圖繪製---private 改成 public
*/
public class Node {
public T el;//儲存的數據元素
public Node left;//左子
public Node right;//右子
public int deep;//!爲方便視圖繪製---增長節點樹深
/**
* 構造函數
*
* @param left 左子
* @param right 右子
* @param el 儲存的數據元素
*/
private Node(Node left, Node right, T el) {
this.el = el;
this.left = left;
this.right = right;
}
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;
}
}
}
複製代碼
感受就像順藤插瓜,一個瓜,兩個叉,比較[待插入瓜]和[當前瓜]的個頭大小
大了放右邊,小了放左邊,直到摸不到瓜了,就把待插入的插上。github
/**
* 添加節點
*
* @param el 節點元素
*/
public void add(T el) {
root = addNode(root, el);
}
/**
* 返回插入新節點後的二分搜索樹的根
*
* @param target 目標節點
* @param el 插入元素
* @return 插入新節點後的二分搜索樹的根
*/
private Node addNode(Node target, T el) {
if (target == null) {
size++;
return new Node(null, null, el);
}
if (el.compareTo(target.el) <= 0) {
target.left = addNode(target.left, el);
target.left.deep = target.deep + 1;//!爲方便視圖繪製---維護deep
} else if (el.compareTo(target.el) > 0) {
target.right = addNode(target.right, el);
target.right.deep = target.deep + 1;//!爲方便視圖繪製---維護deep
}
return target;
}
複製代碼
6, 2, 8, 1, 4, 3
[ 6, 2, 8, 1, 4, 3 ]
複製代碼
插入的形象演示:其中。
表示null
算法
6 6 6 6 6 6
/ \ ---> / \ ---> / \ ---> / \ ---> / \ ---> / \
。 。 2 。 2 8 2 8 2 8 2 8
/ \ / \ / \ / \ / \ / \ / \ / \ / \
。 。 。 。。 。 1 。 。 。 1 4 。 。 1 4 。 。
/ \ / \ / \ / \ / \
。 。 。 。。 。 。 。。 3
複製代碼
5
時的遞歸:searchTree.add(5);
複製代碼
這真是正宗的
順藤摸瓜
,想找最小值,一直往左摸,想找最大值,一直往右摸。編程
/**
* 獲取最小值:暴露的方法
*
* @return 樹的最大元素
*/
public E getMin() {
return getMinNode(root).el;
}
/**
* 獲取最小值所在的節點 :內部方法
*
* @param node 目標節點
* @return 最小值節點
*/
private Node<E> getMinNode(Node<E> node) {
//左子不爲空就一直拿左子,直到左子爲空
if (node.left == null) {
return node;
}
node = getMinNode(node.left);
return node;
}
複製代碼
node.left == null
說明一直再往左找,整個遞歸過程當中node.left = removeMinNode(node.left);
從根節點開始,它們都在等待左側值,直到發現到最左邊了,便將最小值節點的右側節點返回出去
這時前面等待的人接到了最小值的右側,而後最小值被從樹上移除了。canvas
/**
* 從二分搜索樹中刪除最大值所在節點
*
* @return 刪除的元素
*/
public E removeMin() {
E ret = getMin();
root = removeMinNode(root);
return ret;
}
/**
* 刪除掉以node爲根的二分搜索樹中的最小節點 返回刪除節點後新的二分搜索樹
*
* @param node 目標節點
* @return 刪除節點後新的二分搜索樹的根
*/
private Node removeMinNode(Node node) {
if (node.left == null) {
Node rightNode = node.right;
node.right = null;
return rightNode;
}
node.left = removeMinNode(node.left);
return node;
}
複製代碼
原理基本一致,就不畫圖了。數組
/**
* 獲取最大值:暴露的方法
*
* @return 樹的最大元素
*/
public E getMax() {
return getMaxNode(root).el;
}
/**
* 獲取最大值所在的節點:內部方法
*
* @param node 目標節點
* @return 最小值節點
*/
private Node<E> getMaxNode(Node<E> node) {
//右子不爲空就一直拿右子,直到右子爲空
return node.right == null ? node : getMaxNode(node.right);
}
複製代碼
原理基本一致,就不畫圖了。bash
/**
* 從二分搜索樹中刪除最大值所在節點
*
* @return 刪除的元素
*/
public E removeMax() {
E ret = getMax();
root = removeMaxNode(root);
return ret;
}
/**
* 刪除掉以node爲根的二分搜索樹中的最小節點 返回刪除節點後新的二分搜索樹的根
*
* @param node 目標節點
* @return 刪除節點後新的二分搜索樹的根
*/
private Node removeMinNode(Node node) {
if (node.left == null) {
Node rightNode = node.right;
node.right = null;
return rightNode;
}
node.left = removeMinNode(node.left);
return node;
}
複製代碼
想一下一羣西瓜按二分搜索樹排列,怎麼看是否包含10kg的西瓜?
和root西瓜比較:小了就每每左走,由於右邊的都比root大,一下就排除一半,很爽有沒有
而後繼續比較,直到最後也沒有,那就不包含。微信
/**
* 否存在el元素
* @param el 元素
* @return 是否存在
*/
public boolean contains(E el) {
return contains(el, root);
}
/**
* 以root爲根節點的二叉樹是否存在el元素
*
* @param el 待測定元素
* @param node 目標節點
* @return 是否存在el元素
*/
private boolean contains(E el, Node<E> node) {
if (node == null) {
return false;
}
if (el.compareTo(node.el) == 0) {
return true;
}
boolean isSmallThan = el.compareTo(node.el) < 0;
//若是小於,向左側查找
return contains(el, isSmallThan ? node.left : node.right);
}
複製代碼
層序遍歷、前序遍歷、中序遍歷、後序遍歷,聽起來挺嚇人其實就是摸瓜的時候何時記錄一下
這裏是用List裝一下,方便獲取結果,你也能夠用打印來看,不過感受有點low數據結構
代碼基本一致,就是在遍歷左右子時,放到籃子裏的時機不一樣,分爲了前、中、後
前序遍歷:父-->左-->右(如:6父,2左,2爲父而左1,1非父,2右4,4爲父而左3,以此循之)
中序遍歷:左-->父-->右
後序遍歷:左-->右-->父
/**
* 二分搜索樹的前序遍歷(用戶使用)
*/
public void orderPer(List<T> els) {
orderPerNode(root, els);
}
/**
* 二分搜索樹的中序遍歷(用戶使用)
*/
public void orderIn(List<T> els) {
orderNodeIn(root, els);
}
/**
* 二分搜索樹的後序遍歷(用戶使用)
*/
public void orderPost(List<T> els) {
orderNodePost(root, els);
}
/**
* 前序遍歷以target爲根的二分搜索樹
*
* @param target 目標樹根節點
*/
private void orderPerNode(Node target, List<T> els) {
if (target == null) {
return;
}
els.add(target.el);
orderPerNode(target.left, els);
orderPerNode(target.right, els);
}
/**
* 中序遍歷以target爲根的二分搜索樹
*
* @param target 目標樹根節點
*/
private void orderNodeIn(Node target, List<T> els) {
if (target == null) {
return;
}
orderNodeIn(target.left, els);
els.add(target.el);
orderNodeIn(target.right, els);
}
/**
* 後序遍歷以target爲根的二分搜索樹
*
* @param target 目標樹根節點
*/
private void orderNodePost(Node target, List<T> els)
if (target == null) {
return;
}
orderNodePost(target.left, els);
orderNodePost(target.right, els);
els.add(target.el);
}
複製代碼
感受挺有意思的:仍是用個栗子說明吧
6元素先入隊,排在最前面,而後走了登個記(放在list裏),把左右兩個孩子2,8留下了,隊列:8-->2
而後2登個記(放在list裏)走了,把它的孩子1,4放在隊尾,這時候排隊的是:4-->1-->8,集合裏6,2
而後8登個記(放在list裏)走了,它沒有孩子,這時候排隊的是:4-->1,集合裏6,2,8
而後1登個記(放在list裏)走了,它沒有孩子,這時候排隊的是:4,集合裏6,2,8,1
而後4登個記(放在list裏)走了,把它的孩子3,5放在隊尾,這時候排隊的是:5-->3,集合裏6,2,8,1,4
都出隊事後:6,2,8,1,4,3,5-------------一層一層的遍歷完了,是否是很神奇
複製代碼
/**
* 二分搜索樹的層序遍歷,使用隊列實現
*/
public void orderLevel( List<T> els) {
Queue<Node> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
Node cur = queue.remove();
els.add(cur.el);
//節點出隊時將孩子入隊
if (cur.left != null) {
queue.add(cur.left);
}
if (cur.right != null) {
queue.add(cur.right);
}
}
}
複製代碼
移除節點:首先相似包含操做,找一下與傳入元素相同是的節點
刪除的最大難點在於對目標節點孩子的處理,按照樹型可分爲:
RIGHT_NULL:若是目標只有一個左子,能夠按照刪除最小值的思路
LEFT_NULL:只有一個右子,能夠按照刪除最大值的思路
LEAF:若是自己就是葉子節點,就不用考慮那麼多了,愛怎麼刪怎麼刪
FULL:若是左右都有孩子,你總得找個繼承人接班吧,才能走..
複製代碼
2
時:首先2走了,要找到繼承人:這裏用後繼節點,將它右側的樹中的最小節點當作繼承人
//找後繼節點
Node successor = getMinNode(target.right);
successor.right = removeMinNode(target.right);
successor.left = target.left;
target.left = target.right = null;
return successor;
複製代碼
/**
* 移除節點
*
* @param el 節點元素
*/
public void remove(T el) {
root = removeNode(root, el);
}
複製代碼
/**
* 刪除掉以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;
size--;
return rightNode;
case RIGHT_NULL:
Node leftNode = target.left;
target.left = null;
size--;
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;
}
複製代碼
好了,二叉樹的基本操做都講了以遍,下面說說繪圖的核心方法:
/**
* 繪製結構
*
* @param canvas
*/
private void dataView(Canvas canvas) {
if (!mTreeBalls.isEmpty()) {
canvas.save();
canvas.translate(ROOT_X, ROOT_Y);
BinarySearchTree<TreeNode<E>>.Node root = mTreeBalls.getRoot();
canvas.drawCircle(0, 0, NODE_RADIUS, mPaint);
canvas.drawText(root.el.data.toString(), 0, 10, mTxtPaint);
drawNode(canvas, root);
canvas.restore();
}
}
private void drawNode(Canvas canvas, BinarySearchTree<TreeNode<E>>.Node node) {
float thta = (float) ((60 - node.deep * 10) * Math.PI / 180);//父節點與子節點豎直方向夾角
int lineLen = (int) (150 / ((node.deep + .5)));//線長
float offsetX = (float) (NODE_RADIUS * Math.sin(thta));//將起點偏移圓心X,到圓上
float offsetY = (float) (NODE_RADIUS * Math.cos(thta));//將起點偏移圓心X,到圓上
//畫布移動的X
float translateOffsetX = (float) ((lineLen + 2 * NODE_RADIUS) * Math.sin(thta));
//畫布移動的Y
float translateOffsetY = (float) ((lineLen + 2 * NODE_RADIUS) * Math.cos(thta));
float moveX = (float) (lineLen * Math.sin(thta));//線移動的X
float moveY = (float) (lineLen * Math.cos(thta));//線移動的Y
if (node.right != null) {
canvas.save();
canvas.translate(translateOffsetX, translateOffsetY);//每次將畫布移到右子的圓心
canvas.drawCircle(0, 0, NODE_RADIUS, mPaint);//畫圓
mPath.reset();//畫線
mPath.moveTo(-offsetX, -offsetY);
mPath.lineTo(-offsetX, -offsetY);
mPath.rLineTo(-moveX, -moveY);
canvas.drawPath(mPath, mPathPaint);
canvas.drawText(node.right.el.data.toString(), 0, 10, mTxtPaint);//畫字
drawNode(canvas, node.right);
canvas.restore();
}
if (node.left != null) {//同理
canvas.save();
canvas.translate(-translateOffsetX, translateOffsetY);
mPath.reset();
mPath.moveTo(offsetX, -offsetY);
mPath.rLineTo(moveX, -moveY);
canvas.drawPath(mPath, mPathPaint);
canvas.drawCircle(0, 0, NODE_RADIUS, mPaint);
canvas.drawText(node.left.el.data.toString(), 0, 10, mTxtPaint);
drawNode(canvas, node.left);
canvas.restore();
}
}
複製代碼
看得見的數據結構Android版之開篇前言
看得見的數據結構Android版之數組表(數據結構篇)
看得見的數據結構Android版之數組表(視圖篇)
看得見的數據結構Android版之單鏈表篇
看得見的數據結構Android版之雙鏈表篇
看得見的數據結構Android版之棧篇
看得見的數據結構Android版之隊列篇
看得見的數據結構Android版之二分搜索樹篇
更多數據結構---之後再說吧
項目源碼 | 日期 | 備註 |
---|---|---|
V0.1--github | 2018-11-25 | 看得見的數據結構Android版之二分搜索樹結構的實現 |
筆名 | 微信 | 愛好 | |
---|---|---|---|
張風捷特烈 | 1981462002 | zdl1994328 | 語言 |
個人github | 個人簡書 | 個人掘金 | 我的網站 |
1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大編程愛好者共同交流
3----我的能力有限,若有不正之處歡迎你們批評指證,一定虛心改正
4----看到這裏,我在此感謝你的喜歡與支持