数据结构--树与二叉树

第5章 : 树与二叉树

1 二叉树的存储结构

1.1 顺序存储结构

#define MaxSize 100
struct TreeNode{
    ElemType value; //结点中数据元素
    bool isEmpty;   //结点是否为空
};

TreeNode t[MaxSize];

1.2 链式存储结构

struct ElemType{
    int value;
};
typedef struct BiTNode{
    ElemType data; 
    struct BiTNode *lchild,*rchild; //左、右孩子指针
    struct BiTNode *parent; //父节点指针
}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->rchild = p;

2 二叉树的遍历

2.1 先序遍历

void PreOrder(BiTree T){
    if(T != NULL){
        visit(T);
        PreOrder(T->lchild);
        PreOrder(T->rchild);
    }
}

2.2 中序遍历

void InOrder(BiTree T){
    if(T != NULL){
        PreOrder(T->lchild);
        visit(T);
        PreOrder(T->rchild);
    }
}

2.3 后序遍历

void PostOrder(BiTree T){
    if(T != NULL){
        PreOrder(T->lchild);
        PreOrder(T->rchild);
        visit(T);
    }
}

2.4 层序遍历

//二叉树的结点(链式存储)
typedef struct BiTree{
    char data;
    struct BiTNode *lchild,*rchild; 
}BiTNode,*BiTree;

//链式队列结点
typedef struct LinkNode{
    BiTNode *data;   //保存指针而不是结点
    struct LinkNode *next; 
}LinkNode;

typedef struct{
    LinkNode *front,*rear;
}LinkQueue;

void LeverOrder(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->rchild);   //右孩子入队
    }
}

3 线索二叉树

3.1 线索二叉树结点

typedef struct ThreadNode{
    ElemType data;
    struct ThreadNode *lchild,*rchild;  
    int ltag,rtag;   //tag==0,表示指针指向孩子,tag==1,表示指针是“线索”
}ThreadNode,*ThreadTree;

3.2 中序线索化

typedef struct ThreadNode{
    ElemType data;
    struct ThreadNode *lchild,*rchild;
    int ltag,rtag;
}ThreadNode,*ThreadTree;

ThreadNode *pre = NULL; //全局变量pre,指向当前访问结点的前驱
void CreateInThread(ThreadTree T){
    pre = NULL;
    if(T != NULL){  //非空二叉树才可以进行线索化
        InThread(T)    //中序线索化二叉树
        if(pre->rchild == NULL)
            pre->rchild = 1;
    }
}
void InThread(ThreadNode 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;
}

3.3 先序线索化

为了避免先序线索化中左孩子与前驱线索冲突,故使用ltag来进行区别

void PreThread(ThreadNode T){
    if(T != NULL){
   			visit(T);
   			if(T->ltag == 0) //lchild不是前驱线索
            InThread(T->lchild);
        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;
}

3.4 后序线索化

void PostThread(ThreadNode T){
    if(T != NULL){
        InThread(T->lchild);
        InThread(T->rchild);
      	visit(T);
    }
}

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

4 中序线索二叉树

4.1 找中序后继

ThreadNode *FirstNode(ThreadNode *p){
    //循环找到最左下结点
    while(p->ltag == 0)
        p = p->lchild;
    return 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 = FirstNode(T);p != NULL;p = Nextnode(p))
        visit(p);
}

4.2 找中序前驱

ThreadNode *LastNode(ThreadNode *p){
    //循环找到最右下结点
    while(p->rtag == 0)
        p = p->rchild;
    return p;
}

ThreadNode *Prenode(ThreadNode *p){
    if(p->ltag == 0) return FirstNode(p->lchild);
    else return p->lchild;      //rtag==1直接返回后继线索
}
//对中序线索二叉树进行逆向中序遍历
void RevInorder(ThreadNode *T){
    for(ThreadNode *p = LastNode(T);p != NULL;p = Prenode(p))
        visit(p);
}

5 树、森林

5.1 树的存储结构

5.1.1 双亲表示法(顺序存储)
#define MAX_TREE_SIZE 100
typedef struct{
    ElemType data;
    int parent;
}PTNode;

typedef struct {
    PTNode nodes[MAX_TREE_SIZE];
    int n;
}PTree;
5.1.2 孩子表示法(顺序+链式存储)
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;

6 并查集

6.1 并查集

#define SIZE 100
int     UFSets[SIZE]; //集合元素数组(双亲指针数组)

//并查集的初始化
void Initial(int S[]){
    for(int i = 0;i < size;i++){
        S[i] = -1;
    }
}

//Find操作(函数在并查集S中查找并返回包含元素x的🌲的根
int Find(int S[]){
    while(S[x] >= 0)
        x = S[x];
    return x;
}

void Union(int S[],int Root1,int Root2){
    if(Root1 == Root2)
        return;
    //将根Root2连接到另一根Root1的下面
    S[Root2] = Root1;
}

6.2 对于Union的优化:

:$\lfloor \log_2x+1 \rfloor$

故优化过后,Find操作最坏时间复杂度为:
O ( log ⁡ 2 n ) O(\log_2n) O(log2n)

void Union(int S[],int Root1,int Root2){
    if(Root1 == Root2)
        return;
    //将根Root2连接到另一根Root1的下面
    if(S[Root2] > S[Root1]){  //Root2结点更少,因为数组值为负数,越大代表绝对值越小
        S[Root1] += Root2;   //累加结点总数
        S[Root2] = Root1;		//小树合并到大树
    }
    else{
        S[Root2] += Root1;
        S[Root1] = Root2;
    }
    S[Root2] = Root1;
}

6.3 对于Find的优化

// Find “查”操作优化,先找到根结点,再进行“压缩路径”
int Find(int S[],int x){
    int root = x;
    while(S[root] >= 0)
        root = S[root];  //循环找到根
    while(x != root){    //压缩路径
        int t = S[x];    //t指向x的父节点
        S[x] = root;     //x直接挂到根下
        x = t;
    }
    return root;        //返回结点编号
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值