浙大《数据结构》第三章:树(上)

注:本文使用的网课资源为中国大学MOOC

https://www.icourse163.org/course/ZJU-93001

查找

查找:根据某个给定的关键字K,从集合R中找出关键字与K相同的记录。

静态查找:集合中的记录是固定的,没有插入删除的操作,只有查找;

动态查找:集合中记录是动态变化的,除查找,还可能发生插入和删除。

静态查找

方法1:顺序查找

int SequentialSearch( StaticTable *Tbl, ElementType K)
{
    //在表Tbl[1]~Tbl[n]中查找关键字为K的数据元素
    int i;
    Tbl->Element[0] = K; //建立哨兵
    for (i=Tbl->Length; Tbl->Element[i]!=K; i--);
    return i; //查找成果则返回所在单元下标;不成功则返回0
}

顺序查找的时间复杂度为O(n)。

方法2:二分查找

int BinarySearch( StaticTable *Tbl, ElementType K)
{
    int left, right, mid, NotFound = -1;

    left = 1; //初始左边界
    right = Tbl->Length; //初始右边界
    while ( left <= right )
    {
        mid = (left+right)/2; //计算中间元素坐标
        if ( K < Tbl->Element[mid] )
            right = mid-1; //调整右边界
        else if ( K > Tbl->Element[mid] )
            left = mid+1; //调整左边界
        else
            return mid; //查找成功,返回数据元素的下标
    }
    return NotFound; //查找不成功,返回-1
}

二分查找算法具有对数时间复杂度O(log(N))。

树的定义

树(Tree): n(n≥0)个结点构成的有限集合。当n=0时,称为空树,对于任意一棵非空树,它具备以下性质:

  • 树中有一个称为“根(root)”的特殊结点,用r表示;
  • 其余结点可分为若干个互不相交的有限集,其中每一个集合本身又是一棵树,称为原来树的子树(SubTree).
  • 子树是不相交的;
  • 除了根结点外,每个结点有且仅有一个父结点;
  • 一棵N个结点的树有N-1条边。

树的基本术语

  1. 结点的度(Degree):结点的子树个数
  2. 树的度:树的所有结点中最大的读数
  3. 叶结点(Leaf):度为0的结点
  4. 父结点(parent):有子树的结点是其子树的根结点的父结点
  5. 子节点(child):若A结点是B结点的父结点,则称B结点是A结点的子结点;子结点也称为孩子结点
  6. 兄弟结点(sibling):具有同意父结点的各结点是彼此的兄弟结点
  7. 路径和路径长度:从结点\(n_1\)到\(n_k\)的路径为一个结点序列\(n_1,n_2,...,n_k\),\(n_i\)是\(n_{i+1}\)的父结点。路径所包含的边的个数为路径的长度。
  8. 祖先结点(ancestor):沿树根到某一结点路径上的所有结点都是这个结点的祖先结点。
  9. 子孙结点(descendant):某一结点的子树中的所有结点是这个结点的子孙
  10. 结点的层次(level):规定根结点在1层,其他任意结点的层数是其父结点的层数加1。
  11. 树的深度(depth):树中所有结点中最大层次是这棵树的深度。

二叉树及其存储结构

定义

二叉树T:一个有穷的结点组合

  • 这个集合可以为空
  • 若不为空,则它是由根结点和称为其左子树\(T_L\)和右子树\(T_R\)的两个不相交的二叉树组成。

特殊的二叉树

性质

  1. 一个二叉树第i层的最大结点数为:\(2^{i-1},i \geq 1.\).
  2. 深度为K的二叉树有最大结点总数为\(2^K-1,K \geq 1.\).
  3. 对于任何非空二叉树T,若\(n_0\)表示叶结点的个数(没有子树)、\(n_2\)是度为2的非叶结点个数(有左右两个子树),那么二者满足关系\(n_0=n_2+1\)
    (二叉树的结点总数为\(n_0+n_1+n_2\))

抽象数据类型定义

类型名称:二叉树

数据对象集:一个有穷的结点集合。若不为空,则由根结点和其左、右二叉子树组成。

操作集:\(BT \in BinTree\),\(Item \in ElementType\),重要操作有:

Boolean IsEmpty( BinTree BT); //判别BT是否为空
void Traversal( BinTree BT ); //遍历,按某顺序访问每一个结点
BinTree CreatBinTree();//创建一个二叉树

/* 常见的遍历方法 */
void PreOrderTraversal( BinTree BT ); //先序:根-左-右
void InOrderTraversal( BinTree BT ); //中序:左-根-右
void PostOrderTraversal( BinTree BT ); //后序:左-右-根
void LevelOrderTraversal( BinTree BT ); //层次遍历:从上到下,从左到右

存储结构

顺序存储结构

完全二叉树:按从上到下、从左到右顺序存储n个结点的完全二叉树的结点父子关系:

  • 非根结点(序号i>1)的父结点的序号是(i/2)。
  • 结点(序号i)的左孩子结点序号是2i(需2i<=n,否则没有左孩子)
  • 结点(序号i)的右孩子结点序号是2i+1(需2i+1<=n,否则没有右孩子)

链表存储结构

typedef struct TreeNode *Position
typedef Position BinTree
struct TreeNode
{
    ElementTyoe Data;
    BinTree Left;
    BinTree right;
}

二叉树的遍历

递归遍历

(1) 先序遍历

遍历过程:

  1. 访问根结点
  2. 先序遍历其左子树
  3. 先序遍历其右子树
void PreOrderTraversal( BinTree BT )
{
    if ( BT )
    {
        printf("%d", BT->Data); //根结点
        PreOrderTraversal( BT->Left ); //左子树
        PreOrderTraversal( BT->Right ); //右子树
    }
}

(2) 中序遍历

遍历过程:

  1. 中序遍历其左子树
  2. 访问根结点
  3. 中序遍历其右子树
void InOrderTraversal( BinTree BT )
{
    if ( BT )
    {
        InOrderTraversal( BT->Left ); //左子树
        printf("%d", BT->Data); //根结点
        InOrderTraversal( BT->Right ); //右子树
    }
}

(3) 后序遍历

遍历过程:

  1. 后序遍历其左子树
  2. 后序遍历其右子树
  3. 访问根结点
void PostOrderTraversal( BinTree BT )
{
    if ( BT )
    {
        PostOrderTraversal( BT->Left ); //左子树
        PostOrderTraversal( BT->Right ); //右子树
        printf("%d", BT->Data); //根结点
    }
}

非递归遍历

先序、中序和后序遍历过程中结果结点的路线是一样的,只是访问各结点的时机不同。

  • 先序遍历是第一次"遇到"该结点时访问
  • 中序遍历是第二次"遇到"该结点(此时该结点从左子树返回)时访问
  • 后序遍历是第三次"遇到"该结点(此时该结点从右子树返回)时访问

非递归算法实现的基本思路:使用堆栈

(1) 中序遍历

  1. 遇到一个结点,就把它压栈,并去遍历它的左子树;
  2. 当这个左子树遍历结束后,从栈顶弹出这个结点并访问它;
  3. 然后按其右指针再去中序遍历该结点的右子树;
void InOrderTraversal( BinTree BT )
{
    BinTree T = BT;
    Stack S = CreatStack(MaxSize); //创建并初始化堆栈S
    while (T || !IsEmpty(S))
    {
        while (T) //一直向左并将沿途结点压入堆栈
        {
            Push(S, T);
            T = T->Left;
        }
        if ( !IsEmpty(S))
        {
            T = Pop(S); //结点弹出堆栈
            printf("%5d", T->Data); //打印结点
            T = T->Right; //转向右子树
        }
    }
}

(2) 先序遍历

void PreOrderTraversal( BinTree BT )
{
    BinTree T = BT;
    Stack S = CreateStack();  // 创建并初始化堆栈 S
    while(T || !IsEmpty(S))  // 当树不为空或堆栈不空
    {
        while(T)
        {
            Push(S,T);    // 压栈,第一次遇到该结点
            printf("%d",T->Data);  // 访问结点
            T = T->Left;   // 遍历左子树
        }
        if(!IsEmpty(S)) // 当堆栈不空
        {
            T = Pop(S);    // 出栈,第二次遇到该结点
            T = T->Right;  // 访问右结点
        }
    }
} 

(3) 后序遍历

先序的访问顺序是root, left, right 假设将先序左右对调,则顺序变成root, right, left,暂定称之为“反序”。后序遍历的访问顺序为left, right,root ,刚好是“反序”结果的逆向输出。

  1. 反序遍历二叉树,具体方法为:将先序遍历代码中的left 和right 对调即可。数据存在堆栈S中。
  2. 在先序遍历过程中,每次Push节点后紧接着print结点。对应的,在反序遍历时,将print结点改为把当前结点Push到堆栈N中。
  3. 反序遍历完成后,堆栈N的压栈顺序即为反序遍历的输出结果。此时再将堆栈N中的结果pop并print,即为“反序”结果的逆向,也就是后序遍历的结果。
void PostOrderTraversal( BinTree BT )
{
    BinTree T = BT;
    Stack S = CreatStack();
    Stack N = CreatStack(); //创建并初始化栈N
    wihile ( T || !IsEmpty(S) )
    {
        while(T) /*一直向右并将沿途结点压入堆栈*/
        {
            Push(S,T);
            Push(N,T);  //将遍历到的结点压栈,用于反向
            T = T->Right;  //这里是Right
        }
        if ( !IsEmpty(S) )
        {
            T = Pop(S);
            T = T->Left;
        }
    }
    while ( !IsEmpty(N) )
    {
        T = Pop(N);
        printf("%5d", T->Data); //将 N 栈中的数据依次弹出并打印
    }
}

层次遍历

队列实现:

  1. 根结点入队;
  2. 从队列中取出一个元素,并访问该元素;
  3. 若该元素所指结点的左右孩子结点非空,则将其左右孩子的指针入队。
void LevelTraversal( BinTree BT )
{
    if ( !BT )
        return; //如果是空树直接返回
    BinTree T;
    Queue Q = CreatQueue(MaxSize); //创建并初始化队列Q
    AddQ( Q, BT );
    while (!IsEmpty(Q))
    {
        T = DeleteQ( Q );
        printf("%d\n", T->Data); // 访问取出队列的结点
        if ( T->Left )
            AddQ( Q, T->Left );
        if ( T->Right )
            AddQ( Q, T->Right );
    }
}

举例

(1) 求二叉树的高度

int PostOrderGetHeight( BinTree BT )
{
    int HL, HR, MaxH;
    if ( BT )
    {
        HL = PostOrderGetHeight( BT->Left );
        HR = PostOrderGetHeight( BT->Right );
        MaxH = (HL>HR) ? HL :HR; // 取左右子树较大的深度
        return ( MaxH+1 );
    }
    else
        return 0;  // 空树深度为0
}

(2) 根据先序和中序遍历来确定一棵二叉树

分析:

  1. 根据先序遍历序列的第一个结点确定根结点;
  2. 根据根结点在中序遍历序列中分割出左右两个子序列;
  3. 对左子树和右子树分别递归使用相同的方法继续分解;

例如:

前序:ABCDEFG

中序:CBDAFEG

先序遍历为"根左右",则 A 是根,对应可以划分出中序中:(CBD)A(FEG),CBD 为左子树,FEG 为右子树,

再根据前序的 BCD,B 为根,划分出中序中(C(B)D)A(FEG),则 C D 分别是 B 的左右子树…最后可得树为:

     A
    /     B    E
  / \  /  C   D F  G

二叉树的同构

题意理解

给定两棵树T1和T2。如果T1可以通过若干次左右孩子互换就变成T2,则我们称这两棵树是“同构”的。

输入格式:

  • 现在一行中给出概述的结点数,随后N行
  • 第i行对应编号第i个结点,给出该结点中存储的字母、其左孩子结点的编号、右孩子结点的编号
  • 如果孩子结点为空,则在相应的位置上给出“-”。

输入样例:

程序框架搭建

int main()
{
    Tree R1, R2; // 利用结构数组表示二叉树,静态链表

    R1 = BuildTree(T1); // 建立二叉树1
    R2 = BuildTree(T2); // 建立二叉树2
    if ( Isomorphic(R1, R2) ) // 判别是否同构并输出
        printf("Yes\n");
    else
        printf("No\n");

    return 0;

}

程序实现

#include <stdio.h>
#include <stdlib.h>  //调用malloc()和free()
#include <malloc.h>  //调用malloc()和free()
#include <windows.h> //windows.h里定义了关于创建窗口,消息循环等函数S

#define MaxTree 10
#define Null -1

struct TreeNode
{
    char item;
    int Left;
    int Right;              // 注:这里的left和right分别为输入数据的左右子结点索引
} T1[MaxTree], T2[MaxTree]; // 结构数组表示二叉树

/* 创建树并返回根结点 */
int BuildTree(struct TreeNode T[])
{
    int i, N;
    int Root = 0;
    int check[MaxTree];
    char cl, cr;
    scanf("%d", &N);
    if ( !N )
        return Null;
    for (i = 0; i < N; i++)
        check[i] = 0;
    for (i = 0; i < N; i++)
    {
        printf("%d: ",i);
        scanf("%c %c %c", &T[i].item, &cl, &cr); // 注意输入时不要有空格,且字母要小写,eg.a12
        fflush(stdin); // 微软系统中清空缓冲区的功能函数
        // 注意‘’和“”的区别,双引号括起来的是字符串指针,单引号括起来的才是字符
        if (cl != '-')
        {
            T[i].Left = cl - '0';
            check[T[i].Left] = 1;
        }
        else
            T[i].Left = Null;
        if (cr != '-')
        {
            T[i].Right = cr - '0';
            check[T[i].Right] = 1;
        }
        else
            T[i].Right = Null;
    }
    for (i = 0; i < N; i++)
        if (!check[i])
            break;
    Root = i;
    return Root;
}

/* 判断二叉树是否重构 */
int Ismorphic(int R1, int R2)
{
    if ((R1 == Null) && (R2 == Null)) // 均为空
        return 1;
    if (((R1 == Null) && (R2 != Null)) || ((R1 != Null) && (R2 == Null))) // 其中一个为空
        return 0;
    if ((T1[R1].Left == Null) && (T2[R1].Left == Null)) // 根结点均没有左子树
        return Ismorphic(T1[R1].Right, T2[R2].Right);
    if (((T1[R1].Left != Null) && (T2[R2].Left != Null)) && ((T1[T1[R1].Left].item) == (T2[T2[R2].Left].item)))
        return (Ismorphic(T1[R1].Left, T2[R2].Left) && Ismorphic(T1[R1].Right, T2[R2].Right));
    else
        return (Ismorphic(T1[R1].Left, T2[R2].Right) && Ismorphic(T1[R1].Right, T2[R2].Left));
}

int main()
{
    int R1, R2; // 利用结构数组表示二叉树,静态链表

    printf("T1:\n");
    R1 = BuildTree(T1); // 建立二叉树1
    printf("T2:\n");
    R2 = BuildTree(T2);     // 建立二叉树2
    if (Ismorphic(R1, R2)) // 判别是否同构并输出
        printf("Yes\n");
    else
        printf("No\n");

    system("pause"); //程序暂停,显示按下任意键继续
    return 0;
}

原文地址:https://www.cnblogs.com/Superorange/p/12514512.html

时间: 2024-10-11 10:46:20

浙大《数据结构》第三章:树(上)的相关文章

数据结构——第三章树和二叉树:02二叉树

1.二叉树的存储结构: (1)二叉树的顺序存储表示: #define MAX_TREE_SIZE 100 //二叉树的最大结点数 typedef TElemType SqBiTree[MAX_TREE_SIZE]; SqBiTree bt; (2)二叉树的链式存储表示: ①二叉链表: typedef struct BiTNode //结点结构 { TElemType data; struct BiTNode *lchild, *rchild; //左右孩子指针 } BiTNode, *BiTre

数据结构——第三章树和二叉树:03树和森林

1.树的三种存储结构: (1)双亲表示法: #define MAX_TREE_SIZE 100 结点结构: typedef struct PTNode { Elem data; int parent; //双亲位置域 } PTNode; (2)孩子双亲链表表示法: typedef struct PTNode { Elem data; int parent; //双亲位置域 struct CTNode* nextchild; } *ChildPtr; (3)树的二叉链表(孩子-兄弟)存储表示法:

数据结构——第三章树和二叉树:01树和二叉树的类型定义

1.树的类型定义: (1)数据对象D:D是具有相同特性的数据元素的集合. (2)数据关系R:若D为空集,则成为空树 否则:在D中存在唯一的称为根的数据元素root.当n>1时,其余结点可分为n(n>0)个互不相交的有限集T1, T2, T3, ..., Tm,其中每一棵子集本身又是一课符合本定义的树,称为根root的子树. 2.树的基本操作: (1)查找类: ①Root(T):求树的根结点 ②Value(T, cur_e):求当前结点的元素值 ③Parent(T, cur_e):求当前结点的双

【数据结构】第6章 树(上)

第一次用markdown-..好高端的赶脚 数据结构第6章 树(上) §6.1 树的定义和基本术语 树是n(n>=0)个结点的有限集 在非空树中有且仅有一个特定的根(root) 树的结构定义是一个递归的定义,即在树的定义中又用到了树的概念,有嵌套集合表示法,广义表表示法和凹入表示法等. 术语: 结点,结点的度(子树个数),叶(度为0),树的度(最大结点度),孩子,父亲,兄弟,祖先(上溯所有结点),子孙(下溯所有结点),层次,堂兄弟,树的深度(最大层次),有序树(左右子树有顺序,如二叉树),无序树

C语言数据结构——第三章 栈和队列

三.栈和队列 栈和队列是两种重要的线性结构.从数据结构的角度来看,栈和队列也是线性表,它的特殊性在于栈和队列的基本操作是线性表操作的子集,它们的操作相对于线性表来说是受到限制的,因此,可以称其为限定性的数据结构. 从数据类型的角度看,它们是和线性表大不相同的两种重要的抽象数据类型,在面向对象的程序设计中,它们是多型数据类型. 3.1-栈 3.1.1-抽象数据类型栈的定义 栈:是限定仅在表尾进行插入或删除操作的线性表,又可以称为后进先出的线性表. 栈顶:栈的表尾端 栈底:栈的表头端 空栈:不含元素

数据结构-王道2017-第4章 树与二叉树-树、森林

1.树的存储结构有多种,既可以采用顺序存储结构,也可以采用链式存储结构,都要求能唯一地反映出树中各结点之间的逻辑关系,三种常用的存储结构 1)双亲表示法 采用一组连续空间来存储每个结点,同时在每个结点中增设一个伪指针,指示其双亲节点在数组中的位置,根节点下标为0,其伪指针域为-1. #define MAX_TREE_SIZE 100 //树中最多结点数 typedef struct{ //树的结点定义 ElemType data; //数据元素 int parent; //双亲位置域 }PTNo

“AS3.0高级动画编程”学习:第三章等角投影(上)

什么是等角投影(isometric)? 原作者:菩提树下的杨过出处:http://yjmyzz.cnblogs.com 刚接触这个概念时,我也很茫然,百度+google了N天后,找到了一些文章: [转载]等角(斜45度)游戏与数学  ( 原文链接:http://www.javaeye.com/articles/1225) [转载]使用illustrator和正交投影原理以及基本三视图制图   (http://www.vanqy.cn/index.php/2009/03/working-with-

【自考】数据结构第三章,数组,期末不挂科指南,第5篇

数组 概念如下 数组可以看成线性表的一种推广,其实就是一种线性表,一维数组又称为向量 数据由一组具有相同类型的数据元素组成,并存储在一组连续的存储单元中 若一维数组中的数据元素又是一维数组结构,则称为二维数组 依次类推,可以得到 三维数组和多维数组 数组基本运算 数组通常只有两种基本运算 读:给定一组下标,返回该位置的元素内容 写:给定一组下标,修改该位置的元素内容 数组的存储结构 一维数组元素的内存单元地址是连续的 二维数组可有两种存储方法:一种是以列序为主序的存储:另一种是以行序为主序的存储

数据结构-王道2017-第4章 树与二叉树-二叉树的遍历

typedef int ElemType; typedef struct BitNode { ElemType data; //数据域 struct BitNode *lchild, *rchild; //左右孩子指针 }BitNode,*BitTree; void visit(BitNode *b) { printf("%d ", b->data); } //无论采用哪种遍历方法,时间复杂度都是O(n),因为每个结点都访问一次且仅访问一次,递归工作栈的栈深恰好为树的深度,空间复

数据结构——第五章 树与二叉树

树是一对多的结构 结点:树的小圆圈 度:结点有多少个分叉 叶子结点:结点的度为0 双亲:parent 孩子:child 二叉树:树的度不超过2 满二叉树:每一层都是满的 完全二叉树:除了最后一层都是满的,最后一层左边都是齐全连续的. 性质1:对一颗二叉树,第i层最多有2的i-1次方个 性质2:对一颗二叉树,最多有2的i次方-1个 性质3:n0=n2+1 n0+n1+n2=n(n0--结点度为0的个数,n2--结点度为2的个数) 性质4:具有n个结点的完全二叉树深度为 math.floor(log