数据结构与算法----树

**树与图的代码

树---大题

非空树的特性:

  • 有且仅有一个根节点

  • 没有后继的结点称为“叶子结点”(终端结点)

  • 有后继的结点称为“分支结点”(非终端结点)

  • 除了根节点外,任何一个结点都有且仅有一个前驱

  • 每个结点可以有0个或多个

**节点的层次:从上往下数

**节点的高度:从下往上数

树的高度:总共有几层

节点的度:有几个分支

树的度:各个结点度的最大值

性质

  • 结点数=总度数(边数)+1;

  • 度为m的树:

    • 任意节点的度<=m;

    • 至少有一个节点度=m;

    • 一定是非空树,至少有m+1个结点

  • m叉树:

    • 任意节点的度<=m;

    • 允许所有结点的度都<m;

    • 可以是空树

  • 度为m的树第i层至多有m^(i-1)个节点(i>=1)。

  • 高度为h的m叉树至多有(m^h-1/m-1)个结点。

  • 高度为h的m叉树至少有h个节点

  • 高度为h,度为m的树至少有h+m-1个结点

  • 具有n个节点的m叉树的最小高度为[logm(n(m-1)+1)]

存储结构

  • 双亲表示法(顺序存储):每个结点中保存指向双亲的“指针”。

  • #define MAX_TREE_SIZE 100
    typedef struct{
        ElemType data;
        int parent;
    }PTNode;
    typedef struct{
        PTNode nodes[MAX_TREE_SIZE];
        int n
    }PTree;
  • 孩子表示法(顺序+链式存储)

  • struct CTNode{
        int child;
        struct CTNode *next;
    };
    typedef struct{
        ElemType data;
        struct CTNode *firstChild;
    }CTBox;
    typedef struct{
        CTBox nodes[MAX_TREE_SIZE];
        int n,r;
    }CTree;
  • 孩子兄弟表示法(链式存储)

  • typedef struct CSNode{
        ElemType data;
        struct CSNode *firstchild,*nextsibling//二叉链表//第一个孩子和右兄弟指针
    }CSNode,*CSTree;

遍历

  • 先根遍历

    先根遍历序列与这颗树相应二叉树的先序序列相同。

    void PreOrder(TreeNode *R){
        if(R!=NULL){
            visit(R);//访问根节点
            while(R还有下一个子树T)
                PreOrder(T);
        }
    }
  • 后根遍历

后根遍历序列与这颗树相应二叉树的中序序列相同。

void PostOrder(TreeNode *R){
    if(R!=NULL){
        while(R还有下一个子树T)
            PostOrder(T);
        visit(R);//访问根节点
    }
}
  • 层次遍历(队列实现)

    • 若树非空,则根节点入队

    • 若队列非空,队头元素出队并访问,同时将该元素的孩子依次入队

    • 重复二直到队列为空

二叉树

  • n个节点的有限集合或空二叉树

  • 由一个根节点和两个互不相交的被称为根的左子树和右子树组成。左子树和右子树又分别是一棵二叉树

  • 特点

    • 每个结点最多有两棵子树

    • 左右子树不能颠倒(有序树)

  • 五种状态

    • 空二叉树

    • 只有左子树

    • 只有右子树

    • 只有根节点

    • 左右子树都有

  • 满二叉树

    • 一颗高度为h,且含有2^h-1个节点的二叉树

  • 完全二叉树

    • 有且仅当其每个结点都与高度为h的满二叉树中编号为1-n的结点一一对应时,成为完全二叉树。

  • 二叉排序树

    • 左子树上的所有结点的关键字均小于根节点的关键字

    • 右子树上的所有结点的关键字均大于根节点的关键字

    • 左子树和右子树又各是一颗二叉排序树

  • 平衡二叉树

    • 树上任一结点的左子树和右子树的深度之差不超过1

常考性质

  • 设非空二叉树中度为0,1,2的结点数分别为n0,n1,n2,则n0=n2+1;//叶子结点数为度为2的结点数+1

  • 假设树中结点总数为n,则

    • n=n0+n1+n2

    • n=n1+2n2+1

  • 具有n个(n>0)结点的完全二叉树的高度h为[log2(n+1)]或[log2n]+1

  • 对于完全二叉树,可以由总结点数n推出度为0,1,2的结点个数

    • 若完全二叉树有2k个结点则必有n1=1;n0=k;n2=k-1;

    • 若完全二叉树有2k-1个结点则必有n1=0;n0=k;n2=k-1;

  • 非空二叉树第二层至多有2^(k-1)个节点

存储结构

  • 顺序存储

  • #define MaxSize 100
    struct TreeNode{
        ElemType value;
        bool isEmpty;
    }
    TreeNode t[MaxSize];//按照从上至下,从左至右的顺序一次存储完全二叉树的各个结点
    for(int i=0;i<MaxSize;i++){
        t[i],isEmpty=true;
    }//初始化时所有结点标记为空

    i的左孩子 --2i

    i的右孩子 --2i+1

    i的父节点 --[i/2]

    i所在的层次 --[log2(n+1)]或[log2n]+1

  • 链式存储

  • n个节点的二叉链表共有n+1个空链域

  • struct ElemType{
        int value;
    };
    typedef struct BiTNode{
        ElemType data;
        struct BitNode *lchild,*rchild;
    }BiTNode,*BiTree;
    //定义一个空树
    BiTree root =NULL;
    //插入根节点
    root =(BiTree)malloc(sizeof(BiTNode));
    root->data = {1};
    root->lchild=NULL;
    root->rchild=NULL;
    //插入新的结点
    BiTNode *p = (BiTNode *)malloc(sizeof(BiTNode));
    p->data={2};
    p->lchild=NULL;
    p->rchild=NULL;
    root->lchild=p;
  • 二叉树的遍历

    • 先序遍历:根左右O(h+1)

    • void PreOrder(BiTree T){
          if(T!=NULL){
              visit(T);
              PreOrder(T->lchild);
              PreOrder(T->rchild);
          }
      }
    • 中序遍历:左根右

    • void InOrder(BiTree T){
          if(T!=NULL){
              InOrder(T->lchild);
              visit(T);
              InOrder(T->rchild);
          }
      }
    • 后序遍历:左右根

    • void PostOrder(BiTree T){
          if(T!=NULL){
              PostOrder(T->lchild);
              PostOrder(T->rchild);
              visit(T);   
          }
      }
    • 层序遍历

      • 思想:

        • 初始化一个辅助队列

        • 根结点入队

        • 若队列非空,则队头结点出队,访问该结点,并将其左右孩子插入队尾

        • 重复3直至队列空

    • void LevelOrder(BiTree T){
          LinkQueue Q;
          InitQueue(Q);
          BiTree p;
          EnQueue(Q,T)
          while(!IsEmpty(Q)){
              DeQueue(Q,p);
              visit(p);
              if(p->lchild!=NULL){
                  EnQueue(Q,p->Lchild);
              }
              if(p->rchild!=NULL){
                  EnQueue(Q,p->r3child);
              }
          }
      }

线索二叉树

前驱线索:由左孩子指针充当;

后继线索:由右孩子指针充当;

存储结构:

typedef struct ThreadNode{
    ElemType data;
    struct ThreadNode *lchild,*rchild;
    int ltag,rtag;//左右线索标志;tag==0,表示指针指向孩子;tag==1,表示指针是“线索”
}ThreadNode,*ThreadTree;
void InThread(ThreadTree T){
    if(T!=NULL){
        InThread(T->lchild);
        visit(T);
        InThread(T->rchild);
    }
}
void visit(ThreadNode *q){
    if(q->lchild==NULL){
        q->lchild = pre;
        q->ltag = 1;
    }
    if(pre!=NULL&&pre->rchild==NULL){
        pre->rchild=q;
        pre->rtag=1;
    }
    pre=q;
}
ThreadNode *pre=NULL;

二叉树找中序后继

//找到以p为根的子树中,第一个被中序遍历的结点
ThreadNode *Firstnode(ThreadNode *p){
    //循环找到最左下节点(不一定是叶节点)
    while(p->ltag==0)
        p=p->lchild;
    return p;
}
//在中序线索二叉树中找到结点p的后继结点
ThreadNode *Nextnode(ThreadNode *p){
    //右子树中最左下结点
    if(p->rtag==0)return Firstnode(p->rchild);
    else return p->rchild;//rtag==1,直接返回后继线索
}
//在中序线索二叉树进行中序遍历(利用线索实现的非递归算法)
void Inorder(ThreadNode *T){
    for(ThreadNode *p=Firsrnode(T);p!=NULL;p=nextnode(p))
    visit(p);
}

二叉树找中序前驱

//找到以p为根的子树中,最后一个被中序遍历的结点
ThreadNode *Lastnode(ThreadNode *p){
	//循环找到最右下节点(不一定是叶节点)
	while(p->rtag==0)
		p=p->rchild;
	return p;
}
//在中序线索二叉树中找到结点p的前驱结点
ThreadNode *Prenode(ThreadNode *p){
	//左子树中最右下结点
	if(p->ltag==0)return Lastnode(p->lchild);
	else return p->lchild;//ltag==1,直接返回后继线索
}
//在中序线索二叉树进行逆向中序遍历(利用线索实现的非递归算法)
void RevInorder(ThreadNode *T){
	for(ThreadNode *p=Lasrnode(T);p!=NULL;p=Prenode(p))
	visit(p);
}

二叉树找先序后继:若有左孩子,则后继为其左孩子,无左孩子后继结点为其右孩子;

二叉树找先序前驱:(p左孩子,有父节点)p的父节点为其前驱;(p右孩子,左兄弟为空)p的父节点为其前驱;(p右孩子,左兄弟非空)p的前驱为左兄弟子树中最后一个被先序遍历的结点;(p根节点)无前驱;

二叉树找后序后继:(p右孩子,有父节点)p的父节点为其后继;(p左孩子,右兄弟为空,有父节点)p的父节点为其后继;(p左孩子,右兄弟非空,有父节点)右兄弟子树中第一个被后序遍历的结点;(p根节点)没有后继

二叉树找后序前驱:(有右孩子)右孩子为其前驱;(无右孩子)左孩子为其前驱;

有序树

树中节点的各子树从左到右是有次序的,不能互换。

无序树

树中节点的各子树从左到右是无次序的,可以互换。

森林

m棵互不相交的树的集合。

遍历

  • 先序遍历:

    • 若森林非空,则按如下规则进行遍历:

    • 访问森林中第一棵树的根节点。

    • 先序遍历第一棵树中根节点的子树森林。

    • 先序遍历除去第一棵树之后剩余的树构成的森林

    等同于依次对二叉树的先序遍历

  • 中序遍历

    • 若森林非空,则按如下规则进行遍历:

    • 中序遍历森林中的第一棵树的根节点的子树森林。

    • 访问第一棵树的根节点

    • 中序遍历除去第一棵树之后剩余的树构成的森林。

    等同于依次对二叉树的中序遍历

哈夫曼树

带权路径长度:

结点的权:有某种显示含义的数值。

树的带权路径长度WPL:书中所有叶子结点的带权路径长度之和

1)每个初始结点最终都成为叶结点,且全职越小的结点到根结点的路径长度越大

2)哈夫曼树的结点总数2n-1

3)哈夫曼树中不存在度为1的结点

4)哈夫曼树并不唯一,但WPL必然相同且为最优

哈夫曼编码

固定长度编码--每个字符用相等长度的二进制位表示

可变长度编码--允许对不同字符用不等长的二进制位表示

若没有一个编码是另一个编码的前缀,则称这样的编码为前缀编码

由哈夫曼树得到哈夫曼编码--字符集中的每个字符作为一个叶子结点,各个字符出现的频度作为结点的权值,根据之前介绍的方法构造哈夫曼树

并查集、

Find--"查"操作:确定一个指定元素所属集合;

union--”并“操作:将两个不相交的集合合并为一个

注:并查集是逻辑结构--集合的一种具体实现,只进行”并“和”查“两种基本操作

#define SIZE 13
int UFSets[SIZE]; //集合元素数组
//初始化并查集
void Initial(int S[]){
    for(int i=0;i<SIZE;i++)
        S[i]=-1;
}
//Find"查"操作,找x所属的集合(返回x所属根结点)O(n)/O(log2n)
int Find(int S[],int x){
    while(S[x]>=0)      //循环寻找x的根
        x=S[x];
    return x;   //根的s[]小于
}
//Union"并"操作,将两个集合合并为一个O(1)
void Union(int S[],int Root1,int Root2){
    //要求Root1与Root2是不同的集合
    if(Root1=Root2)     return;
    //将根Root2连接到另一根Root1下面
    S[Root2]=Root1;
}
//优化思路:
//1)用根节点的绝对值表示树的结点总数
//2)Union操作,让小树合并到大树
//Union"并"操作,小树合并到大树
//该方法构造的树高不超过[log2n]+1//终极:O(na(n))
void Union(int S[],int Root1,int Root2){
    if(Root1==Root2)    return;
    if(S[Root2]>S[Root1]){ //Root2结点数更少
        S[Root1]+= S[Root2];//累加结点总数
        S[Root2]=Root1;//小树合并到大树
    }else{
        S[Root2]+=S[Root1];
        S[Root1]=Root2;
    }
}
//终极优化Find(压缩路径)O(a(n))
int Find(int S[],int x){
    int root = x;
    while(S[root]>=0)   root = S[root];//循环找到根
    while(x!=root){     //循环寻找x的根
        int t=S[x];
        S[x] = root;
        x=t;
    }
    return root;    //返回根节点编号
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值