二叉樹、平衡二叉樹原理及例項(一)
阿新 • • 發佈:2019-02-06
最近閒來無事,研究了一下二叉樹。怪了,非平衡二叉樹,兩三個小時就搞定了生成方法,以及幾個相關的小方法。但是到了平衡二叉樹,愣是把我折磨的兩天,都卡在左旋轉和右旋轉那裡了。不過因禍得福啊,兩天後,正在為了旋轉抓頭撓腮的我,靈光一閃,半個小時就把旋轉那一塊完成了。畢竟折磨了兩天多,不能讓成果浪費,現在分享出來。
樹的概念
樹是一種資料結構,是由多個節點物件按照一定順序組成的資料結構。
如上圖,
1、節點A是根節點。
2、節點B是節點A的左子節點。
3、節點C是節點A的右子節點。
4、節點A是節點B和節點C的父節點。
另外,根據上圖這個滿二叉樹,還可以看出二叉樹的規律還有:
1、每個節點最多有2個子節點。
2、每個節點最多隻有1個父節點(根節點沒有父節點)。
3、每層的最大節點數為:(2^(L-1))個(L代表第幾層)。
4、一個二叉樹的最大節點數為:(2^T - 1)(T代表最大層數)
樹的作用
要說樹的作用,還是要用上圖的滿二叉樹來舉例。
圖中共有15個節點物件,如果放在集合List(陣列)中,那麼我要隨機查詢一個物件(14節點),需要從list(0)開始,逐個遍歷list中的物件,那麼最大遍歷次數是15;如果按照上圖滿二叉樹存放,那麼最大遍歷次數是4(順序是:根節點1→右子節點2→右子節點7→左子節點14。如何確定順序,在後面的平衡二叉樹中會講解)。
由此可見,樹結構用來查詢,效率很高。而且用來新增、刪除操作,也很簡單,只要找到對應的節點,修改子節點或者父節點即可。
如下圖的刪除邏輯:如果要刪除B節點,只要將A節點的左子節點指向D,將D節點的父節點指向A即可。
下面給出二叉樹的生成邏輯。
MyNode節點物件:
/** * 節點物件類 * @author 劉 * @date 2018年8月5日 */ public class MyNode implements Serializable{ private static final long serialVersionUID = 1L; public MyNode() { } public MyNode(int value) { this.value = value; } private int value;//根據value值對節點進行排序 private MyNode leftNode;//左子節點 private MyNode rightNode;//右子節點 private MyNode parentNode;//父節點 public int getValue() { return value; } public void setValue(int value) { this.value = value; } public MyNode getLeftNode() { return leftNode; } public void setLeftNode(MyNode leftNode) { this.leftNode = leftNode; } public MyNode getRightNode() { return rightNode; } public void setRightNode(MyNode rightNode) { this.rightNode = rightNode; } public MyNode getParentNode() { return parentNode; } public void setParentNode(MyNode parentNode) { this.parentNode = parentNode; } }
BTreeDemo.java
/**
* 二叉樹demo
* @author 劉
* @date 2018年8月5日
*/
public class BTreeDemo {
public int maxDepth = 0;
public MyNode[] inits = {new MyNode(6),new MyNode(7),new MyNode(4),new MyNode(2),new MyNode(5),new MyNode(1)};
public static void main(String[] args) {
BTreeDemo demo = new BTreeDemo();
MyNode node = demo.sorting2Tree();
demo.printNode(node);
}
/**
* 獲取一個二叉樹的深度
* @author 劉
* @author 2018年8月5日
* @parameter
* @return
*/
public int getTreeDepth(MyNode node){
int depth = 0;//定義二叉樹的深度
if(node == null){
return 0;
}
List<MyNode> list = new ArrayList<MyNode>();
List<MyNode> tmpList = new ArrayList<MyNode>();
list.add(node);
//如果第L層有節點,則depth++,繼續獲取L+1層的所有子節點
while(true){
if(list == null || list.size() <= 0){
break;
}
depth++;
for(int j = 0; j < list.size(); j++){
if(list.get(j).getLeftNode() != null){
tmpList.add(list.get(j).getLeftNode());
}
if(list.get(j).getRightNode() != null){
tmpList.add(list.get(j).getRightNode());
}
}
list.clear();
list.addAll(tmpList);
tmpList.clear();
}
return depth;
}
/**
* 獲取二叉樹某一層有效節點數
* @param node 二叉樹
* @param level 第幾層
* @return List<MyNode>
*/
public List<MyNode> getLevelNodeList(MyNode node, int level){
List<MyNode> list = new ArrayList<MyNode>();
if(level <= 0 || node == null){
return list;
}
list.add(node);
List<MyNode> tmpList = new ArrayList<MyNode>();
//因為層數是從1開始計算,所以這裡需要將level-1。
for(int i = 0; i < level - 1; i++){
for(int j = 0; j < list.size(); j++){
if(list.get(j).getLeftNode() != null){
tmpList.add(list.get(j).getLeftNode());
}
if(list.get(j).getRightNode() != null){
tmpList.add(list.get(j).getRightNode());
}
}
list.clear();
list.addAll(tmpList);
tmpList.clear();
}
return list;
}
/**
* 將目標二叉樹結構以value值的形式打印出來,不存在節點用#代替,列印的結果放到txt檔案,然後改成csv,會看到二叉樹的結構,如下
* @param node 目標二叉樹
* ,,,,,,,6,,,,,,,
,,,4,,,,,,,,7,,,
,2,,,,5,,,,#,,,,#,
1,,3,,#,,#,,#,,#,,#,,#
*/
public void printNode(MyNode node){
StringBuffer sb = new StringBuffer();
int maxDepth = getTreeDepth(node);
if(maxDepth > 0){
for(int i = 0; i < maxDepth; i++){
//i層第一個節點的左邊/最後一個節點的右邊需要幾個逗號分隔符
int maxJ = (int)Math.pow(2, maxDepth - i - 1) - 1;
for(int j = 0; j < maxJ; j++){
sb.append(",");
}
//i層的最大節點數
int levelCount = (int)Math.pow(2, i);
//i層相鄰兩個節點中間的間隔數
int gapCount = (int)(Math.pow(2, maxDepth - i) - 1);
for(int j = 0; j < levelCount; j++){
sb.append(getNodeValue(node, i + 1, j + 1, maxDepth));
if(levelCount > 1 && j < levelCount - 1){
for(int k = 0; k <= gapCount; k++){
sb.append(",");
}
}
}
for(int j = 0; j < maxJ; j++){
sb.append(",");
}
sb.append("\n");
}
System.out.println(sb.toString());
}
}
/**
* 將一組MyNode排序成二叉樹(非平衡)
* @return
*/
public MyNode sorting2Tree(){
MyNode node = inits[0];
for(int i = 1; i < inits.length; i++){
interateCreateNode(node,inits[i]);
}
return node;
}
/**
* 遞迴方法生成有序二叉樹(非平衡)
* @param node 節點
* @param newNode 新節點
*/
public void interateCreateNode(MyNode node, MyNode newNode){
/**
* 如果新節點的value<=原節點的value值:
* ①:如果原節點的左子節點不為空,則用原節點的【左子節】點和【newNode】進行比較(遞迴呼叫本方法)
* ②:如果原節點的左子節點為空,則將newNode設定為原節點的左子節點
*/
if(newNode.getValue() <= node.getValue()){
if(node.getLeftNode() != null){
interateCreateNode(node.getLeftNode(), newNode);
}else{
newNode.setParentNode(node);
node.setLeftNode(newNode);
}
}else{
//原理同上
if(node.getRightNode() != null){
interateCreateNode(node.getRightNode(), newNode);
}else{
newNode.setParentNode(node);
node.setRightNode(newNode);
}
}
}
/**
* 根據層數和序號,獲取二叉樹對應節點的value值
* @param node 二叉樹
* @param level 層數(根節點level=1)
* @param index 序號(從左到右,從1開始)
* @return 對應節點的value值,如果不存在該節點,則返回#
*/
public String getNodeValue(MyNode node, int level, int index, int maxDepth){
if(maxDepth == 0){
return "請先計算深度";
}
if(level > maxDepth){
return "超出最大層數!";
}
if(index < 1 || index > Math.pow(2, level - 1)){
return "超出序號範圍!";
}
if(node == null){
return "樹不能為空!";
}
return this.interateGetNodeValue(node, level, index);
}
/**
* 根據層數和序號,獲取二叉樹對應節點的value值
* @param node 二叉樹
* @param level 層數(根節點level=1)
* @param index 序號(從左到右,從1開始)
* @return 對應節點的value值,如果不存在該節點,則返回#
*/
private String interateGetNodeValue(MyNode node, int level, int index){
//如果只有一層,則直接返回node的value值
if(level == 1){
return String.valueOf(node == null ? "#" : node.getValue());
}
//如果是取第二層的節點,則判斷是否為空後,直接返回value值。
if(level == 2){
if(index == 1){
return String.valueOf(node.getLeftNode() == null ? "#" : node.getLeftNode().getValue());
}else{
return String.valueOf(node.getRightNode() == null ? "#" : node.getRightNode().getValue());
}
}
/**
* 當層數>=3時,需要迭代執行本方法,將層數執行到第二層,用level=2返回value值
* ①:先判斷目標節點在二叉樹的左邊還是右邊(以根節點位置為豎軸,判斷目標節點在豎軸的左邊還是右邊)
* ②:如果在左邊,則將【根節點的左子節點,level-1,newIndex】座位引數,呼叫本方法。
*/
//目標層的最大節點數
int levelCount = (int)(Math.pow(2, level - 1));
//level-1後,新的序號(從左到右,從1開始)
int newIndex = 0;
//level-1後,新的根節點
MyNode newNode = null;
if(index > levelCount / 2){
newIndex = index - levelCount / 2;
newNode = node.getRightNode();
}else{
newIndex = index;
newNode = node.getLeftNode();
}
if(newNode == null){
return "#";
}
return interateGetNodeValue(newNode, level - 1, newIndex);
}
}
節點新增的步驟如下圖: