时间复杂度

博客园 首页 新随笔 联系 订阅 管理

树是一种一对多的逻辑结构,树的子树之间没有关系。

度:结点拥有的子树数量。

树的度:树中所有结点的度的最大值。

结点的深度:从根开始,自顶向下计数。

结点的高度:从叶结点开始,自底向上计数。

树的性质:①树的结点数等于所有结点的度数加1;②度为m的树中第i层上至多有mi-1个结点(i>=1);③度为h的m叉数至多有(mh-1)/(m-1)个结点;④具有n个结点的m叉树的最小高度为[logm(n(m-1)+1)]。

树的表示方法:

①双亲表示法(顺序表示法):根节点parent=-1

typedef char ElemType;
typedef struct TNode{
    ElemType data;                     //结点数据 
    int parent;                        //该结点双亲在数组中的下标 
}TNode;                                //结点数据类型 
#define MaxSize 50
typedef struct{
    TNode nodes[MaxSize];              //结点数组 
    int n;                             //结点数量 
}Tree;                                 //树的双亲表示结构

②孩子表示法:把每个孩子的孩子结点排列起来存储成一个单链表,n个结点就有n个单链表,n个单链表的头指针又存储在一个顺序表(数组)中

typedef char ElemType;
typedef struct CNode{
    int child;                         //该孩子在表头数组的下标 
    struct CNode *next;                //指向该结点的下一个孩子结点 
}CNode,*Child;                         //孩子结点数据类型 
typedef struct{
    ElemType data;                     //结点数据域 
    Child firstchild;                  //指向该结点的第一个孩子结点 
}TNode;                                //孩子结点数据类型
 #define MaxSize 100
 typedef struct{
     TNode nodes[MaxSize];             //结点数据域 
     int n;                            //树中的结点个数 
 }Tree;                                //树的孩子表示结构

③孩子兄弟表示法:分别指向孩子结点和兄弟结点,其结点结构为:

 typedef char ElemType;
 typedef struct CSNode{
     ElemType data;                          //该结点的数据域 
     struct CSNode *firstchild,*rightsib;    //指向该结点的第一个孩子结点和该结点的右兄弟结点 
 }CSNode;                                    //孩子兄弟结点数据类型

二叉树:①每个结点最多有两棵子树;②左右子树有顺序。

二叉树的种类:①斜二叉树;②满二叉树;③完全二叉树。

满二叉树是绝对的等腰三角形,每一个非叶子结点都有两个子树,而完全二叉树的结点个数随意,只需满足其层序遍历的序号与满二叉树相同即可。

二叉树的顺序存储:用一组地址连续的存储单元依次自上向下,自左至右存储完全二叉树上的结点元素,非完全二叉树需在对应空缺编号处保留数组空位,这是一种极其浪费的存储方式。

二叉树的链式存储:

①二叉链表:两个指针指向左右孩子结点。其实现为:

typedef struct BiTNode{
    ELemType data;
    struct BiTNode *lchild,*rchild;
}BiTNode,*BiTree;  

②三叉链表:在二叉链表的基础上添加一个指针指向双亲。

二叉树的遍历:按某种次序依次访问树中的每个结点,使得每个结点均被访问人一次,而且仅被访问一次。

有以下四种方法:

①先序遍历:a.访问根节点;b.先序遍历左子树;c.先序遍历右子树。

递归实现:

void PreOrder(BiTree T){
    if(T!=NULL){
        printf("%c",T->data);        //访问根节点 
        PreOrder(T->lchild);         //递归遍历左子树 
        PreOrder(T->rchild);         //递归遍历右子树 
    }
} 

非递归实现:在实现过程中利用了栈的思想

void PreOrder(BiTree b){
    InitStack(s);
    BiTree p=b;                        //工作指针p 
    while(p||!IsEmpty(s)){
        while(p){
            printf("%c",p->data);    
            Push(s,p);                 //进栈 
            p=p->lchild;
        }
        if(!IsEmpty(s)){
            p=P0p(s);
            p=p->rchild;
        }
    }
} 

②中序遍历:a.中序遍历左子树;b.访问根节点;c.中序遍历右子树。

递归实现:

void InOrder(BiTree T){
    if(T!=NULL){
        InOrder(T->lchild);          //递归遍历左子树 
        printf("%c",T->data);        //访问根节点 
        InOrder(T->rchild);          //递归遍历右子树 
    }
} 

非递归实现:

void InOrder(BiTree b){
    InitStack(s);
    BiTree p=b;                        
    while(p||!IsEmpty(s)){
        while(p){    
            Push(s,p);                
            p=p->lchild;
        }
        p=Pop(s);
        printf("%c",p->data);
        p=p->rchild;
    }
} 

③后序遍历:a.后序遍历左子树;b.后序遍历右子树;c.访问根节点。

递归实现:

void PostOrder(BiTree T){
    if(T!=NULL){
        PostOrder(T->lchild);        //递归遍历左子树 
        PostOrder(T->rchild);        //递归遍历右子树 
        printf("%c",T->data);        //访问根节点 
    }
} 

非递归实现:

void PostOrder(BiTree b){
    InitStack(s);
    BiTree p=b,r=NULL;                        //工作指针p,辅助指针r                    
    while(p||!IsEmpty(s)){                    //从根节点到最左下角的左子树都入栈 
        if(p){    
            Push(s,p);                
            p=p->lchild;
        }
        else{
            GetTop(s,p);                      //取栈顶,不是出栈 
            if(p->rchild&&p->rchild!=r)       //如果栈顶存在右子树且未被访问过,就去访问 
                p=p->rchild;
            else{                             //不存在右子树或者被访问过,则出栈 
                Pop(s,p);
                printf("%c",p->data);
                r=p;
                p=NULL;
            }
        }
    }
} 

④层序遍历:从上向下逐层遍历,在同一层中,从左到右对结点逐个访问。

层序遍历没有递归实现,只有非递归实现:其采用了队列的思想

void LevelOrder(BiTree b){
    InitQueue(Q);
    BiTree p;
    EnQueue(Q,b);                      //根结点入队 
    while(!IsEmpty(Q)){                //队列不空循环 
        DeQueue(Q,p)                   //队头元素出队 
        printf("%c",p->data);
        if(p->lchild!=NULL)
            EnQueue(Q,p->lchild);
        if(p->rchild!=NULL)
            EnQueue(Q,p->rchild);
    }
} 

如何将一棵树转化成二叉树:①将同一结点的各个孩子用线串起来;②将每个结点的子树分支,从左往右,除了第一个以外全部删除。

将二叉树转化成树:将二叉树从上到下分层,并调节成水平方向,之后是正向转化的逆过程。

一个重要结论:n个结点的二叉链表,每一个结点都有指向左右孩子的结点指针,所以一共有2n个指针,而n个结点的二叉树一共有n-1条分支,也就是存在2n-(n-1)=n+1个空指针。

线索二叉树:指向前驱和后继的指针称为线索,加上线索的二叉链表就称为线索链表,相应的二叉树就称为线索二叉树。对二叉树以某种次序遍历使其变成线索二叉树的过程叫作线索化。

下面我们以中序遍历作为例子,实现线索二叉树,并对其遍历:

线索二叉树的实现:

typedef struct ThreadTNode{
    ELemType data;
    struct ThreadTNode *lchild,*rchild;
    int ltag,rtag;            //tag=0指向孩子,tag=1指向前驱或者后继 
}ThreadTNode,*ThreadTree;  
void InThread(ThreadTree &p,ThreadTree &pre){
    //pre指向中序遍历时上一个刚刚访问过的结点,初值为NULL 
    if(p){
        InThread(p->lchild,pre);
        if(p->lchild==NULL){
            p->lchild=pre;
            p->ltag=1;
        }
        if(pre!=NULL&&pre->rchild==NULL){
            pre->rchild=p;
            pre->rtag=1;
        }
        pre=p;
        InThread(p->rchild,pre);
    }
}

对线索二叉树进行中序遍历:

void InOrderTraverse(ThreadTree T){
    ThreadTree p=T;
    while(p){
        while(p->ltag==0) p=p->lchild;
        printf("%c",p->data);
        while(p->ltag==1&&p->rchild){
            p=p->rchild;
            printf("%c",p->data);
        }
        p->rchild;
    }
} 

哈夫曼树中相关概念:

权:树中结点相关的数值;

路径长度:从树中某个结点到另一个结点之间的分支数目(经过的边数);

带权路径长度:从树的根结点到任意结点的路径长度与该结点权值的乘积;

树的带权路径长度:树中所有结点带权路径长度的和。

哈夫曼树的定义:含有n个带权叶子结点的二叉树中,其中带权路径长度最小的二叉树,也称为最优二叉树。

posted on 2019-03-28 10:38  时间复杂度  阅读(556)  评论(0编辑  收藏  举报