树和二叉树BinaryTree基础讲解C

目录

1.树的概念

1.1二叉树概念

1.1.1满二叉树

1.1.2完全二叉树

2.二叉树的存储结构

2.1 顺序结构

2.1.1 堆的概念

2.1.2 堆的创建

2.1.3 堆排序

3. 二叉树遍历

4 .二叉树节点个数、高度、查找等

5.层序遍历

6.二叉树的创建和销毁


1.树的概念

树是一种非线性 的数据结构,它是由 n n>=0 )个有限结点组成一个具有层次关系的集合。 把做树是因 为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。

 

 树的根部称之为根节点,根节点之前并没有其它节点,也就是说根节点没有前驱节点

除根节点外,其余结点被分成N(N>0)个互不相交的集合T1T2……Tn,其中每一个集Tn又是一棵结构与树类似的子树。每棵子树的根结点有且只有一个前驱,可以有0个或多个后继。

由此可以看出,树是递归定义 的。
我们需要注意的的是树这一结构中,子树不可相交,否则就不是树形结构

 树的一些基础概念是需要我们记住的,理解之后不用背也能记得下来

节点的度 :一个节点含有的子树的个数称为该节点的度; 
叶节点或终端节点 :度为 0 的节点称为叶节点; (也叫叶子节点)
非终端节点或分支节点 :度不为 0 的节点;
双亲节点或父节点 :若一个节点含有子节点,则这个节点称为其子节点的父节点;
孩子节点或子节点 :一个节点含有的子树的根节点称为该节点的子节点;
兄弟节点 :具有相同父节点的节点互称为兄弟节点; 
树的度 :一棵树中,最大的节点的度称为树的度; 如上图:树的度为7;
节点的层次 :从根开始定义起,根为第 1 层,根的子节点为第 2 层,以此类推;
树的高度或深度 :树中节点的最大层次; 如上图:树的高度为 4;
子孙 :以某节点为根的子树中任一节点都称为该节点的子孙;
森林 :由 N N>0 )棵互不相交的树的集合称为森林;

1.1二叉树概念

1. 二叉树不存在度大于 2 的结点
2. 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树

1.1.1满二叉树

一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是
说,如果一个二叉树的层数为 K ,且结点总数是  2^K-1,则它就是满二叉树。

 二叉树的性质

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

1.1.2完全二叉树

完全二叉树是效率很高的数据结构,完全二叉树是由满二叉树而引出来的。对于深度为 K 的,有n 个结点的二叉树,当且仅当其每一个结点都与深度为 K 的满二叉树中编号从 1 n 的结点一一对应时称之为完全二叉树。 要注意的是 满二叉树是一种特殊的完全二叉树

要注意从左到右连续才是完全二叉树

2.二叉树的存储结构

二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。

2.1 顺序结构

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

2.1.1 堆的概念

堆的性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
大根堆与小根堆

2.1.2 堆的创建

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int HPDatetype;
typedef struct Heap {
	HPDatetype* a;
	int size;
	int capacity;

}HP;
//堆
//初始化
void HeapInit(HP* hp);
//交换数据
void swap(HPDatetype* p1, HPDatetype* p2);
//插入
void HeapPush(HP* hp,HPDatetype x);
//删除
void HeapPop(HP* hp);
//销毁
void HeapDestory(HP* hp);
//打印 方便观察
void HeapPrint(HP* hp);
//查看堆顶数据
HPDatetype HeapTop(HP* hp);
//判空
bool HeapEmpty(HP* hp);
//堆中多少个数据
int HeapSize(HP* hp);

功能实现

堆向下调整算法
现在我们给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。 向下调整算法有一个前提 :左右子树必须是一个堆,才能调整。
void HeapPrint(HP* hp)
{
	assert(hp);
	for (int i = 0;i < hp->size; i++)
	{
		printf("%d ", hp->a[i]);
	}
	printf("\n");
}

void HeapInit(HP* hp)
{
	assert(hp);
	hp->a = NULL;
	hp->capacity = hp->size = 0;
}

void swap(HPDatetype* p1, HPDatetype* p2)
{
	HPDatetype tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

//插入数据的时候向上调整 建的小根堆,逻辑取反建大根堆
void Adjustup(HPDatetype* a, int child)
{
	int parent = (child - 1) / 2;
	while (child>0)
	{
		if (a[child] < a[parent])
		{
			swap(&a[child],&a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

//插入
void HeapPush(HP* hp, HPDatetype x)
{
	assert(hp);
	if (hp->size == hp->capacity)
	{
		int newcapacity = hp->capacity == 0 ? 4:hp->capacity * 2;
		HPDatetype* tmp = (HPDatetype*)realloc(hp->a, sizeof(HPDatetype) * newcapacity);
		if (tmp == NULL)
		{
			printf("realloc fail!");
			exit(-1);
		}
		hp->a = tmp;
		hp->capacity = newcapacity;

	}
	hp->a[hp->size] = x;
	hp->size++;
	Adjustup(hp->a, hp->size-1);//插入后需要调整(此处排序方式小根堆)
}

//删除时用向下调整建堆,因为删除数据后不一定是堆了,要重新调整
void Adjustdown(HPDatetype* a, int size,int parent)
{
	
	int child = parent * 2 + 1;


	while (child < size)
	{
		if (child+1< size &&a[child + 1] < a[child])
		{
			child++;
		}
		if (a[parent] > a[child])
		{
			swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}

}
//   输出用(小根堆,输出最小的
void HeapPop(HP* hp)
{
	assert(hp);
	assert(hp->size>0);
	swap(&hp->a[0], &hp->a[hp->size - 1]);
	hp->size--;
	Adjustdown(hp->a,hp->size,0);


	//向下调整
}

void HeapDestory(HP* hp)
{
	assert(hp);
	free(hp->a);
	hp->a = NULL;
	hp->capacity = hp->size = 0;
}
HPDatetype HeapTop(HP* hp)
{
	assert(hp);
	assert(hp->size > 0);

	return hp->a[0];
}

//判空
bool HeapEmpty(HP* hp)
{
	assert(hp);

	return hp->size == 0;
}

int HeapSize(HP* hp)
{
	assert(hp);
	return hp->size;
}

2.1.3 堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:
1. 建堆
升序:建大堆(堆顶数据为最大数,与堆底数据交换后POP,重新建堆)
降序:建小堆(堆顶数据为最小数,与堆底数据交换后POP,重新建堆)
2. 利用堆删除思想来进行排序
建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序
利用我们上面写好的堆
void HeapTest1()
{

	int a[] = { 32,15,89,45,35,85,31,74,96,78 };
	int n = sizeof(a) / sizeof(a[0]);

	//建堆1
	//for (int i = 1; i < n; i++)
	//{
	//	AdjustUp(a, i);
	//}

	建堆2 向下调整前提:左右子树必须是堆
	for (int i = (n-1-1)/2; i>=0; i--)
	{
		AdjustDown(a, n, i);//倒着找
	}


	for (int i = 0; i < n; i++)
	{
		printf("%d ", a[i]);
	}
}


void HeapSort(int* a, int n)
{
	
	
		for (int i = (n - 1 - 1) / 2; i >= 0; i--)
		{
			AdjustDown(a, n, i);//倒着找
		}
		int end = n - 1;
		while (end > 0)
		{
			Swap(&a[0], &a[end]);
			AdjustDown(a, end, 0);
			end--;
		}
	
}

int main()
{
	//HeapTest();
	HeapTest1();
	return 0;
}

3. 二叉树遍历

学习二叉树结构,最简单的方式就是遍历。所谓 二叉树遍历 (Traversal) 是按照某种特定的规则,依次对二叉 树中的节点进行相应的操作,并且每个节点只操作一次 。访问结点所做的操作依赖于具体的应用问题。 遍历是二叉树上最重要的运算之一,也是二叉树上进行其它运算的基础。
按照规则,二叉树的遍历有: 前序 / 中序 / 后序的递归结构遍历
1. 前序遍历 (Preorder Traversal 亦称先序遍历 )—— 访问根结点的操作发生在遍历其左右子树之前。
2. 中序遍历 (Inorder Traversal)—— 访问根结点的操作发生在遍历其左右子树之中(间)。
3. 后序遍历 (Postorder Traversal)—— 访问根结点的操作发生在遍历其左右子树之后。
简单的来说就是访问顺序不同,前序遍历:根 左子树 右子树,中序遍历:左子树 根 右子树,后续遍历:左子树 右子树 根
下面举个例子练习一下
前序遍历

中序遍历

后序遍历

 程序实现

void preorder(struct TreeNode* root, int* tmp, int* i)//tmp数组地址,*i是0
{
	if (root == NULL)
	{
		return;
	}
	tmp[(*i)++] = root->val;
	preorder(root->left, tmp, i);
	preorder(root->right, tmp, i);
}

void Inorder(struct TreeNode* root, int* tmp, int* i)
{
	if (root == NULL)
	{
		return;
	}
	
	preorder(root->left, tmp, i);
    tmp[(*i)++] = root->val;
	preorder(root->right, tmp, i);
}

void preorder(struct TreeNode* root, int* tmp, int* i)
{
	if (root == NULL)
	{
		return;
	}
	
	preorder(root->left, tmp, i);
	preorder(root->right, tmp, i);
    tmp[(*i)++] = root->val;
}

4 .二叉树节点个数、高度、查找等

//节点个数
int BinaryTreeSize(BTNode* root)
{
	
	if (root == NULL)
	{
		return;
	}
	
	return 1 + BinaryTreeSize(root->left) + BinaryTreeSize(root->right);

}

// 二叉树叶子节点个数
int BinaryTreeLeafSize(BTNode* root)
{
	if (root == NULL)
	{
		return;
	}
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	return BinaryTreeSize(root->left) + BinaryTreeSize(root->right);
}


// 二叉树查找值为x的节点
BTNode* BinaryTreeFind(BTNode* root, BTDataType x)
{
	if (root == NULL)
	{
		return;
	}
	if (root->data == x)
	{
		return root;
	}
	BinaryTreeFind(root->left, x);
	BinaryTreeFind(root->right, x);

	return NULL;
}

二叉树第K层节点个数

我们的思路可以一层一层往下找,每次k--,直到k=1时停止,也就是到达要找的层数,还是从整棵树向左右子树划分的思想


// 二叉树第k层节点个数
int BinaryTreeLevelKSize(BTNode* root, int k)//一层一层往下找
{
	assert(k >= 1);
	if (root = NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
	return BinaryTreeLevelKSize(root->left, k - 1) + BinaryTreeLevelKSize(root->right, k - 1);
}

5.层序遍历

层序遍历 :除了先序遍历、中序遍历、后序遍历外,还可以对二叉树进行层序遍历。设二叉树的根节点所在层数为1 ,层序遍历就是从所在二叉树的根节点出发,首先访问第一层的树根节点,然后从左到右访问第 2 层 上的节点,接着是第三层的节点,以此类推,自上而下,自左至右逐层访问树的结点的过程就是层序遍历。
层序遍历可以使用队列进行遍历(先入先出)
typedef struct BinaryTreeNode* QDataType;
typedef struct QueneNode {
	struct QueneNode* next;
	QDataType data;
}QNode;

typedef struct Quene {
	QNode* head;
	QNode* tail;
}Queue;

void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = pq->tail = NULL;
	
}

void QueueDestory(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* m = cur->next;
		free(cur);
		cur = m;
	}
	pq->head = pq->tail = NULL;
}

void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;

	if (pq->head == NULL)
	{
		pq->head = pq->tail = newnode;
    }
	else
	{
		pq->tail->next = newnode;
		pq->tail = newnode;
	}
}

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	if (pq->head == pq->tail)
	{
		free(pq->head);
		pq->head = pq->tail == NULL;
	}
	else
	{
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
}

QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->head->data;
}

QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));
	return pq->tail->data;
}

bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL;
}

int QueueSize(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	int size = 0;
	while (cur)
	{
		++size;
		cur = cur->next;
	}
	return size;
}
void BinaryTreeLevelOrder(BTNode* root)
{
	assert(root);
	Queue p;
	QueueInit(&p);
	QueuePush(&p, root);

	while (!QueueEmpty(&p))
	{
		BTNode* head=QueueFront(&p);//存的是新Root地址
		printf("%c ", head->data);

		QueuePop(&p);
		// 依次插入
		if (head->left)
		{
			QueuePush(&p, head->left);
		}
		if (head->right)
		{
			QueuePush(&p, head->right);
		}
	}
	printf("\n");
	QueueDestory(&p);
}

6.二叉树的创建和销毁

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef char BTDataType;

typedef struct BinaryTreeNode
{
	BTDataType data;
	struct BinaryTreeNode* left;
	struct BinaryTreeNode* right;
}BTNode;

// 以通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树为例
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi);
// 二叉树销毁
void BinaryTreeDestory(BTNode** root);
BTNode root;

// 通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树
BTNode* BinaryTreeCreate(BTDataType* a, int n, int* pi)
{
	
	if (a[*pi] == '#' || *pi >= n)
	{
		(*pi)++;
		return NULL;
    }
	BTNode* tmp = (BTNode*)malloc(sizeof(BTNode));
	assert(tmp);
	tmp->data = a[*pi];
	(*pi)++;
	tmp->left = BinaryTreeCreate(a, n, *pi);
	tmp->right = BinaryTreeCreate(a, n, *pi);

	return tmp;
}
// 二叉树销毁
void BinaryTreeDestory(BTNode** root)
{
	if (*root)
	{
		BinaryTreeDestory((*root)->left);
		BinaryTreeDestory((*root)->right);
		free(*root);

		*root = NULL;
	}
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
您可以使用以下方法来实现输出所有二叉树直径及其路径长度的功能: ```java import java.util.ArrayList; import java.util.List; public class BinaryTree<T> { private Node<T> root; // 其他方法省略... public static <T> void diameterAll(BinaryTree<T> bitree) { if (bitree.root == null) { System.out.println("二叉树为空!"); return; } List<Node<T>> path = new ArrayList<>(); int[] diameter = new int[1]; diameterAllHelper(bitree.root, path, diameter); System.out.println("直径:"); for (int i = 0; i < path.size(); i++) { Node<T> node = path.get(i); System.out.print(node.data + " "); if (i < path.size() - 1) { System.out.print("-> "); } } System.out.println(); System.out.println("路径长度:" + diameter[0]); } private static <T> int diameterAllHelper(Node<T> node, List<Node<T>> path, int[] diameter) { if (node == null) { return 0; } int leftHeight = diameterAllHelper(node.left, path, diameter); int rightHeight = diameterAllHelper(node.right, path, diameter); int currentDiameter = leftHeight + rightHeight + 1; if (currentDiameter > diameter[0]) { diameter[0] = currentDiameter; path.clear(); addPath(node.left, path); path.add(node); addPath(node.right, path); } return Math.max(leftHeight, rightHeight) + 1; } private static <T> void addPath(Node<T> node, List<Node<T>> path) { if (node == null) { return; } addPath(node.left, path); path.add(node); addPath(node.right, path); } private static class Node<T> { private T data; private Node<T> left; private Node<T> right; public Node(T data) { this.data = data; } } } ``` 您可以将上述代码保存为`BinaryTree.java`文件,并在您的程序使用`BinaryTree.diameterAll(bitree)`来输出所有二叉树直径及其路径长度。请注意,上述代码假设您已经实现了其他必要的二叉树操作方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值