二叉树 初阶 总结

2024-08-06 08:22:27 浏览数 (1)

树的基础认知

结点的度:一个结点含有的子树的个数称为该结点的度; 如上图:A的为6 叶结点或终端结点:度为0的结点称为叶结点; 如上图:B、C、H、I...等结点为叶结点 非终端结点或分支结点:度不为0的结点; 如上图:D、E、F、G...等结点为分支结点 双亲结点或父结点:若一个结点含有子结点,则这个结点称为其子结点的父结点; 如上图:A是B的父结点 孩子结点或子结点:一个结点含有的子树的根结点称为该结点的子结点; 如上图:B是A的孩子结点 兄弟结点:具有相同父结点的结点互称为兄弟结点; 如上图:B、C是兄弟结点 树的度:一棵树中,最大的结点的度称为树的度; 如上图:树的度为6 结点的层次:从根开始定义起,根为第1层,根的子结点为第2层,以此类推; 树的高度或深度:树中结点的最大层次; 如上图:树的高度为4 堂兄弟结点:双亲在同一层的结点互为堂兄弟;如上图:H、I互为兄弟结点 结点的祖先:从根到该结点所经分支上的所有结点;如上图:A是所有结点的祖先 子孙:以某结点为根的子树中任一结点都称为该结点的子孙。如上图:所有结点都是A的子孙 森林:由m(m>0)棵互不相交的树的集合称为森林;

树的表示

代码语言:javascript复制
typedef int DataType;
struct Node
{
struct Node* left; // 左节点
struct Node* right; // 右节点
DataType val; // 结点中的数据域
};

满二叉树和完全二叉树

        这里很好辨析,满二叉树就是满的完全二叉树

树的性质

         1. 若规定根结点的层数为1,则一棵非空二叉树的第2^(i-1)层上最多有 个结点.          2. 若规定根结点的层数为1,则深度为h的二叉树的最大结点数是2^h - 1.          3. 对任何一棵二叉树, 如果度为0其叶结点个数为n, 度为2的分支结点个数m为 ,则有m=n+1           4. 若规定根结点的层数为1,具有n个结点的满二叉树的深度,h=log(n 1).          5. 对于具有n个结点的完全二叉树,如果按照从上至下从左至右的数组顺序对所有结点从0开                始编号,则对于序号为i的结点有:               1. 若i>0,i位置结点的双亲序号:(i-1)/2;i=0,i为根结点编号,无双亲结点               2. 若2i 1<n,左孩子序号:2i 1,2i 1>=n否则无左孩子               3. 若2i 2<n,右孩子序号:2i 2,2i 2>=n否则无右孩子

树的两种存储结构

        顺序存储

        顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。

         链式存储

        二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。

代码语言:javascript复制
typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{
struct BinTreeNode* left; // 指向当前结点左孩子
struct BinTreeNode* right; // 指向当前结点右孩子
BTDataType data; // 当前结点值域
}
// 三叉链
struct BinaryTreeNode
{
struct BinTreeNode* parent; // 指向当前结点的双亲
struct BinTreeNode* left; // 指向当前结点左孩子
struct BinTreeNode* right; // 指向当前结点右孩子
BTDataType data; // 当前结点值域
};

 堆

        为了能更好的认识二叉树,我们需要认识一下堆。

        普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段

        堆分小根堆,和大根堆。小根堆是根比孩子小,大根堆是跟比孩子大。

                             小根堆                                                               大根堆

        代码实现

代码语言:javascript复制
typedef int HPDataType;
typedef struct Heap
{
HPDataType* _a;
int _size;
int _capacity;
}Heap;
// 堆的构建
void HeapCreate(Heap* hp, HPDataType* a, int n);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);

 向下调整算法

        现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根结点开始的向下调整算法可以把它调整成一个小堆。向下调整算法有一个前提:左右子树必须是一个堆,才能调整。

代码语言:javascript复制
int true[] = {1,3,5,7,9,2,4,6,8,10};
代码语言:javascript复制
void HeapDown(HPDataType* a, int size,int parent)
{
	int child = parent * 2   1;//通过父母找孩子 
	while (child < size)//孩子小于整个堆的大小
	{
		if (child  1 < size && a[child] > a[child   1])//右孩子小于整个堆且左孩子大
		{
			child  ;//调整到右孩子来看 这里调整到更小或者更大的孩子那一边进行调整
		}
		if (a[parent] > a[child])//如果父母比孩子大或小就交换位置
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2   1;
		}
		else
		{
			break;
		}
	}
}

 向上调整算法

代码语言:javascript复制
void HeapUp(HPDataType * a,int child)
{
	int parent = (child - 1) / 2;//通过孩子找父母
	while (child != 0)
	{
		if (a[parent] > a[child])//根据大小关系交换位置
		{
			Swap(&a[parent], &a[child]);
			child = (child - 1) / 2;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

其它部分: 

代码语言:javascript复制
//堆的初始化
void HeapInit(Heap* php)
{
	assert(php);
	php->_a = NULL;
	php->_capacity = php->_size = 0;
}
// 堆的销毁
void HeapDestory(Heap* hp)
{
	assert(hp);
	free(hp->_a);
	hp->_a = NULL;
	hp->_capacity = hp->_size = 0;
}
// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
	//扩容
	if (hp->_capacity == hp->_size)
	{
		int newcapacity = hp->_capacity == 0 ? 4 : 2 * hp->_capacity;
		Heap* newa = (Heap*)realloc(hp->_a, newcapacity * sizeof(HPDataType));
		if (newa == NULL)
		{
			perror("error realloc");
			exit(1);
		}
		hp->_a = newa;
		hp->_capacity = newcapacity;
	}
	//插入
	hp->_a[hp->_size] = x;
	hp->_size  ;
	//向上变换函数
	HeapUp(hp->_a, hp->_size-1);
}
// 堆的删除
void HeapPop(Heap* hp)
{
	assert(hp && hp->_size > 0);
	Swap(&hp->_a[0], &hp->_a[hp->_size - 1]);
	hp->_size--;
	HeapDown(hp->_a, hp->_size, 0);
}
// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
	assert(hp && hp->_size);
	return hp->_a[0];
}
// 堆的数据个数
int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->_size;
}
// 堆的判空
int HeapEmpty(Heap* hp)
{
	assert(hp);
	return hp->_size == 0;
}

 二叉树

        手搓二叉树

代码语言:javascript复制
typedef int BTDataType;
typedef struct BinaryTreeNode
{
BTDataType _data;
struct BinaryTreeNode* _left;
struct BinaryTreeNode* _right;
}BTNode;
BTNode* CreatBinaryTree()
{
BTNode* node1 = BuyNode(1);
BTNode* node2 = BuyNode(2);
BTNode* node3 = BuyNode(3);
BTNode* node4 = BuyNode(4);
BTNode* node5 = BuyNode(5);
BTNode* node6 = BuyNode(6);
node1->_left = node2;
node1->_right = node4;
node2->_left = node3;
node4->_left = node5;
node4->_right = node6;
return node1;
}

二叉树的三种遍历

前序遍历 中序遍历 后序遍历 也就是 根左右 左根右 右根左 

根左右就是先根再左再右的遍历 以此类推

代码语言:javascript复制
// 二叉树前序遍历 
void BinaryTreePrevOrder(BTNode* root)
{
	if (root == NULL)
		return;
	printf("%c", root->_data);
	BinaryTreePrevOrder(root->_left);
	BinaryTreePrevOrder(root->_right);
}
// 二叉树中序遍历
void BinaryTreeInOrder(BTNode* root)
{
	if (root == NULL)
		return;	
	BinaryTreePrevOrder(root->_left);
	printf("%c", root->_data);
	BinaryTreePrevOrder(root->_right);
}
// 二叉树后序遍历
void BinaryTreePostOrder(BTNode* root)
{
	if (root == NULL)
		return;
	BinaryTreePrevOrder(root->_left);
	BinaryTreePrevOrder(root->_right);
	printf("%c", root->_data);
}

 他们的区别就是在什么时候”输出“

依照遍历创建二叉树

代码语言:javascript复制
// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a,int* pi)
{
	if (a[*pi] == '#')
	{
		(*pi)  ;
		return NULL;
	}

	BTNode* root = (BTNode*)malloc(sizeof(BTNode));
	root->_data = a[*pi];
	(*pi)  ;
	root->_left = BinaryTreeCreate(a, pi);
	root->_right = BinaryTreeCreate(a, pi);
	return root;
}

层序遍历

         层序遍历需要用到队列

        队列代码:

代码语言:javascript复制
typedef struct BinaryTreeNode* QDataType;

typedef struct QueueNode
{
	struct QueueNode * next;
	QDataType val;
}QNode;

typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;

void QueueInit(Queue* p)
{
	assert(p);
	p->phead = p->ptail = NULL;
	p->size = 0;
}
void QueuePush(Queue* p, QDataType x)//尾插
{
	assert(p);
	QNode* next = (QNode*)malloc(sizeof(QNode));
	if (next == NULL)
	{
		perror("error malloc");
		exit(1);
	}
	next->next = NULL;
	next->val = x;
	if (p->phead != NULL)
	{
		p->ptail->next = next;
		p->ptail = p->ptail->next;
	}
	else
	{
		p->phead = p->ptail = next;
	}
	p->size  ;
}
//头删
void QueuePop(Queue* p)
{
	assert(p&&p->phead);
	if (p->phead->next == NULL)
	{
		free(p->phead);
		p->phead = p->ptail = NULL;
	}
	else
	{
		QNode* ptr = p->phead->next;
		free(p->phead);
		p->phead = ptr;
	}
	p->size--;
}
//读取队列的长度
QDataType QueueSize(Queue* p)
{
	assert(p);
	return p->size;
}
//摧毁队列
void QueueDestroy(Queue* p)
{
	assert(p);
	while (p->phead)
	{
		QueuePop(p);
	}
	p->phead = p->ptail = NULL;
	p->size = 0;
}

bool QueueEmpty(Queue* p)
{
	assert(p);
	return p->size == 0;
}
//查看头部元素
QDataType QueueFront(Queue* p)
{
	assert(p && p->ptail);
	return p->phead->val;
}
//查看尾部元素
QDataType QueueBack(Queue* p)
{
	assert(p && p->ptail);
	return p->ptail->val;
}

 层序遍历代码

代码语言:javascript复制
void BinaryTreeLevelOrder(BTNode* root)//广度优先遍历
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		printf("%c", front->_data);
		if (front->_left)
			QueuePush(&q, front->_left);
		if (front->_right)
			QueuePush(&q, front->_right);
	}
	QueueDestroy(&q);
}

这里的思路是利用队列 进一个出一个进行 也就是从左到右 从上到下依次入队列 出队列的检查

判断二叉树是否是完全二叉树

代码语言:javascript复制
// 判断二叉树是否是完全二叉树
bool BinaryTreeComplete(BTNode* root)
{
	Queue q;
	QueueInit(&q);
	if (root)
		QueuePush(&q, root);
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front == NULL)
		{
			break;
		}
		QueuePush(&q, front->_left);
		QueuePush(&q, front->_right);
	}
	while (!QueueEmpty(&q))
	{
		BTNode* front = QueueFront(&q);
		QueuePop(&q);
		if (front)
		{
			QueueDestroy(&q);
			return false;
		}
	}
	QueueDestroy(&q);
	return true;
}

 在层序遍历的基础上,如果找到了第一个NULL,那么就要保证之后的元素都是NULL

原因是因为 完全二叉树的结构是在不完整的上一层必然完整 而且 不完整的下一层不能有新的元素存在 层序且统计 首个NULL存在的层数 而且也统计了 该层的下一层的元素 如果该NULL之后的元素有非NULL 就不是完全二叉树。

其它:

代码语言:javascript复制
// 二叉树销毁
void BinaryTreeDestory(BTNode** root)//这个用后续遍历进行销毁操作
{
	if (*root == NULL)
		return;

	BinaryTreeDestory(&(*root)->_left);
	BinaryTreeDestory(&(*root)->_right);
	free(*root);
	*root = NULL;
}
// 二叉树节点个数
int BinaryTreeSize(BTNode* root)
{
	if (root == NULL)
		return 0;	
	return 1   BinaryTreeSize(root->_left)   BinaryTreeSize(root->_right);	
}
// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)
		return 0;
	if (root->_left == root->_right)
		return 1;

	return BinaryTreeLeafSize(root->_left)   BinaryTreeLeafSize(root->_right);
}
// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)
{
	if (root == NULL)
		return 0;
	if (k == 1)
		return 1;
	return BinaryTreeLevelKSize(root->_left, k - 1)   BinaryTreeLevelKSize(root->_right, k - 1);
}
// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
		return NULL;
	if (root->_data == x)
		return root;

	BTNode* left = BinaryTreeFind(root->_left, x);
	BTNode* right = BinaryTreeFind(root->_right, x);

	if (left && left->_data == x)
		return left;
	if (right && right->_data == x)
		return right;
}

0 人点赞