舒文未来目标: 进大厂啊进大厂~🤪.让我家人放松一些,努力让生活更棒,好耶!
舒文现状:大一菜鸡,从食品转码,目前已经结识了很多学习的朋友.(挺棒的👍)
博客目的:写博客是为了记录自己的学习路径.也是为了让面试官眼前一亮,然后就是装逼.
小小推荐: 我在CSDN和朋友创建管理了一个社区大家如果感兴趣的话可以来看看👉非科班转码社区-CSDN社区云).👈
(舒文会在该社区进行答疑和打卡大家一起加入这个大家庭啊😍)
现阶段目标: 好好学习基础知识多多了解计算机行业情况.保持良好的身体素质,多多交朋友,多多犯错多多从错误中学习😍.
上一个我期文章介绍树和树的计算公式👉树的介绍&树的计算公式
🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀🏀
前言
此篇文章是使用完全二叉树实现小堆然后用小堆进行堆排序,堆排序的时间复杂度为logN空间复杂度为O(N);后续还会优化吧空间复杂度改一下
正文
先讲一下二叉树的储存结构吧
二叉树的储存结构
顺序结构
顺序结构存储就是使用数组来存储,一般使用数组只适合表示完全二叉树,因为不是完全二叉树会有空间的浪费。而现实中使用中只有堆才会使用数组来存储,关于堆我们在后面会专门讲解。二叉树顺序存储在物理上是一个数组,在逻辑上是一颗二叉树。
及顺序表按照从左到右从上到下的顺序依次储存.
顺序表存储的结构元素的双亲和孩子都是可以通过公式找到的
- father = (child-1)/2; //一定是先-1再除二
- child = father*2+1; //但是这个我们只能得到左孩子要想得到右孩子必须++
如果是非完全二叉树的格式就按照完全二叉树的将无元素的地区空出
链式结构
二叉树的链式存储结构是指,用链表来表示一棵二叉树,即用链来指示元素的逻辑关系。 通常的方法是链表中每个结点由三个域组成,数据域和左右指针域,左右指针分别用来给出该结点左孩子和右孩子所在的链结点的存储地址 。链式结构又分为二叉链和三叉链,当前我们学习中一般都是二叉链,后面课程学到高阶数据结构如红黑树等会用到三叉链。
链式结构就是按照普通的链表和指针进行连接.
typedef int BTDataType;
// 二叉链
struct BinaryTreeNode
{
struct BinTreeNode* _pLeft; // 指向当前节点左孩子
struct BinTreeNode* _pRight; // 指向当前节点右孩子
BTDataType _data; // 当前节点值域
};
// 三叉链
struct BinaryTreeNode
{
struct BinTreeNode* _pParent; // 指向当前节点的双亲
struct BinTreeNode* _pLeft; // 指向当前节点左孩子
struct BinTreeNode* _pRight; // 指向当前节点右孩子
BTDataType _data; // 当前节点值域
};
堆的概念
我先讲一下自己的理解:
堆分为小堆和大堆,我讲一下小堆大家一定也就可以理解大堆了🤪.
讲道理还是图文来讲比较合适
先文字描述: 小堆就是根节点为所有数据中最小的,其他子结构的根也为最小的值.
图:
堆是一棵完全二叉树(一定是完全二叉树)
正式理解
二叉树的顺序结构的实现&堆的实现
堆的实现其实就是用二叉树的顺序结构实现的所以我干脆放在一起讲🤪.
我的思路还是先看整体代码再讲解代码实现难点.
来看一眼我们的代码们吧
Heap.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int HPDataType;
//通过顺序表实现树的堆 这个为小堆
typedef struct Heap
{
HPDataType* data;
size_t size;
size_t capacity;
}Heap;
//创建堆
void HeapCreat(Heap* obj);
//摧毁堆
void HeapDestory(Heap* obj);
//插入数据
void HeapPush(Heap*obj,HPDataType x);
//数据打印
void HeapPrint(Heap* obj);
//删除数据
void HeapPop(Heap* obj);
//堆顶数据
HPDataType HeapTop(Heap* obj);
//堆的个数
size_t HeapSize(Heap* obj);
//堆的判空
bool HeapEmpty(Heap* obj);
Heap.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
void HeapCreat(Heap* obj)
{
assert(obj);
obj->data = (HPDataType*)malloc(sizeof(HPDataType)*4);
assert(obj->data);
obj->size = 0;
obj->capacity = 4;
}
void HeapDestory(Heap* obj)
{
assert(obj);
free(obj->data);
obj->size = obj->capacity = 0;
}
void AdJustUp(Heap* obj)
{
size_t child = obj->size-1;
size_t parent = (child - 1) / 2;
while (child)
{
if (obj->data[parent] > obj -> data[child])
{
HPDataType tmp = obj->data[child];
obj->data[child] = obj->data[parent];
obj->data[parent] = tmp;
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void HeapPush(Heap* obj, HPDataType x)
{
assert(obj);
if (obj->size == obj->capacity)
{
obj->capacity *= 2;
HPDataType* tmp = (HPDataType*)realloc(obj->data, obj->capacity*sizeof(HPDataType));
assert(tmp);
obj->data = tmp;
}
obj->data[obj->size] = x;
++obj->size;
//进行向下调整以保证堆的格式
AdJustUp(obj);
}
void HeapPrint(Heap* obj)
{
for (size_t i = 0; i < obj->size; i++)
{
printf("%d ", obj->data[i]);
}
}
void AdJustDown(Heap* obj)
{
size_t father = 0;
size_t child = father*2+1;
while (child < obj->size)
{
//选出最小的孩子
if (child+1<obj->size && obj->data[child] > obj->data[child + 1])
{
child++;
}
if (obj->data[child] < obj->data[father])
{
HPDataType tmp = obj->data[child];
obj->data[child] = obj->data[father];
obj->data[father] = tmp;
father = child;
child = father * 2 + 1;
}
else
{
break;
}
}
}
void HeapPop(Heap* obj)
{
assert(obj);
assert(obj->size > 0);
obj->data[0] = obj->data[obj->size - 1];
obj->size--;
AdJustDown(obj);
}
HPDataType HeapTop(Heap* obj)
{
assert(obj);
assert(obj->size);
return obj->data[0];
}
size_t HeapSize(Heap* obj)
{
assert(obj);
return obj->size;
}
bool HeapEmpty(Heap* obj)
{
assert(obj);
return obj->size == 0;
}
main.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"
//堆的测试
void HeapTest()
{
Heap hp = {0};
HeapCreat(&hp);
HeapPush(&hp, 10);
HeapPush(&hp, 18);
HeapPush(&hp, 19);
HeapPush(&hp, 25);
HeapPush(&hp, 28);
HeapPush(&hp, 43);
HeapPush(&hp, 65);
HeapPush(&hp, 49);
HeapPush(&hp, 27);
HeapPush(&hp, 37);
HeapPop(&hp);
HeapPrint(&hp);
HeapDestory(&hp);
}
//堆排序
void HeapSort(int* a, int size)
{
Heap hp;
HeapCreat(&hp);
for (int i = 0; i < size; ++i)
{
HeapPush(&hp, a[i]);
}
size_t j = 0;
while (!HeapEmpty(&hp))
{
a[j] = HeapTop(&hp);
j++;
HeapPop(&hp);
}
HeapDestory(&hp);
}
int main()
{
//TestHeap();
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]);
}
printf("\n");
return 0;
}
咱主要讲Heap.c所需要讲解的部分
主要讲解部分
下方是结构题的内容
typedef struct Heap
{
HPDataType* data;//用于储存数据
size_t size; //大小
size_t capacity;//容量
}Heap;
实话实说需要重点讲解的主要是两个函数.
其中
void AdJustUp(Heap* obj)
{
size_t child = obj->size-1;
size_t parent = (child - 1) / 2;
while (child)
{
if (obj->data[parent] > obj -> data[child])
{
HPDataType tmp = obj->data[child];
obj->data[child] = obj->data[parent];
obj->data[parent] = tmp;
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
这个函数是嵌套在HeapPush中的一个用于调整数据位置的函数,我们在HeapPush的时候是将数据直接放在我们顺序表的末尾处,然后通过AdJustUp函数对数据进行调整.将本来插入后可能不是堆情况调整成堆的格式.
我们通过图来了解这种情况: 比如我们的数值原来有着: 2 3 5 6 7 8
这时我们插入一个1来看看我们的AdJustUp函数是如何将顺序表的储存情况重新换成堆形势.
其实很简单我们通过while循环和if语句来判断末尾部的父亲是否大于我们要改变位置的节点如果大于就交换他们的位置如果小于就直接进行break语句跳出循环即可.
然后使我们的向下调整函数.
void AdJustDown(Heap* obj)
{
size_t father = 0;
size_t child = father*2+1;//得到的是左孩子
while (child < obj->size)
{
//选出最小的孩子
if (child+1<obj->size&&obj->data[child] > obj->data[child + 1])
{
child++;
}
if (obj->data[child] < obj->data[father])
{
HPDataType tmp = obj->data[child];
obj->data[child] = obj->data[father];
obj->data[father] = tmp;
father = child;
child = father * 2 + 1;
}
else
{
break;
}
}
}
void HeapPop(Heap* obj)
{
assert(obj);
assert(obj->size > 0);
obj->data[0] = obj->data[obj->size - 1];
obj->size--;
AdJustDown(obj);
}
我们的向下调整函数也是在HeapPop函数内作为内嵌(至于为啥要把向下调整专门分装成一个函数,我个人认为是为了代码的整洁性吧).
HeapPop 的功能是删除顺序表的定元素也就是下标为0的元素,但是我们如果先将下标为0的元素换成我们在尾的元素然后进行对size进行--
即可.
然后我们即可进行向下调整的操作.
老样子我们使用图文调整的形式给大家展示.
我们先比较左右还在的大小选一个最小的与父亲节点交换在只有一个孩子的情况至于左孩子比较即可👍.
结尾
这部分的代码实现其实也挺简单的,我们想用堆排序也只需要从得到头元素然后删头只要堆为空即可👍.