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); } }