Hello!各位铁子们,我们叕见面了,今天我们要学的是是一种新的数据结构—堆,首先我们先来了解一下数这种数据结构!
一.树
1.树是⼀种⾮线性的数据结构,它是由 n(n>=0) 个有限结点组成⼀个具有层次关系的集合。在树中,有一个特殊的节点—根节点,它没有前驱结点。每棵子树有且只有一个前驱结点,但可以有0个、1个或多个后继结点。因此,我们说,树是递归定义的。
2. 注意:
(1)在树形结构中,每棵子树是不能有交集的,否则就不能构成树形结构。
(2)每棵子树都是不相交的。
(3)除了根节点,每棵子树都只有一个父节点。
(4)一棵具有n个节点的树有n-1条边。
3.我们在表示树形结构时,用的最多的方法就是"孩子兄弟表示法"。如下图。
struct Tree
{
int data; //节点中保存的数据
struct Tree* child; //指向它的孩子节点
struct Tree* brother; //指向它的兄弟节点
}
二.二叉树
1.在树形结构中,我们最常用的就是二叉树。一棵二叉树是节点的有限集合,该集合是由一个根节点再加上两课分别叫做左子树和右子树的二叉树组成或者为空。
2.二叉树的特点:
(1)二叉树不存在度大于2的节点。
(2)二叉树为有序树,左右子树的顺序不能颠倒。
(3)二叉树可以是空树(即有0个节点)。
3.两种特殊的二叉树
3.1 满二叉树
3.1.1 定义:一棵二叉树,若它每一层的节点数都达到最大,它就是一棵满
二叉树。即若二叉树有k层节点,它的总结点数为2^k-1,则它就是满二叉树。
以下就是一棵满二叉树。
3.2 完全二叉树
3.2.1 特点:
(1)一棵二叉树,除了最后一层节点,其他层的节点数目均达到最大。
(2)最后一层的节点数不一定达到最大。
(3)每一层的节点都是从左到右依次排列的。
3.2.2
满二叉树也是一种完全二叉树,且完全二叉树是一种效率很高的数据结构。
3.3 二叉树的性质
规定根节点对应第一层,则二叉树有如下性质:
(1)一棵非空二叉树的第i层最多有 2^(i-1) 个节点。
(2)一棵高度为h的二叉树最多有 2^h-1 个节点。
(3)一棵有n个节点的满二叉树的高度为 h=log(n+1)。
三. 二叉树的存储结构
二叉树一般有两种存储结构,分别是顺序结构和链式结构。
1.顺序结构
1.1 顺序结构就是利用数组来存储,一般完全二叉树适合用顺序结构存储,因为不是完全二叉树用数组存储会造成空间的浪费。
1.2 事实上我们一般用数组来存储 ‘堆’ (一种完全二叉树) 这种数据结构,注意这里的 堆 和操作系统虚拟进程地址空间中的堆是完全不同的概念,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
1.3 堆的概念
堆可以分为大根堆和小根堆。根节点最大的堆称为大根堆,根节点最小的堆称为小根堆。如下图:
1.4 堆的性质:
(1)堆总是一棵完全二叉树。
(2)堆中某个节点的值总是不大于或不小于它对应父节点的值。
1.5 对于具有 n 个结点的完全⼆叉树,如果按照从上⾄下从左⾄右的数组顺序对所有结点从
0 开始编号,则对于序号为 i 的结点有:
(1)若 i>0 , i 位置结点的双亲序号: (i-1)/2 ; i=0 , i 为根结点编号,⽆双亲结点。
(2)若 2i+1<n ,左孩⼦序号: 2i+1 , 2i+1>=n 否则⽆左孩⼦。
(3)若 2i+2<n ,右孩⼦序号: 2i+2 , 2i+2>=n 否则⽆右孩⼦。
1.6. 堆的实现
声明:
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
//定义堆的结构---利用顺序结构(数组)来实现---大堆或小堆
typedef int HPDatatype;
typedef struct Heap
{
HPDatatype* arr;
int size; //数组中有效数据个数
int capacity; //数组空间大小
}HP;
//堆的初始化
void HpInit(HP* php);
//堆的销毁
void HpDestroy(HP* php);
//堆的打印
void HpPrint(HP* php);
//入堆
void HpPush(HP* php, HPDatatype x);
//判断堆是否为空
bool HpEmpty(HP* php);
//出堆
void HpPop(HP* php);
//获取堆顶元素
HPDatatype HpTop(HP* php);
//求堆的大小
int HpSize(HP* php);
//向下调整算法
void AdjustDown(HPDatatype* arr, int parent, int n);
//向上调整算法
void AdjustUp(HPDatatype* arr, int child);
//交换两个数
void swap(int* a, int* b);
实现:
#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"
//堆的初始化
void HpInit(HP* php)
{
assert(php);
php->arr = NULL;
php->capacity = php->size = 0;
}
//堆的销毁
void HpDestroy(HP* php)
{
if (php->arr)
free(php->arr);
php->arr = NULL;
php->capacity = php->size = 0;
}
//堆的打印
void HpPrint(HP* php)
{
int i = 0;
for (i = 0;i < php->size;i++)
{
printf("%d ", php->arr[i]);
}
printf("\n");
}
void swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
//向上调整算法
void AdjustUp(HPDatatype* arr, int child)
{
int parent = (child - 1) / 2;
while (child>0)
{
//小堆:<
//大堆:>
if (arr[child] < arr[parent])
{
swap(&arr[child], &arr[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 : 2 * php->capacity;
HPDatatype* tmp = (HPDatatype*)realloc(php->arr, newCapacity * sizeof(HPDatatype));
if (tmp == NULL)
{
perror("realloc fail!\n");
exit(1);
}
php->arr = tmp;
php->capacity = newCapacity;
}
php->arr[php->size] = x;
//向上调整算法
AdjustUp(php->arr, php->size);
php->size++;
}
//判断堆是否为空
bool HpEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
//向下调整算法
void AdjustDown(HPDatatype* arr,int parent, int n)
{
int child = parent * 2 + 1;
while (child<n)
{
//小堆:>
//大堆:<
if (child + 1 < n && arr[child] > arr[child + 1])
{
child++;
}
//小堆:<
//大堆:>
if (arr[child] < arr[parent])
{
swap(&arr[child], &arr[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
//出堆---只能出堆顶元素
void HpPop(HP* php)
{
assert(!HpEmpty(php));
//将堆顶和最后一个元素交换
swap(&php->arr[0], &php->arr[--php->size]);
//向下调整算法
AdjustDown(php->arr, 0, php->size);
}
//获取堆顶元素
HPDatatype HpTop(HP* php)
{
assert(!HpEmpty(php));
return php->arr[0];
}
//求堆的大小
int HpSize(HP* php)
{
assert(php);
return php->size;
}
测试代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"
//10 15 56 25 30 70
//数组的打印
void arrPrint(int* arr,int n)
{
int i = 0;
for (i = 0;i < n;i++)
{
printf("%d ",arr[i]);
}
printf("\n");
}
void test01()
{
HP hp;
HpInit(&hp); //堆的初始化
HpPush(&hp, 56); //入堆
HpPush(&hp, 35); //入堆
HpPush(&hp, 30); //入堆
HpPush(&hp, 10); //入堆
HpPush(&hp, 70); //入堆
HpPush(&hp, 15); //入堆
HpPrint(&hp);
printf("%d ", HpSize(&hp)); //打印堆的大小
//HpPop(&hp); //出堆
//HpPrint(&hp);
//HpPop(&hp); //出堆
//HpPrint(&hp);
//HpPop(&hp); //出堆
//HpPrint(&hp);
//HpPop(&hp); //出堆
//HpPrint(&hp);
//HpPop(&hp); //出堆
//HpPrint(&hp);
//HpPop(&hp); //出堆
//HpPrint(&hp);
while (!HpEmpty(&hp))
{
int top = HpTop(&hp); //获取堆顶元素
printf("%d ", top);
HpPop(&hp);
}
printf("%d", HpSize(&hp)); //打印堆的大小
HpDestroy(&hp); //堆的销毁
}
void HeapSort1(int* arr, int n)
{
HP hp1; //堆排序---借助了堆的数据结构---不适用
HpInit(&hp1);
int i = 0;
for (i = 0;i < n;i++)
{
HpPush(&hp1, arr[i]);
}
i = 0;
while (!HpEmpty(&hp1))
{
arr[i++]=HpTop(&hp1); //获取堆顶元素
HpPop(&hp1);
}
}
void test02()
{
int arr[6] = { 10 ,15, 56, 25, 30, 70 };
arrPrint(arr, 6);
HeapSort1(arr, 6);
arrPrint(arr, 6);
}
//堆排序---借助堆的思想---排升序建大堆,排降序建小堆
void HeapSort(int* arr, int n)
{
//int i = 0;
//向下调整建堆---时间复杂度:O(n)
//for (i = (n - 1 - 1) / 2;i >= 0;i--)
//{
// AdjustDown(arr, i, n); 时间复杂度:O(logn)
//}
//向上调整建堆---时间复杂度:O(nlogn)
int i = 0;
for(i=0;i<n;i++)
{
AdjustUp(arr, i); //时间复杂度:O(logn)
}
//堆排序---降序
int end = n - 1;
while (end > 0)
{
swap(&arr[0], &arr[end]);
AdjustDown(arr, 0, end);
end--;
}
//堆排序---升序
/*int end = n - 1;
while (end > 0)
{
swap(&arr[0], &arr[end]);
AdjustDown(arr, 0, end);
end--;
}*/
}
int main1()
{
test01();
//test02();
int arr[6] = { 70 ,15, 56, 25, 30, 10 };
arrPrint(arr, 6);
HeapSort(arr, 6);
arrPrint(arr, 6);
return 0;
}
//*******************************************************************************//
//TOP-K问题---有n个数据,选出前k个最大(或最小)的数据,n>>k
void CreateNDate()
{
//创造100000个数据
int n = 100000;
srand(time(0));
const char* file = "data.txt";
FILE* fin = fopen(file, "w");
if (fin == NULL)
{
perror("fopen error");
return;
}
for (int i = 0; i < n; ++i)
{
int x = (rand() + i) % 1000000; //生成随机数
fprintf(fin, "%d\n", x);
}
fclose(fin);
}
void TopK()
{
printf("请输入K: ");
int k = 0; //k为要求的前k个最大(或最小)的数据个数
scanf("%d", &k);
//这里求最大的前k个数据
const char* file = "data.txt";
FILE* fout = fopen(file, "r"); //以只读方式打开文件
if (fout == NULL)
{
perror("fopen fail!\n");
exit(1);
}
int* minHeap = (int*)malloc(sizeof(int) * k); //创建新数组保存前k个数据
if (minHeap == NULL) //不要写成'='了
{
perror("malloc fail!\n");
exit(2);
}
int i = 0;
for (i = 0;i < k;i++)
{
fscanf(fout, "%d", &minHeap[i]); //读取前k个数据到数据中
}
for (i = (k - 1 - 1) / 2;i >= 0;i--) //先将数组调整为堆结构
{
//向下调整建堆
AdjustDown(minHeap, i, k);
}
int x = 0;
while (fscanf(fout, "%d", &x) != EOF) //再读入后n-k个数据
{
if (x > minHeap[0]) //若x大,则x放到堆顶上
{
minHeap[0] = x;
AdjustDown(minHeap, 0, k);
}
}
for (i = 0;i < k;i++)
{
printf("%d ", minHeap[i]);
}
fclose(fout);
}
int main()
{
//CreateNDate(); //创建数据
//TopK();
int arr[6] = { 70 ,15, 56, 25, 30, 10 };
arrPrint(arr, 6);
HeapSort(arr, 6);
arrPrint(arr, 6);
return 0;
}
看到这里,相信各位铁子们对堆这种数据结构已经有了进一步的了解,想和博主继续学习进步的就一键三连吧!