一、树的概念
二、二叉树
从上图可以看出:
1. 二叉树不存在度大于2的结点
三、特殊的二叉树
四、二叉树的顺序结构以及实现
五、堆的概念以及结构
堆就是以二叉树的顺序存储方式来存储元素,同时又要满足父亲结点存储数据都要大于儿子结点存储数据(也可以是父亲结点数据都要小于儿子结点数据)的一种数据结构。堆只有两种即大堆和小堆,大堆就是父亲结点数据大于儿子结点数据,小堆则反之。
堆的性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
六、堆的实现
1、函数的声明
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;//有效数据个数
int capacity;//空间大小
}HP;
void Swap(HPDataType* p1, HPDataType* p2);//交换函数
void AdjustUp(HPDataType* a, int child);//向上调整
void AdjustDown(HPDataType* a, int n, int parent);//向下调整
void HPInit(HP* php);//初始化
void HPDestroy(HP* php);//销毁
void HPPush(HP* php, HPDataType x);//插入
void HPPop(HP* php);//删除树顶
HPDataType HPTop(HP* php);//树顶的数据
bool HPEmpty(HP* php);//判断是否为空
2、函数实现
a、初始化和销毁
void HPInit(HP* php)//初始化
{
assert(php);
php->a = NULL;
php->capacity = php->size = 0;
}
void HPDestroy(HP* php)//销毁
{
assert(php);
free(php->a);
php->a = NULL;
php->capacity = php->size = 0;
}
b、获取堆顶元素和判断是否为空
HPDataType HPTop(HP* php)//树顶的数据
{
assert(php);
assert(php->a);
assert(php->size > 0);
return php->a[0];
}
bool HPEmpty(HP* php)//判断是否为空
{
assert(php);
return php->size == 0;
}
c、向上调整算法以及插入
向上调整即我们插入一个数据是要保持还是堆的形式(以小堆为例):
父亲节点(n-1)/2,所以我们会有入下代码
void AdjustUp(HPDataType* 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 HPPush(HP* php, HPDataType x)//插入
{
assert(php);
if (php->size == php->capacity)
{
int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
HPDataType* tmp = (HPDataType*)realloc(php->a, newcapacity * sizeof(HPDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
php->a = tmp;
php->capacity = newcapacity;
}
php->a[php->size] = x;//插入
php->size++;
AdjustUp(php->a, php->size - 1);//将要插入的数据进行向上调整
}
d、向下调整以及删除
删除是删除堆顶的数据,如果我们重新插入时间复杂的更高,这里就会用到向下调整的算法,我们会将堆顶数据和堆底数据进行交换,这样堆顶的左右数仍然是小堆,比较两个儿子之间的大小,与更小的那个交换(这样保证出来一定是小堆),循环即可。
左儿子为2*n+1:
void AdjustDown(HPDataType* a, int n, int parent)//向下调整
{
// 先假设左孩子小
int child = parent * 2 + 1;
while (child<n)//最后一次交换后child最坏情况在堆底,如果写出parent<n则可能导致越界访问
{
if (a[child] > a[child + 1]&& child + 1 < n)//每一次都要找最小的所以放循环里,同理防止++后产生越界
{
child++;
}
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
有了以上算法我们可以很容易写出删除函数
void HPPop(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);
}
以上是小堆的,要是想改成大堆,只需要将大小判断改一下就可以了。
七、堆排序
法一:粗暴一点的方法,直接将一个一个数据插入堆中,然后依次取堆顶数据,取出来就是有顺序的。(大堆取出来是升序,小堆是降序)。但是这样空间复杂度为O(N)。(这里不进行演示)
方法二就是正经的堆排序算法。
1、向上调整的方法
前面我们提到的插入是指往堆中插入,而这里是一种伪插入,即将最后的数据依次向上调整最后出来就是堆。
若建大堆则为向上调整,建小堆为向下调整(因为这里博主向上向下调整是以建小堆为前提所以这里是这样调整)。这里以降序为例。
实际上就是先认为该数组只有最后一个元素,往头部取插,那么前面的几个数据则一定是堆结束时堆建好 ,这样就避免了申请空间的问题。
当堆建好后,我们先将堆顶数据和堆底数据交换,在向下调整使其保持为堆,那么每次堆底数据为最小的,然后让倒数第二给交换,到结束使堆顶为最大数据,堆底为最小数据。
void AdjustUp(HPDataType* 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 HeapSort(int* a, int n)
{
// 降序,建小堆
// 升序,建大堆
for (int i = 1; i < n; i++)
{
AdjustUp(a, i);
}
int end = n - 1;
while (end > 0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
--end;
}
}
void TestHeap2()
{
int a[] = { 4,2,8,1,5,6,9,7,2,7,9 };
HeapSort(a, sizeof(a) / sizeof(int));
}
假设每一层的数据都需要调整
2、只用向下调整法
以如下堆为例
我们从每一个树出发,用向下调整法,将每一个树都变为大堆 ,即每个夫节点都向下调整,最后出来即为大堆。
void AdjustDown(HPDataType* a, int n, int parent)//向下调整
{
// 先假设左孩子大
int child = parent * 2 + 1;
while (child<n)//最后一次交换后child最坏情况在堆底,如果写出parent<n则可能导致越界访问
{
if (a[child] < a[child + 1]&& child + 1 < n)//每一次都要找最大的所以放循环里,同理防止++后产生越界
{
child++;
}
if (a[child] > a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapSort(int* a, int n)
{
// 降序,建小堆
// 升序,建大堆
/*for (int i = 1; i < n; i++)
{
AdjustUp(a, i);
}*/
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;
}
}
void TestHeap2()
{
int a[] = { 4,2,8,1,5,6,9,7,2,7,9 };
HeapSort(a, sizeof(a) / sizeof(int));
}
假设每一层的数据都需要调整