数据结构与算法学习四(基于JavaScript)----树

树是一种非线性的数据结构。由n(n>=0)个节点组成.

  • 如果n=0,是一颗空数。
  • 如果n>0,树有一个特殊的节点,这个节点没有父节点,称为根节点(root)
  • 除根节点之外的其余数据元素被分为m(m>=0)个互不相交的集合T1,T2…Tm-1,其中每一个集合Ti(1<=i<=m),本身也是一棵树,被称作原数的子树。
    下图是一棵树
    树

树的相关概念

1、节点

   它包含数据项,和指向其它节点的指针,上图中的树有11个节点。

2、节点的度

有几个分支(箭头),度就是几。
树的度

3、叶节点

度为0的节点被称为叶节点。如上图的5 7 8 9 10 11

4、分支节点

除了叶节点之外的节点就是分支节点。1 2 3 4 6

5、子女节点

若节点m有子树,则这棵子树的根节点就是节点m的子女节点,如2,3,4都是1的子女节点。

6、父节点

若节点m有子女节点,则m为子女节点的父节点,如1是2,3,4的父节点 2是5,6的父节点。

7、兄弟节点

同一个父节点的子女节点互称为兄弟。5和6是兄弟节点

8、祖先节点

从跟节点到该节点所经过分支上的所有节点,如节点5,它的祖先节点为1 2

9、子孙节点

某一个节点的子女,以及这些子女节点的子女都是该节点的的子孙节点。如5 6 11都是2的子孙节点

10、节点所在的层次

根节点在第一层,它的子女在第二层,依次类推。
节点层次

11、树的深度

树中距离根节点最远的节点所处的层次就是树的深度。上图中树的深度是4.

12、树的高度

叶节点的高度为1,非叶节点的高度都是它的子女节点高度的最大值加1,高度与深度的数值是相等的,但计算方式不一样。

13、树的度

树中节点的度的最大值,

14、有序树

树中节点的各子树T1,T2。。。是有次序的,T1是第一颗子树,T2是第2颗子树。

15、无序树

树中节点的各子树之间的次序不重要,可以相互交换位置。

16、森林

森林是m(m>=0)颗数的集合

二叉树

二叉树是一种特殊情况,每个节点最多有两个子女,分别为左子女和右子女。二叉树中不存在度大于2的节点。
二叉树的子树有左右之分,次序不能颠倒。
二叉树

二叉树的性质

  • 二叉树的第i(i>=1)层,最多存在2的i-1次方个节点。
  • 深度为k(k>=0)的二叉树,最少有K个节点,最多有2的k-1次方个节点。
  • 对于一个非空二叉树,叶节点的数量等于度为2的节点数量加1。

特殊二叉树

  • 满二叉树
    深度为k的满二叉树,是有2的k-1次方个节点的二叉树,每一层都达到了可以容纳的最大数量的节点。
  • 完全二叉树
    深度为k的完全二叉树,从第一层到第k-1层都是满的,第k层,或是满的,或是从右向左连续缺若干个节点。

下面我们来定义一颗树:

//定义二叉树
var BinTreeNode = function(data){
    this.data = data;//数据
    this.leftChild = null;//左节点
    this.rightChild = null;
    this.parentNode = null;//父节点
}

function BinTree(){
    var root = null;//根节点
    //初始化  采用广义表表示的建立二叉树方法
    this.init_tree = function(string){
        var stack = new Stack();
        var k = 0;//标识识别的是左子树还是右子树
        var new_node = null;
        for (let i = 0; i < string.length; i++) {
            var item = string[i];
            // console.log(item)
            if(item=="#"){
                break;
            }
            if(item=="("){
                stack.push(new_node);
                k=1;
            }else if(item==","){
                k=2;
            }else if(item==")"){
                stack.pop();
            }else{
                new_node = new BinTreeNode(item);//创建节点
                if(root == null){
                    root = new_node;
                   
                }else{
                    //k==1 说明new_node是左子节点
                    if(k==1){
                        var top_item = stack.top();
                        top_item.leftChild = new_node;
                        new_node.parentNode = top_item;
                    }else if(k==2){
                        var top_item = stack.top();
                        top_item.rightChild = new_node;
                        new_node.parentNode = top_item;
                    }
                }
            }
            
        }
    }
    //返回根节点
    this.get_root = function(){
        return root;
    }
    //中序遍历(遍历顺序:当前节点的左节点->当前节点->当前节点的右节点)
    this.in_order = function(node){
        if(node==null){
            return ;
        }
        this.in_order(node.leftChild);
        console.log(node.data);
        this.in_order(node.rightChild)
    }
    //前序遍历(遍历顺序:当前节点->当前节点的左节点->当前节点的右节点)
    this.pre_order = function(node){
        if(node==null){
            return ;
        }
        console.log(node.data)
        this.pre_order(node.leftChild);
        this.pre_order(node.rightChild)
    }
    // 后序遍历(遍历顺序:当前节点的左节点->当前节点的右节点->当前节点)
    this.post_order = function(node){
        if(node==null){
            return ;
        }
        this.post_order(node.leftChild);
        this.post_order(node.rightChild);
        console.log(node.data);
    }
    var tree_node_count = function(node){
        //左子树的节点数量+右子树节点的数量+当前节点
        if(node==null){
            return 0;
        }
        var left_node_count = tree_node_count(node.leftChild);
        var right_node_count = tree_node_count(node.rightChild);
        return left_node_count + right_node_count + 1;
    }
    //树的节点个数
    this.size = function(){
        return tree_node_count(root)
    }
    var tree_height = function(node){
        if(node==null){
            return 0;
        }
        //先计算左子树的高度
        var left_child_height = tree_height(node.leftChild);
        // 再计算右子树的高度
        var right_child_height = tree_height(node.rightChild);
        //比较大小 返回大的
        if(left_child_height>right_child_height){
            return left_child_height + 1;
        }else{
            return right_child_height + 1;
        }
    }
    // 树的高度
    this.height = function(){
        return tree_height(root);
    }
    var find_node = function(node,data){
        if(node==null){
            return null;
        }
        //当前节点的值等于data
        if(node.data==data){
            return node;
        }
        //先到左子树里去找
        var left_res = find_node(node.leftChild,data);
        if(left_res){//找到了
            return left_res;
        }
        // 没找到 ,去右子树里找
        return find_node(node.rightChild,data)
    }
    //查找节点
    this.find = function(data){
        return find_node(root,data);
    }
}

上面代码中init_tree的实现思路:
init_tree的实现 它接收一个表示二叉树的广义表,创建一棵树。
以广义表A(B(D,E(G,)),C(,F))#为例,算法如下:

  • 广义表的表名放在表前,表示树的根节点,括号中的是根的左右子树。

  • 每个节点的左右子树用逗号隔开,如果仅有右子树没有左子树,逗号不省略。

  • 整个广义表的最后加上特殊符号#表示输入结束。
    广义表

  • 遍历广义表字符串,来建立一颗二叉树。

  • 遇到左括号的时候,说明前面有一个节点,这个括号里的两个节点都是它的子节点,但是子节点后面还会有子节点,因此,

  • 我们需要一个先进后出的数据结构把前面的节点保存下来,这样栈顶就是当前要处理的两个节点的父节点。

  • 逗号分隔了左右子树,因此需要一个变量来标识遇到的是左子树还是右子树,假设这个变量为k,遇到左括号的时候,k=1,表示开始识别左子树,

  • 遇到逗号,k=2表示开始识别右子树。

  • 遇到右括号,说明一棵子树结束了,那么栈顶的元素就是这颗子树的根节点,执行pop方法出栈。

这里需要用到一个栈,栈的实现:

function Stack(){
    var items = [];
    // 从栈顶添加元素,---压栈
    this.push = function(item){
        items.push(item)
    }
    //弹出栈顶元素
    this.pop = function(){
        return items.pop();
    }
    // 返回栈顶元素
    this.top = function(){
        return items[items.length-1];
    }
    // 判断栈是否为空
    this.isEmpty = function(){
        return items.length == 0;
    }
    // 返回栈的长度
    this.size = function(){
        return items.length;
    }
    // 清空栈
    this.clear = function(){
        items = [];
    }
}

测试代码:

var bt = new BinTree();
bt.init_tree("A(B(D,E(G,)),C(,F))#")
var root = bt.get_root();

 bt.in_order(root);// D B G E A C F 
 console.log(bt.size());//7

树的练习

1、求一颗树的镜像

对于一棵树,如果每个节点的左右子树互换位置,那么就变成了这颗树的镜像。

// 思路1
var mirror_1 = function(node){
    if(node==null){
        return ;
    }
    //互换位置
    var temp = node.leftChild;
    node.leftChild = node.rightChild;
    node.rightChild = temp;
    mirror_1(node.leftChild);
    mirror_1(node.rightChild);
}
//思路2 
var mirror_2 = function(node){
    if(node==null){
        return ;
    }
   var left = mirror_2(node.leftChild);
   var right = mirror_2(node.rightChild);
   node.leftChild = right;
   node.rightChild = left;
   return node;
}
mirror_1(root);
bt.in_order(root);// F C A E G B D 

2、使用非递归的方式实现前序遍历

思路:递归转为非递归,可以用while循环。
前序遍历的顺序是当前节点->当前节点的左节点->当前节点的右节点。
在while循环里面,让curr_node = curr_node.leftChild,就可以实现对左子树的访问处理。问题在于,处理左子树之后,要找到对应的右子树,需要一种数据结构,能够在左子树访问结束后返回对应的右子树。栈可以实现。
树
以上图为例:
开始curr_node = 1;
1、处理curr_node(1),3入栈,curr_node = curr_node.leftChild;
2、处理curr_node(2),5入栈,curr_node = curr_node.leftChild;
3、处理curr_node(4),处理结束,不能继续访问左子树,执行pop,curr_node = 5;
4、处理curr_node(5),处理结束,不能继续访问左子树,执行pop,curr_node = 3;
5、处理curr_node(3),7入栈,curr_node = curr_node.leftChild;
6、处理curr_node(6),处理结束,不能继续访问左子树,执行pop,curr_node = 7;
7、处理curr_node(7),处理结束,栈里没有元素。结束。

// 前序遍历的非递归实现 
function pre_order(node){
    var stack = new Stack();
    var curr_node = node;
    while(curr_node){
        console.log(curr_node.data)
        if(curr_node.rightChild){
            stack.push(curr_node.rightChild)
        }
        if(curr_node.leftChild){
            curr_node = curr_node.leftChild;
        }else{
            //没有左子树
            curr_node = stack.pop(); 
        }
    }
}
pre_order(root);

猜你喜欢

转载自blog.csdn.net/guo187/article/details/88347066
今日推荐