7.树和二叉树

1.树的基本概念

▷ 树的定义:

由一个或多个(n≥0)结点组成的有限集合T,有且仅有一个结点称为根(root),当n>1时,其余的结点分为m(m≥0)个互不相交的有限集合T1,T2,…,Tm。每个集合本身又是棵树,被称作这个根的子树 。

▷树的结构特点

■非线性结构,有一个直接前驱,但可能有多个直接后继(1:n)

■树的定义具有递归性,树中还有树。

■树可以为空,即节点个数为0。

▷ 若干术语

■根:即根结点(没有前驱)

■叶子:即终端结点(没有后继)

■森林:指m棵不相交的树的集合(例如删除A后的子树个数)

■有序树:结点各子树从左至右有序,不能互换(左为第一)

■无序树:结点各子树可互换位置。

■双亲:即上层的那个结点(直接前驱) parent

■孩子:即下层结点的子树 (直接后继) child

■兄弟:同一双亲下的同层结点(孩子之间互称兄弟)sibling

■堂兄弟:即双亲位于同一层的结点(但并非同一双亲)cousin

■祖先:即从根到该结点所经分支的所有结点

■子孙:即该结点下层子树中的任一结点

■结点:即树的数据元素

■结点的度:结点挂接的子树数(有几个直接后继就是几度)

■结点的层次:从根到该结点的层数(根结点算第一层)

■终端结点:即度为0的结点,即叶子

■分支结点:除树根以外的结点(也称为内部结点)

■树的度:所有结点度中的最大值(Max{各结点的度})

■树的深度(或高度): 指所有结点中最大的层数(Max{各结点的层次})

上图中的结点数= 13,树的度= 3,树的深度= 4

2.树的表示法

2.1图形表示法

事物之间的逻辑关系可以通过数的形式很直观的表示出来,如下图:

2.2广义表表示法

用广义表表示法表示上图:

中国(河北(保定,石家庄),广东(广州,东莞),山东(青岛,济南))

根作为由子树森林组成的表的名字写在表的左边。

2.3左孩子右兄弟表示法

左孩子右兄弟表示法可以将一颗多叉树转化为一颗二叉树:

节点的结构:

节点有两个指针域,其中一个指针指向子节点,另一个指针指向其兄弟节点。

3.二叉树概念

3.1二叉树基本概念

定义:

■n(n≥0)个结点的有限集合,由一个根结点以及两棵互不相交的、分别称为左子树和右子树的二叉树组成 。

逻辑结构:

一对二(1:2)

基本特征:

■每个结点最多只有两棵子树(不存在度大于2的结点);

■左子树和右子树次序不能颠倒(有序树)。

基本形态:

二叉树性质

■性质1: 在二叉树的第i层上至多有2i-1个结点(i>0)

■性质2: 深度为k的二叉树至多有2k-1个结点(k>0)

■性质3: 对于任何一棵二叉树,若度为2的结点数有n2个,则叶子数(n0)必定为n2+1 (即n0=n2+1)

概念解释:

满二叉树

一棵深度为k 且有2k -1个结点的二叉树。

特点:每层都“充满”了结点

完全二叉树

除最后一层外,每一层上的节点数均达到最大值;在最后一层上只缺少右边的若干结点

理解:k-1层与满二叉树完全相同,第k层结点尽力靠左

■性质4: 具有n个结点的完全二叉树的深度必为[log2n]+1

(如 log215 在计算器上点击 15 log / 2 log =)

性质5: 对完全二叉树,若从上至下、从左至右编号,则编号为i 的结点,其左孩子编号必为2i,其右孩子编号必为2i+1;其双亲的编号必为i/2(i=1 时为根,除外)

使用此性质可以使用完全二叉树实现树的顺序存储。

如果不是完全二叉树咋整???

------ 将其转换成完全二叉树即可

-------------------------------缺点:①浪费空间;②插入、删除不便 ------------------------------------------------

3.2二叉树的表示

二叉链表示法

一般从根结点开始存储。相应地,访问树中结点时也只能从根开始。

存储结构

结点数据类型定义:

typedef struct BiTNode
{
  int data;
  struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;

三叉链表表示法

存储结构

每个节点有三个指针域,其中两个分别指向子节点(左孩子,右孩子),还有一个指针指向该节点的父节点。

节点数据类型定义

//三叉链表
typedef struct TriTNode 
{
	int data;
	//左右孩子指针
	struct TriTNode *lchild, *rchild;
	struct TriTNode *parent;
}TriTNode, *TriTree;

3.3二叉树的遍历

▷遍历定义

指按某条搜索路线遍访每个结点且不重复(又称周游)。

▷遍历用途

它是树结构插入、删除、修改、查找和排序运算的前提,是二叉树一切运算的基础和核心。

▷遍历方法

牢记一种约定,对每个结点的查看都是“先左后右”

限定先左后右,树的遍历有三种实现方案:

DLR LDR LRD
()序遍历 ()序遍历 ()序遍历

DLR — 先序遍历,即先根再左再右

LDR — 中序遍历,即先左再根再右

LRD — 后序遍历,即先左再右再根

注:“先、中、后”的意思是指访问的结点D是先于子树出现还是后于子树出现。

从递归的角度看,这三种算法是完全相同的,或者说这三种遍历算法的访问路径是相同的,只是访问结点的时机不同。

从虚线的出发点到终点的路径上,每个结点经过3次。

第1次经过时访问=先序遍历

第2次经过时访问=中序遍历

第3次经过时访问=后序遍历

//03 二叉树的递归遍历
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

struct BinaryNode
{
	//数据域
	char ch;
	//指针域
	struct BinaryNode * lChild;
	struct BinaryNode * rChild;
};


void recursion(struct BinaryNode * root)
{
	if (root == NULL)
	{
		return;
	}

	//先序遍历
	printf("%c ", root->ch);
	recursion(root->lChild);
	recursion(root->rChild);
}


void test01()
{
	struct BinaryNode nodeA = { 'A', NULL, NULL };
	struct BinaryNode nodeB = { 'B', NULL, NULL };
	struct BinaryNode nodeC = { 'C', NULL, NULL };
	struct BinaryNode nodeD = { 'D', NULL, NULL };
	struct BinaryNode nodeE = { 'E', NULL, NULL };
	struct BinaryNode nodeF = { 'F', NULL, NULL };
	struct BinaryNode nodeG = { 'G', NULL, NULL };
	struct BinaryNode nodeH = { 'H', NULL, NULL };

	//建立关系
	nodeA.lChild = &nodeB;
	nodeA.rChild = &nodeF;

	nodeB.rChild = &nodeC;
	
	nodeC.lChild = &nodeD;
	nodeC.rChild = &nodeE;

	nodeF.rChild = &nodeG;

	nodeG.lChild = &nodeH;

	//递归遍历
	recursion(&nodeA);
}

int main()
{
	test01();

	system("pause");
	return EXIT_SUCCESS;
}

3.4二叉树编程实践

3.4.1 计算二叉树叶子节点数目

3.4.2 计算二叉树高度(深度)

思想:

●求根结点左子树高度,根结点右子树高度,比较的子树最大高度,再+1。

●若左子树还是树,重复步骤1;若右子树还是树,重复步骤1。

3.4.3 拷贝二叉树

思想:

●malloc新结点

●拷贝左子树,拷贝右子树,让新结点连接左子树,右子树。

若左子树还是树,重复步骤1、2;若右子树还是树,重复步骤1、2。

3.5 二叉树的非递归遍历

利用栈容器可以实现二叉树的非递归遍历

首先将每个节点都设置一个标志,默认标志为假,根据节点的的状态进行如下流程。

执行上述流程,可以得到先序遍历的结果,如果想得到其他二叉树遍历结果,修改2.4步骤即可。

05 二叉树非递归遍历.c

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include "seqStack.h"

struct BinaryNode
{
	//数据域
	char ch;
	//指针域
	struct BinaryNode * lChild;
	struct BinaryNode * rChild;
	//标志
	int flag;
};


/*
1、将根节点 入栈 
2、只要栈中元素个数大于 0  执行循环
	获取栈顶元素
	出栈
	如果标志位真  直接输出  并且执行下一次循环
	如果为假 将标志改为真
	将右子树  左子树 根 入栈
	执行下一次循环
*/

void nonRecursion(struct BinaryNode * root)
{
	//初始化栈
	SeqStack myStack = init_SeqStack();

	push_SeqStack(myStack, root);

	while (size_SeqStack(myStack) > 0)
	{
		//获取栈顶元素
		struct BinaryNode * pTop = top_SeqStack(myStack);

		//出栈
		pop_SeqStack(myStack);

		//如果标志位真  直接输出  并且执行下一次循环
		if (pTop->flag == 1)
		{
			printf("%c ", pTop->ch);
			continue;
		}
		//如果为假 将标志改为真
		pTop->flag = 1;

		//将右子树  左子树 根 入栈
		if (pTop->rChild != NULL)
		{
			push_SeqStack(myStack, pTop->rChild);
		}
		if (pTop->lChild != NULL)
		{
			push_SeqStack(myStack, pTop->lChild);
		}
		push_SeqStack(myStack, pTop);

	}

	//销毁栈
	destroy_SeqStack(myStack);
}

void test01()
{
	struct BinaryNode nodeA = { 'A', NULL, NULL,0 };
	struct BinaryNode nodeB = { 'B', NULL, NULL,0 };
	struct BinaryNode nodeC = { 'C', NULL, NULL,0 };
	struct BinaryNode nodeD = { 'D', NULL, NULL,0 };
	struct BinaryNode nodeE = { 'E', NULL, NULL,0 };
	struct BinaryNode nodeF = { 'F', NULL, NULL,0 };
	struct BinaryNode nodeG = { 'G', NULL, NULL,0 };
	struct BinaryNode nodeH = { 'H', NULL, NULL,0 };

	//建立关系
	nodeA.lChild = &nodeB;
	nodeA.rChild = &nodeF;

	nodeB.rChild = &nodeC;

	nodeC.lChild = &nodeD;
	nodeC.rChild = &nodeE;

	nodeF.rChild = &nodeG;

	nodeG.lChild = &nodeH;

	//非递归遍历
	nonRecursion(&nodeA);
}

int main()
{
	test01();

	system("pause");
	return EXIT_SUCCESS;
}

seqStack.h

#pragma  once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

#define  MAX 1024

//struct SStack
//{
//	void * data[MAX];  //栈的数组
//
//	int m_Size; //栈大小
//};

typedef void * SeqStack;

//初始化栈
SeqStack init_SeqStack();

//入栈
void push_SeqStack(SeqStack stack, void * data);

//出栈
void pop_SeqStack(SeqStack stack);

//返回栈顶
void * top_SeqStack(SeqStack stack);

//返回栈大小
int size_SeqStack(SeqStack stack);

//判断栈是否为空
int isEmpty_SeqStack(SeqStack stack);

//销毁栈
void destroy_SeqStack(SeqStack stack);

seqStack.c

#include "seqStack.h"

struct SStack
{
	void * data[MAX];  //栈的数组

	int m_Size; //栈大小
};

//初始化栈
SeqStack init_SeqStack()
{
	struct SStack * myStack = malloc(sizeof(struct SStack));

	if (myStack == NULL)
	{
		return NULL;
	}

	//初始化数组
	memset(myStack->data, 0, sizeof(void *)* MAX);

	//初始化栈大小
	myStack->m_Size = 0;

	return myStack;
}
//入栈
void push_SeqStack(SeqStack stack, void * data)
{
	//入栈本质  --- 数组尾插
	if (stack == NULL)
	{
		return;
	}
	if (data == NULL)
	{
		return;
	}

	struct SStack * mystack = stack;
	if (mystack->m_Size == MAX)
	{
		return;
	}

	mystack->data[mystack->m_Size] = data;

	mystack->m_Size++;
}
//出栈
void pop_SeqStack(SeqStack stack)
{
	//出栈本质  --- 数组尾删
	if (stack == NULL)
	{
		return;
	}

	struct SStack * mystack = stack;

	if (mystack->m_Size == 0)
	{
		return;
	}

	mystack->data[mystack->m_Size - 1] = NULL;

	mystack->m_Size--;

}
//返回栈顶
void * top_SeqStack(SeqStack stack)
{
	if (stack == NULL)
	{
		return NULL;
	}

	struct SStack * mystack = stack;

	if (mystack->m_Size == 0)
	{
		return NULL;
	}
	return mystack->data[mystack->m_Size - 1];
}
//返回栈大小
int size_SeqStack(SeqStack stack)
{
	if (stack == NULL)
	{
		return -1;
	}

	struct SStack * mystack = stack;

	return mystack->m_Size;
}

//判断栈是否为空
int isEmpty_SeqStack(SeqStack stack)
{
	if (stack == NULL)
	{
		return -1;//返回-1代表真  空栈
	}

	struct SStack * mystack = stack;

	if (mystack->m_Size == 0)
	{
		return 1;
	}

	return 0; //返回0 代表 不是空栈

}
//销毁栈
void destroy_SeqStack(SeqStack stack)
{
	if (stack == NULL)
	{
		return;
	}

	free(stack);
	stack = NULL;
}

参考资料来源:

黑马程序员

posted @ 2023-06-06 17:41  CodeMagicianT  阅读(57)  评论(0编辑  收藏  举报