Javascript之數據結構與算法的自平衡二叉搜索樹(AVL)實現

Javascript之數據結構與算法的自平衡二叉搜索樹(AVL)實現

簡介

AVL樹是一種自平衡樹。添加或移除節點時, AVL樹會嘗試自平衡。任意一個節點(不論深
度)的左子樹和右子樹高度最多相差1。添加或移除節點時, AVL樹會儘可能嘗試轉換爲完全樹。
平衡因子是在AVL樹中,需要對每個節點計算右子樹高度( hr)和左子樹高度( hl)的差值,該值( hr- hl)應爲0、1或-1。如果結果不是這三個值之一,則需要平衡該AVL樹。
計算平衡因子:

this[heightNode]=function(node){
    if(node==null){
        return -1;
    }else{
        return Math.max(this[heightNode](node.left),this[heightNode](node.right))+1;
    }
}

AVL旋轉是向AVL樹插入節點時,可以執行單旋轉或雙旋轉兩種平衡操作,分別對應四種場景:

  • 右-右( RR):向左的單旋轉
  • 左-左( LL):向右的單旋轉
  • 左-右( LR):向右的雙旋轉
  • 右-左( RL):向左的雙旋轉

RR:

this[rotationRR]=function(node){//當插入的節點在右右節點
    let tmp=node.right;
    node.right=tmp.left;
    tmp.left=node;
    return tmp;
}

LL:

this[rotationLL]=function(node){//當插入的節點在左左節點
   let tmp=node.left;
    node.left=tmp.right;
    tmp.right=node;
    return tmp;
}

LR:

this[rotationLR]=function(node){//當插入的節點在左右節點
   node.left=this[rotationRR](node.left);//向左單旋轉
    return this[rotationLL](node);//再向右單旋轉
}

RL:

this[rotationRL]=function(node){//當插入的節點在右左節點
   node.right=this[rotationLL](node.right);//向右單旋轉
    return this[rotationRR](node);//再向左單旋轉
}

代碼實現

let insertNode=Symbol();//僞私有函數
let inOrderTraverseNode=Symbol();//中序遍歷
let preOrderTraverseNode=Symbol();//先序遍歷
let postOrderTraverseNode=Symbol();//後序遍歷
let minNode=Symbol();//最小值節點
let maxNode=Symbol();//最大值節點
let searchNode=Symbol();//搜索特定的值
let removeNode=Symbol();//移除一個節點
let findMinNode=Symbol();//搜索某個節點的最小值

let heightNode=Symbol();//計算節點高度
let rotationRR=Symbol();//向左單旋轉
let rotationLL=Symbol();//向右單旋轉
let rotationLR=Symbol();//向右雙旋轉
let rotationRL=Symbol();//向左雙旋轉
class Node{
    constructor(key){
        this.key=key;
        this.left=null;
        this.right=null;
    }
}
class AVLTree{//自平衡二叉樹
    constructor(){
        this.root=null;
        this[insertNode]=function(node,newNode){
            if(newNode.key<node.key){
                if(node.left==null){
                    node.left=newNode;
                }else{
                    node.left=this[insertNode](node.left,newNode);
                    if(node.left!=null){//判斷加入到左支後是否平衡
                        //確認是否需要平衡
                        if((this[heightNode](node.left)-this[heightNode](node.right))>1){
                            //旋轉
                            if(newNode.key<node.left.key){
                                node=this[rotationLL](node);
                            }else{
                                node=this[rotationLR](node);
                            }
                        }
                    }
                }
            }else{
                if(node.right==null){
                    node.right=newNode;
                }else{
                    node.right=this[insertNode](node.right,newNode);
                    if(node.right!=null){
                        //確認是否需要平衡
                        if((this[heightNode](node.right)-this[heightNode](node.left))>1){
                            //旋轉
                            if(newNode.key>node.right.key){
                                node=this[rotationRR](node);
                            }else{
                                node=this[rotationRL](node);
                            }
                        }
                    }
                }
            }
            return node;
        };
        this[heightNode]=function(node){
            if(node==null){
                return -1;
            }else{
                return Math.max(this[heightNode](node.left),this[heightNode](node.right))+1;
            }
        }
        //中序遍歷是一種以上行順序訪問BST所有節點的遍歷方式,也就是以從最小到最大的順序訪問所有節點。中序遍歷的一種應用就是對樹進行排序操作。
        this[inOrderTraverseNode]=function(node,callback){
            if(node!=null){
                this[inOrderTraverseNode](node.left,callback);//左
                callback(node.key);//根
                this[inOrderTraverseNode](node.right,callback);//右
            }
        };
        //先序遍歷是以優先於後代節點的順序訪問每個節點的。先序遍歷的一種應用是打印一個結構化的文檔
        this[preOrderTraverseNode]=function(node,callback){
            if(node!=null){
                callback(node.key);//根
                this[inOrderTraverseNode](node.left,callback);//左
                this[inOrderTraverseNode](node.right,callback);//右

            }
        }
        //後序遍歷則是先訪問節點的後代節點,再訪問節點本身。後序遍歷的一種應用是計算一個目錄和它的子目錄中所有文件所佔空間的大小。
        this[postOrderTraverseNode]=function(node,callback){
            if(node!=null){
                this[inOrderTraverseNode](node.left,callback);//左
                this[inOrderTraverseNode](node.right,callback);//右
                callback(node.key);//根
            }
        }
        //搜索最小值
        this[minNode]=function(node){
            if(node){
                while(node&&node.left!=null){
                    node=node.left;
                }
                return node.key;
            }
            return null;
        }
        //搜索最大值
        this[maxNode]=function(node){
            if(node){
                while(node&&node.right!=null){
                    node=node.right;
                }
                return node.key;
            }
            return null;
        }
        //搜索特定值
        this[searchNode]=function(node,key){
            if(node==null){
                return false;
            }
            if(key<node.key){
                return this[searchNode](node.left,key);
            }else if(key>node.key){
                return this[searchNode](node.right,key);
            }else{
                return true;
            }
        }
        //移除一個節點
        this[removeNode]=function(node,key){
            if(node==null){
                return null;
            }
            if(key<node.key){
                node.left=this[removeNode](node.left,key);
                return node;
            }else if(key>node.key){
                node.right=this[removeNode](node.right,key);
                return node;
            }else{
                if(node.left==null&&node.right==null){//無子節點
                    node=null;
                    return node;
                }
                if(node.left==null){//只有右子節點
                    node=node.right;//將原右子節點替換被刪除根節點,即刪除掉根節點
                    return node;
                }else if(node.right==null){
                    node=node.left;
                    return node;
                }else{//同時擁有兩個子節點
                    let aux=this[findMinNode](node.right);//找到被刪除節點的右支最小的值
                    node.key=aux.key;//右支最小值替換被刪除的值
                    node.right=this[removeNode](node.right,aux.key);//刪除原右支最小值的節點
                    return node;
                }
                
            }
        }
        this[findMinNode]=function(node){
            while(node&&node.left!=null){
                node=node.left;
            }
            return node;
        }
        this[rotationRR]=function(node){//當插入的節點在右右節點
            let tmp=node.right;
            node.right=tmp.left;
            tmp.left=node;
            return tmp;
        }
        this[rotationLL]=function(node){//當插入的節點在左左節點
            let tmp=node.left;
            node.left=tmp.right;
            tmp.right=node;
            return tmp;
        }
        this[rotationLR]=function(node){//當插入的節點在左右節點
            node.left=this[rotationRR](node.left);//向左單旋轉
            return this[rotationLL](node);//再向右單旋轉
        }
        this[rotationRL]=function(node){//當插入的節點在右左節點
            node.right=this[rotationLL](node.right);//向右單旋轉
            return this[rotationRR](node);//再向左單旋轉
        }
    }
    insert(key){
        let newNode=new Node(key);
        if(this.root==null){//樹爲空
            this.root=newNode;
        }else{//樹不爲空
            this[insertNode](this.root,newNode);
        }
    }
    inOrderTraverse(callback){
        this[inOrderTraverseNode](this.root,callback);
    }
    preOrderTraverse(callback){
        this[preOrderTraverseNode](this.root,callback);
    }
    postOrderTraverse(callback){
        this[postOrderTraverseNode](this.root,callback);
    }
    min(){
        return this[minNode](this.root);
    }
    max(){
        return this[maxNode](this.root);
    }
    search(key){
        return this[searchNode](this.root,key); 
    }
    remove(key){
        this.root=this[removeNode](this.root,key)
    }

}

let tree=new AVLTree();
tree.insert(7);
tree.insert(15);
tree.insert(5);
tree.insert(3);
tree.insert(9);
tree.insert(8);
tree.insert(10);
tree.insert(13);
tree.insert(12);
tree.insert(14);
tree.insert(20);
tree.insert(18);
tree.insert(25);
tree.insert(6);
tree.inOrderTraverse(function(value){
    console.log("inOrderTraverse:",value);
})
tree.preOrderTraverse(function(value){
    console.log("preOrderTraverse:",value);
})
tree.postOrderTraverse(function(value){
    console.log("postOrderTraverse:",value);
})
console.log(tree.min());
console.log(tree.max());
console.log(tree.search(8))//true
tree.remove(8)
console.log(tree.search(8))//false

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章