当前位置: 代码迷 >> 综合 >> 数据结构基础笔记(4)
  详细解决方案

数据结构基础笔记(4)

热度:27   发布时间:2024-02-09 22:10:49.0

树

1.性质:树中的结点数等于所有结点的度数加1

2.二叉树 :每个结点最多两个度  左右子树

3.二叉树的性质 :非空二叉树上的叶子结点树等于度为2的结点数加1

一、逻辑结构:

1.满二叉树  最多结点的二叉树(叶结点一定在最后一层) 每一层都满

2.完全二叉树  结点与满二叉树一一对应

3.二叉排序树  左子树上所有结点关键字均小于该结点,右子树所有结点关键字均大于该 结点

4.平衡二叉树  任意节点的左子树与右子树的深度之差不超过1

二、存储结构:

1.顺序存储 (数组存储)

      用一组连续的存储单元依次自上而下、自左至右存储完全二叉树上的结点元素。 

      逻辑关系的表示  在完全二叉树中,结点i的左子树为2i,右子树为2i+1

                                对于非完全二叉树,补为结点为空的完全二叉树

      顺序存储在最坏的情况下会非常浪费存储空间,比较适合完全二叉树

2.链式存储 (链表存储)

     用链表来存放一颗二叉树,二叉树中每个结点用链表的一个链结点来存储

     左孩子指针域+数据域+右孩子指d针域

     typedef struct BiTnode{

                 ElemType data;

                 struct BiTnode *lchild, *rchild;

      }BiTnode, *BiTnode;

     含有n个结点的二叉链表中,有n+1个空链域         

三、二叉树的遍历---按某条路径访问树中的每个结点,树的每个结点均被访问一次,而且只访问一次。

1.先序遍历  根-左-右    (左子树比右子树先访问)

先序遍历--时间复杂度 O(n)

void PreOrder(BiTree T){

    if(T!=NULL){     //根结点非空

        visit(T);

        PreOrder(T->lchild);

        PreOrder(T->rchild);

    }

}

2.中序遍历  左-根-右

中序遍历--时间复杂度 O(n)

void InOrder(BiTree T){

    if(T!=NULL){

        InOrder(T->lchild);

        visit(T);

        InOrder(T->rchild);

    }

}

3.后序遍历  左-右-根

后序遍历--时间复杂度 O(n)

void PostOrder(BiTree T){

    if(T!=NULL){

        PostOrder(T->lchild);

        PostOrder(T->rchild);

        visit(T);

    }

}

4.中序遍历的非递归算法--借助栈(任何一个递归算法都可以转换为非递归算法)

算法思想:

初始时依次扫描根节点的所有左侧结点并将他们一一进栈;

出栈一个结点,访问它;

扫描该结点的右孩子结点并将其进栈;

依次扫描右孩子结点的所有左侧结点并一一进栈;

反复该过程直到栈空为止。

void InOrder2(BiTree T){

    InitStack(s);   //初始化

    BiTree p=T;

    while(p||!IsEmpty(s)){

        if(p){           //p非空

            push(s,p);

            p=p->lchild;

        }else{

            Pop(s,p);

            visit(p);

            p=p->rchild;

        }

    }

}

5.层次遍历--借助队列

算法思想:

初始将根入队并访问根结点;

若有左子树,则将左子树的根入队;

若有右子树,则将右子树的根入队;

然后出队,访问该结点;

反复该过程直到队列为空

void levelOrder(BiTree T){

    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->rchild);

    }

}

6.先序/后序遍历序列+中序遍历序列可以确定唯一一棵二叉树   

   后序+先序不可以确定一棵二叉树

   层次+中序可以确定一棵二叉树

四、线索二叉树

1.线索化  若无左子树,则将左指针指向其前驱结点;

               若无右子树,则将右指针指向其后继结点。

标志域tag  0   指针域指向孩子结点

                  1   指针域指向前后结点

typedef struct ThreadNode{

    ElemType data;

    struct ThreadNode *lchild,*rchild;

    int ltag,rtag;

}ThreadNode,*ThreadTree;

这种结点结构构成的二叉链表作为二叉树的存储结构,称为线索链表,线索化的二叉树称为线索二叉树。

2.类型

先序线索二叉树

中序线索二叉树(常用)

后序线索二叉树

3.中序线索二叉树

a)寻找前驱结点的方法

   若左指针为线索,则其指向结点为前驱结点

   若左指针为左孩子,则其左子树的最右侧结点为前驱结点

b)寻找后继结点的方法

   若右指针为线索,则其指向结点为后驱结点

   若右指针为右孩子,则其右子树的最左侧结点为后驱结点

c)中序线索二叉树线索化:

   void Inthread(ThreadTree &p,ThreadTree &pre){

    if(p!=NULL){

        Inthread(p->lchild,pre);

        if(p->lchild==NULL){

            p->lchild=pre;

            p->ltag=1;

        }

        pre=p;

        Inthread(p->rchild,pre);

    }

}

五、树的存储结构

1.双亲表示法

2.孩子表示法

3.孩子兄弟表示法  二叉表示法  (左孩子右兄弟)

六、树与二叉树的转换

每个结点左指针指向它的第一个孩子结点,右指针指向它在树中相邻兄弟结点。

七、森林与二叉树的转换

将每一棵树转换为二叉树,将每棵二叉树的根依次作为上一棵二叉树的右子树。

八、树和森林的遍历

1.树的遍历

 a)先根遍历     树的先根遍历序列与这棵树对应二叉树的先序遍历序列相同

 b)后根遍历     树的后根遍历序列与这棵树对应二叉树的中序遍历序列相同

  c)层次遍历

2.森林的遍历

 a)先序遍历   若非空,则访问第一棵树的根节点,先序遍历第一棵树的子树森林,先序遍历除去第一棵树之后剩余的树构成的子树森林。(树的先根遍历访问每一棵树)--森林的先序遍历与对应二叉树先序遍历相同

 b)中序遍历    树的后根遍历访问每一棵树--森林的中序遍历序列与二叉树对应的中序遍历序列相同

九、树的应用

1.并查集  一种简单的集合表示

通常用树的双亲表示法作为并查集的存储结构。

2.二叉排序树  中序遍历序列是一个递增的有序序列   左<根<右

3.平衡二叉树的查找效率是最高的  

平均查找长度取决于树的高度

4.平衡二叉树的判断  

利用递归的后序遍历过程

a.判断左子树是一平衡二叉树

b.判断右子树是一平衡二叉

c.判断以该结点为根的二叉树为平衡二叉树

判断条件--若左子树和右子树均为平衡二叉树,且左子树与右子树高度差的绝对值小于等于1,则平衡。

5.哈夫曼树---哈夫曼树也称最优二叉树,含有n个带权叶节点带权路径长度最小的二叉树

a)带权路径长度  

b)路径长度---边的个数

c)结点的权---结点被赋予的数值

d)树的带权路径长度 ---树中所有叶节点的带权路径长度之和

e)哈夫曼树的构造算法

 (1) 将n个结点作为n棵仅含有一个根节点的二叉树,构成森林F;

 (2)生成一个新节点,并从F中找出权值最小的两棵树作为左右子树,且新节点的权值为两棵子树之和;

 (3) 从F中删除这两个树,并将新生成的树加入到F中

 (4) 重复bc步骤,直到F中只有 一棵树为止

6.前缀编码   没有一个编码是另一个编码的前缀