【初级数据结构】树与堆

树与堆

本文对树仅做简单介绍,不深入,以后会产出有关树的深入文章。

1、树的概念

先来看一张图:

这种结构是不是很赏心悦目呢?应该很适合用来存储数据吧。

树形结构就是与之类似的一种结构

树形结构指的是数据元素之间存在着“一对多”的树形关系的数据结构,是一类重要的非线性数据结构

每棵树都有根结点,结点与结点之间的关系在下图中有说明:

结点的度:一个结点含有子树的个数

树的度:一棵树中的最大结点的度

结点层次:从根结点开始定义,为第0层,其子结点为第1层(上图错误)

树的高度/深度:树中最大的结点层次,(空树深度为0),高度从底向上定义,深度从根结点向下定义

森林:N(>=0) 棵互不相交的树组成的集合

2、树的性质

image-20221222211923633

3、树的储存

双亲表示法

每个结点存储其双亲在数组中的位置

typedef struct Node
{
    int data;//数据
    int parents;//双亲在数组中位置
}Node;

typedef struct
{
    Node newnode[10];//大小可变
    int r;//根
    int n;//结点数
}tree;

这样的结构方便寻找双亲结点,但不利于寻找子结点。还有什么好方法呢?

孩子表示法

这个方法的逻辑就是:用单链表存储每个结点的子结点,这些单链表的头指针都被存储在一个一维数组中,可以结合上图理解

typedef struct LsNode
{
    int child;
    struct LsNode* next;
}ChildNode;

typedef struct
{
    int data;
    ChildNode FirChild;
}FirNode;

typedef struct
{
    FirNode node[10];//大小可变
    int r;//根
    int n;//结点数
}

这个方法对于寻找子结点以及兄弟结点都是比较方便的,但是寻找双亲就麻烦了。

4、树的相关题目

  1. 在一颗度为3的树中,度为3的结点有2个,度为2的结点有1个,度为1的结点有2个,则叶子结点有( 6)个

    设度为i的节点个数为ni, 该树总共有n个节点,则n=n0+n1+n2+n3.

    n个节点的树的总边数为n-1条.

    根据度的定义,总边数与度之间的关系为:n-1=0*n0+1*n1+2*n2+3*n3.

    联立两个方程求解,可以得到n0 = n2 + 2n3 + 1, n0=6

  2. 一颗拥有1000个结点的树度为4,则它的最小深度是(6

    如果这棵树每一层都是满的,则它的深度最小,假设它为一个四叉树,高度为h,则这个数的节点个数为(4^h - 1) / 3,当h = 5, 最大节点数为341, 当h = 6, 最大节点数为1365,所以最小深度应该为6。

  3. 一颗完全二叉树有1001个结点,其叶子结点的个数是( 501

    该题需要用到二叉树性质:在任意二叉树中,度为0的节点都比度为2的节点多1个,即 n0 = n2 + 1

    另外,在完全二叉树中,如果节点总个数为奇数,则没有度为1的节点,如果节点总个数为偶数,只有一个度为1的节点

    因此:n0 + n1 + n2 = 1001 节点总数为奇数,没有度为1的节点

    n0 + 0 + n2 = 2*n0-1 = 1001 n0 = 501

  4. 在一颗完全二叉树中,某一个结点没有其左孩子,则该结点一定( 是叶结点

    完全二叉树中如果一个节点没有左孩子,则一定没有右孩子,必定为一个叶子节点,最后一层一定为叶子节点,但是倒数第二层也可能存在叶子节点。

1、堆的概念

堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:

  • 堆中某个结点的值总是不大于或不小于其父结点的值;
  • 堆总是一棵完全二叉树。
  • 将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。(见下图)

2、主要算法

以下算法都以小堆为例

向下调整算法(堆的删除)
  1. 先设定根节点为当前节点(通过下标获取,标记为cur),比较左右子树的值,找出更小的值,用child来标记。
  2. 比较child和cur的值,如果child比cur小,则不满足小堆的规则,需要进行交换。
  3. 如果child比cur大,满足小堆的规则,不需要交换,调整结束。
  4. 处理完一个节点之后,从当前的child出发,循环之前的过程。
image-20221222221458825

图中为小堆的向下调整算法示意图,大端变换数字即可,就不再附图。

这个算法在堆的删除操作中会用到。

void ShiftDown(int* arr, int n, int cur)
{
	int child = 2 * cur + 1;
	while (child < n)
	{
		if (child + 1 < n && arr[child + 1] < arr[child])
		{
			++child;
		}
		if (arr[child] < arr[cur])
		{
			Swap(&arr[child], &arr[cur]);
			cur = child;
			child = 2 * cur + 1;
		}
		else
		{
			break;
		}
	}
}
向上调整算法(堆的插入)
  1. 先设定倒数的第一个叶子节点为当前节点(通过下标获取,标记为cur),找出他的父亲节点,用parent来标记。
  2. 比较parent和cur的值,如果cur比parent小,则不满足小堆的规则,需要进行交换。
  3. 如果cur比parent大,满足小堆的规则,不需要交换,调整结束。
  4. 处理完一个节点之后,从当前的parent出发,循环之前的过程。

逻辑与向下调整算法虽然相反,但是异曲同工。

void ShiftUp(int* arr, int n, int cur)
{
	int father = (cur - 1) / 2;
	while (cur > 0)//因为是向上调整,所以cur要满足>0的条件
	{
		if (arr[cur] < arr[father])
		{
			Swap(&arr[cur], &arr[father]);
			cur = father;
			father = (cur - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

3、堆的实现(小堆)

1、堆的定义
typedef int HPDataType;//定义堆的数据类型
typedef struct Heap
{
	HPDataType* data;
	int size;
	int capacity;
}Heap;
2、交换
void Swap(int* a, int* b)
{
	int temp = *a;
	*a = *b;
	*b = temp;
}
3、检查容量
void CheckCapacity(Heap* hp)
{
	if (hp->capacity == hp->size)
	{
		int newcapacity = hp->capacity == 0 ? 1 : 2 * hp->capacity;
		hp->data = (HPDataType*)realloc(hp->data, sizeof(HPDataType) * newcapacity);
		hp->capacity = newcapacity;
	}
}
4、堆的初始化
void HeapInit(Heap* hp)
{
	assert(hp);
	hp->data = NULL;
	hp->capacity = hp->size = 0;
}
5、堆的创建
void HeapCreate(Heap* hp, HPDataType* a, int n)
{
	assert(hp);
	hp->data = (HPDataType*)malloc(sizeof(HPDataType) * n);

	memcpy(hp->data, a, sizeof(HPDataType) * n);

	hp->capacity = hp->size = n;

	for (int i = (n - 2) / 2; i >= 0; i--)
	{
		ShiftDown(hp->data, n, i);
	}
}
6、堆的销毁
void HeapDestory(Heap* hp)
{
	assert(hp);
	free(hp->data);
	hp->data = NULL;
	hp->capacity = hp->size = 0;
}
7、堆的插入
void HeapPush(Heap* hp, HPDataType x)
{
	assert(hp);
	CheckCapacity(hp);
	hp->data[hp->size++] = x;
	ShiftUp(hp->data, hp->size, hp->size - 1);
}
8、堆的删除
void HeapPop(Heap* hp)
{
	if (hp->size > 0)
	{
		Swap(&hp->data[0], &hp->data[hp->size - 1]);//删除的是根,所以交换根与最后一个结点,再size--、向下调整
		hp->size--;
		ShiftDown(hp->data, hp->size - 1, 0);
	}
}
9、获取根
HPDataType HeapTop(Heap* hp)
{
	assert(hp);
	return hp->data[0];
}
10、堆的大小
int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->size;
}
11、堆的判空
int HeapEmpty(Heap* hp)
{
	if (hp->size == 0 || hp == NULL)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}
9、获取根
HPDataType HeapTop(Heap* hp)
{
	assert(hp);
	return hp->data[0];
}
10、堆的大小
int HeapSize(Heap* hp)
{
	assert(hp);
	return hp->size;
}
11、堆的判空
int HeapEmpty(Heap* hp)
{
	if (hp->size == 0 || hp == NULL)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值