数据结构与算法 —— 05 树

1.树(Tree):

树是 n(n>=0) 个结点的有限集。
当 n=0 时称为空树。在任意一颗非空树中:有且仅有一个特定的称为根(Root)的结点
当 n>1 时,其余结点可以分为 m(m>0) 个互不相交的有限集 T1,T2,..,Tm, 其中每一个集合本身又是一颗树,并且称为根的子树(SubTree)

树的分类结构图


            ┌ 普通树             ┌ 斜树(左斜树、右斜树)
            │                     │ 
      ┌ 树1 ┤ 二叉树(BinaryTree)  ┤ 满二叉树:树深 log(n+1)
      │     │                     │
      │     │                     └ 完全二叉树(重要): 树深 [logn]+1         
      │     └ ...
      │
森林 ─┤ 树2
      │ ...
      │
      └

本质:是一对多的数据结构

(1)结点分类

结点的度:结点拥有的子树的数称为该结点的度(Degree)。
叶结点:度为零的结点称为叶结点
分支结点(非终端结点,内部结点): 度不为 0 的结点
树的度:指该树中各结点的度的最大值称为该树的度。

(2)结点的关系

孩子:结点的子树的根称为该结点的孩子(Child)
双亲:该结点称为其孩子的双亲(Parent)

(3)结点的层次(这个层次的概念是针对结点而言的)

从根开始定义起,根为第一层,根的孩子为第二层。

(4)树的深度(高度)

树中结点的最大层次称为树的深度(Deep),很显然,该结点肯定是树的根结点了。

注意树的深度树的度是不同的概念

(5)有序树和无序树

树中个结点的子树从左至右(或从右至左)是有序的,不能互换,则称该树为有序树,否则为无序树

(6)森林

是 m(m>=0)棵互不相交的树的集合

2.树的存储结构

这里已经不能单纯的采用前面的顺序存储、链式存储,介绍三种存储方式:双亲表示法、孩子表示法、孩子兄弟表示法

(1)双亲表示法(以父结点的角度)

用一组连续的空间存储树的结点,同时在每一个结点中,附设一个指示器指示双亲结点在数组中的位置。

┌────────┬────────┐
│ data   │ parent │
└────────┴────────┘

'结点描述'

public class PNode<T> { 
    public int parent; //父结点的位置
    public T data; //数据域
}
(2)孩子表示法(以孩子结点的角度)

将每个结点的孩子结点排列起来,以单链表的形式作为存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空,然后n个头指针又组成一个线性表,采用顺序存储结构,存放入一个一维数组中。

1  A -> 1 -> 2
2  B -> 3
3  C -> 4 -> 5
4  D -> 6 -> 7 -> 8
5  E -> 9
6  F

为此要使用两种结点结构:
(1)表头结点

┌────┬──────────┐
│data│firstchild│
└────┴──────────┘

(2)孩子结点

┌─────┬────┐
│child│next│
└─────┴────┘

优点:方便查找某个结点的兄弟,只需要遍历相关结点的孩子链表即可。遍历整颗树也是很方便,循环输出整个头结点数组即可

#######(3)孩子兄弟表示法(以结点的兄弟为角度)
优点是将一颗复杂的树变成了一颗二叉树

┌────┬──────────┬──────────┐
│data│firstchild│rightchild│
└────┴──────────┴──────────┘

'代码描述'
(1)树的结点描述

public class TreeNode<T> {
    public TreeNode<T> lChild; //左孩子
    public TreeNode<T> rChild; //右孩子
    private T data; //数据域
    //结点初始化
    public TreeNode() {
        data = null;
        lChild = null;
        rChild = null;          
    }
    public TreeNode(T x) {
        data = x;
        lChild = null;
        rChild = null;          
    }       
}

'树的数据类型定义'

public class Tree {
    //其他的一些操作
    ...
}
3.二叉树(Binary Tree)

是一种特殊的树。(普通树是可以和二叉树相互转换)
定义:是 n(n>=0) 个结点的有限集合,该集合或者为空集(称为空二叉树),或者有一个根结点和两棵互不相交的、分别为根结点的左子树和右子树的二叉树组成。

(1)二叉树的特点

1)由于每个结点最多有两棵子树,所以二叉树中不存在度大于2的结点;侧面也证明了,二叉树的度 <= 2
2)左子树和右子树是有顺序的,不可以颠倒
3)即使二叉树中某个结点只有一棵子树,也要区分是左子树还是右子树。

(2)特殊的二叉树

斜树:所有结点都只有左子树或都只有右子树。分别称为左斜树、右斜树
满二叉树(有2个条件):所有的分支结点都有左子树和右子树,且所有的叶子结点在同一层上。

**满二叉树的特点: **

  1. 叶子结点只能出现在最下层
  2. 非叶子结点的度均为 2
  3. 在同样深度的二叉树中,满二叉树的结点个数最多,叶子结点最多

③ 完全二叉树:如果编号为 i(1<= i <= n) 的结点与同样深度的满二叉树中编号为 i 的结点在二叉树中的位置完全相同,则称为完全二叉树。

完全二叉树的特点:
1)树的编号是连续
2)叶子结点只能出现在最下两层
3)最下层的叶子结点一定集中在左部连续的位置

(3)二叉树的性质(理解记忆)

1)在二叉树的第 i 层上至多有 2^(i-1)个结点
2)深度为k的二叉树至多有 2^k-1 个结点(k >= 1)
注:当为满二叉树的时候,结点个数为:2^k-1

3)对任何一颗二叉树T, 如果其终端结点(即叶子结点)数为n0,度为2的结点数为 n2,则有 n0=n2+1;
4)具有 n 个结点的满二叉树的深度为: ** log(n+1)**
5)具有 n 个结点的完全二叉树的深度为: ** [logn]+1**

注意:这是针对于完全二叉树,不是满二叉树
推导过程:

由于深度为 k 的满二叉树的结点个数:n = 2^k-1
所以,深度为 k 的完全二叉树的结点个数:n, 满足:
      2^(k-1)-1 < n <= 2^k-1
由于n是整数,因此
——> 2^(k-1) - 1 < n < 2^k
   ——> 2^(k-1) <= n < 2^k
        —— k-1 < logn <= k
由于k取整数  ——> k = [logn]+1 

6)如果对一棵有 n 个结点的完全二叉树(易知其深度[logn]+1),将其结点按层编号(从第1层到[logn]+1)层,从左到右),对任意结点 i (i<= i <= n)有:
ⅰ) i = 1, 则i为根结点,无双亲,如果i>1, 则其双亲结点编号:[i/2]
ⅱ) 如果 2i>n, 则结点 i 无左孩子(且i为叶子结点);否则其左孩子编号:2i
ⅲ) 如果 2i+1>n, 则无右孩子;否则右孩子的编号:2i+1

(4)二叉树的存储

存储方式和普通树的存储方式还是有很大的差别,尤其是它可以实现顺序存储

1)顺序存储结构

用一维数组存储二叉树中的结点,并且结点的存储位置是可以反映出各个结点之间的逻辑关系。(这点是不同于普通树)

对于"完全二叉树"而言:

┌───┬───┬───┬───┬───┬───┬───┐
│ A │ B │ C │ D │ E │ F │ G │
└───┴───┴───┴───┴───┴───┴───┘

对于"普通的二叉树"可以当成完全二叉树来存储,只是把没有结点的地方设为"^"

┌───┬───┬───┬───┬───┬───┬───┐
│ A │ B │ C │ ^ │ E │ ^ │ G │
└───┴───┴───┴───┴───┴───┴───┘

对于"斜二叉树"而言就有些浪费了空间,因此,这种顺序存储结构比较适合"完全二叉树"

2)链式存储结构

由于二叉树的每个结点最多有2个孩子,因此,其结点可以设计成如下形式:

┌────────┬─────┬────────┐
│ lChild │ data│ rChild │
└────────┴─────┴────────┘

'代码描述'

public class BinTreNode<T> {
    T data; //数据域
    BinTreNode<T> lChild; //左孩子结点指针
    BinTreNode<T> rChild; //右孩子节点指针
    public BinTreNode() {
        this.data = null;
        this.lChild = null;
        this.rChild = null;                 
    }
    public BinTreNode(T x) {
        this.data = x;
        this.lChild = null;
        this.rChild = null;                 
    }
}

注意:如果为了方便找某个结点的双亲结点,就同普通树中处理方式一样,增加一个指向其双亲结点 parent 的指针域即可:

┌──────┬────┬──────┬──────┐
│lChild│data│rChild│parent│
└──────┴────┴──────┴──────┘
(5)遍历二叉树(Traversing binary Tree)

含义:是指从根结点出发,按照某种"次序"依次"访问"二叉树的所有结点并且只被访问一次。

二叉树的遍历不同于线性数据结构:
因为线性数据结构中结点都是有唯一的前驱或后继结点,这使得遍历结果是唯一确定;
然而,在二叉树(普通树也是如此)中每个结点的后继结点不唯一,可以有多种选择,因此选择
不同,遍历结果也就不同了。

二叉树:

        A
      ╱ ╲
     B      C
   ╱    ╱  ╲
  D     E      F
╱  ╲   ╲
G      H    I
二叉树常见的遍历方式:

1)前序遍历: ABDGHCEIF
规则:若二叉树为空,则遍历结果返回空。否则先访问根结点、左子树、右子树

2)中序遍历: GDHBAEICF
规则:若二叉树为空,则遍历结果返回空。否则先从根结点开始(不是访问根结点),中序遍历根结点的左子树、根结点、右子树

3)后序遍历: GHDBIEFCA
规则:若二叉树为空,则遍历结果返回空。否则先从根结点开始(不是访问根结点),后序遍历根结点的左子树、右子树、根结点

4)层序遍历(层次遍历):ABCDEFGHI
规则:若二叉树为空,则遍历结果返回空。否则从树的根结点开始遍历,从上至下,逐层遍历访问

**注意: **
1.前、中、后遍历方式,是针对"根结点"来说的
2.为什么要研究遍历?
因为计算机只会处理线性序列,因此,我们需要研究如何把树这种非线性序列转变为线性序列。
3.已知前序遍历序列和中序遍历序列,可以唯一的确定一颗二叉树
已知后序遍历序列和中序遍历序列,可以唯一的确定一颗二叉树
但是,已知前序和后序遍历序列,无法唯一的确定一颗二叉树

'遍历代码描述':采用递归的方式很容易的完成

/**
 * 二叉树的遍历方式
 */
//前序遍历
public void preOrder(BinaTreNode<T> node) {
    if (node == null) {
        return ;
    }
    //打印根结点
    System.out.print(node.data);
    preOrder(node.lChild);
    preOrder(node.rChild);      
}

//中序遍历
public void inOrder(BinaTreNode<T> node) {
    if (node == null) {
        return ;
    }
    inOrder(node.lChild);
    //打印根结点
    System.out.print(node.data);
    inOrder(node.rChild);       
}

//后序遍历
public void postOrder(BinaTreNode<T> node) {
    if (node == null) {
        return ;
    }
    postOrder(node.lChild);
    postOrder(node.rChild);
    //打印根结点
    System.out.print(node.data);
}

//层序遍历:使用队列来实现层序遍历
public void levelOrder() {
    BinaTreNode<T>[] queue = new BinaTreNode[this.maxNodes];
    int front = -1; //队首指针
    int rear = 0; //队尾指针        
    
    if (this.root == null) {
        return;
    }
    queue[rear] = this.root;//二叉树的根结点进队
    //若队不为空,则继续遍历
    while(rear != front) {
        front ++;
        //打印根结点
        System.out.print(queue[front].data);
        //将队首结点的左孩子进队
        if (queue[front].lChild != null) {
            rear ++;
            queue[rear] = queue[front].lChild;
        }
        //将队首的右孩子也进队
        if (queue[front].rChild != null) {
            rear ++;
            queue[rear] = queue[front].rChild;
        }
    }           
}
4.线索二叉树(Thread BinaryTree)

要是能知道二叉树每一个结点的直接前驱结点或后驱结点是谁,将会为二叉树的其他操作带来方便。但是,二叉树在存储结点的时候,并没有反映出来每一个结点的直接前驱结点或后驱结点是谁。只能在二叉树的某种遍历过程中动态的得到这些信息。

一个具有 n 个结点的二叉树,对于其二叉链表存储,一共有 2n 个指针域(每个结点有左右两个孩子指针域),n-1 个分支线(即两个结点之间连接线),因此,还有 2n-(n-1)=n+1 个指针域是空的,白白的浪费,没有利用。因此,可以考虑使用这些空闲的指针域:
将某个结点空闲的左指针域(lChild) 用来存储该结点在某种遍历下的直接前驱结点
将某个结点空闲的右指针域(rChild) 用来存储该结点在某种遍历下的直接后继结点

我们将这种指向前驱和后继的指针称为线索(Thread),加了线索的二叉树称为线索二叉树

线索二叉树的结点结构:

┌────────┬────────┬──────┬────────┬────────┐
│  ltag  │ lChild │ data │ rChild │ rtag   │
└────────┴────────┴──────┴────────┴────────┘

ltag, rtal 是两个标志位(各只占了 1bit 空间),分别用来表 lChild 和 rChild 是表示左(右)孩子结点还是前驱(后继)结点。

    ┌ 0, 表示左孩子指针

ltag = │
└ 1, 表示前驱结点指针

    ┌ 0, 表示右孩子指针

rtag = │
└ 1, 表示后继结点指针

线索二叉树因遍历顺序不同,获得的线索二叉树也不同:
前序线索二叉树
中序线索二叉树
后序线索二叉树

'结点代码描述'

/**
 * 线索二叉树的结点
 * @author Administrator
 *
 */
public class ThreadedTreNode<T> {
    public T data; //数据域
    public ThreadedTreNode<T> lChild; //左指针域
    public ThreadedTreNode<T> rChild; //右指针域
    //左标志位, 这是为了后面代码方便才写成boolean类型的
    public boolean ltag; //true表示为前驱结点指针
    //右标志位, 这是为了后面代码方便才写成boolean类型的
    public boolean rtag; //true表示后继结点指针
    
    public ThreadedTreNode() {
        data = null;
        lChild = null;
        rChild = null;
        ltag = false; //默认表示左右孩子
        rtag = false;       
    }
    
    public ThreadedTreNode(T x) {
        data = x;
        lChild = null;
        rChild = null;
        ltag = false;
        rtag = false;       
    }
}

'线索二叉树代码描述'

/**
 * 线索二叉树
 * @author Administrator
 *
 */
public class ThreadedTree<T> {
    /**
    * 头结点,只是为了方便操作而增设的.
    * 其结构与其他线索二叉树的结点结构一样,只是数据域不存放信息,其
    * 左指针指向二叉树的根结点,右指针指向自己。
    * 而原二叉树在某种遍历下的第一个结点的前驱线索和最后一个结点的后继线索
    * 都指向该头结点
    */
    private ThreadedTreNode<T> head; //
    private ThreadedTreNode<T> pre; //表示刚刚访问过的结点
    
    //创建一棵包含头结点的线索二叉树
    public ThreadedTree() {
        this.head = new ThreadedTreNode<T>();       
    }
    
    /**
     * 通过中序遍历的序列对二叉树进行线索化
     * @return
     */
    public boolean startInThreading() {
        if(head == null) {
            return false;           
        }
        //设置head结点为头结点,其左子结点指向根结点
        head.ltag = false; 
        head.rtag = true;
        head.rChild = head; //头结点的右指针指向自身。
        if(head.lChild == null) {
            //若二叉树为空,则左指针指向自身
            head.lChild = head;
        } else {
            //pre始终指向刚刚访问过的结点。
            pre = head; //设置默认的前驱结点
            inThreading(head); //按中序遍历进行中序线索化
            //对最后一个结点线索化
            pre.rChild = head;
            pre.rtag = true;
        }
        
        return true;        
    }
    
    //中序完成二叉树线索化
    private void inThreading(ThreadedTreNode<T> p) {
        //p表示指向当前结点
        if(p == null) {
            return;         
        }
        inThreading(p.lChild); //左子树线索化
        
        if(p.lChild == null) {
            //表明当前结点的没有左孩子(左指针域为空),因此,该结点是有前驱结点的。
            // 此时,其前驱结点 pre 刚刚被访问过
            //线索化
            p.ltag = true; //表明左指针是前驱结点指针
            p.lChild = pre;         
        }
        
        // 由于此时p结点的后继还没有被访问到,只能对他的前驱结点pre的右指针进行判断
        if(pre.rChild == null) {
            //表明 p 是 pre 的后继
            pre.rtag = true;
            pre.rChild = p;         
        }
        
        pre = p; //保持 pre 指向 p 的前驱      
        inThreading(p.rChild); //右子树线索化     
    }
    
    //遍历二叉线索树
    public void traversing() {
        ThreadedTreNode<T> node = head.lChild;
        if(node == null) {
            return;         
        }
        while(!node.ltag) {
            //寻找中序序列的首结点
            node = node.lChild;
            do {
                if (node != null) {
                    System.out.println(node.data);
                    node = searchPostNode(node);
                }
            } while (node.rChild != head);
        }
    }
    /**
     * 寻找中序的后继结点
     * @param node
     * @return
     */
    public ThreadedTreNode<T> searchPostNode(ThreadedTreNode<T> node) {
        ThreadedTreNode<T> q = node.rChild;
        if (!node.rtag) {
            while(!q.rtag) {
                q = q.lChild;               
            }
        }
        return q;       
    }
    
    /**
     * 寻找中序的前继结点
     * @param node
     * @return
     */
    public ThreadedTreNode<T> searchPreNode(ThreadedTreNode<T> node) {
        ThreadedTreNode<T> q = node.lChild;
        if (!node.ltag) {
            while(!q.ltag) {
                q = q.rChild;               
            }
        }
        return q;       
    }
}
5.普通树、森林、二叉树之间的转换

(1)转换
1)树 ——> 二叉树
步骤:1) 在所有的兄弟之间加一条连线
2) 对树中每一个结点,只保留它与第一个孩子的连线,删除与其他孩子的连线
3) 层次调整。简单的理解:想像用手捏住根结点往起来一提溜,靠重力下垂,
便可得到调整后的层次

2)森林 ——> 二叉树
步骤:1) 把每个树转换为二叉树
2) 第一个二叉树不动,从第二棵开始,依次把后一棵二叉树的根结点作为前一棵根结点的右孩子

3)二叉树 ——> 树
是上面树到二叉树的逆过程
4)二叉树 ——> 森林
如果这棵二叉树有右孩子,那么该二叉树就能转换为森林是上面森林到二叉树的逆过程

(2)树与森林的遍历
1)树的遍历
先根遍历 (类似先序遍历)
后跟遍历 (类似后跟遍历)
2)森林遍历
前序遍历(先访问第一棵树,每棵树内用先根遍历)
后序遍历(先访问第一棵树,每棵树内用后跟遍历)

注意:森林的前序遍历和二叉树的前序遍历结果相同
森林的后序遍历和二叉树的中序遍历结果相同

因此,当以二叉链表来存储树时,其先根遍历和后根遍历算法完全同二叉树的前序遍历和后序遍历

这样就可以将树和森林这种复杂问题进行简单处理

6.二叉树的应用:Huffman树与Huffman编码

(1)几个概念:
1)路径长度:从树中一个结点到另一个结点之间的分支(其实就是结点之间的连线)构成两个结点之间的路径,而把这条路径上的的分支(即连线)数目(之和)称做路径长度。
注意:"路径长度" 是针对任意两个结点间而言的

2)树的路径长度:指从树根到每一个结点的路径长度之和(对就是字面意思_)
3)结点的带权路径长度:该结点到树根结点之间的路径长度与该结点上权值的乘积
4)树的带权路径(WPL):树中所有叶子结点的带权路径之和

WPL = ∑ W(k)*L(K)

其中,W(k)为叶子结点的权值,L(k)为叶子结点的路径长度

5)Huffman树:把WPL最小的二叉树称为Huffman树

(2)如何构造Huffman树 ?

根据Huffman树的定义知:要想使WPL最小,必须是权值越大的叶子结点越靠近根结点,而权值越小的叶子结点越远离根结点。

基本思想如下:
ⅰ)把所有包含权值的数据元素(w1, w2, ..., wn)看成离散的叶子结点,并组成"结点集合": F={w1, w2, ..., wn}

ⅱ)从集合中选取权值最小的和次小的两个叶子结点作为左右子树构造成一棵新的二叉树,则该二叉树的根结点(记为,R(i),i表示第i个合成的根结点 )的权值为其左右子树根结点的权值之和

ⅲ)从结点集合中剔除刚选取过的作为左右子树的那两个叶子结点,并将新构建的二叉树的根结点(为R(i) )加入到结点集合中。

ⅳ)重复(ⅱ)(ⅲ)两步,当集合中只剩下一个结点时,该结点就是所建立的Huffman树的根结点,该二叉树便为Huffman树

注意:对于一组给定的叶子结点所组成的Huffman树,其树形可能不相同,但其WPL一定是相等的,且为最小

(3)Huffman编码

Huffman树最早是用于优化电文编码的。减小电文编码长度,节约存储或传输成本。

如:A B   C   D   E   F (字符,即叶子结点)
    27  8   15  15  30  5 (字符出现的频率或权值)

构造Huffman树
将Huffman树的左分支代表0,右分支代表1
    则,相应的Huffman编码:

    A     B      C      D   E     F
    01  1001    101     00  11  1000
    
                ○
            ╱     ╲
          ╱         ╲
        (42)           (58)
      ╱   ╲       ╱  ╲
    D(15)   A(27)  (28)   E(30)
                  ╱  ╲
                (13)  C(15)
               ╱  ╲
             F(5)  B(8)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • 数据结构和算法--二叉树的实现 几种二叉树 1、二叉树 和普通的树相比,二叉树有如下特点: 每个结点最多只有两棵子...
    sunhaiyu阅读 6,433评论 0 14
  • B树的定义 一棵m阶的B树满足下列条件: 树中每个结点至多有m个孩子。 除根结点和叶子结点外,其它每个结点至少有m...
    文档随手记阅读 13,201评论 0 25
  • 1.树的定义 树是n(n>=0)个结点的有限集.n=0时称为空树.在任意一颗非空树种:(1)有且仅有一个特定的称为...
    e40c669177be阅读 2,806评论 1 14
  • 第一章 绪论 什么是数据结构? 数据结构的定义:数据结构是相互之间存在一种或多种特定关系的数据元素的集合。 第二章...
    SeanCheney阅读 5,758评论 0 19
  • 前面讲到的顺序表、栈和队列都是一对一的线性结构,这节讲一对多的线性结构——树。「一对多」就是指一个元素只能有一个前...
    Alent阅读 2,229评论 1 28