数据结构之二叉树

1、基本概念

  二叉树,顾名思义,就是子节点最多只有两个的树。由于最多只有两个,可以区分左右,我们称子节点为左子女和右子女,次序不可颠倒。根据二叉树的结构,我们可以得到以下特性:
1. 第i层的节点个数最多为2^(i-1)(i>=1)个;
2. 深度为h的二叉树最多有2^h -1(h>=1)个节点,最少为h个;
3. 若叶节点的个数为n1,度数为2的节点个数为n2,则n1=n2+1;

2、特殊结构

  当二叉树满足一些特殊的条件时,会形成特殊的结构,可以很好的作为其他数据结构的底层结构,目前有如下几种特殊情况:
1. 完全二叉树。若二叉树的高度为h,则除了第h层的其他层都达到该层节点的最大个数,同时第h层的节点是从左至右连续排列的。由于节点顺序是紧密排列的,可以使用序列容器进行存储,主要应用于二叉堆。以下是完全二叉树的特性,设定根节点的索引位置为1:
* 对于索引位置为i的节点,其到根节点的节点总数也为i;
* 对于索引位置为i的节点,其左子女的索引位置为2*i,右子女的索引位置为2*i+1;
* 对于索引位置为i(i>1)的节点,其父节点的索引位置为int(i/2);
* 对于索引位置为i的节点,若节点总数为n且i>int(n/2),则该节点为叶子节点;
* 对于索引位置为i的节点,若节点总数为n且int((n-1)/2)>i,则该节点必有两个子节点;
* 对于任意节点,若其右子树的深度为d,则左子树的深度为d或d+1;
* 若子节点总数为n,则叶子节点个数为l= math.ceil(n/2)。
2. 满二叉树。二叉树的每一层的节点个数都达到最大个数,因而满二叉树一定是完全二叉树,但完全二叉树不一定是满二叉树。
3. 平衡二叉树。对于树中的任意节点,其左右子树的高度相差不超过1。平衡二叉树要保证高度平衡,避免出现链表式的树。

3、查找节点

  在树中查找节点,就是采用递归方法,从根节点开始,一次判读根节点、左子树、右子树,从而找的对应的节点,一下是实现代码:

bool BTree::contains(int key, BTreeNode* &node)
{
    if (node == NULL)
    {
        return false;
    }

    if (key == node->data)
    {
        return true;
    }

    return contains(key,node->leftChild) || contains(key,node->rightChild);
}

4、插入节点

  二叉树在插入时是没有固定规则,可自行定义,下面我使用构造满二叉树的方式进行插入,这里通过queue找到节点的插入位置,代码如下:

void BTree::insert(int key, BTreeNode* &node)
{
    if (node == NULL)
    {
        node = new BTreeNode(key);
    }
    else
    {
        //按照插入顺序放置
        queue<BTreeNode*> q;
        q.push(node);
        while(!q.empty())
        {
            BTreeNode* temp = q.front();
            q.pop();

            if (temp->leftChild == NULL)
            {
                temp->leftChild = new BTreeNode(key);
                return;
            }
            if (temp->rightChild == NULL)
            {
                temp->rightChild = new BTreeNode(key);
                return;
            }

            q.push(temp->leftChild);
            q.push(temp->rightChild);
        }
    }
}

5、删除节点

  二叉树删除节点也是没有规则的,但不管使用什么规则,都要考虑下面三种情况:
1. 删除节点没有子节点。这种情况最简单,将该节点的父节点指向该节点的指针置为空,然后删除该节点即可;
2. 删除节点有一个子节点。此时将该节点的父节点指向该节点的指针置为指向改节点的子节点,然后删除该节点;
3. 删除节点有两个子节点。这里采用的策略是先查找到该节点的右子树中的最后一个节点,然后将查找到的节点的键值与该节点的键值进行交换,最后使用递归方法删除查找到的节点。
  下面是二叉树删除的实现代码:

void BTree::remove(int key, BTreeNode* &node)
{
    if (node == NULL)
    {
        return;
    }

    if (key == node->data)
    {
        if (node->leftChild != NULL && node->rightChild != NULL)
        {
            //查找子树的最后一个节点
            queue<BTreeNode*> q;
            q.push(node->leftChild);
            q.push(node->rightChild);
            while(true)
            {
                BTreeNode* curNode = q.front();
                q.pop();

                if (curNode->leftChild != NULL)
                {
                    q.push(curNode->leftChild);
                }

                if (curNode->rightChild != NULL)
                {
                    q.push(curNode->rightChild);
                }

                if (q.empty())
                {
                    node->data = curNode->data;
                    curNode->data = key;
                    remove(key,node);
                    return;
                }
            }
        } 
        else
        {
            BTreeNode* temp = node;
            node = (node->leftChild != NULL)?node->leftChild:node->rightChild;
            delete temp;
            return;
        }
    }

    remove(key,node->leftChild);
    remove(key,node->rightChild);
}

6、清理操作

  若要清理整棵树的节点,可以使用递归的方法清理节点的左右子树,然后释放节点自己,下面是实现代码:

void BTree::clear(BTreeNode* &node)
{
    if (node == NULL)
    {
        return;
    }
    clear(node->leftChild);
    clear(node->rightChild);
    delete node;
    node = NULL;
}

7、综合结果

  下面,我们对算法进行测试,结果如下:

BTree* tree = new BTree();
for (int i = 0; i < 10; ++i)
{
    tree->insert(i);
}

cout<<"contains 5:"<<tree->contains(5)<<endl; //contains 5:1
cout<<"contains 10:"<<tree->contains(10)<<endl; //contains 10:0

BTreeNode* minNode = tree->findMin();
if (minNode != NULL)
{
    cout<<"findMin:"<<minNode->data<<endl; //findMin:0
}
BTreeNode* maxNode = tree->findMax();
if (minNode != NULL)
{
    cout<<"findMax:"<<maxNode->data<<endl; //findMax:9
}

tree->breadthFirstTravel();  //bft:0 1 2 3 4 5 6 7 8 9
tree->depthFirstTravel_DLR(); //depthFirstTravel_DLR:0 1 3 7 8 4 9 2 5 6
tree->depthFirstTravel_LDR(); //depthFirstTravel_LDR:7 3 8 1 9 4 0 5 2 6
tree->depthFirstTravel_LRD(); //depthFirstTravel_LRD:7 8 3 9 4 1 5 6 2 0

tree->remove(1);
tree->breadthFirstTravel();  //bft:0 9 2 3 4 5 6 7 8
tree->depthFirstTravel_DLR(); //depthFirstTravel_DLR:0 9 3 7 8 4 2 5 6
tree->depthFirstTravel_LDR(); //depthFirstTravel_LDR:7 3 8 9 4 0 5 2 6
tree->depthFirstTravel_LRD(); //depthFirstTravel_LRD:7 8 3 4 9 5 6 2 0

tree->clear();
tree->breadthFirstTravel();  //bft:

8、效率分析

  二叉树的查找操作需要遍历整棵树,其算法复杂度为O(n);插入操作根据插入策略的不同而不同的,平均查找到插入位置的算法复杂度为O(logn);删除操作由于要先查找到节点,其算法复杂度以查找操作一致。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值