上一期我们学习了树和二叉树的定义,其中我们了解到了两种特殊的二叉树:满二叉树和完全二叉树。
今天我们还要学习一种新的结构:堆
那这种结构和二叉树有什么联系呢???
通过观察我们可以发现,完全二叉树和满二叉树可以看作是一个连续的结构,所以我们可以使用顺序存储,但是这种结构还不算是堆结构,那么堆又和这种结构有什么联系呢???
下面是两种堆的结构:一种是大堆,一种是小堆
那么什么是大堆和小堆呢?
顾名思义,小堆就是根节点都比孩子结点小的
大堆就是根节点都比孩子结点大的
下面就是两种堆的结构:
那么我们学习堆有什么意义呢?下面是堆的几种运用场景:
后面我们会有所了解。
这里我们先来了解一下堆排序
通过对比我们可以发现,堆排序相比冒泡排序的时间复杂度是远远要好的
下面我们就正式进入堆的学习中。
我们先来看堆的定义:
因为堆是顺序结构,所以我们定义的方式和顺序表是一致的。
在实现堆中我们需要使用两种算法,一种是向上调整算法,一种是向下调整算法。
(假设在小堆中)在向上调整算法中,我们插入50,我们需要将它的值与根节点的值进行比较,
如果比根节点小我们就进行交换。直到我们的孩子结点走到最顶端,也就是下标为0的位置。
那么我们要怎么寻找每次的孩子结点和父结点的下标呢?
通过观察我们可以发现孩子结点和自己的父结点有以下规律:
所以每一次结束比较后我们都要将父结点赋值给孩子节点,并让父结点走向当前孩子结点的父结点。所以下面是代码的实现
所以在堆中我们每次插入数据都需要调整值的顺序。但是,我们思考后会发现,我们的向上和向下调整算法都只能调整父结点和孩子结点的关系,而不能改变兄弟结点的关系
所以我们的堆插入数据的函数如下
下面我们来写“删除堆顶元素”函数
我们将堆顶元素认为是下标为0所在位置的元素,有一些人就想当然将后面的数据覆盖上去,但是,这一种思路是不对的,我们仔细思考一下,我们这样子做,我们的结点间的父子关系就全都不对了,也就是我们的堆就不是堆了,顺序已经被打乱了
那么我们应该怎么做呢???
这里我们的另一种向调下整算法就起到了重要的作用。
删除时,首先我们将首元素和末尾元素的值交换,之后我们只要将size--就可以删除尾部元素,也就是原来的堆顶的元素,之后我们再将堆顶的元素向下调整。
那么向下调整的算法是怎样书写的呢???
向下调整中,我们定义父结点,之后通过上面的父结点和子节点的关系,我们可以找到左子节点,
左子节点和右子节点下标相差1,我们通过比较左子节点和右子节点的值,我们选出最小的,若此时的父结点的值大于最小的子节点,我们就进行交换,之后一直走循环,直到父结点走到动态数组的最后面
下面是向下调整算法的书写
下面是删除堆顶元素的函数:
其中HPEmpty函数是判断堆是否为空的函数
到这里我们就可以解决topk问题,比如我们要取出一千万数的前十名,我们就可以返回堆顶元素,之后pop9次就可以了。
这里我们要了解topk问题的具有现实的意义,比如我们在点外卖时,我们要选取销量前十的我们就可以使用topk解决问题
下面是全部代码
void HPInit(HP* php)//初始化堆堆空间
{php->size = 0;php->a = NULL;php->capcity = 0;
}
void swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}//交换函数
void AdjustUp(int* a, int Child)
{int child = Child;int parent = (child - 1) / 2;while(child > 0)//直到孩子结点走到下标为0的位置{if (a[child] < a[parent]){swap((a) + child, (a) + parent);//发现子节点的值比父节点小就进行交换child = parent;//将父结点赋值给孩子结点parent = (child - 1) / 2;//走向下一个父结点}else{break;//若孩子结点大就退出循环,即不需要交换}}return;
}
void HPPush(HP* php, HPDatatype x)//插入数据
{if(php->size == php->capcity){int newcapcity = php->capcity == 0 ? 4 :php->capcity * 2;//看空间是否足够HPDatatype* a = (HPDatatype*)realloc(php->a, sizeof(HPDatatype) * newcapcity);if (a == NULL){perror("malloc_fail");return;}php->a = a;php->capcity = newcapcity;}php->a[php->size] = x;php->size++;//开辟新的空间并存放数据AdjustUp(php->a,php->size - 1);//通过向上调整顺序形成小堆
}
bool HPEmpty(HP* php)
{return php->size == 0;
}
void AdjustDown(int* a,int n ,int parent)
{assert(a);int child = parent * 2 + 1;while(child < n){if (child + 1 < n && a[child] > a[child + 1])//选出较小的子节点{child += 1;}if (a[parent] > a[child])//父节点都比两个子节点小就返回{swap(a + parent, a + child);//与较小的子节点交换位置//往下再寻找parent = child;child = parent * 2 + 1;}else{break;//若父结点的值小于最小子节点的值我们就退出循环,即这个堆还是小堆}}
}
void HPPop(HP* php)//删除堆顶元素
{assert(php);assert(!HPEmpty(php));swap(php->a, php->a + php->size - 1);//交换顶部元素和底部元素php->size--;AdjustDown(php->a,php->size,0);//向下调整
}
int HPTop(HP* php)//返回堆顶的元素
{return php->a[0];
}
void HPDestroy(HP* php)//销毁堆空间
{assert(php);free(php->a);php->a = NULL;
}
以上就是本次的全部内容,谢谢大家观看!!!