堆的概念及结构
堆一般是一颗完全二叉树。堆又根据不同的特性又分为小堆和大堆。
小堆特性:父亲<=孩子
大堆特性:父亲>=孩子
堆不一定有序,但有序一定是堆。
注意:堆有两个地方都有提到,分别在数据结构和内存中。在数据结构中的堆一般是一颗二叉树,内存中的堆是指存放malloc等动态开辟内存的空间的区域。
堆的实现
堆的定义
堆的初始化和销毁就和顺序表一样实现
堆的插入与顺序表的插入不同,我们把数据尾插进堆后,要对堆进行向上调整
向上调整我们要通过完全二叉树的特性:父亲下标=(孩子下标-1)/2来向上判断,假设我们要建小堆,那我们只需要判断a[parent]是否大于a[child],如果大于,则需要把a[parent]与a[child]进行互换,在把parent和child向上找。如果某次判断的a[parent]小于a[child]
就不需要再次替换了,退出循环即可。
循环的判断为child>0,如果我们替换到最后换到对顶了,就说明我们插入的数是最小值,child就会指向0(首元素)。
接着就是删除元素,由于堆的特性,删除堆尾没有意义,只有删除堆顶才有实际意义,而且顺序表的尾删比头删更容易实现。我们对堆的元素进行获取时,要先获取堆顶元素再删除,删除时我们要先把堆顶元素与堆低元素互换,再进行尾删。
尾删之后,我们新的堆除了堆顶,堆顶的左子树和右子树都是还是小堆,我们在对堆顶元素进行向下调整就能让全部元素为堆。
向下调整就是利用完全二叉树的另一个特性:孩子下标=父亲下标*2+1或者+2;找到两个孩子,比较两个孩子,小的孩子与父亲交换。
我们先假设左边的孩子是小的,再在循环内先做判断,如果不是,则child+1换成右孩子。注意:我们在比较两个孩子的时候,为了防止越界访问,child+1要小于size。
接着在while内判断,如果父亲大于小的孩子,就把孩子与父亲互换,再继续把下标往下调,如果某次孩子与较小的孩子比较时,父亲更小,那就说明不用调整了。
循环条件的判断就是如果我们选中的孩子,大于size了,就说明我们超出了原数组的范围,就不需要继续调整了。
最后的来获取堆顶元素和堆的判空还有堆的个数就容易实现了。