C的抽象数据类型:二叉树

1 二叉树

  二叉树的每个节点都包含一个项用来存储数据,以及两个指向其他子节点的指针用来链接结构;是一种二分查找的树形结构;

  当数据按顺序排列时,使用二叉树从中间的节点开始查找,每次都能排除一半的数据量,效率较高;只是编程较为复杂。

  不过当二叉树子树的排列不平衡时,查找效率挺低的;可能还不如链表实用;

  定义:二叉查找树的每个节点都有两个子树,左子树的节点都在父节点的前面,右子树的节点都在父节点的后面,子树允许为空,但不允许相同;

2  结构体定义

//定义二叉树项数最大值为10,如果size==MAXITEMS,表示二叉树满了
#define MAXITEMS 10  
typedef struct item
{
    char petname[20];
    char petkind[20];
} Item;

/*二叉树结构体:Tree,只要一个根节点就可以确定二叉树的位置了*/
typedef struct tree
{
    Node * root; //根节点          
    int size;    //节点数size,通过size来判断二叉树的项数,满,空;          
} Tree;

/*二叉树节点:Node******_____________________________************
**********************|*********节点的数据项*********|***********
**********************|**左子节点指针***右子节点指针**|***********/
typedef struct node
{
    Item item;
    struct node * left;    
    struct node * right;  
} Node;

/*删除节点时,用来存储被删除节点的地址和被删除节点的父节点;*/
typedef struct pair {
    Node * parent;
    Node * child;
} Pair;

3 向二叉树中添加项

//向二叉树中添加新的节点,新节点的数据为 const Item * pi;
bool AddItem(const Item * pi, Tree * ptree)
{
    Node * new_node;

    if  (TreeIsFull(ptree))     //1,检查树是否已满;
    {
        fprintf(stderr,"列表存储已满\n");
        return false;             
    }
    if (SeekItem(pi, ptree).child != NULL)  //2,检查树是否已有相同项;
    {
        fprintf(stderr, "二叉树的项要求不重复\n");
        return false;             
    }
    new_node = MakeNode(pi);      //3,创建新节点;
    if (new_node == NULL)
    {
        fprintf(stderr, "Couldn't create node\n");
        return false;             
    }
    ptree->size++;

    //4,添加新节点
    if (ptree->root == NULL)      
        ptree->root = new_node;//如果二叉树为空,则直接添加节点;   
    else                          
        AddNode(new_node,ptree->root); //如果二叉树不为空,递归查找添加节点;
    return true;                 
}

static Node * MakeNode(const Item * pi)
{
    Node * new_node;
    new_node = (Node *) malloc(sizeof(Node));
    if (new_node != NULL)
    {
        new_node->item = *pi;//将数据放入新节点的item中;
        new_node->left = NULL;//因为是新节点,还没有子节点;
        new_node->right = NULL;
    } 
    return new_node;
}

//对于添加节点的函数,可以使用递归来添加;
//先比较节点的数据项,看看当前节点是属于左节点还是右节点;
//如果左子节点或右子节点刚好为空,就可以直接存入;否则递归调用直到找到空子节点; 
static void AddNode (Node * new_node, Node * root)
{   //先new_node->item表示item变量,然后&new_node->item表示传入item变量的地址;
    if (ToLeft(&new_node->item, &root->item))       //表示新节点在父节点的左边
    {
        if (root->left == NULL)        //1.刚好root节点的左子节点为空,
            root->left = new_node;     //于是直接放进去; 
        else  //2.root的两个子节点都不为空,不知道新节点应该放在哪个子节点里;
            AddNode(new_node, root->left);          //递归调用继续比较;
    }
    else if (ToRight(&new_node->item, &root->item)) //表示新节点在父节点的右边
    {
        if (root->right == NULL)
            root->right = new_node;
        else
            AddNode(new_node, root->right);
    }
    else     //新节点与父子节点相等,二叉树的节点数据要求不能重复,返回错误;
    {
        fprintf(stderr,"location error in AddNode()\n");
        exit(1);
    }
}
//对于这种用来比较数据的指针,可以给指针加个const,防止数据误修改;
static bool ToLeft(const Item * i1, const Item * i2)
{
    int comp1;
//字符串比较,strcmp();返回值映射i1 ? i2中间的比较符;
    if ((comp1 = strcmp(i1->petname, i2->petname)) < 0)
        return true;
    else if (comp1 == 0 &&
                strcmp(i1->petkind, i2->petkind) < 0 )
        return true;
    else
        return false;
}

static bool ToRight(const Item * i1, const Item * i2)
{
    int comp1;
    if ((comp1 = strcmp(i1->petname, i2->petname)) > 0)
        return true;
    else if (comp1 == 0 &&
                strcmp(i1->petkind, i2->petkind) > 0 )
        return true;
    else
        return false;
}

4 向二叉树中删除项

//删除二叉树中,节点数据为const Item * Pi的节点;
bool DeleteItem(const Item * pi, Tree * ptree)
{
    Pair look;//child存储待删除节点的地址,parent存储待删除节点的父节点;
    
    look = SeekItem(pi, ptree);//1,查找该节点的地址;
    if (look.child == NULL)  //找不到该节点;节点都没有,自然不用删除;
        return false;
 
    if (look.parent == NULL) //如果该节点是二叉树的根节点;
        DeleteNode(&ptree->root);//删除根节点;
    else if (look.parent->left == look.child)  //如果该节点是根节点的左子节点
        DeleteNode(&look.parent->left);//删除根节点的左子节点;
    else
        DeleteNode(&look.parent->right);//删除根节点的右子节点;
    ptree->size--;      //二叉树节点统计减1

    return true;
}

/*查找一下item项在二叉树中的位置,
**返回的look.parent为查找项的父节点;
**返回的look.child为null或item项,是item在二叉树中的位置;*/
static Pair SeekItem(const Item * pi, const Tree * ptree)
{
    Pair look;
    look.parent = NULL;
    look.child = ptree->root;
    if (look.child == NULL)
        return look;        
 
    while (look.child != NULL)
    {
        if (ToLeft(pi, &(look.child->item)))
        {
            look.parent = look.child;
            look.child = look.child->left;
        }
        else if (ToRight(pi, &(look.child->item)))
        {
            look.parent = look.child;
            look.child = look.child->right;
        }
        else       /* must be same if not to left or right    */
            break; /* look.child is address of node with item */
    }
 
    return look;  
    //数据所在根节点为look.parent;look.child为NULL,为数据应该插入的位置节点;
}

/*传入参数是待删除节点在父节点中的位置,为2级指针来着;
**因为Node *指针本身需要修改,如果只传入Node * ptr,那么修改之后的指针还要返回给父节点;
**为了方便,我们把被修改指针的地址也传进来,然后把修改后的指针放到该地址里;*/
static void DeleteNode(Node **ptr)
{
    Node * temp;
    puts((*ptr)->item.petname);//不知道为什么要打印字符串,感觉然并软;
    if ( (*ptr)->left == NULL)//如果待删除节点的左子节点为空;
    {
        temp = *ptr;
        *ptr = (*ptr)->right;//那只要把待删除节点的右子节点放到待删除节点的父子节点上;
        free(temp);//于是可以删除节点了,节点剩下的二叉树也接到父节点上了;
    }
    else if ( (*ptr)->right == NULL)
    {
        temp = *ptr;
        *ptr = (*ptr)->left;
        free(temp);
    }
    else//如果待删除节点的两个节点都有子树;
    {
    //先找到左子树最右边的NULL;
        for (temp = (*ptr)->left; temp->right != NULL;temp = temp->right)
            continue;
        //然后把右子树连接上NULL;此时的左子树包含了左子树和右子树;
        temp->right = (*ptr)->right;
        temp = *ptr;
        *ptr =(*ptr)->left;
        free(temp);
    }
}

static void DeleteAllNodes(Node * root)
{
    Node * pright;

    if (root != NULL)
    {
        pright = root->right;
        DeleteAllNodes(root->left);
        free(root);
        DeleteAllNodes(pright);
    }
}

5 一些简单的使用函数

/*初始化新的二叉树结构体;*/
void InitializeTree(Tree * ptree)
{
    ptree->root = NULL;
    ptree->size = 0;
}
/*判断二叉树的空,满,项数*/
bool TreeIsEmpty(const Tree * ptree)
{
    if (ptree->root == NULL)
        return true;
    else
        return false;
}
bool TreeIsFull(const Tree * ptree)
{
    if (ptree->size == MAXITEMS)
        return true;
    else
        return false;
}
int TreeItemCount(const Tree * ptree)
{
    return ptree->size;
}

//删除树的所有节点
void DeleteAll(Tree * ptree)
{
    if (ptree != NULL)
        DeleteAllNodes(ptree->root);
    ptree->root = NULL;
    ptree->size = 0;
}

//查看树中是否已有当前项
bool InTree(const Item * pi, const Tree * ptree)
{
    return (SeekItem(pi, ptree).child == NULL) ? false : true;
}

//递归遍历树的节点
void Traverse (const Tree * ptree, void (* pfun)(Item item))
{
    if (ptree != NULL)
        InOrder(ptree->root, pfun);
}
/* 递归遍历节点,通过定义的函数来处理节点数据; */
static void InOrder(const Node * root, void (* pfun)(Item item))
{
    if (root != NULL)
    {
        InOrder(root->left, pfun);
        (*pfun)(root->item);
        InOrder(root->right, pfun);
    }
}
posted @ 2020-06-09 19:49  caesura_k  阅读(370)  评论(0编辑  收藏  举报