[笔记]数据结构-树

预备知识

树可以用几种方式定义。

定义树的一种自然的方式是递归的方法。

一棵树是一些节点的集合。

这个集合可以是空集;若非空,则一棵树由称作根的节点r以及0个或多个非空的(子)树组成,这些子树中每一棵的根都被来自根r的一条有向边所连接。

每一颗子树的根叫做根r的儿子,而r是每一棵子树的根的父亲。

从递归的定义中可以看出,一棵树是N个节点和N-1条边的集合,其中的一个节点叫做根。

没有儿子的节点称为树叶。

具有相同父亲的节点称为兄弟。

每个节点到它自己有一条长为0的路径。
从一棵树中从根到每个节点恰好存在一条路径。

对任意节点ni,ni的深度(depth)为从根到ni的唯一路径的长。
因此,根的深度为0。

ni的高(height)是从ni到一片树叶的最长路径的长。 因此所有的树叶的高都是0. 一棵树的高等于它的根的高。

一个树的深度等于它的最深的树叶的深度;
该深度总是等于这棵树的高。

如果存在从ni到nk的一条路径,那么ni是nk的一位祖先而nk是ni的一个后裔。
如果ni≠nk,那么ni是nk的一位真祖先而nk是ni的一个真后裔。

树的实现

因为每个节点的儿子数可以变化很大并且实现不知道,所以将每个节点的所有儿子都放在树节点的链表中。

typedef struct TreeNode *PtrToNode;

struct TreeNode
{
    ElementType Element;
    PtrToNode   FirstChild;
    PtrToNode   NextSibling;
}

树的遍历及应用

树有很多应用。流行的用法之一是包含许多常用操作系统中的目录结构。

先序遍历

在对节点的处理工作是在他的诸多儿子节点被处理之前进行的。

后序遍历

在一个节点处的工作是在他的诸多儿子节点被计算后进行的。

二叉树

二叉树是一棵树,其中每个节点都不能有多于两个的儿子。

二叉树的一个性质是平均二叉树的深度要比N小得多。

实现

树节点的声明在结构上类似与双链表的声明,一个节点就是由key信息加上两个指向其他节点的指针(Left和Right)组成的结构。

应用于联表上的许多法则也可以应用到树上。

typedef struct TreeNode *PtrToNode;
typedef struct PtrToNode Tree;

struct TreeNode
{
    ElementType Element;
    Tree        Left;
    Tree        Right;
}

二叉树的主要用处之一是在编译器的设计领域。

表达式树

表达式树的树叶是操作数,比如常数或变量,而其他的节点为操作符。

我们可以将通过递归计算左子树和右子树所得到的值应用在根处的算符操作中而算出表达树T的值。这种一般的方法(左,节点,右)称为中序遍历。

如果我们另一个遍历策略是先打印出左子树、右子树、然后打印运算符。那么这是称为后序遍历。

再就是我们先打印出运算符,然后我们递归的打印出左子树和右子树。这种遍历策略称为先序遍历。

查找树ADT-二叉查找树

二叉树的一个重要应用是它们在查找中的使用。

使二叉树成为二叉查找树的性质是,对于树中每个节点X,它的左子树中所有关键字值小于X的关键字值,而他的右子树中所有关键字值大于X的关键字值。

二叉查找树的平均深度是O(logN),所以我们一般不同担心栈空间被用尽。

MakeEmpty

这个操作主要用于初始化。

SearchTree MakeEmpty(SearchTree T)
{
    if(T != NULL)
    {
        MakeEmpty(T->Left);
        MakeEmpty(T->Right);
        free(T);
    }
    return NULL;
}

Find

这个操作一般需要返回指向树T中具有关键字X的节点的指针,如果这样的节点不存在则返回NULL。

注意测试的顺序。
关键的问题是我们首先要对是否为空树进行测试,否则就可能在NULL指针上兜圈子。其余的测试应该使得最不可能的情况安排在最后进行。

Position Find(ElementType x, SearchTree T)
{
    if(T == NULL)
        return NULL;
    if(X < T->Element)
        return Find(X, T->Left);
    else if(X > T->Element)
        return Find(X, T->Right);
    else
        return T;
}

FindMin FindMax

返回树中最小元和最大元的位置。
执行FindMin,从根开始并且只要有左儿子就向左执行。终止点是最小的元素。
FindMax类似,不再赘述。

Position FindMin(SearchTree T)
{
    if(T == NULL)
        return NULL
    else if(T->Left == NULL)
        return T;
    else
        return FindMin(T->Left);
Position FindMax(SearchTree T)
{
    if (T!=NULL)
        while(T->Right != NULL)
            T = T->Right;
    return T;

注意语句对程序的改变,如T->Right = T->Right->Right 这样的语句将会产生一些变化。

Insert

为了将X插入到树T中,你可以像用Find那样沿着树查找。如果找到X,则什么也不用做(或做一些更新)。否则将X插入到遍历的路径上的最后一点上。

重复元的插入可以通过在节点记录中保留一个附加域以指示发生的频率来处理。
如果关键字只是一个更大结构的一部分,那么这种方法行不通,此时我们可以把具有相同关键字的所有结构保留在一个辅助数据结构中,如表或是另一棵查找树中。

SearchTree Insert(ElementType X, SearchTree T)
{
    if(T == NULL)
    {
        /* create and return a one-node tree*/
        T = malloc(sizeof(struct TreeNode));
        if(T==NULL)
            FatalError("Out of space!!");
        else
        {
            T->Element = X;
            T->Left = T->Right = NULL;
        }
    }
    else if (X < T->Element)
        T->Left = Insert(X, T->Left);
    else if( X > T->Element )
        T->Right = Insert(X, T->Right);
    /*Else X is in the tree already;we'll do nothing*/
    return T;

Delete

一旦发现要被删除的节点,我们就需要考虑几种可能的情况。
如果节点是一片树叶,那么它可以被立即删除。
如果节点有一个儿子,则该节点可以在其父节点调整指针绕过该节点后被删除。
注意,所删除的节点现在已不可再引用,而该节点只有在指向它的指针已被省去的情况下才能够去掉。

复杂的情况是处理具有两个儿子的节点。
一般的删除策略是用其右子树的最小的数据代替该节点的数据并递归的删除那个节点。因为右子树中的最小的节点不可能有左儿子,所以第二次删除要容易。

如果删除的次数不多,则通常使用的策略是懒惰删除:当一个元素要被删除时,它仍留在树中,而是只做了个被删除的记号。再有如果被删除的关键字是重新插入的,那么分配一个新单元的开销就避免了。

SearchTree Delete(ElementType X, SearchTree T)
{
    Position TmpCell;
    if (T == NULL)
        Error("Element not found");
    else if(X < T->Element)
        T->Left = Delete(X, T->Left);
    else if(X > T->Element)
        T->Right = Delete(X, T->Right);
    else if(T->Left && T->Right)
    {   
        TmpCell = FindMin(T->Right);
        T->Element = TmpCell->Element;
        T->Right = Delete(T->Element, T->Right);
    }
    else
    {
        TmpCell = T;
        if(T->Left == NULL)
            T = T->Right;
        else if(T->Right == NULL)
            T = T->Left;
        free(TmpCell);
    }
    return T;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容