javascript資料結構與演算法-- 二叉樹

龍恩0707發表於2015-03-23

javascript資料結構與演算法-- 二叉樹

  樹是電腦科學中經常用到的一種資料結構。樹是一種非線性的資料結構,以分成的方式儲存資料,樹被用來儲存具有層級關係的資料,比如檔案系統的檔案,樹還被用來儲存有序列表。我們要研究的是二叉樹,在二叉樹上查詢元素非常快,為二叉樹新增元素或者刪除元素,也是非常快的。

樹的基本結構示意圖如下:

我們現在最主要的是要來學習二叉樹,二叉樹是一種特殊的樹,它的特徵是 子節點個數不超過2個。如下圖就是二叉樹的基本結構示意圖如下:

二叉樹是一種特殊的樹,相對較少的值儲存在左節點上,較大的值儲存在右節點中。這一特性使得查詢的效率非常高,對於數值型和非數值型的資料,比如單詞和字串都是一樣。

下面我們來學習插入節點的操作吧!

1.   二叉樹是由節點組成的,所以我們需要定義一個物件node,可以儲存資料,也可以儲存其他節點的連結(left 和 right),show()方法用來顯示儲存在節點中的資料。Node程式碼如下:

 function Node(data,left,right) {
    this.data = data;
    this.left = left;
    this.right = right;
    this.show = show;
}

插入節點分析如下:

程式碼如下:

function Node(data,left,right) {
    this.data = data;
    this.left = left;
    this.right = right;
    this.show = show;
}
function show() {
    return this.data;
}
function BST() {
    this.root = null;
    this.insert = insert;
    this.inOrder = inOrder;
}
function insert(data) {
    var n = new Node(data,null,null);
    if(this.root == null) {
        this.root = n;
    }else {
        var current = this.root;
        var parent;
        while(current) {
            parent = current;
            if(data <  current.data) {
                current = current.left;
                if(current == null) {
                    parent.left = n;
                    break;
                }
            }else {
                current = current.right;
                if(current == null) {
                    parent.right = n;
                    break;
                }
            }
        }
    }
}
初始程式碼如下:
var nums = new BST();
nums.insert(23);
nums.insert(45);
nums.insert(16);
nums.insert(37);
nums.insert(3);
nums.insert(99);
nums.insert(22);

示意圖如下:

1. 執行insert(23)時候,由於根節點== null 所以 根節點為23.

2. 執行insert(45)的時候,根節點不等於null,因此進入while語句;由於45 > 大於根節點23 所以就進入else語句,當前current的值如下圖:

當執行 current = current.right; 這句程式碼時候,當前current值變為null了,然後進行if判斷程式碼如下:

if(current == null) {
         parent.right = n;
         break;

}

所以45為根節點的右節點了。跳出迴圈語句;

3. 執行insert(16)的時候,根節點不等於null,因此進入while語句,由於16 < 小於根節點23,所以就進入if語句,那麼當前的current值如下:

當執行到 current = current.left; 的時候,current的值就變為null,所以接著往下執行程式碼:

if(current == null) {
    parent.left = n;
    break;
}

就把當前的節點16插入到根節點的左節點上。

4. 接著執行 insert(37) 的時候,根節點不等於null,因此進入else語句中的while語句,由於37 大於根節點23,所以就進入while語句中的else語句,當前的current值為:

當執行current = current.right;這句程式碼的時候,那麼當前current = 45的那個節點(如上圖所示);當再執行下面的程式碼:

if(current == null) {
    parent.right = n;
    break;
}

那麼current != null 所以接著進入下一次while迴圈,執行這句程式碼後;parent = current;

那麼parent = 45的那個節點了,current值如下所示:

接著進入if語句判斷,由於當前的根節點是45,所以37 小於根節點 45了,所以就進入if語句程式碼如下:

if(data <  current.data) {
    current = current.left;
    if(current == null) {
        parent.left = n;
        break;
    }
}

Current = current.left 因此current = null; 繼續執行上面的if語句判斷是否為null的時候,因此就把37放入根節點為45的左節點上了。

5. 直接執行insert(3); 的時候,根節點不為空,所以就進入else語句的while語句中,由於當前的data = 3,所以執行如下if判斷程式碼:

if(data <  current.data) {
    current = current.left;
    if(current == null) {
        parent.left = n;
        break;
    }
}

插入的節點值3 小於 根節點23,進入if語句裡面執行,但是當前的current值如下:

所以當執行 current = current.left 的時候,那麼current = 16的那個節點了,如下所示:

因此current 不等於null,所以就執行到下一次while迴圈,繼續進入while中的if判斷,由於當前的根節點是16,所以也就進入了if裡面的程式碼去執行,在執行這句程式碼後:

current = current.left; 由上圖可知:current = null;current就等於null了;再執行程式碼如下:

if(current == null) {
    parent.left = n;
    break;
}

就把節點3 插入到當前的根節點為16的左節點了。

6. 執行insert(99)的時候;當前的根節點23 小於 99,那麼就進入else語句了,那麼current值就等於如下:

當執行 current = current.right; 的時候 ,那麼current 就等於如下:

再接著執行程式碼:

if(current == null) {
    parent.right = n;
    break;
}

如上圖所示,current並不等於null,所以執行下一次while迴圈,繼續進入while中的else語句,那麼當前的current值如下:

當執行current = current.right;這句程式碼的時候,那麼current 就等於 null了,所以執行if語句程式碼如下:

if(current == null) {
    parent.right = n;
    break;
}

就把99節點插入到當前的根節點為45節點的右節點了。

7. 執行 insert(22);的時候,由於根節點為23,所以節點22 小於 23,所以進入while中的if語句裡面了,那麼當前current值如下:

當執行 current = current.left; 的時候,那麼current值變為如下所示:

所以執行 if語句程式碼如下:

if(current == null) {
    parent.left = n;
    break;
}

不等於null,所以斤進入while下一次迴圈,由於當前的根節點16 小於插入的節點22 ,所以就進入else語句了,那麼當前的current值如下:

再執行這句程式碼 current = current.right; 那麼current就等於null了;因此就把節點22插入到根節點為16上面的右節點上了;

以上是插入節點的整個流程!

二:遍歷二叉查詢樹;

遍歷二叉樹的方法有三種,中序,先序和後序。

1. 中序;

如下圖所示:

中序遍歷使用遞迴的方式實現,該方法需要以升序訪問樹中的所有節點,先訪問左子樹,再訪問根節點,最後訪問右子樹。

程式碼如下:

// 中序遍歷
function inOrder(node) {
       if(!(node == null)) {
           inOrder(node.left);
           console.log(node.show());
           inOrder(node.right);
       }
}

程式碼分析如下:

JS所有程式碼如下:

function Node(data,left,right) {
    this.data = data;
    this.left = left;
    this.right = right;
    this.show = show;
}

function show() {
    return this.data;
}

function BST() {
    this.root = null;
    this.insert = insert;
    this.inOrder = inOrder;
}

function insert(data) {
    var n = new Node(data,null,null);
    if(this.root == null) {
        this.root = n;
    }else {
        var current = this.root;
        var parent;
        while(current) {
            parent = current;
            if(data <  current.data) {
                current = current.left;
                if(current == null) {
                    parent.left = n;
                    break;
                }
            }else {
                current = current.right;
                if(current == null) {
                    parent.right = n;
                    break;
                }
            }
        }
    }
}
// 中序遍歷
function inOrder(node) {
    if(!(node == null)) {
        inOrder(node.left);
        console.log(node.show());
        inOrder(node.right);
    }
}
程式碼初始化如下:
var nums = new BST();
nums.insert(23);
nums.insert(45);
nums.insert(16);
nums.insert(37);
nums.insert(3);
nums.insert(99);
nums.insert(22);
inOrder(nums.root);

2. 先序:先序遍歷先訪問根節點,然後以同樣方式訪問左子樹和右子樹。如下圖所示:

程式碼如下:

// 先序遍歷 
function preOrder(node) {
       if(!(node == null)) {
           console.log(node.show());
           preOrder(node.left);
           preOrder(node.right);
        }
}

JS所有程式碼如下:

function Node(data,left,right) {
    this.data = data;
    this.left = left;
    this.right = right;
    this.show = show;
}
function show() {
    return this.data;
}
function BST() {
    this.root = null;
    this.insert = insert;
    this.inOrder = inOrder;
}
function insert(data) {
    var n = new Node(data,null,null);
    if(this.root == null) {
        this.root = n;
    }else {
        var current = this.root;
        var parent;
        while(current) {
            parent = current;
            if(data <  current.data) {
                current = current.left;
                if(current == null) {
                    parent.left = n;
                    break;
                }
            }else {
                current = current.right;
                if(current == null) {
                    parent.right = n;
                    break;
                }
            }
        }
    }
}
// 中序遍歷
function inOrder(node) {
    if(!(node == null)) {
        inOrder(node.left);
        console.log(node.show());
        inOrder(node.right);
    }
}

// 先序遍歷 
function preOrder(node) {
    if(!(node == null)) {
        console.log(node.show());
        preOrder(node.left);
        preOrder(node.right);
    }
}
初始化程式碼如下:
var nums = new BST();
nums.insert(23);
nums.insert(45);
nums.insert(16);
nums.insert(37);
nums.insert(3);
nums.insert(99);
nums.insert(22);
console.log("--------------");
preOrder(nums.root);

先序遍歷列印如下:

3. 後序:後序遍歷先訪問葉子節點,從左子樹到右子樹,再到根節點,如下所示:

JS程式碼如下:

// 後序遍歷
function postOrder(node) {
    if(!(node == null)) {
        postOrder(node.left);
        postOrder(node.right);
        console.log("後序遍歷"+node.show());
    }
}

所有的JS程式碼如下:

function Node(data,left,right) {
    this.data = data;
    this.left = left;
    this.right = right;
    this.show = show;
}

function show() {
    return this.data;
}

function BST() {
    this.root = null;
    this.insert = insert;
    this.inOrder = inOrder;
}

function insert(data) {
    var n = new Node(data,null,null);
    if(this.root == null) {
        this.root = n;
    }else {
        var current = this.root;
        var parent;
        while(current) {
            parent = current;
            if(data <  current.data) {
                current = current.left;
                if(current == null) {
                    parent.left = n;
                    break;
                }
            }else {
                current = current.right;
                if(current == null) {
                    parent.right = n;
                    break;
                }
            }
        }
    }
}
// 中序遍歷
function inOrder(node) {
    if(!(node == null)) {
        inOrder(node.left);
        console.log(node.show());
        inOrder(node.right);
    }
}

// 先序遍歷 
function preOrder(node) {
    if(!(node == null)) {
        console.log(node.show());
        preOrder(node.left);
        preOrder(node.right);
    }
}

// 後序遍歷
function postOrder(node) {
    if(!(node == null)) {
        postOrder(node.left);
        postOrder(node.right);
        console.log("後序遍歷"+node.show());
    }
}
頁面初始化如下:
var nums = new BST();
nums.insert(23);
nums.insert(45);
nums.insert(16);
nums.insert(37);
nums.insert(3);
nums.insert(99);
nums.insert(22);
console.log("--------------");
postOrder(nums.root);

列印如下:

在二叉查詢樹上進行查詢

查詢二叉樹上的最小值與最大值非常簡單,因為較小的值總是在左子節點上,在二叉樹上查詢最小值,只需要遍歷左子樹,直到找到最後一個節點。

1. 二叉樹查詢最小值

程式碼如下:

// 二叉樹查詢最小值
function getMin(){
    var current = this.root;
    while(!(current.left == null)) {
         current = current.left;
      }
    return current.data;
}

所有JS程式碼如下:

function Node(data,left,right) {
    this.data = data;
    this.left = left;
    this.right = right;
    this.show = show;
}

function show() {
    return this.data;
}

function BST() {
    this.root = null;
    this.insert = insert;
    this.inOrder = inOrder;
    this.getMin = getMin;
}

function insert(data) {
    var n = new Node(data,null,null);
    if(this.root == null) {
        this.root = n;
    }else {
        var current = this.root;
        var parent;
        while(current) {
            parent = current;
            if(data <  current.data) {
                current = current.left;
                if(current == null) {
                    parent.left = n;
                    break;
                }
            }else {
                current = current.right;
                if(current == null) {
                    parent.right = n;
                    break;
                }
            }
        }
    }
}
// 中序遍歷
function inOrder(node) {
    if(!(node == null)) {
        inOrder(node.left);
        console.log(node.show());
        inOrder(node.right);
    }
}

// 先序遍歷 
function preOrder(node) {
    if(!(node == null)) {
        console.log(node.show());
        preOrder(node.left);
        preOrder(node.right);
    }
}

// 後序遍歷
function postOrder(node) {
    if(!(node == null)) {
        postOrder(node.left);
        postOrder(node.right);
        console.log("後序遍歷"+node.show());
    }
}

// 二叉樹查詢最小值
function getMin(){
    var current = this.root;
    while(!(current.left == null)) {
        current = current.left;
    }
    return current.data;
}
測試程式碼初始化如下:
var nums = new BST();
nums.insert(23);
nums.insert(45);
nums.insert(16);
nums.insert(37);
nums.insert(3);
nums.insert(99);
nums.insert(22);
var min = nums.getMin();
console.log(min);  // 列印出3

程式碼分析如下:

1 當執行到getMin()方法內的 var current = this.root的時候,當前的this.root的值為如下:

 

因此進入while內的迴圈,執行到程式碼:

current = current.left,current.left值如下:

賦值給current,因此current等於上面的節點。接著繼續迴圈遍歷while,執行到程式碼 current = current.left , current.left值變成如下:

然後值賦值給current。再繼續遍歷,進入while迴圈,while(!(current.left == null)) {}程式碼判斷,由上圖可知;current.left = null,因此就跳出整個while迴圈,因此列印3出來。

2.在二叉樹上查詢最大值;只需遍歷右子樹,直到查到最後一個節點,該節點上儲存的值即為最大值。

JS程式碼如下:

// 二叉樹上查詢最大值
function getMax() {
    var current = this.root;
    while(!(current.right == null)) {
        current = current.right;
    }
    return current.data;
}

下面是所有的JS程式碼:

function Node(data,left,right) {
    this.data = data;
    this.left = left;
    this.right = right;
    this.show = show;
}

function show() {
    return this.data;
}

function BST() {
    this.root = null;
    this.insert = insert;
    this.inOrder = inOrder;
    this.getMin = getMin;
    this.getMax = getMax;
}

function insert(data) {
    var n = new Node(data,null,null);
    if(this.root == null) {
        this.root = n;
    }else {
        var current = this.root;
        var parent;
        while(current) {
            parent = current;
            if(data <  current.data) {
                current = current.left;
                if(current == null) {
                    parent.left = n;
                    break;
                }
            }else {
                current = current.right;
                if(current == null) {
                    parent.right = n;
                    break;
                }
            }
        }
    }
}
// 中序遍歷
function inOrder(node) {
    if(!(node == null)) {
        inOrder(node.left);
        console.log(node.show());
        inOrder(node.right);
    }
}

// 先序遍歷 
function preOrder(node) {
    if(!(node == null)) {
        console.log(node.show());
        preOrder(node.left);
        preOrder(node.right);
    }
}

// 後序遍歷
function postOrder(node) {
    if(!(node == null)) {
        postOrder(node.left);
        postOrder(node.right);
        console.log("後序遍歷"+node.show());
    }
}

// 二叉樹查詢最小值
function getMin(){
    var current = this.root;
    while(!(current.left == null)) {
        current = current.left;
    }
    return current.data;
}

// 二叉樹上查詢最大值
function getMax() {
    var current = this.root;
    while(!(current.right == null)) {
        current = current.right;
    }
    return current.data;
}
HTML初始化如下:
var nums = new BST();
nums.insert(23);
nums.insert(45);
nums.insert(16);
nums.insert(37);
nums.insert(3);
nums.insert(99);
nums.insert(22);
var min = nums.getMin();
console.log(min);

var max = nums.getMax();
console.log(max);

分析還是和上面最小值分析一個道理,這裡就不分析了。

上面2個方法返回最小值和最大值,但是有時候,我們希望方法返回儲存最小值和最大值的節點,這很好實現,只需要修改方法,讓它返回當前節點,而不是節點中儲存的資料即可。

在二叉樹上查詢給定值

 在二叉樹上查詢給定值,需要比較該值和當前節點上的值得大小。通過比較,就能確定如果給定值不在當前節點時,就要向左遍歷和向右遍歷了。

程式碼如下:

// 查詢給定值
function find(data) {
    var current = this.root;
    while(current != null) {
        if(current.data == data) {
            return current;
        }else if(data < current.data) {
            current = current.left;
        }else {
            current = current.right;
        }
    }
    return null;
}

程式碼分析如下:

比如現在的二叉樹是如下這個樣子:

頁面初始化 查詢二叉樹上的45 節點,程式碼初始化如下:

var value = nums.find("45");

截圖如下:

然後就return 45的節點上了。

所有的JS程式碼如下:

function Node(data,left,right) {
    this.data = data;
    this.left = left;
    this.right = right;
    this.show = show;
}

function show() {
    return this.data;
}

function BST() {
    this.root = null;
    this.insert = insert;
    this.inOrder = inOrder;
    this.getMin = getMin;
    this.getMax = getMax;
    this.find = find;
}

function insert(data) {
    var n = new Node(data,null,null);
    if(this.root == null) {
        this.root = n;
    }else {
        var current = this.root;
        var parent;
        while(current) {
            parent = current;
            if(data <  current.data) {
                current = current.left;
                if(current == null) {
                    parent.left = n;
                    break;
                }
            }else {
                current = current.right;
                if(current == null) {
                    parent.right = n;
                    break;
                }
            }
        }
    }
}
// 中序遍歷
function inOrder(node) {
    if(!(node == null)) {
        inOrder(node.left);
        console.log(node.show());
        inOrder(node.right);
    }
}

// 先序遍歷 
function preOrder(node) {
    if(!(node == null)) {
        console.log(node.show());
        preOrder(node.left);
        preOrder(node.right);
    }
}

// 後序遍歷
function postOrder(node) {
    if(!(node == null)) {
        postOrder(node.left);
        postOrder(node.right);
        console.log("後序遍歷"+node.show());
    }
}

// 二叉樹查詢最小值
function getMin(){
    var current = this.root;
    while(!(current.left == null)) {
        current = current.left;
    }
    return current.data;
}

// 二叉樹上查詢最大值
function getMax() {
    var current = this.root;
    while(!(current.right == null)) {
        current = current.right;
    }
    return current.data;
}

// 查詢給定值
function find(data) {
    var current = this.root;
    while(current != null) {
        if(current.data == data) {
            return current;
        }else if(data < current.data) {
            current = current.left;
        }else {
            current = current.right;
        }
    }
    return null;
}

從二叉查詢樹上刪除節點。

原理:從二叉樹上刪除節點首先要判斷當前節點是否包含待刪除的資料,如果包含,則刪除該節點;如果不包含,則要比較當前節點上的資料和待刪除的資料。如果待刪除資料小於當前節點上的資料,則要移到當前節點的左子節點繼續比較;如果刪除的資料大於當前節點上的資料,則移至當前節點的右子節點繼續比較。

如果待刪除節點是葉子節點(沒有子節點的節點),那麼只需要將父節點指向它的連結指向null;

如果待刪除的節點只包含一個子節點,那麼原本指向它的節點就得做點調整,使其指向它的子節點。

最後,如果待刪除節點包含2個子節點,正確的做法有2種,1:要麼查詢待刪除節點左子樹上的最大值,要麼查詢其右子樹上的最小值。這裡我們選擇後一種;

下面是我們刪除節點的JS程式碼如下:

function remove(data) {
    root = removeNode(this.root,data);
}
function getSmallest(node) {
   if (node.left == null) {
      return node;
   }
   else {
      return getSmallest(node.left);
   }
}
function removeNode(node,data) {
    if(node == null) {
        return null;
    }
    if(data == node.data) {
        // 沒有子節點的節點
        if(node.left == null && node.right == null) {
            return null;
        } 
        // 沒有左子節點的節點
        if(node.left == null) {
            return node.right;
        }
        // 沒有右子節點的節點
        if(node.right == null) {
            return node.left;
        }
        // 有2個子節點的節點
        var tempNode = getSmallest(node.right);
        node.data = tempNode.data;
        node.right = removeNode(node.right,tempNode.data);
        return node;
    }else if(data < node.data) {
        node.left = removeNode(node.left,data);
        return node;
    }else {
        node.right = removeNode(node.right,data);
        return node;
    }
}

我們還是以上面的二叉樹來分析下程式碼原理:

1. 比如我現在要刪除根節點為23的節點,程式碼初始化如下:

   nums.remove(23);

執行這個程式碼後 var tempNode = getSmallest(node.right); 就指向45的那個節點了,如下:

然後執行下面的獲取右子樹上的最小值的方法;

function getSmallest(node) {
   if (node.left == null) {
      return node;
   }
   else {
      return getSmallest(node.left);
   }
}

裡面使用遞迴的方式執行程式碼,當node.left == null 時候,就返回當前node節點;如下:

如上所示,當node等於37的時候 就返回node為37的節點。

下面繼續執行第二句程式碼;如下:

node.data = tempNode.data; 那麼node.data = 37了;下面是node節點的截圖如下:

接著繼續執行下面的程式碼

node.right = removeNode(node.right,tempNode.data);

同時又使用遞迴的方式removeNode()方法;返回如下節點:

所有的JS程式碼如下:

function Node(data,left,right) {
    this.data = data;
    this.left = left;
    this.right = right;
    this.show = show;
}

function show() {
    return this.data;
}

function BST() {
    this.root = null;
    this.insert = insert;
    this.inOrder = inOrder;
    this.getMin = getMin;
    this.getMax = getMax;
    this.find = find;
    this.remove = remove;
}

function insert(data) {
    var n = new Node(data,null,null);
    if(this.root == null) {
        this.root = n;
    }else {
        var current = this.root;
        var parent;
        while(current) {
            parent = current;
            if(data <  current.data) {
                current = current.left;
                if(current == null) {
                    parent.left = n;
                    break;
                }
            }else {
                current = current.right;
                if(current == null) {
                    parent.right = n;
                    break;
                }
            }
        }
    }
}
// 中序遍歷
function inOrder(node) {
    if(!(node == null)) {
        inOrder(node.left);
        console.log(node.show());
        inOrder(node.right);
    }
}

// 先序遍歷 
function preOrder(node) {
    if(!(node == null)) {
        console.log(node.show());
        preOrder(node.left);
        preOrder(node.right);
    }
}

// 後序遍歷
function postOrder(node) {
    if(!(node == null)) {
        postOrder(node.left);
        postOrder(node.right);
        console.log("後序遍歷"+node.show());
    }
}

// 二叉樹查詢最小值
function getMin(){
    var current = this.root;
    while(!(current.left == null)) {
        current = current.left;
    }
    return current.data;
}

// 二叉樹上查詢最大值
function getMax() {
    var current = this.root;
    while(!(current.right == null)) {
        current = current.right;
    }
    return current.data;
}

// 查詢給定值
function find(data) {
    var current = this.root;
    while(current != null) {
        if(current.data == data) {
            return current;
        }else if(data < current.data) {
            current = current.left;
        }else {
            current = current.right;
        }
    }
    return null;
}

function remove(data) {
    root = removeNode(this.root,data);
}
function getSmallest(node) {
   if (node.left == null) {
      return node;
   }
   else {
      return getSmallest(node.left);
   }
}
function removeNode(node,data) {
    if(node == null) {
        return null;
    }
    if(data == node.data) {
        // 沒有子節點的節點
        if(node.left == null && node.right == null) {
            return null;
        } 
        // 沒有左子節點的節點
        if(node.left == null) {
            return node.right;
        }
        // 沒有右子節點的節點
        if(node.right == null) {
            return node.left;
        }
        // 有2個子節點的節點
        var tempNode = getSmallest(node.right);
        node.data = tempNode.data;
        node.right = removeNode(node.right,tempNode.data);
        return node;
    }else if(data < node.data) {
        node.left = removeNode(node.left,data);
        return node;
    }else {
        node.right = removeNode(node.right,data);
        return node;
    }
}
程式碼初始化如下:
var nums = new BST();
nums.insert(23);
nums.insert(45);
nums.insert(16);
nums.insert(37);
nums.insert(3);
nums.insert(99);
nums.insert(22);
var min = nums.getMin();
console.log(min);
var max = nums.getMax();
console.log(max);
var value = nums.find("45");
console.log(value);
nums.remove(23);

 

相關文章