樹形結(jié)構(gòu)

樹是一種分層數(shù)據(jù)的抽象模型。它和散列表一樣是一種非順序數(shù)據(jù)結(jié)構(gòu),它對于存儲需要快速查找的數(shù)據(jù)非常有用船侧。

樹的相關(guān)術(shù)語

一個樹結(jié)構(gòu)包含一系列存在父子關(guān)系的節(jié)點(diǎn)。每個節(jié)點(diǎn)都有一個父節(jié)點(diǎn)(除了頂部的第一個節(jié)點(diǎn))以及零個或多個子節(jié)點(diǎn)厅各。


樹形結(jié)構(gòu)圖

根節(jié)點(diǎn):位于樹頂部的節(jié)點(diǎn)叫做根節(jié)點(diǎn)勺爱,沒有父節(jié)點(diǎn)。
內(nèi)部節(jié)點(diǎn)和外部節(jié)點(diǎn):樹中每個元素都叫做節(jié)點(diǎn)讯检,節(jié)點(diǎn)分為內(nèi)部節(jié)點(diǎn)和外部節(jié)點(diǎn)琐鲁。至少有一個子節(jié)點(diǎn)的節(jié)點(diǎn)被稱為內(nèi)部節(jié)點(diǎn)(B、D人灼、C围段、E)。沒有子節(jié)點(diǎn)的節(jié)點(diǎn)被稱為外部節(jié)點(diǎn)或葉節(jié)點(diǎn)(G投放、H奈泪、I、F)灸芳。
節(jié)點(diǎn)的祖先和后代:一個節(jié)點(diǎn)(除了根節(jié)點(diǎn))的祖先包括父節(jié)點(diǎn)涝桅、祖父節(jié)點(diǎn)、曾祖父節(jié)點(diǎn)等烙样。一個節(jié)點(diǎn)的后代包括子節(jié)點(diǎn)冯遂、孫子節(jié)點(diǎn)、曾孫節(jié)點(diǎn)等谒获。例如D節(jié)點(diǎn)的祖先節(jié)點(diǎn)有B節(jié)點(diǎn)和A節(jié)點(diǎn)蛤肌,后代節(jié)點(diǎn)有G節(jié)點(diǎn)和H節(jié)點(diǎn)。
子樹:子樹由節(jié)點(diǎn)和它的后代構(gòu)成批狱。例如節(jié)點(diǎn)D裸准、G、H赔硫、就構(gòu)成了上圖中樹的一顆子樹炒俱。
節(jié)點(diǎn)深度:節(jié)點(diǎn)的深度取決于它的祖先元素的節(jié)點(diǎn)數(shù)量。比如節(jié)點(diǎn)G有三個祖先節(jié)點(diǎn)D、B权悟、A砸王,所有它的深度為3。
樹的高度取決于所有節(jié)點(diǎn)深度的最大值僵芹。一棵樹可以分解成層級处硬。根節(jié)點(diǎn)在0層,B節(jié)點(diǎn)在1層拇派、D節(jié)點(diǎn)在2層荷辕、G節(jié)點(diǎn)在3層,以此類推件豌。所有上圖中的樹高度為3疮方。

二叉樹和二叉樹搜索

二叉樹中的節(jié)點(diǎn)最多只能有兩個子節(jié)點(diǎn):一個是左側(cè)幾點(diǎn),一個是右側(cè)節(jié)點(diǎn)茧彤。
二叉樹搜索樹(BST):是二叉搜索樹的一種骡显,但是它只允許在左側(cè)節(jié)點(diǎn)存儲(比父節(jié)點(diǎn))小的值,在右側(cè)節(jié)點(diǎn)存儲(比父節(jié)點(diǎn))大的值曾掂。
中序遍歷:是一種以上行順序訪問BST所有節(jié)點(diǎn)的遍歷方式惫谤,也就是以從小到最大的順序訪問所有節(jié)點(diǎn)。
先序遍歷:是以優(yōu)先于后代節(jié)點(diǎn)的順序訪問每個節(jié)點(diǎn)的珠洗。先序遍歷的一種應(yīng)用是打印一個結(jié)構(gòu)化的文檔溜歪。
后序遍歷:是先訪問節(jié)點(diǎn)的后代節(jié)點(diǎn),在訪問節(jié)點(diǎn)本身许蓖。后序遍歷的一種應(yīng)用是計(jì)算一個目錄和它的子目錄所有文件所占空間的大小蝴猪。

function BinarySearchTree() {
    var Node = function (key) {
        this.key = key;
        this.left = null;
        this.right = null;
    };
    var insertNode = function (node, newNode) {
        if(newNode.key < node.key) {
            if (node.left === null) {
                node.left = newNode;
            } else {
                insertNode(node.left, newNode);
            }
        } else {
            if(node.right === null) {
                node.right = newNode;
            } else {
                insertNode(node.right, newNode);
            }
        }
    };

    var inOrderTraversalNode = function (node, callback) {
        //中序遍歷
        if(node !== null) {
            inOrderTraversalNode(node.left, callback);
            callback(node.key);
            inOrderTraversalNode(node.right, callback);
        }
    };
    var preOrderTraversalNode = function (node, callback) {
        //先序遍歷
        if(node !== null) {
          callback(node.key);
          preOrderTraversalNode(node.left, callback);
          preOrderTraversalNode(node.right, callback);
        }
    };

    var postOrderTraversalNode = function (node, callback) {
        //后序遍歷
        if(node !== null) {
            postOrderTraversalNode(node.left, callback);
            postOrderTraversalNode(node.right, callback);
            callback(node.key);
        }
    };

    var minNode = function (node) {
        //查找最最小值
        if(node !== null) {
            while (node && node.left !== null) {
                node = node.left;
            }
            return node.key;
        }
        return null;
    };

    var maxNode = function (node) {
        //查找最大值
        if(node !== null) {
            while (node && node.right !== null) {
                node = node.right;
            }
            return node.key;
        }
        return null;
    };
    var getMixNode = function (node) {
        //查找鍵值最小的節(jié)點(diǎn)返回
        if(node !== null) {
            while (node && node.left !== null) {
                node = getMixNode(node.left);
            }
            return node;
        }
        return null;
    };

    var searchNode = function (node, key) {
        //在樹中查找一個鍵是否存在
        if(node === null) {
            return false;
        }
        if (key < node.key) {
            return searchNode(node.left, key);
        }else if (key > node.key) {
            return searchNode(node.right, key);
        }else {
            return true;
        }
    };

    var removeNode = function (node, key) {
        if (node === null) {
            //當(dāng)節(jié)點(diǎn)等于null說明不存在直接返回null
            return null;
        }

        if(key < node.key) {
            node.left = removeNode(node.left, key);
            return node;
        }else if(key > node.key) {
            node.right = removeNode(node.right, key);
            return node;
        }else {
            if(node.left === null && node.right === null) {
                return null;
            }
            if(node.left === null) {
                node = node.right;
                return node;
            }
            if(node.right === null) {
                node = node.left;
                return node;
            }
            var aux = getMixNode(node.right);
            node.key = aux.key;
            removeNode(node.right, aux.key);
        }
    };

    var root = null;

    this.insert = function (key) {
        //向樹中插入一個新鍵
        var newNode = new Node(key);
        if (root === null) {
            root = newNode;
        }else {
            insertNode(root, newNode);
        }
    };

    this.search = function (key) {
        //在樹中查找一個鍵,如果存在返回true否則false
        return searchNode(root, key);
    };

    this.inOrderTraversal = function (callback) {
        //通過中序遍歷方式遍歷所有節(jié)點(diǎn)
        inOrderTraversalNode(root, callback);
    };

    this.preOrderTraversal = function (callback) {
        //通過先序遍歷方式遍歷所有節(jié)點(diǎn)
        preOrderTraversalNode(root, callback);
    };

    this.postOrderTraversal = function (callback) {
        //通過后續(xù)遍歷方式遍歷所有節(jié)點(diǎn)
        postOrderTraversalNode(root, callback);
    };
    this.min = function () {
        //返回樹中最小值/鍵
        return minNode(root);
    };

    this.max = function () {
      //返回樹中最大值/鍵
        return maxNode(root);
    };

    this.remove = function (key) {
        //從樹中移除某個鍵
        return removeNode(root, key);
    };

}


var tree = new BinarySearchTree();
tree.insert(55);
tree.insert(88);
tree.insert(17);
tree.insert(65);
tree.insert(4);
tree.insert(5);
tree.insert(6);
tree.insert(5);
tree.insert(8);
tree.insert(78);
tree.insert(87);
tree.insert(27);
tree.insert(17);
tree.insert(28);

function printNode(value) {
    console.log(value);
}

tree.inOrderTraversal(printNode); //4 5 5 6 8 17 17  27 28 55 65 78 87 88
tree.preOrderTraversal(printNode);//55 17 4 5 6 5 8 27 17 28 88 65 78 87
tree.postOrderTraversal(printNode);// 5 8 6 5 4 17 28 27 17 87 78 65 88 55
console.log(tree.min()); //4

console.log(tree.search(20)); //false
tree.remove(88); //false
console.log(tree.max()); //87

查找樹中的最大最小值:因?yàn)樵跇渲胁沧Γ畲笾翟跇涞淖詈笠粚幼钣覀?cè)自阱,最小值在樹的最后一層最左側(cè)。所以通過遞歸(邊界條件為節(jié)點(diǎn)是否還有左側(cè)或者右側(cè)子節(jié)點(diǎn))的方式米酬,當(dāng)子節(jié)點(diǎn)為null時返回當(dāng)前鍵沛豌。
移除一個節(jié)點(diǎn):通過遞歸調(diào)用,查找到符合要移除的鍵的節(jié)點(diǎn)淮逻。當(dāng)鍵比當(dāng)前節(jié)點(diǎn)的值小琼懊,就沿著左側(cè)繼續(xù)查找。當(dāng)鍵比當(dāng)前節(jié)點(diǎn)的值大就沿著右側(cè)查找爬早。當(dāng)查找到的節(jié)點(diǎn)沒有子節(jié)點(diǎn)時,就會返回一個null值启妹,然后將最后一次遞歸調(diào)用傳入的節(jié)點(diǎn)(也就是和鍵相符合的節(jié)點(diǎn))設(shè)置成返回的null值筛严,然后返回此節(jié)點(diǎn)。

移除有一個左側(cè)或者右側(cè)的節(jié)點(diǎn):當(dāng)此節(jié)點(diǎn)左側(cè)沒有節(jié)點(diǎn)時饶米,就把把對此節(jié)點(diǎn)的引用指向此節(jié)點(diǎn)的右側(cè)子節(jié)點(diǎn)桨啃。當(dāng)此節(jié)點(diǎn)右側(cè)沒有節(jié)點(diǎn)時车胡,就把把對此節(jié)點(diǎn)的引用指向此節(jié)點(diǎn)的左側(cè)子節(jié)點(diǎn)。
移除有兩個子節(jié)點(diǎn)的節(jié)點(diǎn):當(dāng)要移除有兩個字節(jié)點(diǎn)的節(jié)點(diǎn)時照瘾,它的繼承者就是它右邊子樹中最小的節(jié)點(diǎn)匈棘。所以首先查找到它右側(cè)最小節(jié)點(diǎn),然后將此節(jié)點(diǎn)的值改為它右側(cè)最小節(jié)點(diǎn)的值析命,然后再移除右側(cè)最小節(jié)點(diǎn)主卫,因?yàn)樗呀?jīng)替代了別的節(jié)點(diǎn)位置。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鹃愤,一起剝皮案震驚了整個濱河市簇搅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌软吐,老刑警劉巖瘩将,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異凹耙,居然都是意外死亡姿现,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進(jìn)店門肖抱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來备典,“玉大人,你說我怎么就攤上這事虐沥⌒芫” “怎么了?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵欲险,是天一觀的道長镐依。 經(jīng)常有香客問我,道長天试,這世上最難降的妖魔是什么槐壳? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮喜每,結(jié)果婚禮上务唐,老公的妹妹穿的比我還像新娘。我一直安慰自己带兜,他們只是感情好枫笛,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著刚照,像睡著了一般刑巧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天啊楚,我揣著相機(jī)與錄音吠冤,去河邊找鬼。 笑死恭理,一個胖子當(dāng)著我的面吹牛拯辙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播颜价,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼涯保,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了拍嵌?” 一聲冷哼從身側(cè)響起遭赂,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎横辆,沒想到半個月后撇他,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡狈蚤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年困肩,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脆侮。...
    茶點(diǎn)故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡锌畸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出靖避,到底是詐尸還是另有隱情潭枣,我是刑警寧澤,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布幻捏,位于F島的核電站盆犁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏篡九。R本人自食惡果不足惜谐岁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望榛臼。 院中可真熱鬧伊佃,春花似錦、人聲如沸沛善。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽金刁。三九已至迷捧,卻和暖如春织咧,著一層夾襖步出監(jiān)牢的瞬間胀葱,已是汗流浹背漠秋。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留抵屿,地道東北人庆锦。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像轧葛,于是被迫代替她去往敵國和親搂抒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評論 2 359

推薦閱讀更多精彩內(nèi)容