【数据结构】树

我的未来我决定 提交于 2020-02-16 18:28:58

客观世界中许多事物存在层次关系
eg:人类社会家谱
社会组织结构
图书信息管理

分层次组织在管理上具有更高的效率
查找(Searching):给定某个关键字K,从集合R中找出关键字与K相同的记录

静态查找:集合中记录是固定的
没有插入和删除操作,只有查找
动态查找:集合中记录是动态变化的
除查找,还可能发生查找和删除

静态查找

  1. 顺序查找
typedef struct LNode *List;
struct LNode{
    ElementType Element[MAXSIZE];
    int Length;
}
int SequentialSearch(List Tb1, ElementType K){
    int i;
    Tb1->Element[0]=K;//哨兵!这样可以减少判断
    for(i=Tb1->Length;Tb1->Element[i]!=K;i--);
    return i;
}
  1. 二分查找 Binary Search O(log(n))
    假设n个数据元素的关键字满足有序\(k_1<k_2<k_3<...<k_n\),并且是连续存放(数组),那么可以进行二分查找。
typedef LNode *List;
struct LNode{
    ElementType Element[MAXSIZE];
    int Length;
}
int BinarySearch(List PtrL,ElementType k){
    int left,right,mid;

    left=1;
    right=PtrL->Length;

    while(left<=right){
        mid=(right+left)/2;
        if(PtrL->Element[mid]>k) right=mid-1;
        if(PtrL->Element[mid]<k) left=mid+1;
        else return mid;
    }
    return NotFound;
}

11个元素的二分查找判定树
判定树上每个结点需要的查找次数刚好为该结点所在的层数,查找成功时查找次数不会超过判定树的深度。n个结点的判定树的深度为logn+1
\[ASL=(4*4+4*3+2*2+1)/11=3\]

树定义

树:n个结点构成的有限集合

当n=0时,称为空树
对于任一棵非空树(n>0),它具备一下性质:
(1)树中有一个称为“根”的特殊结点,用r表示
(2)其余结点可分为m个互不相交的有限集\(T_1,T_2,...,T_m\),其中每个集合本身又是一棵树,称为原来树的“子树(SubTree)”。

子树是不相交的;
除了根结点外,每个结点有且仅有一个父结点;
一棵N个结点的树有N-1条边

基本术语

  1. 结点的度(Degree):结点的子树的个数
  2. 树的度:树的所有结点中最大的度数
  3. 叶结点:度为0的结点
  4. 父结点: 有子树的结点是其子树的根结点的父结点
  5. 子结点:
  6. 兄弟结点:
  7. 路径和路径长度:
  8. 祖先结点:
  9. 子孙结点:
  10. 结点的层次(level):规定根结点在1曾,其他任一结点的层数是其父结点的层数加一
  11. 树的深度(Depth):树中所有结点中最大层次是这棵树的深度

树的表示

  1. 数组表示法:关系表达不清楚
  2. 多个指针的链表: 比如每个结点3个叉,则3n个指针,而实际只用n-1个,所以浪费了2n+1个指针
  3. 儿子-兄弟表示法:第一个指针指向其第一个child,第二个指针指向其第一个兄弟
    结构是统一的;空间浪费少;
    旋转45度可以看成二叉树(知道为什么二叉树重要了吧!)

二叉树的定义

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

二叉树有五种基本形态
二叉树的子树有左右顺序之分

  1. 特殊的二叉树
    (1)斜二叉树
    (2)完美二叉树/满二叉树
    (3)完全二叉树:有n个结点的二叉树,对树中结点按从上至下、从左到右顺序进行编号,编号为i结点与满二叉树中变好为i结点在二叉树中位置相同

  2. 二叉树几个重要性质
    (1)第i层最大结点数为:\(2^{i-1},i>=1\)
    (2)深度为k的二叉树最大结点数为:\(2^k-1,k>=1\)
    (3)对任何非空二叉树T,若\(n_0\)表示叶结点的个数,\(n_2\)是度为2的非叶结点个数,那么两者满足关系\(n_0=n_2+1\)
    \[n_0+n_1+n_2-1=0n_0+n_1+2n_2\]

  3. ADT二叉树
类型名称:二叉树
数据对象集:一个有穷的结点集合
若不为空,则由根结点和其左右二叉子树组成
操作集:
1. Booloen isEmpty(BinTree BT):判断BT是否为空
2. void Traversal(BinTree BT):遍历,按某顺序访问每个结点
3. BinTree CreatBinTree():创建一个二叉树

常见的遍历方法:
(1)先序:根、左、右
(2)中序:左、根、右
(3)后序:左、右、根
(4)层次遍历

二叉树的存储结构

  1. 顺序存储结构
    完全二叉树:按从上至下、从左至右顺序存储n个结点的完全二叉树的结点父子关系
    非根结点的父结点:i/2取下界
    结点的左孩子:2i
    结点的右孩子:2i+1

一般二叉树也可以用这种结构,但会造成空间浪费(补充成一个完全二叉树)

  1. 链式存储
typedef struct TreeNode *BinTree;
typedef BinTree Position;
struct TreeNode{
    ElementType 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);
    }
}
  1. 中序遍历
    (1)中序遍历其左子树
    (2)访问根结点
    (3)中序遍历其右子树
void InOrderTraversal(BinTree BT){
    if(BT){
        InOrderTraversal(BT->Left);
        printf("%d",BT->Data);
        InOrderTraversal(BT->Right);
    }
}
  1. 后序遍历
    (1)后序遍历其左子树
    (2)后序遍历其右子树
    (3)访问根结点
void PostOrderTraversal(BinTree BT){
    if(BT){
        PostOrderTraversal(BT->Left);
        PostOrderTraversal(BT->Right);
        printf("%d",BT->Data);
    }
}

先序、中序和后序遍历过程,遍历过程中经过结点的路线一样,只是访问各结点的时机不同(妙啊!)

  1. 非递归遍历
    中序遍历非递归遍历算法
    非递归算法实现的基本思路:使用堆栈

遇到一个结点,就把它压栈,病区遍历它的左子树;
当左子树遍历结束后,从栈顶弹出这个结点并访问它;
然后按其右指针再去中序遍历结点的右子树

void InOrderTraversal(BinTree BT){
    BinTree T=BT;
    Stack S=CreatStack(MaxSize);

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

反思:
这一段,太妙了!怎么能想出这一段呢?首先,把遍历路径的感觉找到。遍历的路径都是先一直向左,向左的过程中不断把元素压到栈中,向左到头以后,推出栈内的元素并访问。然后在其右指针中遍历该结点的右子树

void PreOrderTraversal(BinTree BT){
    BinTree T=BT;
    Stack S=CreatStack(MaxSize);

    while(T||!IsEmpty(S)){//最妙的就是这个循环,结束的条件就是树访问完并且堆栈空了
        while(T){
            Push(S,T);
            printf("%5d",T->Data);
            T=T->Left;
        }
    if(!IsEmpty(S)){
        T=Pop(S);
        T=T->Right;
        }
}
void PostOrderTraversal(BinTree BT){
    BinTree T=BT;
    Stack S=CreatStack(MaxSize);

    while(T||!IsEmpty(S)){//最妙的就是这个循环,结束的条件就是树访问完并且堆栈空了
        while(T){
            Push(S,T);
            T=T->Left;
        }
    if(!IsEmpty(S)){
        T=Pop(S);
        T=T->Right;
        }
        printf("%5d",T->Data);//还得再想想
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!