博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C Primer+Plus(十七)高级数据表示(三)
阅读量:6333 次
发布时间:2019-06-22

本文共 5009 字,大约阅读时间需要 16 分钟。

第十七章 高级数据表示

四、二叉搜索树

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

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

 

转载于:https://www.cnblogs.com/tsembrace/p/3186821.html

你可能感兴趣的文章
Hadoop MapReduce编程 API入门系列之分区和合并(十四)
查看>>
判断二叉树是否平衡、是否完全二叉树、是否二叉排序树
查看>>
并查集的应用之求解无向图中的连接分量个数
查看>>
7个神奇的jQuery 3D插件
查看>>
在线浏览PDF之PDF.JS (附demo)
查看>>
波形捕捉:(3)"捕捉设备"性能
查看>>
AliOS Things lorawanapp应用介绍
查看>>
美国人的网站推广方式千奇百怪
查看>>
java web学习-1
查看>>
用maven+springMVC创建一个项目
查看>>
linux设备驱动第四篇:以oops信息定位代码行为例谈驱动调试方法
查看>>
redis知识点整理
查看>>
Hello World
查看>>
Spring3全注解配置
查看>>
ThreadLocal真会内存泄露?
查看>>
IntelliJ IDEA
查看>>
低版本mybatis不能用PageHeper插件的时候用这个分页
查看>>
javaweb使用自定义id,快速编码与生成ID
查看>>
[leetcode] Add Two Numbers
查看>>
elasticsearch suggest 的几种使用-completion 的基本 使用
查看>>