数据结构-树与二叉树(Tree and Binary)-存储结构与基本操作

一. 二叉树(Binary Tree)

1. 存储结构

1.1 顺序存储结构(完全二叉树、满二叉树)

struct TreeNode {
    int value;      					// 结点中的数据元素
    bool isEmpty;   					// 结点是否为空
};

1.2 链式存储结构(一般二叉树)

typedef struct BiTNode {
    int data;                           // 数据域
    struct BiTNode *lchild, *rchild;    // 左、右孩子指针
}BiTNode, *BiTree;

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) {
        InOrder(T->lchild);     		// 递归遍历左子树
        visit(T);               		// 访问根结点
        InOrder(T->rchild);     		// 递归遍历右子树
    }
}

2.3 后序遍历

// 后序遍历
void PostOrder(BiTree T) {
    if (T != NULL) {
        PostOrder(T->lchild);   		// 递归遍历左子树
        PostOrder(T->rchild);   		// 递归遍历右子树
        visit(T);               		// 访问根结点
    }
}

2.4 层次遍历

// 层次遍历
void LevelOrder(BiTree T) {
    LinkQueue Q;
    InitQueue(Q);                   	// 初始化辅助队列
    BiTree p;
    EnQueue(Q, T);                  	// 根结点入队
    while (!QueueEmpty(Q)) {        	// 队列不空则循环
        DeQueue(Q, p);              	// 队头结点出队
        visit(p);                   	// 访问队头结点
        if (p->lchild !=NULL) {
            EnQueue(Q, p->lchild);  	// 左孩子入队
        }
        if (p->rchild != NULL) {
            EnQueue(Q, p->rchild);  	// 右孩子入队
        }
    }
}

二. 线索二叉树(Threaded Binary Tree)

1. 存储结构

typedef struct ThreadNode {
    int data;                           // 数据元素
    struct ThreadNode *lchild, *rchild; // 左、右孩子指针
    int ltag, rtag;                     // 左、右线索标志
}ThreadNode, *ThreadTree;

2. 创建

ThreadNode *pre = NULL;                 // 全局变量,指向当前访问结点的前驱。

// 访问结点:连接该结点与前驱结点的线索信息。
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;                            // 标记当前结点成为刚访问过的结点
}

2.1 线索化二叉树

  1. 先序线索二叉树
void PreThread(ThreadTree T) {
    if (T != NULL) {
        visit(T);
        // 如果不判断ltag,那么当ltag==1时会回到pre,陷入循环。
        // 在中序和后序线索化中不用判断,因为visit(q)时,q的左子树已经被遍历处理过,不会再被访问。
        if (T->ltag == 0) {             // lchild不是前驱线索
            PreThread(T->lchild);
        }
        PreThread(T->rchild);
    }
}
  1. 中序线索二叉树
void InThread(ThreadTree T) {
    if (T != NULL) {
        InThread(T->lchild);            // 中序线索化左子树
        visit(T);                       // 访问根结点
        InThread(T->rchild);            // 中序线索化右子树
    }
}
  1. 后序线索二叉树
void PostThread(ThreadTree T) {
    if (T != NULL) {
        PostThread(T->lchild);          // 后序线索化左子树
        PostThread(T->rchild);          // 后序线索化右子树
        visit(T);                       // 访问根结点
    }
}

2.2 创建线索二叉树

  1. 先序线索二叉树
void CreatePreThread(ThreadTree T) {
    pre = NULL;
    if (T != NULL) {
        PreThread(T);                   // 通过先序遍历线索化二叉树
        // 处理遍历的最后一个结点
        if (pre->rchild = NULL) {
            pre->rtag = 1;
        }
    }
}
  1. 中序线索二叉树
void CreateInThread(ThreadTree T) {
    pre = NULL;
    if (T != NULL) {
        InThread(T);                    // 通过中序遍历线索化二叉树
        // 处理遍历的最后一个结点
        if (pre->rchild = NULL) {
            pre->rtag = 1;
        }
    }
}
  1. 后序线索二叉树
void CreatePostThread(ThreadTree T) {
    pre = NULL;
    if (T != NULL) {
        PostThread(T);                  // 通过后序遍历线索化二叉树
        // 处理遍历的最后一个结点
        if (pre->rchild = NULL) {
            pre->rtag = 1;
        }
    }
}

3. 寻找后继结点和前驱结点

3.1 寻找后继结点

  1. 先序线索二叉树

如果 rtag == 1 ,直接返回后继线索;
否则 rtag == 0 ,p必有右孩子。如果p有左孩子,则左孩子为后继;如果p无左孩子,则右孩子为后继。

ThreadNode *Nextnode(ThreadNode *p) {
    if (p->rtag == 1) {
        return p->rchild;
    }
    if (p->ltag == 0) {
    	return p->lchild;
    }
    return p->rchild;
}
  1. 中序线索二叉树

如果 rtag == 1 ,直接返回后继线索;
否则遍历右子树,找到第一个访问的结点(右子树中最左下的结点)为后继。

ThreadNode *Firstnode(ThreadNode *p) {
    while (p->ltag == 0) {
        p =  p->lchild;
    }
    return p;
}

ThreadNode *Nextnode(ThreadNode *p) {
    if (p->rtag == 1) {
        return p->rchild;
    }
    return Firstnode(p->rchild);
}
  1. 后序线索二叉树

如果 rtag == 1 ,直接返回后继线索;
否则 rtag == 0 ,p必有右孩子。因为在后序遍历中,左右子树的结点只可能是根的前驱,不可能是后继,所以只能从头开始后序遍历寻找后继结点。

ThreadNode *Nextnode(ThreadNode *p) {
    if (p->rtag == 1) {
        return p->rchild;
    }
    // 从头开始后序遍历寻找后继结点
    // ...

}

如果改为三叉链表,增加1个指向双亲结点的指针,则有以下4种情况:

  • 可以找到p的双亲结点,且p是右孩子:p的双亲结点即为后驱。
  • 可以找到p的双亲结点,且p是左孩子,其右兄弟为空:p的双亲结点即为后驱。
  • 可以找到p的双亲结点,且p是左孩子,其右兄弟非空:p的后驱为右兄弟子树中第一个被后序遍历的结点。
  • p是根结点,没有后驱结点。

3.2 寻找前驱结点

  1. 先序线索二叉树

如果 ltag == 1 ,直接返回前驱线索;
否则 ltag == 0 ,p必有左孩子。因为在先序遍历中,左右子树中的结点只可能是根的后继,不可能是前驱,所以只能从头开始先序遍历寻找前驱结点。(类似在后序线索二叉树中寻找后继结点)

ThreadNode *Prenode(ThreadNode *p) {
    if (p->ltag == 1) {
        return p->lchild;
    }
    // 从头开始先序遍历寻找前驱结点
    // ...
}

如果改为三叉链表,增加1个指向双亲结点的指针,则有以下4种情况:

  • 可以找到p的双亲结点,且p是左孩子:p的双亲结点即为前驱。
  • 可以找到p的双亲结点,且p是右孩子,其左兄弟为空:p的双亲结点即为前驱。
  • 可以找到p的双亲结点,且p是右孩子,其左兄弟非空:p的前驱为左兄弟子树中最后一个被先序遍历的结点。
  • p是根结点,没有前驱结点。
  1. 中序线索二叉树

如果 ltag == 1 ,直接返回前驱线索;
否则遍历左子树,找到最后一个访问的结点(左子树中最右下的结点)为前驱。

ThreadNode *Lastnode(ThreadNode *p) {
    while (p->rtag == 0) {
        p = p->rchild;
    }
    return p;
}

ThreadNode *Prenode(ThreadNode *p) {
    if (p->ltag == 0) {
        return Lastnode(p->lchild);
    }
    return p->lchild;
}
  1. 后序线索二叉树

如果 ltag == 1 ,直接返回前驱线索;
否则 ltag == 0 ,p必有左孩子。如果p有右孩子,则右孩子为后序;如果p没有右孩子,则左孩子为后序。(类似在先序线索二叉树中找后继结点)

ThreadNode *Prenode(ThreadNode *p) {
    if (p->ltag == 1) {
        return p->lchild;
    }
    if(p->rtag == 0) {
        return p->rchild;
    }
    return p->lchild;
}

4. 遍历

4.1 正向遍历

找到第一个结点,然后依次找到结点的后继,直到其后继为空。

// 正向遍历
void Inorder(ThreadNode *T) {
    for (ThreadNode *p = Firstnode(T); p != NULL; p = Nextnode(p)) {
        // ...
    }
}

4.2 逆向遍历

找到最后一个结点,然后依次找到结点的前驱,直到其前驱为空。

// 逆向遍历
void ReInorder(ThreadNode *T) {
    for (ThreadNode *p = Lastnode(T); p != NULL; p = Prenode(p)) {
        // ...
    }
}

三. 树

1. 存储结构

1.1 双亲表示法

#define MAX_TREE_SIZE 100           	// 树中最多结点树
typedef struct {                    	// 树的结点定义
    int data;                       	// 数据元素
    int parent;                     	// 双亲位置域
}PTNode;

typedef struct {                    	// 树的类型定义
    PTNode nodes[MAX_TREE_SIZE];    
    int n;                          	// 结点数
}PTree;

1.2 孩子表示法

struct CTNode {
    int child;                      	// 孩子结点在数组中的位置
    struct CTNode *next;            	// 下一个孩子
};

typedef struct {
    int data;
    struct CTNode *firstChild;      	// 第一个孩子
}CTBox;

typedef struct {
    CTBox nodes[MAX_TREE_SIZE];
    int n;                          	// 结点数
    int r;                          	// 根的位置
}CTree;

1.3 孩子兄弟表示法(重点)

typedef struct CSNode {
    int data;                       	// 数据域
    struct CSNode *firstchild;      	// 第一个孩子
    struct CSNode *nextsibling;     	// 右兄弟
}CSNode, *CSTree;
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值