数据结构与算法—二叉树(tree)

目录

二叉树

二叉查找树

1、二叉树的查找

2、二叉树的插入

3、二叉树的删除

与散列表相比,二叉树的优势

平衡二叉树(AVL)

红黑树

几种二叉树的比较


二叉树

如何存储一颗二叉树

1、使用链表,链表的节点存储  左右节点+数据

2、使用数组的顺序存储法,(左子节点 2*i 右子节点 2*i+1 仅仅浪费了下标为0的存储位置)

        如果不是完全二叉树,浪费的空间比较多。

二叉树的遍历:前序遍历,中序遍历,后序遍历;

二叉查找树

树的任意一个节点,左子树的值小于这个节点的值,而右子树的值,大于这个节点的值。

1、二叉树的查找

  1. 先取根节点,如果根节点等于要查找的数据,返回;
  2. 如果根节点数据比要查找的数小,那就在右子树中递归查找;否则在左子树中递归查找;
struct node *find(struct root *root,unsigned long data)
{
        struct node * n = root->r;
        while(n)
        {
                if(n->data == data)
                        return n;
                if(data < n->data)
                        n = n->left;
                else
                        n = n->right;
        }
        return NULL;
}

2、二叉树的插入

  1. 如果插入的数据比节点数据大,并且节点的右子树为空,就将新数据直接插到右节点位置;
  2. 如果右节点不为空,再次递归遍历右子树,查找插入位置。
  3. 同理,如果要插入的数据比节点数小,并且左子树为空,插入到左子树的位置。
  4. 如果不为空,就再遍历左子树,找到插入位置。 
void insert(struct root *root,struct node *new)
{
        struct node *parent;
        if(!root->r){
                root->r = new;
                return;
        }
        parent = root->r;

        while(1)
        {
                if(new->data == parent->data)
                        break;
                if(new->data < parent->data)
                {
                        if(!parent->left)
                        {
                                parent->left = new;
                                break;
                        }
                        parent = parent->left;

                }
                else
                {
                        if(!parent->right)
                        {
                                parent->right = new;
                                break;
                        }
                        parent = parent->right;
                }
        }
}

3、二叉树的删除

  1. 如果要删除的节点只有一个子节点,只需要更新父节点中,指向要删除节点的指针,让它指向要删除节点的子节点就可以了。
  2. 如果要删除的节点没有子节点,只需要直接将父节点中,指向要删除节点的指针为Null
  3. 如果要删除的节点有两个子节点。需要找到这个节点的右子树中的最小节点,把它替换到要删除的节点上,然后再删除这个最小节点;因为最小节点肯定没有左子节点。
     
struct node * delete(struct root *root,unsigned long data)
{
        struct node *n = root->r,**p = &root->r;
        struct node *child;

        while(n && n->data != data)
        {
                if(data < n->data)
                {
                        p = &n->left;
                        n = n->left;
                }
                else
                {
                        p = &n->right;
                        n = n->right;
                }
        }

        if(!n)
                return NULL;

        if(n->left && n->right)
        {
                struct node *rn = n->right,**rp = &n->right;
                while(rn->left)
                {
                        rp = &rn->left;
                        rn = rn->left;
                }

                n->data = rn->data;
                n = rn;
                p = rp;
        }
        child = n->left ? n->left : n->right;
        *p = child;
        return NULL;
}

中序遍历二叉查找树,可以输出有序的数据序列,时间复杂度O(n),非常高效。

与散列表相比,二叉树的优势

  1. 散列表的数据时无序的,如果要输出有序数据,需要排序;而二叉树,只需要中序遍历,可以在O(n)的时间复杂度内,输出有序序列
  2. 散列表扩容耗时多,遇到冲突时,性能不稳定;平衡二叉查找树性能非常稳定,时间复杂度O(logn)
  3. 散列表查找操作时间复杂度是常量级,但哈希冲突存在,常量不一定比logn小。查找速度不一定比o(logn)块,加上哈希的耗时,也就不一定有平衡二叉树效率高。
  4. 散列表的构造比二叉树要复杂。散列函数,冲突解决,扩容等。平衡二叉查找树值需要考虑平衡问题,方案成熟稳定。
  5. 避免散列冲突,散列装载因子不能太大,特别是开放寻址法解决冲突,不然会浪费时间。 

二叉查找树存在的问题

二叉查找树在频繁的动态更新过程中,可能会出现树的高度远大于logn的情况,从而导致各个操作的效率下降。在极端情况下,二叉树会退化为链表。时间复杂度O(n)

解决复杂度退化问题,需要设计一种平衡二叉查找树红黑树

平衡二叉树(AVL)

平衡二叉树特点

  • 任意一个节点的左右子树的高度相差不能大于1
  • 完全二叉树、满二叉树都是平衡二叉树
  • 非完全二叉树也有可能是平衡二叉树 

查找效率很高,但是维护平衡,每次插入删除都要做调整,复杂 耗时

红黑树

它是一种不严格的平衡二叉查找树,维护成本比AVL树低。

树中的节点,一类被标记为黑色,一类被标记为红色

红黑树特点

  • 根节点是黑色
  • 每个叶子节点都是黑色的空节点,也就是说,叶子节点不存储数据。
  • 任何相邻的节点都不能同时为红色;
  • 每个节点,从该节点到达其叶子节点的所有路径,都包含相同数目的黑色节点。 

红黑树的高度近似2logn,比AVL树高度大了一倍,在性能上下降并不多

几种二叉树的比较

二叉查找树: 左节点<其节点<右节点,左右子树的高度差可能很大,最大退化成链表
平衡二叉树:避免左右子树高度相差不能大于1,过多的调整 O(logn)
红黑树:不规范的平衡二叉树,根节点是黑色的;

  • 叶子节点都是黑色的空节点;任何相邻的节点都不能同时为红色;
  • 每个字节到达其可达叶子节点的所有路径,都包含相同数据的黑色节点;
  • 插入、删除、查找的时间复杂度O(logn);保证每次插入最多只用三次旋转就能达到平衡。

:顶点大于或者小于左右子树

猜你喜欢

转载自blog.csdn.net/WANGYONGZIXUE/article/details/129250678