概念:
父结点、子结点、叶子结点、非叶子结点
1、堆分为:(完全二叉树)
(1)大根堆:在整个堆中,左子树、右子树的数据都比父结点的数据小。
(2)小根堆:在整个堆中,左子树、右子树的数据都比父结点的数据大。
2、给出父结点n求子结点
左子树:2*n+1
右子树:2*(n+1)
3、给出子结点n求父结点
(n-1)/2
举例及思想、步骤:
举例:生序排列-调整成大根堆,过程如下: (一般升序采用大根堆,降序采用小根堆)
(1)将初始待排序关键字序列(R1,R2....Rn)构建成大顶堆,此堆为初始的无序区
(2)初始化大根堆:
从最后一个非叶子结点开始从下至上进行调整。
比较大小:从父节点、左孩子节点、右孩子节点三者中选择最大者 跟父节点进行交换(交换之后可能造成被交换的孩子节点不满足堆的性质,∴交换后哪一路分支变化了,就继续调整,这时是从顶点开始往下调整)。
有了初始堆之后就可以进行排序了。
(3)将堆顶元素R[1]与末尾元素R[n]交换,将最大元素"沉"到数组末端,交换后堆长度减1,此时得到新的无序区(R1,R2,......Rn-1)和新的有序区(Rn)
(4)由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,......Rn-1)调整为新堆,
然后再次将堆顶元素R[1]与当前无序区最后一个元素交换,得到新的无序区(R1,R2....Rn-2)和新的有序区(Rn-1,Rn)。
不断重复此“调整+交换”过程直到有序区的元素个数为n-1,则整个排序过程完成
代码实现:
void headAdjust(int arr[], int fNode, int pos)
// 数据 开始的父结点 最多改的下标值(边界控制)
{
for (int maxNode = 2 * fNode + 1; maxNode <= pos; maxNode = 2 * maxNode + 1)
//左右结点中较大的结点 左子结点 下一个左子结点
{
if (maxNode < pos && arr[maxNode] < arr[maxNode + 1])
//判断右子结点 左<右子结点的值
{
maxNode++;
//较大,结点改为右子结点
}
//左、右子结点中较大的<父结点的值,不交换值
if (arr[maxNode] <= arr[fNode])break;
//存在左、右子结点中较大的>父结点的值, 交换maxNode
int tmp = arr[maxNode];
arr[maxNode] = arr[fNode];
arr[fNode] = tmp;
fNode = maxNode;//赋新的父结点
}
}
void heapSort(int arr[], int len)
{
int index = len - 1;//最后的结点编号
for (int i = (index - 1) / 2; i >= 0; --i)
//i 最后一个父结点,从非叶子结点开始,从下往上调整
{
headAdjust(arr, i, index);
/*index
精确:调整到最后一个叶子结点
粗:index/len-1∵最后一个叶子结点就算有子结点,值也>len-1
*/
}
for (int j = index; j >= 0; --j)
{
//待排序列与arr[0]交换
int tmp = arr[0];
arr[0] = arr[j];
arr[j] = tmp;
headAdjust(arr, 0, j - 1);
//len-1-i-1
}
}
void heapShow(int arr[], int len)
{
for (int i = 0; i < len; ++i)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = {26,1,56,6,5,69,2,37,88,42};
int len = sizeof(arr) / sizeof(arr[0]);
heapShow(arr, len);
heapSort(arr, len);
heapShow(arr, len);
return 0;
}
总结:
时间复杂度:平均O(nlog2n),最好O(nlog2n),最坏O(nlog2n)
空间复杂度:O(1)
不稳定(当有跳跃的交换时,∵ 父子交换数据)