二叉树的堆实现以及链式二叉树

目录

1.树的概念

1.1树的概念

 1.2树的相关概念

1.3树的表示

 2.二叉树概念及其结构

2.1概念

2.2特殊的树

2.3二叉树的储存结构

2.4堆的概念及其结构

2.5堆的实现

2.6Topk-问题

3.二叉树链式结构的实现

3.1 前置说明

3.2二叉链的遍历


1.树的概念

1.1树的概念

   我们之前学习了线性表,链表,栈以及队列。这些数据结构类型。总结一下这些结构都属于线性的结构,但是只有这些结构管理起来数据却还远远不够,就比如我们想要做一个文件结构,我们都知道的文件结构是点开一个里面还有一个。

比如上面这样的,因此树状结构就在这里体现的淋漓尽致了。

 首先树状结构不是线性的,它是一个由n个节点(n>0)个有限节点组成的一个具有层次关系的集合,之所以叫它树状结构是因为它长的像一个倒挂着的树。

  • 树有一个特殊节点,就是第一个节点,称为树的根这个节点没有前驱节点。
  • 除了根以外的节点都称作树的子节点,而子节点的前驱节点也只能有一个。但是可以有好多后继节点。

注意:在树的结构中,子节点之间是不能有相交的。否则就不是树状结构了

 1.2树的相关概念

节点的度 :一个节点含有的子树的个数称为该节点的度; 如上图: 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)棵树组成的集合成为森林。

1.3树的表示

   树的表示方法也有好几种,分别有双亲表示法,孩子兄弟表示法,这里先介绍一下孩子兄弟表示法。

typedef int DataType;
struct Node
{
 struct Node* _firstChild1; // 第一个孩子结点
 struct Node* _pNextBrother; // 指向其下一个兄弟结点
 DataType _data; // 结点中的数据域
};

这里表示为孩子兄弟表示法。

 2.二叉树概念及其结构

2.1概念

一棵二叉树是节点的有限集合:

  1. 该集合可以为空
  2. 由一个根节点和两个子节点构成,可以少于两个子节点。但是不能多于两个。

由此可以看出二叉树的每个节点至多有两个节点。不存在度大于2的节点。而且每个节点有左右之分次序不能颠倒。

2.2特殊的树

  1. 满二叉树:一个二叉树,如果每层的节点数都到最大值,则这个数就是一个满二叉树。它的层数为k那么它的节点数就为2^k-1。
  2. 完全二叉树:完全二叉树是一个效率很高的数据结构。可以用一些不书面的语言来讲,为偶数节点的二叉树成为完全二叉树。

2.3二叉树的储存结构

二叉树可以有两种储存形式,第一种是顺序结构,第二种是链式结构。

  1. 顺序储存 

 顺序储存就是用数组来存,一般只适合用来表示完全二叉树。因为不是完全二叉树会有空间浪费的。一会下面的大小堆的问题就是用顺序表来表示的,功能非常强大。

 还有一种链式储存完全二叉树的结构,这里先不提到。把大小堆问题解决之后再来整。

2.4堆的概念及其结构

  堆可以说堆及是金字塔类型的,一种是从上面到最低下那一层都是按从大到小排列的,称为大堆,相反的则称为小堆

 那么我们已经知道什么是大小堆了,那么它们的用途会在哪里呢?这里我们可以模拟实现一下大堆的生成。然后插入数据

2.5堆的实现

  我们将一组乱序数组插入到堆中,但是我们要保证它是大堆的结构,下面是代码实例和图解,主要是调整的代码:

void AdjustUp(int* hp,Sty child)
{
     //我们再插入数据之后就需要对堆进行排序了,根据之前的
     //我们知道插入的位置都是孩子的位置,我们需要根据孩子
     //的位置算出父亲节点的位置,就是下面这个公式。
	int parent = (child - 1) / 2;
    //结束条件是一直把孩子往上调,直到孩子到达根节点。
	while (child!=0)
	{

		if (hp[parent] <hp[child])
		{
			Swap(&hp[parent], &hp[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else break;
	}
}

我们需要插入的数据就这样会被放到非常妥当的位置啦!

小堆的实现和大堆的一样的。那么我们知道了无论大堆还是小堆都可以进行排序,好像也只能做这么些东西了么。其实堆的应用主要还是用于Topk问题,如果给你N个乱序数你怎么找到这N个中前K个大的数呢?我们可能会想到冒泡或者快排(申明一下我目前还不直到快排,只知道有这个东西。太菜了)。但是用冒泡的话,时间复杂度会到o(n^2)的,无疑是大大消耗了时间。这里我们的大小堆会非常完美的解决这个问题

2.6Topk-问题

  关键点来了啊啊啊啊啊啊,

TOP-K 问题:即求数据结合中前 K 个最大的元素或者最小的元素,一般情况下数据量都比较大
    比如:世界500强,年纪前10名。富豪排行榜。游戏中的前100名活跃玩家。
对于Topk问题,我们已经不能用排序来解决了,因为只是找出他们中的前几个,对他们都进行排序,无疑是杀鸡用牛刀。最佳的方式就是用堆来解决。思路如下:
  1. 用数据集合中前k个元素来建立一个堆
  • 找前k个大的则建一个元素为k的小堆。
  • 找前k个小的则建一个元素为k的大堆。
     2.用剩余的n-k个元素依次与对堆顶数据进行比较,不满足则替换堆顶元素。
void test3()
{
	int n = 10000;
	int* a = (int*)malloc(sizeof(int) * n);
	srand(time(NULL));
	for (size_t i = 0; i < n; ++i)
	{
		a[i] = rand() % 1000000;
	}
	//设置10个比1000000大的数字,然后Top。
	a[1] = 1000000 + 1;
	a[1231] = 1000000 + 2;
	a[6839] = 1000000 + 3;
	a[194] = 1000000 + 4;
	a[289] = 1000000 + 5;
	a[57] = 1000000 + 6;
	a[327] = 1000000 + 7;
	a[99] = 1000000 + 8;
	a[2984] = 1000000 + 9;
	a[3598] = 1000000 + 10;
	//找出10000个数中前10个大的数!
	PrintTopk(a, n, 10);
}
void PrintTopk(int* a, int n, int k)
{
	Hp h1;
	HeapInit(&h1);
	for (size_t i = 0; i < k; i++)
	{
		HeapPushmin(&h1, a[i]);
	}
	for (size_t i = k; i < n; i++)
	{
		if (a[i] > Top(&h1))
		{
            //这里这个两种方法来替换元素都是可以的,
			/*MinPop(&h1);
			HeapPushmin(&h1, a[i]);*/
			h1.a[0] = a[i];
			AdjustDownMin(h1.a, h1.size, 0);
		}
		
	}
	print(&h1);
}

这样下来我们的Topk问题就得到了完美的解决了。

3.二叉树链式结构的实现

3.1 前置说明

  首先我们上面的堆的结构并不属于真正的二叉树的实现,我们只不过使用了顺序表来模拟了叉树中大小堆的实现。链式二叉树才是真正的二叉树的入门结构。下面的代码就是创建链式二叉树的简单结构。

typedef char DataType;
typedef struct BinaryTreeNode
{
    //树的左子树
	struct BinaryTreeNode* left;
    //树的右子树
	struct BinaryTreeNode* right;
    //数据域
	DataType data;
}BTNode;

BTNode* Buynode(DataType x)
{
	BTNode* node = (BTNode*)malloc(sizeof(BTNode));
	node->left = node->right = NULL;
	node->data = x;
	return node;
}
//二叉树的每个节点
BTNode* CreatTree()
{
	BTNode* nodeA = Buynode('A');
	BTNode* nodeB = Buynode('B');
	BTNode* nodeC = Buynode('C');
	BTNode* nodeD = Buynode('D');
	BTNode* nodeE = Buynode('E');
	BTNode* nodeF = Buynode('F');
	//每个节点存的左右子树
	nodeA->left = nodeB;
	nodeA->right = nodeC;
	nodeB->left = nodeD;
	nodeC->left = nodeE;
	nodeC->right = nodeF;
	return nodeA;
}

以上就是简单二叉链的创建,这里最主要的实现二叉链的遍历。

3.2二叉链的遍历

  我们每创建一个对象,需要它实现自己的功能,然后就是对这个结构的访问,之前的结构都属于线性的非常方便访问,那么这个树总分分叉我们该怎么遍历它呢?我们先来看一下链式二叉树的逻辑图吧

其中每个字母都是里面的节点,看到这里我们好像发现了什么,说的不好访问和遍历并不代表不可以实现aaa

  1.  前序遍历(Preorder Traversal 亦称先序遍历)——访问根结点的操作发生在遍历其左右子树之前。
  2. 中序遍历(Inorder Traversal)——访问根结点的操作发生在遍历其左右子树之中(间)。
  3. 后序遍历(Postorder Traversal)——访问根结点的操作发生在遍历其左右子树之后

下面是三种遍历代码,具体方法是用递归实现的,

  • 前序遍历
void PreOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	printf("%c ", root->data);
	PreOrder(root->left);
	PreOrder(root->right);
}
  • 中序遍历
void InOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	InOrder(root->left);
	printf("%c ", root->data);
	InOrder(root->right);
}
  • 后序遍历
void PostOrder(BTNode* root)
{
	if (root == NULL)
	{
		printf("NULL ");
		return;
	}
	PostOrder(root->left);
	PostOrder(root->right);
	printf("%c ", root->data);
}

以上代码看似遍历这个树好像很简单是吧,实则不然,其实它里面的逻辑实现是这样的

二叉树节点个数

    

void BinaryTreeNodeSize(BTNode* root, int* count)
{
	if (root == NULL)
	{
		return;
	}
    //节点不为空就将计数器++,计数需要注意定义一个外部的变量。
	(*count)++;
    //然后遍历左右子树
	BinaryTreeNodeSize(root->left, count);
	BinaryTreeNodeSize(root->right, count);
}

二叉树叶子节点个数

int BinaryTreeLefSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
    //考虑递归的终止条件,叶子节点左右子树都为空。则这个就为叶子节点。
	if (root->left == NULL && root->right == NULL)
	{
		return 1;
	}
	return BinaryTreeLefSize(root->left) + BinaryTreeLefSize(root->right);
}

二叉第k层节点个数

int BinaryTreeLevelSize(BTNode* root, int k)
{
	if (root==NULL)
	{
		return 0;
	}
	if (k == 1)
	{
		return 1;
	}
    //如果树不为空,并且k!=1,说明这棵树第k层节点在子树里面,转换为求它左右子树,并且k-1的节点。
	return BinaryTreeLevelSize(root->left, k - 1) + BinaryTreeLevelSize(root->right, k - 1);
}

 

 我们这个求它第三层的节点个数,相当于求根节点左右子树第二层的节点个数。左右子树第二层节点个数,相当于求它左右子树的左右子树第一层节点个数~~~

二叉树的最大深度

int BinaryTreeDepheSize(BTNode* root)
{
	if (root == NULL)
	{
		return 0;
	}
	int leftDepth = BinaryTreeDepheSize(root->left);
	int rightDepth = BinaryTreeDepheSize(root->right);
	return leftDepth > rightDepth ? leftDepth + 1 : rightDepth + 1;
}

先找终止条件为左右子树做根节点为空了就终止,不然就左右子树大的那个+1~~~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值