• C Primer+Plus(十七)高级数据表示(三)


    第十七章 高级数据表示

    四、二叉搜索树

    先梳理一下链表和数组方式对几种操作的利弊特点:

    a.访问:链表形式,必须从首节点开始找起,直到要访问的节点为止,这个称为顺序访问。而数组方式则方便的多,可直接定位到某个元素,这称为随机访问。

    b.插入/删除:对于链表形式,插入或删除操作仅需要修改前续和后续节点就可以完成;而数组方式需修改被增加或删除元素后面所有的元素。

    c.查找/搜索:其实也是一种访问形式。一般情况,对于顺序排列的“数据堆”,可以通过"折半查找法”非常效率的进行查找。可折半查找,首先得定位到中间的元素,对数组而言,因为有下标,很容易定位;而对于链表定位中间元素,首先要知道数据堆里总共有多少个节点,然后从首节点开始,pnode->next一直操作下去,直至定位到中间元素,再进行比较。

    如果需要一种既方便插入/删除,又方便搜索查找的数据堆形式,那该选用哪种形式呢?我们暂时不必去想如何去设计,去想出这样一种形式我们已经有了答案:二叉树。

    二叉树的特点:每个节点除去item本身信息外,描述该节点还需要两个指针成员,分别为左指针,右指针。和单链表不一样的是,二叉树每个节点链接下去有两个后续节点。

    为使搜索工作可以利用“折半法”,使每个节点按一种顺序的关系排列:对于节点和他的左子节点、右子节点的关系为:左子节点<节点<右子节点。

    这样的一种二叉树,称为“二叉搜索树”(binary search tree.bst)。

    1、二叉搜索树ADT的数据定义描述

    //项目依据实际所需去定义
    typedef something Item;
    
    //二叉树节点定义
    typedef struct node
    {
        Item item;
        struct node *left;
        struct node *right;
    }Node;
    //树的定义
    //通过定位树的根节点可以找到该数,同时增加树的节点数目丰富树的信息
    typedef struct tree
    {
        Node *root;
        int size;
    }Tree; 

    2、二叉搜索树的操作定义

    (1)初始化一棵树

    void InitializeTree(Tree *ptree)
    {
         ptree->root=NULL;
         ptree->size=0;
    }

    (2)判断树是否为空(判断为满也类似)

    int TreeIsEmpty(Tree *ptree)
    {
        return (ptree->size==0);
    }

    (3)向树中添加一个项目

    因为二叉搜索树中不能含有项目相同的节点,所以这里假设树中没有与被添加项目相同的节点项目,并且假设树非满。添加一个项目的几个主要步骤如下:

    step1:找到合适位置;

    step2:设置一个新节点,将添加项目纳入该节点,并使该节点的两个指针成员为空;

    step3:将该新节点放进合适位置,具体为:使合适位置的父节点与其链接

    其中第2、3步都可以简单实现,关键是第1步,如何找到合适位置?

    考虑二叉搜索树的特征:左<父<右。

    那么,先判断x(设x为要添加的项目)在树根节点的左还是右边;

    假设在左,如果刚好左为空,则找到了合适位置;如果左不为空,那就要继续判断:x再以该左节点为根节点的树的左还是右?依次类推进行下去。这里自然想到递归方法。

    关键函数代码如下:

    //Toleft和ToRight函数为判断左右函数
    //对ToLeft(),若左则true,若右则false;ToRight()类似
    void AddNode(Node *newnode,Node *root)
    {
       if(ToLeft(&(newnode->item),&(root->item))
       {
           if(root->left==NULL)
             root->left=newnode;
           else
             AddNode(newnode,root->left);
       }
       if(ToRight(&(newnode->item),&(root->item))
       {
           if(root->right==NULL)
             root->right=newnode;
           else
             AddNode(newnode,root->right);
        }
    }

    (4)查找项目:查找树中包含相同项目的节点;往往查找项目是为了后续操作服务(比如,删除),因此查找项目的函数须返回该项目的父节点,同时也需要返回包含该项目的节点,若没有找到,该返回项即为NULL。

    要返回两个节点指针,一般函数只能返回某个数据类型的单个变量,这里自然又想到创建一个结构,匹配这两个要返回的数据:

    typedef struct pair
    {
       Node *parent;   //指向父节点
       Node *child;    //指向找到的节点
    }Pair;

     用递归方法实现:

    //未测试,以后检查
    Pair SeekItem(const Item *pi,const Tree *ptree)
    {
       Pair look;
       Tree newtree;
       newtree=*ptree;
       if(*pi==newtree.root->item)
       {
          look.parent=NULL;
          look.child=newtree.root;
          return look;
        }
        else 
           if(ToLeft(pi,&(newtree.root->item)))
           {
                newtree.root=newtree.root->left;
                SeekItem(pi,&newtree);
            }
           if(ToRight(pi,&(newtree.root->item)))
           {
                newtree.root=newtree.root->right;
                SeekItem(pi,&newtree);
            }
            else
            {
                 look.parent=NULL;
                 look.child=NULL;
                 return look;
            }
    }

    用while()方法实现:

    Pair SeekItem(const Item *item,const Tree *ptree)
    {
        Pair look;
        look.parent=NULL;
        look.child=ptree->root;
        while(look.child!=NULL)
        {
             if(ToLeft(item,&(look.child->item)))
             {
                 look.parent=look.child;
                 look.child=look.child->left;
                 continue;
              }
             if(ToRight(item,&(look.child->item)))
             {
                 look.parent=look.child;
                 look.child=look.child->right;
                 continue;
              }
              else
                 break;
          }
          return look;
    }
           

    (4)删除项目:

    第一种情况:被删除节点没有子节点,则将被删除节点的父节点的相应成员设为NULL。

    第二种情况:被删除节点只有一个子节点,考虑二叉搜索树的特性,可以知道要做的只是把被删除节点的子树链接到父节点的相应成员。

    第三种情况:被删除节点有两个子节点。分析二叉搜索树的特性:左、右子树任意节点必定都同时大于或小于被删节点的父节点,因此这两个子树都应被归并到父节点的同一边去;同时右子树任意节点都大于左子树任意节点,因此:

    若被删节点位于父节点左边,说明左右子树均小于父节点,则将其左子节点链接到父节点左边,同时在左子节点为根节点的树的右边寻找空位去添加右子树,这个空位是左子树的右边中第一个空缺的右节点(因为这个位置相对于被删除节点的左子树是最大的位置)。

    若被删节点位于父节点右边,说明左右子树均大于父节点,则将其右子节点链接到父节点右边,同时在右子节点为根节点的树的左边寻找空位去添加左子树,这个空位是右子树的左边中第一个空缺的右节点(因为这个位置相对于被删除节点的右子树是最小的位置)。

    关键函数:

    //删除结点函数,参数如何描述?
    //删除操作后被删除结点的父节点的“指向被删结点的指针成员”要重新赋值
    //所以要用指向“父节点指针成员”的指针去描述
    //而父节点指针成员本身是一个Node*指针型,所以参数类型为Node **
    //注意:这里的ptr是指向父节点指针成员(指向被删结点)的指针类型
    //*ptr描述的是父节点中指向被删结点的指针成员,也即被删结点的地址指针类型
    //**ptr则是描述被删除的结点类型
    static void DeleteNode(Node **ptr)
    {
      Node *temp;
      puts((*ptr)->item.petname);
    //*ptr为被删结点父节点的指针成员,该成员指向被删结点,
    //(*ptr)->left即指向被删结点的左子节点
    //若其左子节点为空,将其右子节点链接到父节点原先指向被删结点的指针成员
      if((*ptr)->left==NULL)   
      {
        temp=*ptr;
        *ptr=(*ptr)->right;
        free(temp);
      }
    //若其右子节点为空,将其左子节点链接到父节点原先指向被删结点的指针成员
      else if((*ptr)->right==NULL)
      {
        temp=*ptr;
        *ptr=(*ptr)->left;
        free(temp);
      }
      else  //被删除结点有两个子结点
      {
        for(temp=(*ptr)->left;temp->right!=NULL;temp=temp->right)
            continue;
        temp->right=(*ptr)->right;
        temp=*ptr;
        *ptr=(*ptr)->left;
        free(temp);
      }
    }

     (5)遍历树

    void InOrder(const Node *root,void(*pfun)(Item item))
    {
       if(root!=NULL)
       {
          InOrder(root->left);
          (*pfun)(root->item);
          InOrder(root->right);
        }
    }
    //pfun函数先处理左节点,再处理根节点,最后处理右节点

    (6)清空树

    void DeleteTree(Tree *ptree)
    {
       if(ptree!=NULL)
             DeleteAllNodes(ptree->root);
      ptree->root=NULL;
      ptree->size=0;
    }
    
    static void DeleteAllNodes(Node *root)
    {
        Node *pright;
        if(root!=NULL)
        {
           pright=root->right;
           DeleteAllNodes(root->left);
           free(root);
           DeleteAllNodes(pright);
         }
    }
  • 相关阅读:
    祖国,让我为您写首歌
    提升信任度,是做网络营销成功的必由之路
    名字作诗已成流行语,你“OUT”了吧?
    腾飞天涯
    沈阳首个文化强市评价体系出炉,为沈阳文化振兴打分
    寄语“官员诗人”车延高:我挺你
    “自己选的路,就算跪着也要走完”引热议
    我在月光下想你
    国庆献礼谁为重
    gcc和g++的区别
  • 原文地址:https://www.cnblogs.com/tsembrace/p/3186821.html
Copyright © 2020-2023  润新知