文章目录
下面写的代码可能感觉会很乱,所以加上了目录,意在为了引导,也就是你需要回顾哪个算法,就能够快速定位到该位置。另外,线索二叉树只写了中序线索二叉树。
三、树和二叉树
(一)二叉树的顺序存储结构
- 二叉树的顺序存储结构最适用于存储完全二叉树,存储方法如下:
- 构造完全二叉树
int data[] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
(二)二叉树的链式存储结构——二叉链表存储结构
- 结构体的声明
typedef struct BT_NODE
{
int data; //存放结点中的数据,这里的数据类型可以改变
struct BT_NODE* l_child; //指向左孩子的指针
struct BT_NODE* r_child; //指向右孩子的指针
}BT_NODE;
(三)二叉树的遍历算法
- 在遍历二叉树之前,我们是不是得要有一个二叉链表?下面是构造二叉链表代码
/*
method:
初始化一个树,用二叉链表表示,
param:
bt_node 树
data 这里是一个数组,数组里面的数据是按照完全二叉树的顺序存储结构存储的。
n 是要存储的结点的个数
pos 这里是当前结点位置,初始值为0
此方法大致思路如下:
1
2 3
4 5 6 7
8 9 10 11 12 13 14 15
存储顺序如下:1->2->4->8->9->5->10->11->3->6->12->13->7->14->15
即:每次先存储左孩子,直到最后左孩子的数组越界,然后返回上一层,存储右孩子
*/
int BT_Init(BT_NODE* bt_node, int* data, int n, int pos)
{
BT_NODE* p, * q;
if (n <= 0) //如果输入的个数n小于等于0,直接返回
return ERROR;
if (pos == 0) //因为递归,这个不能重复赋值,所以只在根节点也就是位置为0处,赋值
{ //目的只是为了执行一次
bt_node[0].data = data[0];
bt_node[0].l_child = NULL;
bt_node[0].r_child = NULL;
if (n == 1) //在这里加一个判断,如果输入值为1,返回创建成功
return OK;
}
/************************************************************/
/*************************创建树的核心代码*******************/
/************************************************************/
if(2 * pos + 1 >= n) //2*pos+1 的位置是它的左孩子的值在数组中存放位置。为了防止数组越界
return ERROR;
p = (BT_NODE*)malloc(sizeof(BT_NODE)); //创建内存
p->data = data[2 * pos + 1]; //获取数据
p->l_child = NULL;
p->r_child = NULL;
bt_node->l_child = p; //创建的左孩子将其当作子树的根节点,进行递归
BT_Init(bt_node->l_child, data, n, 2 * pos + 1); //这里是递归操作,意在每次都是先创建左子树,再创建右子树,
//直到左子树数组越界,然后返回上一层,创建右子树
if (2 * pos + 2 >= n)
return ERROR;
q = (BT_NODE*)malloc(sizeof(BT_NODE));
q->data = data[2 * pos + 2]; //2*pos+2 的位置是它的右孩子的值在数组中存放位置。为了防止数组越界
q->l_child = NULL;
q->r_child = NULL;
bt_node->r_child = q; //创建的右孩子将其当作子树的根节点,进行递归
BT_Init(bt_node->r_child, data, n, 2 * pos + 2); //这里是创建右侧分支的结点,仍然是先创建左子树,再创建右子树。
return OK;
}
- 访问二叉树
/*
method:
访问结点
param:
bt_node 二叉树
*/
void Visit(BT_NODE* bt_node)
{
printf("%d ", bt_node->data); //打印出来二叉树的结点值
}
- 先序遍历二叉树
/*
method:
先序遍历二叉树
param:
bt_node 二叉树
*/
void Preorder(BT_NODE* bt_node)
{
if (bt_node != NULL)
{
Visit(bt_node); //先访问根节点
Preorder(bt_node->l_child); //再访问左结点
Preorder(bt_node->r_child); //最后访问右结点
}
}
- 中序遍历二叉树
/*
method:
中序遍历二叉树
param:
bt_node 二叉树
*/
void Infix_Order(BT_NODE* bt_node)
{
if (bt_node != NULL)
{
Infix_Order(bt_node->l_child); //先访问左结点
Visit(bt_node); //再访问根节点
Infix_Order(bt_node->r_child); //最后访问右结点
}
}
- 后序遍历二叉树
/*
method:
后序遍历二叉树
param:
bt_node 二叉树
*/
void Postorder(BT_NODE* bt_node)
{
if (bt_node != NULL)
{
Postorder(bt_node->l_child); //先访问左结点
Postorder(bt_node->r_child); //再访问右结点
Visit(bt_node); //最后访问根节点
}
}
- 层次遍历二叉树
/*
method:
层次遍历二叉树
param:
bt_node 二叉树
*/
void Level(BT_NODE* bt_node)
{
int front = 0, rear = 0;
BT_NODE* que[MAXSIZE]; //定义一个循环队列
BT_NODE* q;
if (bt_node != NULL)
{
rear = (rear + 1) % MAXSIZE; //队尾向后一个位置
que[rear] = bt_node; //根结点入队
while (front != rear) //队内由元素
{
front = (front + 1) % MAXSIZE; //队头指针向后一个位置
q = que[front]; //出队
Visit(q); //遍历
if (q->l_child != NULL) //左结点不为空
{
rear = (rear + 1) % MAXSIZE; //队尾向后一个位置
que[rear] = q->l_child; //根结点入队
}
if (q->r_child != NULL) //右结点不为空
{
rear = (rear + 1) % MAXSIZE; //队尾向后一个位置
que[rear] = q->r_child; //根结点入队
}
}
}
}
下面再写一下二叉树的常见的应用
- 求二叉树的深度
/*
method:
得到树的深度
param:
bt_node 二叉树
思路:
由下往上计数
*/
int Get_Depth(BT_NODE* bt_node)
{
int L_length = 0, R_length = 0;
if (bt_node != NULL)
{
L_length = Get_Depth(bt_node->l_child); //先往下递归,知道找到左叶子结点,开始计数
R_length = Get_Depth(bt_node->r_child); //找右侧叶子结点开始计数
}
else
{
printf( "二叉树为空\n" ) ;
return ERROR;
}
return ((L_length > R_length ? L_length : R_length) + 1);//返回左侧长度和右侧长度的最大值,然后深度加1.
}
- 求二叉树的宽度
typedef struct
{
BT_NODE* p; //记录结点
int layer; //记录层数
}ST;
/*
method:
得到二叉树的宽度
param:
bt_node 二叉树
方法:
先给每个结点加一个层信息,并将所有结点入队,最后循环队列,查找相同层号最多的结点个数,即为二叉树的宽度。
*/
int Get_Width(BT_NODE* bt_node)
{
ST que[MAXSIZE];
int front = 0, rear = 0; //定义一个队列
int width = 0, max = 0;
int i = 0, j = 0, k = 0;
BT_NODE* q;
if (bt_node != NULL) //根节点入队
{
++rear; //队尾向后移动,这里没有定义成循环队列,因为后面要用到查找数组内的全部元素
que[rear].p = bt_node; //入队
que[rear].layer = 1; //根结点为第一层
while (front != rear)
{
++front; //队头向后移动
q = que[front].p; //出队
width = que[front].layer;
if (q->l_child != NULL) //左孩子不为空,入队,层次加1
{
++rear;
que[rear].p = q->l_child; //入队
que[rear].layer = width + 1; //层数加1
}
if (q->r_child != NULL) //右孩子不为空,入队,层次加1
{
++rear;
que[rear].p = q->r_child; //入队
que[rear].layer = width + 1; //层数加1
}
}
for (i = 0; i <= width; ++i) //遍历层数
{
k = 0; //当前层数
for (j = 0; j <= rear; ++j) //遍历创建的队列
{
if (que[j].layer == i) //判断队列中某一层数的结点数
{
k++; //第i层元素个数
}
}
if (max <= k) //获得最大结点数
max = k;
}
return max;
}
else
return ERROR;
}
(四)二叉树的遍历算法的改进——二叉树深度优先算法的非递归实现
- 先序遍历非递归算法
/*
method:
先序遍历二叉树---改进(非递归)
param:
bt_node 二叉树
*/
void Impv_Preorder(BT_NODE *bt_node)
{
if (bt_node != NULL)
{
BT_NODE* p;
BT_NODE* data[MAXSIZE]; //创建一个栈,
int top = -1; //栈顶默认指向-1
data[++top] = bt_node; //根结点入栈
p = bt_node; //使p的指针指向根结点
while (top != -1) //判断是否栈空
{
p = data[top--]; //出栈
Visit(p); //遍历该结点
if (p->r_child != NULL) //因为栈是先进后出,因为右侧 后出栈,所以应该放前面
data[++top] = p->r_child; //入栈
if (p->l_child != NULL)
data[++top] = p->l_child; //入栈
}
}
}
- 中序遍历非递归算法
/*
method:
中序遍历二叉树---改进(非递归)
param:
bt_node 二叉树
*/
void Impv_Index_Order(BT_NODE* bt_node)
{
if (bt_node != NULL)
{
BT_NODE* p;
BT_NODE* data[MAXSIZE]; //创建一个栈
int top = -1; //默认栈指向-1
p = bt_node;
while (top != -1 || p != NULL) //判断是否栈空
{
while (p!= NULL) //先将左孩子全部入栈
{
data[++top] = p;
p = p->l_child;
}
if (top != -1)
{
p = data[top--]; //出栈
Visit(p); //遍历该结点
p = p->r_child;
}
}
}
}
- 后序遍历非递归算法
/*
method:
后序遍历二叉树---改进(非递归)
param:
bt_node 二叉树
方法:
逆后序遍历序列只不过是先序遍历过程中对左右子树遍历顺序交换得到的结果
再将逆后序遍历倒置,就得到了后序遍历
*/
void Impv_Postorder(BT_NODE* bt_node)
{
if (bt_node != NULL)
{
BT_NODE* p;
BT_NODE* data_1[MAXSIZE]; int top_1 = -1; //创建一个栈,top默认栈指向-1
BT_NODE* data_2[MAXSIZE]; int top_2 = -1; //创建一个栈,top默认栈指向-1
p = NULL;
data_1[++top_1] = bt_node; //根结点入栈
while (top_1 != -1 ) //判断是否栈空
{
p = data_1[top_1--]; //出栈
data_2[++top_2] = p; //将p放入创建的第二个栈中
if (p->l_child != NULL)
data_1[++top_1] = p->l_child; //左孩子入栈
if(p->r_child!=NULL)
data_1[++top_1] = p->r_child; //右孩子入栈
}
while (top_2 != -1)
{
p = data_2[top_2--]; //栈的先进后出的特性,将逆后序队列倒置,得到后序遍历
Visit(p); //访问结点
}
}
}
(五)二叉树的遍历算法的改进——线索二叉树
- 结构体声明
typedef struct TBTNODE //线索二叉树结构体
{
int data;
int ltag, rtag; //线索与结点的标志
struct TBTNODE* l_child; //ltag = 0,l_child指向左孩子;ltag = 1,l_child指向前驱结点
struct TBTNODE* r_child; //rtag = 0,r_child指向右孩子;rtag = 1,r_child指向后继结点
}TBTNODE;
- 创造一棵空的线索二叉树(与创建二叉树相同,只是结构体变了,多了两个标志)
/*
method:
初始化一个线索二叉树,用二叉链表表示,此时线索都为空
param:
tbt_node 线索二叉树
data 这里是一个数组,数组里面的数据是按照完全二叉树的顺序存储结构存储的。
n 是要存储的结点的个数
pos 这里是当前结点位置,初始值为0
此方法大致思路如下:
1
2 3
4 5 6 7
8 9 10 11 12 13 14 15
存储顺序如下:1->2->4->8->9->5->10->11->3->6->12->13->7->14->15
即:每次先存储左孩子,直到最后左孩子的数组越界,然后返回上一层,存储右孩子
*/
int TBT_Init(TBTNODE* tbt_node, int* data, int n, int pos)
{
TBTNODE* p, * q;
if (n <= 0) //如果输入的个数n小于等于0,直接返回
return ERROR;
if (pos == 0) //因为递归,这个不能重复赋值,所以只在根节点也就是位置为0处,赋值
{ //目的只是为了执行依次
tbt_node[0].data = data[0];
tbt_node[0].l_child = NULL;
tbt_node[0].r_child = NULL;
tbt_node[0].ltag = 0;
tbt_node[0].rtag = 0;
if (n == 1) //在这里加一个判断,如果输入值为1,返回创建成功
return OK;
}
/************************************************************/
/*************************创建树的核心代码*******************/
/************************************************************/
if (2 * pos + 1 >= n) //2*pos+1 的位置是它的左孩子的值在数组中存放位置。为了防止数组越界
return ERROR;
p = (TBTNODE*)malloc(sizeof(TBTNODE)); //创建内存
p->data = data[2 * pos + 1]; //获取数据
p->l_child = NULL;
p->r_child = NULL;
p->ltag = 0;
p->rtag = 0;
tbt_node->l_child = p; //创建的左孩子将其当作子树的根节点,进行递归
TBT_Init(tbt_node->l_child, data, n, 2 * pos + 1); //这里是递归操作,意在每次都是先创建左子树,再创建右子树,
//直到左子树数组越界,然后返回上一层,创建右子树
if (2 * pos + 2 >= n)
return ERROR;
q = (TBTNODE*)malloc(sizeof(TBTNODE));
q->data = data[2 * pos + 2]; //2*pos+2 的位置是它的右孩子的值在数组中存放位置。为了防止数组越界
q->l_child = NULL;
q->r_child = NULL;
q->ltag = 0;
q->rtag = 0;
tbt_node->r_child = q; //创建的右孩子将其当作子树的根节点,进行递归
TBT_Init(tbt_node->r_child, data, n, 2 * pos + 2); //这里是创建右侧分支的结点,仍然是先创建左子树,再创建右子树。
return OK;
}
- 中序线索化
/*
method:
中序线索化
param:
tbtnode 当前结点
pre_tbtnode 前驱结点
*/
void Index_Order_Thread(TBTNODE *tbtnode, TBTNODE** pre_tbtnode)
{
if (tbtnode != NULL) //判断访问的结点是否为空
{
Index_Order_Thread(tbtnode->l_child,pre_tbtnode); //中序遍历,找到第一个为空的左孩子结点,
if (tbtnode->l_child == NULL) //判断左孩子是否为空,也就是当前结点,线索存在
{
tbtnode->l_child = (*pre_tbtnode); //指针指向前驱
tbtnode->ltag = 1; //标志位置1,代表线索
}
if ((*pre_tbtnode) != NULL && (*pre_tbtnode)->r_child == NULL) //判断pre的右线索是否存在,也就是右孩子为空
{
(*pre_tbtnode)->r_child = tbtnode; //指针指向后继
(*pre_tbtnode)->rtag = 1; //标志位置1,代表线索
}
(*pre_tbtnode) = tbtnode; //更改前驱位置,将访问完的结点,作为新的前驱
tbtnode = tbtnode->r_child;
Index_Order_Thread(tbtnode, pre_tbtnode); //依次类推,进行下一结点
}
}
- 创造一棵线索化的二叉树
/*
method:
创造线索化的二叉树
param:
tbtnode 当前结点
pre_tbtnode 前驱结点
*/
void Create_Index_Thread_Tree(TBTNODE* tbtnode)
{
TBTNODE* pre = NULL; //开始的时候,前驱为空
if (tbtnode != NULL)
{
Index_Order_Thread(tbtnode, &pre); //将二叉树线索化
pre->r_child = NULL; //最后一个结点后继为空
pre->rtag = 1;
}
}
- 中序遍历查找第一个结点
/*
中序遍历找第一个结点
*/
TBTNODE* First(TBTNODE* p)
{
while (p->ltag == 0) //如果左线索不存在,一直查找
p = p->l_child; //p指向p的左孩子,为了递归
return p; //如果查找到了第一个线索,返回该结点的指针
}
- 中序遍历查找最后一个结点
/*
中序遍历找最后一个结点
*/
TBTNODE* Last(TBTNODE* p)
{
while (p->rtag == 0) //如果右线索不存在,一直查找
p = p->r_child; //p指向p的右孩子,为了递归
return p; //如果查找到了第一个线索,返回该结点的指针
}
- 中序遍历查找一个结点的前驱
/*
中序遍历找前驱结点
*/
TBTNODE* Prior(TBTNODE* p)
{
if (p->ltag == 0) //如果该节点左线索不存在,一直向下查找
return Last(p->l_child);
else
return p->l_child; //若存在,直接返回前驱结点
}
- 中序遍历查找一个结点的后继
/*
中序遍历找后继结点
*/
TBTNODE* Next(TBTNODE* p)
{
if (p->rtag == 0) //如果该节点右线索不存在,一直向下查找
return First(p->r_child);
else
return p->r_child; //若存在,直接返回后继结点
}
- 访问函数
/*
访问函数
*/
void Visit_Thread(TBTNODE* tbtnode)
{
printf("%d ", tbtnode->data);
}
- 中序遍历
/*
中序遍历
*/
void In_Thread(TBTNODE* tbtnode)
{
for (TBTNODE* p = First(tbtnode); p != NULL; p = Next(p)) //p指针从第一个结点,一直向后查找
Visit_Thread(p);
}