》》如何用C语言构建一颗二叉树?
第一种方法:
ThreadTree A = (ThreadTree)malloc(sizeof(ThreadNode));
A->data = { 'A' };
A->ltag = 0;
A->rtag = 0;
A->lchild = NULL;
A->rchild = NULL;
ThreadTree B = (ThreadTree)malloc(sizeof(ThreadNode));
B->data = { 'B' };
A->lchild = B;
B->ltag = 0;
B->rtag = 0;
B->lchild = NULL;
B->rchild = NULL;
ThreadTree C = (ThreadTree)malloc(sizeof(ThreadNode));
C->data = { 'C' };
A->rchild = C;
C->lchild = NULL;
C->rchild = NULL;
C->ltag = 0;
C->rtag = 0;
ThreadTree D = (ThreadTree)malloc(sizeof(ThreadNode));
D->data = { 'D' };
B->lchild = D;
D->lchild = NULL;
D->rchild = NULL;
D->ltag = 0;
D->rtag = 0;
ThreadTree E = (ThreadTree)malloc(sizeof(ThreadNode));
E->data = { 'E' };
B->rchild = E;
E->lchild = NULL;
E->rchild = NULL;
E->ltag = 0;
E->rtag = 0;
ThreadTree F = (ThreadTree)malloc(sizeof(ThreadNode));
F->data = { 'F' };
C->lchild = F;
F->lchild = NULL;
F->rchild = NULL;
F->ltag = 0;
F->rtag = 0;
ThreadTree G = (ThreadTree)malloc(sizeof(ThreadNode));
G->data = { 'G' };
D->rchild = G;
G->lchild = NULL;
G->rchild = NULL;
G->ltag = 0;
G->rtag = 0;
第二种方法:
》》了解为什么要利用线索的先前知识
关键:n个结点的二叉树,有n+1个空链域!可用来记录前驱、后继的信息
》》线索二叉树的存储结构
》》中序线索二叉树
- 前驱线索(由左孩子指针充当)
- 后继线索(由右孩子指针充当)
》》中序线索二叉树的存储
》》中序线索化
》》中序线索二叉树找中序后继
》》中序线索二叉树找中序前驱
》》完整代码:
#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
typedef int ElemType;
//线索二叉树结点
typedef struct ThreadNode {
ElemType data;
struct ThreadNode* lchild, * rchild;
int ltag, rtag;//左、右线索标志
}ThreadNode, * ThreadTree;
//创建二叉树
ThreadNode* Create(ThreadNode* bt) {
static int i = 0;
char ch;
//string str = "AB#D##C##";
string str = "ABD#G##E##CF###";
ch = str[i++];
if (ch == '#')bt = NULL;//建立一棵空树
else {
bt = (ThreadTree)malloc(sizeof(ThreadNode)); bt->data = ch;//生成一个结点,数据域为ch
bt->lchild = Create(bt->lchild);//递归建立左子树
bt->rchild = Create(bt->rchild);//递归建立右子树
bt->ltag = 0; bt->rtag = 0;
}
return bt;
}
//中序遍历
void OrdinaryInOrder(ThreadNode* T) {
if (T != NULL) {
OrdinaryInOrder(T->lchild);//中序遍历左子树
printf("%c ", T->data);//访问根节点的数据域
OrdinaryInOrder(T->rchild);//中序遍历右子树
}
}
//中序线索化
void InThread(ThreadTree p,ThreadTree &pre) {
if (p != NULL) {
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 CreateInThread(ThreadTree T) {
ThreadTree pre = NULL;//pre 初始为NULL
if (T != NULL) {//非空二叉树才能线索化
InThread(T,pre);//中序线索化二叉树
pre->rchild = NULL;//处理遍历的最后一个结点
pre->rtag = 1;
}
}
/*
在中序线索二叉树中找到指定结点 *p的中序后继next
①若p->rtag == 1,则next = p->rchild
②p->rtag == 0(p必定有右孩子)
中序遍历--左 根 右
左 根 (左 根 右)
左 根 ((左 根 右) 根 右)
*/
//找到以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 = FirstNode(T); p != NULL; p = NextNode(p)) {
printf("%c ", p->data);//访问根节点的数据域
}
printf("\n");
}
/*
在中序线索二叉树中找到指定结点*p的中序前驱pre
①若p->ltag == 1,则pre = p->lchild;
②若p->ltag == 0(必有左孩子)
中序遍历--左 根 右
(左 根 右) 根 右
((左 根 右) 根 右) 根 右
*/
//找到以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 = LastNode(T); p != NULL; p = PreNode(p)) {
printf("%c ", p->data);//访问根节点的数据域
}
printf("\n");
}
//寻找指定值的结点
ThreadNode* findNode(ThreadTree t, int NodeData) {
if (t == NULL) {
return NULL;
}
if (t->data == NodeData) {
return t;
}
ThreadNode* p;
for (p = FirstNode(t); p != NULL; p = NextNode(p)) {
if (p->data == NodeData) {
return p;
}
}
return NULL;
}
//思考:处理遍历的最后一个结点时,为什么没有判断rchild是否为NULL?
//答案:中序遍历的最后一个结点右孩子指针必为空
int main() {
ThreadNode* p = (ThreadTree)malloc(sizeof(ThreadNode)); //记录目标节点
p->data = 'C';//p指向目标结点
//创建一棵二叉树
ThreadTree T = (ThreadTree)malloc(sizeof(ThreadNode));//创建一颗二叉树
T = Create(T);
printf("---普通中序遍历--- \n");//B D A C
OrdinaryInOrder(T);
printf("\n");
CreateInThread(T);
printf("---中序线索二叉树找后继的中序遍历--- \n");//B D A C
InOrder(T);
printf("---中序线索二叉树找前驱的逆向的中序遍历--- \n");//B D A C
RevInOrder(T);
//printf("%c \n", findNode(T, p->data)->data);
ThreadTree findCurrentNode = (ThreadTree)malloc(sizeof(ThreadNode));
findCurrentNode = findNode(T, p->data);
if (NextNode(findCurrentNode) != NULL) {
printf("%c的后继是:%c", findCurrentNode->data, NextNode(findCurrentNode)->data);
}
else {
printf("%c没有后继了", findCurrentNode->data);
}
printf("\n");
if (PreNode(findCurrentNode) != NULL) {
printf("%c的前驱是:%c", findCurrentNode->data, PreNode(findCurrentNode)->data);
}
else {
printf("%c没有前驱了", findCurrentNode->data);
}
printf("\n");
return 0;
}
》》实验效果
》》先序线索二叉树
》》先序线索二叉树的存储
》》先序线索化(需要解决“转圈问题”)
》》先序线索二叉树找先序后继
》》先序线索二叉树找先序前驱
》》总结
》》先序线索二叉树找先序后继
》》完整代码
#include<iostream>
#include<stdio.h>
#include <stdlib.h>
#include<string.h>
using namespace std;
typedef int ElemType;
//线索二叉树结点
typedef struct ThreadNode {
ElemType data;
struct ThreadNode* lchild, * rchild;
int ltag, rtag;//左、右线索标志
}ThreadNode, * ThreadTree;
//创建二叉树
ThreadNode* Create(ThreadNode* bt) {
static int i = 0;
char ch;
//string str = "AB#D##C##";
string str = "ABD#G##E##CF###";
ch = str[i++];
if (ch == '#')bt = NULL;//建立一棵空树
else {
bt = (ThreadTree)malloc(sizeof(ThreadNode)); bt->data = ch;//生成一个结点,数据域为ch
bt->ltag = 0; bt->rtag = 0;
bt->lchild = Create(bt->lchild);//递归建立左子树
bt->rchild = Create(bt->rchild);//递归建立右子树
}
return bt;
}
//先序遍历
void OrdinaryPreOrder(ThreadNode* T) {
if (T != NULL) {
printf("%c ", T->data);//访问根节点的数据域
OrdinaryPreOrder(T->lchild);//先序遍历左子树
OrdinaryPreOrder(T->rchild);//先序遍历右子树
}
}
//先序线索化
void PreThread(ThreadTree p, ThreadTree& pre) {
if (p != NULL) {
if (p->lchild == NULL) {//左子树为空,建立前驱线索
p->lchild = pre;
p->ltag = 1;
}
if (pre != NULL && pre->rchild == NULL) {
pre->rchild = p;//建立前驱结点的后继线索
pre->rtag = 1;
}
pre = p;
//printf("%c", p->data);
if (p->ltag == 0) {
PreThread(p->lchild, pre);//递归,线索化左子树
}
if (p->rtag == 0) {
PreThread(p->rchild, pre);
}
}
}
//先序线索化二叉树T
void CreatePreThread(ThreadTree& T) {
ThreadTree pre = NULL;//pre 初始为NULL
if (T != NULL) {//非空二叉树才能线索化
PreThread(T, pre);//先序线索化二叉树
if(pre->rchild == NULL)
pre->rtag = 1;
}
}
/*
在先序线索二叉树中找到指定结点*p的先序后继(next)
①若p->rtag == 1,则 next = p->rchild
②若p->rtag == 0
》》若p有左孩子,则先序后继为左孩子
先序遍历---- 根 左 右
根 (根 左 右) 右
》》若p没有左孩子,则先序后继为右孩子
先序遍历---- 根 右
根 (根 左 右)
先序线索二叉树能否找到先序前驱呢?(提示:【先序遍历:根左右】)
答:不能,因为在左右子树中的结点只可能是根的后继,不可能是前驱。
*/
//在先序线索二叉树中找到结点p的后继结点
ThreadNode* NextNode(ThreadNode* p) {
if (p->rtag == 1) {
return p->rchild;
}
else {
if (p->ltag == 0) //若p有左孩子,则先序后继为左孩子
return p->lchild;
else//若p没有左孩子,则先序后继为右孩子
return p->rchild;
}
}
//先序线索二叉树先序遍历
void PreOrder(ThreadTree T) {
for (ThreadNode* p = T; p != NULL; p = NextNode(p)) {
printf("%c ", p->data);//访问根节点的数据域
}
printf("\n");
}
//寻找指定值的结点
ThreadNode* findNode(ThreadTree T, int NodeData) {
if (T == NULL) {
return NULL;
}
if (T->data == NodeData) {
return T;
}
ThreadNode* p;
for (p = T; p != NULL; p = NextNode(p)) {
if (p->data == NodeData) {
return p;
}
}
return NULL;
}
int main() {
ThreadNode* p = (ThreadTree)malloc(sizeof(ThreadNode)); //记录目标节点
p->data = 'C';//p指向目标结点
//创建一棵二叉树
ThreadTree T = (ThreadTree)malloc(sizeof(ThreadNode));//创建一颗二叉树
T = Create(T);
/*
ThreadTree A = (ThreadTree)malloc(sizeof(ThreadNode));
A->data = { 'A' };
A->ltag = 0;
A->rtag = 0;
A->lchild = NULL;
A->rchild = NULL;
ThreadTree B = (ThreadTree)malloc(sizeof(ThreadNode));
B->data = { 'B' };
A->lchild = B;
B->ltag = 0;
B->rtag = 0;
B->lchild = NULL;
B->rchild = NULL;
ThreadTree C = (ThreadTree)malloc(sizeof(ThreadNode));
C->data = { 'C' };
A->rchild = C;
C->lchild = NULL;
C->rchild = NULL;
C->ltag = 0;
C->rtag = 0;
ThreadTree D = (ThreadTree)malloc(sizeof(ThreadNode));
D->data = { 'D' };
B->lchild = D;
D->lchild = NULL;
D->rchild = NULL;
D->ltag = 0;
D->rtag = 0;
ThreadTree E = (ThreadTree)malloc(sizeof(ThreadNode));
E->data = { 'E' };
B->rchild = E;
E->lchild = NULL;
E->rchild = NULL;
E->ltag = 0;
E->rtag = 0;
ThreadTree F = (ThreadTree)malloc(sizeof(ThreadNode));
F->data = { 'F' };
C->lchild = F;
F->lchild = NULL;
F->rchild = NULL;
F->ltag = 0;
F->rtag = 0;
ThreadTree G = (ThreadTree)malloc(sizeof(ThreadNode));
G->data = { 'G' };
D->rchild = G;
G->lchild = NULL;
G->rchild = NULL;
G->ltag = 0;
G->rtag = 0;
*/
printf("---普通先序遍历--- \n");
OrdinaryPreOrder(T);
printf("\n");
//创建先序线索二叉树
CreatePreThread(T);
printf("---先序线索二叉树找后继的先序遍历--- \n");
PreOrder(T);
printf("\n");
//寻找结点
ThreadTree findCurrentNode = (ThreadTree)malloc(sizeof(ThreadNode));
findCurrentNode = findNode(T, p->data);
//寻找结点的后继结点
if (NextNode(findCurrentNode) != NULL) {
printf("%c 的后继是:%c", findCurrentNode->data, NextNode(findCurrentNode)->data);
}
else {
printf("%c 没有后继了", findCurrentNode->data);
}
printf("\n");
return 0;
}
》》实验结果
》》后序线索二叉树
》》后序线索二叉树的存储
》》后序线索化
王道的后序线索化,处理遍历的最后一个结点让pre->rchild = NULL,pre->rtag=1;
但好像可以省去这一步。(仅个人看法,因为去掉之后运行也是正常的,如有错误,欢迎指正!!!)
》》后序线索二叉树找后序后继
》》后序线索二叉树找后序前驱
》》总结
》》后序线索二叉树找后序前驱
》》完整代码
#include<iostream>
#include<stdio.h>
#include <stdlib.h>
#include<string.h>
using namespace std;
typedef int ElemType;
//线索二叉树结点
typedef struct ThreadNode {
ElemType data;
struct ThreadNode* lchild, * rchild;
int ltag, rtag;//左、右线索标志
}ThreadNode, * ThreadTree;
ThreadNode* Create(ThreadNode* bt) {
static int i = 0;
char ch;
//string str = "AB#D##C##";
string str = "ABD#G##E##CF###";
ch = str[i++];
if (ch == '#')bt = NULL;//建立一棵空树
else {
bt = (ThreadTree)malloc(sizeof(ThreadNode)); bt->data = ch;//生成一个结点,数据域为ch
bt->ltag = 0; bt->rtag = 0;
bt->lchild = Create(bt->lchild);//递归建立左子树
bt->rchild = Create(bt->rchild);//递归建立右子树
}
return bt;
}
//后序遍历
void OrdinaryPostOrder(ThreadNode* T) {
if (T != NULL) {
OrdinaryPostOrder(T->lchild);//后序遍历左子树
OrdinaryPostOrder(T->rchild);//后序遍历右子树
printf("%c ", T->data);//访问根节点的数据域
}
}
//后序线索化
void PostThread(ThreadTree p, ThreadTree& pre) {
if (p != NULL) {
PostThread(p->lchild, pre);//递归,线索化左子树
PostThread(p->rchild, 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;//标记当前结点成为刚刚访问的结点
}
}
//后序线索化二叉树
void CreatePostThread(ThreadTree T) {
ThreadTree pre = NULL;
if (T != NULL) {//非空二叉树才能线索化
PostThread(T, pre);//线索化二叉树
if(pre->rchild == NULL)//处理遍历的最后一个结点
pre->rtag = 1;
}
}
/*
在后序线索二叉树中找到指定结点*p的后序前驱(pre)
①若p->ltag == 1,则 pre = p->lchild
②若p->ltag == 0
》》若p有右孩子,则后序前驱为右孩子
先序遍历---- 左 右 根
左 (左 右 根) 根
》》若p没有右孩子,则后序前驱为左孩子
先序遍历---- 左 根
(左 右 根) 根
后序线索二叉树能否找到后序后继呢?(提示:【后序遍历:左右根】)
答:不能,因为在左右子树中的结点只可能是根的前驱,不可能是后继。
*/
//在后序线索二叉树中找到结点p的前驱结点
ThreadNode* PreNode(ThreadNode* p) {
if (p->ltag == 1)
return p->lchild;
else {
if (p->rtag == 0) {
return p->rchild;
}
else {
return p->lchild;
}
}
}
void RevPostOrder(ThreadTree T) {
for (ThreadNode* p = T; p != NULL; p = PreNode(p)) {
printf("%c ", p->data);//访问根节点的数据域
}
printf("\n");
}
//寻找指定值的结点
ThreadNode* findNode(ThreadTree T, int NodeData) {
if (T == NULL) {
return NULL;
}
if (T->data == NodeData) {
return T;
}
ThreadNode* p;
for (p = T; p != NULL; p = PreNode(p)) {
if (p->data == NodeData) {
return p;
}
}
return NULL;
}
int main() {
ThreadNode* p = (ThreadTree)malloc(sizeof(ThreadNode)); //记录目标节点
p->data = 'A';//p指向目标结点
//创建一棵二叉树
ThreadTree T = (ThreadTree)malloc(sizeof(ThreadNode));//创建一颗二叉树
T = Create(T);
printf("---普通后序遍历--- \n");
OrdinaryPostOrder(T);
printf("\n");
//创建后序线索二叉树
CreatePostThread(T);
printf("---后序线索二叉树找前驱的逆向的后序遍历--- \n");
RevPostOrder(T);
printf("\n");
//寻找结点
ThreadNode* findCurrentNode = (ThreadTree)malloc(sizeof(ThreadNode));
findCurrentNode = findNode(T, p->data);
if (PreNode(findCurrentNode) != NULL) {
printf("%c的前驱是:%c", findCurrentNode->data, PreNode(findCurrentNode)->data);
}
else {
printf("%c没有前驱了", findCurrentNode->data);
}
printf("\n");
return 0;
}
》》实验结果
》》三种线索二叉树的对比
本文是根据王道考研的数据结构教学视频中,运用其分析原理思想,再结合自己的一些思路分析和逻辑编程,进行完整的代码的编写。如有不正确的地方,欢迎指正!