目录
二叉树的存储结构
- 二叉树一般可以使用两种结构存储,一种顺序结构,一种链式结构。
- 通过上篇博文的讲解我们得知完全二叉树和满二叉树是可以通过数组来进行存储的,它们间的父子关系可以通过下标来表示。这里再强调下物理结构是是在内存当中实实在在存储的,在物理上是数组,但是在逻辑上要把它看出二叉树。
- 普通的二叉树推荐用链式存储,不适合用数组来存储,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。因为空间利用率高,不会造成浪费。
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
二叉树的堆排序
现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段
堆的概念
堆是一个完全二叉树,它的所有元素按照完全二叉树的顺序存储方式存储在一个一维数组中。堆分为两种:小根堆、大根堆
- 小堆:每一个父结点的值均小于等于其对应的子结点的值,而根结点的值就是最小的。
- 大堆:每一个父结点的值均大于等于其对应的子结点的值,而根结点的值就是最大的。
堆的性质:
- 堆中某个节点的值总是不大于或不小于其父节点的值。
- 堆总是一棵完全二叉树
通过物理结构可以得知以下两点:
- 有序的一定是堆
- 无序的可能是堆
堆向上调整算法
思路:
- 此算法是为了确保插入数据后的堆依然是符合堆的性质而单独封装出来的函数,就好比如我们后续要插入的数字10
- 为了确保在插入数字10后依然是个小根堆,所以要将10和28交换,依次比较父结点parent和子结点child的大小,当父小于子结点的时候,就返回,反之就一直交换,直到根部。
- parent = (child - 1) / 2,我们操控的是数组,但要把它想象成二叉树
代码实现:
#include<iostream>
#include<string>
using namespace std;
typedef int HPDataType; //堆中存储数据的类型
//交换
void Swap(HPDataType* pa, HPDataType* pb)
{
HPDataType tmp = *pa;
*pa = *pb;
*pb = tmp;
}
//向上调整算法
void AdjustUp(HPDataType* a, size_t child)
{
size_t parent = (child - 1) / 2;
while (child > 0)
{
//if (a[child] > a[parent]) //大根堆
if (a[child] < a[parent]) //小根堆
{
Swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
//升序
void HeapSort(int* a, int n)
{
//建堆
int i = 0;
for (i = 1; i < n; i++) //应该从i=1时遍历,因为第一个数据在堆里不需要调整,后续再插入时调整
{
AdjustUp(a, i);
}
}
int main()
{
int a[] = { 4,2,7,8,5,1,0,6 };
HeapSort(a, sizeof(a) / sizeof(int));
for (int i = 0; i < sizeof(a) / sizeof(int); i++)
{
printf("%d ", a[i]);
}
return 0;
}
堆向下调整算法
思路:
顾名思义,从上往下调整,即
- 找出左右孩子中最小的那个
- 跟父亲比较,如果比父亲小,就交换
- 再从交换的孩子位置继续往下调整
最终变为小根堆
#include<iostream>
#include<string>
using namespace std;
typedef int HPDataType; //堆中存储数据的类型
//交换
void Swap(HPDataType* pa, HPDataType* pb)
{
HPDataType tmp = *pa;
*pa = *pb;
*pb = tmp;
}
//向下调整算法
void AdjustDown(HPDataType* a, size_t size, size_t root)
{
int parent = root;
int child = 2 * parent + 1;
while (child < size)
{
//1、确保child的下标对应的值最小,即取左右孩子较小那个
if (child + 1 < size && a[child + 1] < a[child]) //得确保右孩子存在
{
child++; //此时右孩子小
}
//2、如果孩子小于父亲则交换,并继续往下调整
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
parent = child;
child = 2 * parent + 1;
}
else
{
break; //如果中途满足堆的性质,直接返回
}
}
}
//升序
void HeapSort(int* a, int n)
{
//建堆
int i = 0;
//2、向下调整
for (int i = (n - 1 - 1)/2; i >= 0; i--)//从0开始找父节点根
{
A