数据结构之tree

1、基本概念

  tree是节点的集合。这个集合可以是空集;若不是空集,则有一个根节点和零个或多个非空子树,每个子树都被根节点使用一条有向边所连接。
  由树的定义,我们可以知道,树是由节点和有向边组成的,根据树的结构,我们有以下衍生的定义:
- 整棵树最上层的节点称为根节点(root);
- 连接两个节点的称为有向边(edge);
- 有向边的头称为父节点(parent),尾称为子节点(child);
- 没有子节点的节点称为叶节点(leaf);
- 若子节点的最大个数为n,则该树称为n叉树,即最多有两个子节点的树称为二叉树;
- 拥有相同父节点的子节点之间互为兄弟节点(sibling);
- 根节点至任何节点有唯一的路径(path),路径所经过的边的个数称为路径的长度(length);
- 根节点至任一节点的路径长度,称为该节点的深度(depth),则根节点的深度为0;
- 某一节点到其最深的子节点的路径长度称为该节点的高度(height),则树的高度为根节点的高度;
- 若节点a和节点b之间存在一条路径,则a称为b的祖代(ancestor),b称为a的子代(descendant);
- 某一节点和其子节点的个数之和称为该节点的大小(size)。

2、实现结构

  树最直接的实现结构就是有节点数据和指向子节点的链组成,但是子节点的个数是不确定的,如果直接链接,将造成空间的巨大浪费。目前典型的结构定义如下:

struct TreeNode
{
    TreeNode()
    {
        this->data = 0;
        this->firstChild = NULL;
        this->nextSibling = NULL;
    }
    TreeNode(int data)
    {
        this->data = data;
        this->firstChild = NULL;
        this->nextSibling = NULL;
    }

    int data;
    TreeNode* firstChild;
    TreeNode* nextSibling;
};

  这里通过firstChild访问到子节点链表的表头,通过nextSibling是子节点形成链表。这样通过有限的结构,就可满足树无限的扩展。

3、节点遍历

  要访问到树中的每个节点,根据不同的顺序得到的结果是不一样的,目前有一下几种遍历方法:

  • 广度优先遍历。这种遍历方法,按照层次去遍历,依次遍历根节点,子节点及其兄弟节点,然后是下一层的子节点和兄弟节点,以下是实现算法:
void breadthFirstTravel(TreeNode* root)
{
    cout<<"bft:";
    queue<TreeNode*> q;
    q.push(root);
    while (!q.empty())
    {
        TreeNode* node = q.front();
        q.pop();

        while (node != NULL)
        {
            cout <<node->data<<" ";
            if (node->firstChild)
            {
                q.push(node->firstChild);
            }
            node = node->nextSibling;
        }
    }
    cout<<endl;
}
  • 深度优先遍历。这种遍历方法,是沿着树的深度进行遍历,当节点v的的所有子节点都已经访问的后,将回溯到v的父节点继续搜索,不断重复以上过程直到所有节点都被访问到。这里节点的访问顺序不同,得到的结果也是不一致的,目前有三种访问顺序:
    1. 前序遍历。这种顺序,先访问根节点,然后访问其他子树,算法如下:
void depthFirstTravel_DLR(TreeNode* root)
{
    cout<<"dft_DLR:";
    stack<TreeNode* > s;
    s.push(root);
    while (!s.empty())
    {
        TreeNode* node = s.top();
        s.pop();

        cout<<node->data<<" ";

        //记录右兄弟
        if (node->nextSibling)
        {
            s.push(node->nextSibling);
        }

        //记录第一个子节点
        if (node->firstChild)
        {
            s.push(node->firstChild);
        }
    }
    cout<<endl;
}

  这里是先访问根节点,然后是第一个子树,最后是该子树的兄弟子树。由于需要回溯节点,我们需要借助stack。
2. 中序遍历。这种顺序是先访问第一个子树,然后访问根节点,最后子树的兄弟子树。算法如下:

void depthFirstTravel_LDR(TreeNode* root)
{
    //先遍历第一个子女
    TreeNode* child = root->firstChild;
    if (child)
    {
        depthFirstTravel_LDR(child);
    }

    //访问自己
    cout<<root->data<<" ";

    //遍历其他子女
    if (child)
    {
        TreeNode* sibling = child->nextSibling;
        while (sibling)
        {
            depthFirstTravel_LDR(sibling);
            sibling = sibling->nextSibling;
        }
    }
}

  这里直接使用了递归的实现,看起来更加简洁,但递归也是用stack实现的,实际本质是一样的。
3. 后序遍历。这种顺序先遍历所有的子树,然后再访问根节点。算法如下:

void depthFirstTravel_LRD(TreeNode* root)
{
    //先遍历所有子女
    TreeNode* child = root->firstChild;
    if (child)
    {
        depthFirstTravel_LRD(child);

        TreeNode* sibling = child->nextSibling;
        while (sibling)
        {
            depthFirstTravel_LRD(sibling);
            sibling = sibling->nextSibling;
        }
    }

    //访问自己
    cout<<root->data<<" ";
}

  以上是三种遍历顺序,下面是测试代码:

void treeTest()
{
    TreeNode *a = new TreeNode(1);
    TreeNode *b = new TreeNode(2);
    TreeNode *c = new TreeNode(3);
    TreeNode *d = new TreeNode(4);
    TreeNode *e = new TreeNode(5);
    TreeNode *f = new TreeNode(6);
    TreeNode *g = new TreeNode(7);
    TreeNode *h = new TreeNode(8);
    TreeNode *i = new TreeNode(9);

    a->firstChild = b;

    b->firstChild = f;
    b->nextSibling = c;

    c->firstChild = h;
    c->nextSibling = d;

    d->nextSibling = e;

    f->nextSibling = g;

    h->nextSibling = i;

    breadthFirstTravel(a); //bft:1 2 3 4 5 6 7 8 9

    depthFirstTravel_DLR(a);    //dft_DLR:1 2 6 7 3 8 9 4 5

    cout<<"dft_LDR:";
    depthFirstTravel_LDR(a);    //dft_LDR:6 2 7 1 8 3 9 4 5
    cout<<endl;

    cout<<"dft_LRD:";
    depthFirstTravel_LRD(a);    //dft_LRD:6 7 2 8 9 3 4 5 1
    cout<<endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值