基本知识
1.树的定义
树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因 为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。
- 根节点:该节点前面没有前驱节点 例如上图的A是根节点
- 节点的度:一个节点所含有的子树个数为该节点的度 如上图中A的度是6
- 叶节点:度为0的节点 如上图的B C H I P....等都是叶节点
- 树的度:一颗树中,最大的节点的度是树的度 如上图:树的度为6
- 树的深度或高度:树中节点的最大层次 如上图:树的高度为4
2.二叉树
二叉树的要求:
- 二叉树中没有度大于2的节点
- 二叉树的子树有左右之分,次序不能颠倒,因此二叉树是有序树
- 假设该层是第n层,那么该层最多有2^(n-1)个节点
特殊的二叉树:
- 满二叉树:一个二叉树,每一层的节点都是该层的最大值,则称为满二叉树。若满二叉树的层数为k,则节点的总数(2 ^k)-1;
- 完全二叉树:若该完全二叉树的层数为k层,则该树的前k-1层都是满的,最后一层k层没有满,但最后一层的节点必须要是从左向右连续的
堆的实现
1.堆的定义
堆(英语:heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。
2.堆的性质
- 堆中的子节点的数值总是不大于或不小于其父节点的值
- 堆是一个完全二叉树(满二叉树是完全二插树的特殊情况)
- 根节点是最大的称为大堆 大堆的性质是:每个父节点的值都大于子节点的值
- 根节点是最大的称为小堆 小堆的性质是:每个父节点的值都小于子节点的值
3.堆的思路分析
这里采用的是物理结构数组来进行建堆(逻辑结构)
故我们现定义一个结构体变量,并将该堆要将存放的数据类型进行重定义
该堆要实现的功能:1.初始化堆(此时的堆要满组堆的性质)
2.销毁堆
3.对堆进行插入数据
4.删除堆顶(根节点)的数据
5.获取堆顶的数据
4.堆的基本结构
typedef int HPDataType;//将int给重命名为HPDataType,方便以后调整
typedef struct Heap
{
HPDataType* a;//该指针指向的是堆的地址
int size;//数组的数据数量
int capacity;//数组的空间大小
}HP;
5.堆的初始化
5.1基础知识:
- 假设已知堆中父节点的下标是k,则该堆的左孩子节点是2*k+1 该堆的右孩子节点是2*k+2
- 假设已知一个节点的下标是n,则该堆的父节点是(n-1)/2,前提是该节点要有父节点
5.2向下调整算法
向下调整算法的前提条件是:左右子树必须是小堆(由于本文建的是小堆)
该算法的基本思路:先找到左右孩子节点中的最小的节点,然后在将最小的节点与父节点进行比较,
若子节点更小,则将该子节点与父节点进行交换,在将子节点的小标赋给新的父节点,新的子节点为2*新的的父节点下标+1,在循环
要求左右子树是堆的原因,第一层的为根节点,第2层的为子节点,第3层的为子子节点
若左右子树不是堆,则将子节点与根节点进行调整时,并不能保证根节点时堆里最小的, 因为左右子树不是堆,则造成了子节点并不一定比子子节点大,则对子节点与子子节点进 行向下调整算法时,有可能让子节点还是小与根节点
//交换数据函数
void swap(HPDataType* child, HPDataType* parent)
{
HPDataType* tmp = *child;
*child = *parent;
*parent = *tmp;
}
//小堆向下调整算法的作用对象是
//如果一棵树中左右子树都是小堆(或大堆),只有根节点不满足
void AdjustDown(HPDataType* a, int n, int root)
//这里a是指向数组 ,n是数组的数据个数,root(数组下标)是从那一个节点进行操作
{
int parent = root;//这是下标
int child = 2 * parent + 1;//这是左子树的下标
//这里先假设左子树是2个子树中最小的那个
while (child < n)
{
//当右子树才是最小的时候,进行调整,让child + 1赋给child
if (child + 1 < n && a[child] > a[child + 1])
{
//这里child+1<n,是为了防止出现数组越界,
// 因为出现这中情况是,已经没意义了
child = child + 1;
}
if (a[child] < a[parent])
{
swap(&a[child], &a[parent]);
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
5.3建堆
本文中建的堆是小堆
当数组里存放好数据时,我们要对其进行操作,从而使它满足堆的性质
若数组是arr[7]={5,2,7,1,3,4,8} ,这里我们要用向下调整算法,并且我们将对该树中的最后一个度不为0的节点进行操作(定义可知这里堆一定是完全二叉树),然后在从这个节点开始往前进行向下调整
初始化堆—1.创建变量 2.建堆
void HeapInit(HP* php, HPDataType* a, int n)
//在php这个结构体中开辟一个长度为n的数组,并将这个数组拷贝给长度为n的数组a
{
assert(php);
php->a = (HPDataType*)malloc(sizeof(HPDataType) * n);
if (php->a == NULL)
{
printf("malloc fail\n");
exit(-1);
}
memcpy(php->a, a, sizeof(HPDataType) * n);
//注意memcpy的拷贝多少个数据,这里用的是字节数,并非数据数,
//数据数应等于:总的字节数除于一个数据所占有的字节数
//建堆
for (int i = (n - 1 - 1) / 2; i >= 0; --i)
{
AdjustDown(php->a, n, i);
}
php->size = php->capacity = n;
如图所示
6.堆的销毁
void HeapDestroy(HP* php)
{
assert(php);
php->size = php->capacity = 0;
free(php->a);
}
7.堆的数据插入
7.1思路分析
插入数据后,我们要保持它还是堆
1.先判断堆的空间容量是否够,不够就扩容
2.然后在进行数据的插入
7.2向上调整算法
该算法是从后面依次往上进行处理,判断子节点数据与父节点数据的大小,若子节点数据小于父节点,那么就交换数据,然后在将父节点的下标赋给新的子节点的下标,父节点下标为
(子节点的下标-1)/2,
/向上调整算法
void AdjustUp(HPDataType* a, int child)//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;
}
}
}
7.3实现插入
数据的插入,首先要考虑是从堆顶还是堆底进行插入,假设从堆顶进行插入,那么原有的父子关系全部发生了改变,则对其处理,很复杂。若从堆底进行操作,该数据只时影响了一条线,则我们可以用向上调整算法来处理8
/插入数据x,要保持它继续是堆
void HeapPush(HP* php, HPDataType* x)
{
assert(php);
//进行扩容
if (php->size == php->capacity)
{
php->capacity *= 2;
HPDataType tmp = (HPDataType*)realloc(php->a, php->capacity * sizeof(HPDataType));
if (tmp == NULL)
{
exit(-1);
}
php->a = tmp;
}
php->a[php->size] = x;//由于数组的下标是从一开始的
php->size++;
AdjustUp(php->a, php->size- 1);
}
8.堆顶数据的销毁
删除堆顶数据,删除后保持他继续是堆
1.将数组里的第一个与最后一个交换,2.交换后,在将size减一后,此时,满足向下调整的思想,因为此刻除了根节点,左右子树都时小堆,故可在用向下调整算法
void HeapPop(HP* php)
{
assert(php);
assert(php->size > 0);
swap(&php->a[0], &php->a[php->size - 1]);
php->size--;
AdjustDown(php->a, php->size, 0);
}
9.获取堆顶的数据,也就是最值
// 获取堆顶的数据,也就是最值
HPDataType HeapTop(HP * php)
{
assert(php);
assert(php->size>0);
return php->a[0];
}
堆排序
- 排成升序,要建大堆
- 排成降序,要建小堆
思路分析
假设要排成降序
先建好一个小堆,建好小堆后,可以将最后一个数据与第一个数据进行交换,此时数组里的最后一个元素是最小的元素,然后我们要找到次小的,此刻,我们并不对该堆里的最后一个数据处理(即把该节点视为空),故这样就满足了向下调整算法的要求
代码实现
//堆排序问题,分析其时间复杂度
void HeapSort(HP* php,int n)
{
for (int i = (n - 1 - 1) / 2, i >= 0; --i)
{
AdjustDown(php->a, n, i);
}
int end = n - 1;
while (end > 0)
{
swap(&a[0], &a[end]);//系统执行完这个语句后
//此时我们可以对最后一个数据不处理,相当于删了它,然后此时该堆再次满足了向下调整的要求了,我们可以去找到第2小的,
//在重复这中操作,我们就能排序
AdjustDown(a, end, 0);
end--;
}
}