堆排序
1.基础概念
-
二叉树的概念:
二叉树(binary tree)是指树中节点的 度 不大于2的有序树. 在二叉树中除了根节点的每一个节点都仅有一个父节点,所有的节点最多只能有两个
孩子节点。 -
满二叉树的概念:
除最后一层无任何子节点外,每一层上的所有结点都有两个子结点的二叉树。
国内教材定义:
一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
国外(国际)定义:
a binary tree T is full if each node is either a leaf or possesses exactly two childnodes.
-
完全二叉树:
对满二叉树的结点进行编号, 约定编号从根结点起, 自上而下, 自左而右。则深度为k的, 有n个结点的二叉树, 当且仅当其每一个结点都与深度为k的满二叉树中编号从1至n的结点一一对应时 。
-
堆的概念:
堆通常是一个可以被看做一棵完全二叉树。
- 大根堆, 就是说这个完全二叉树中每一棵子树的根节点都大于他的左右孩子(如果有的话)
- 小根堆,就是说这个完全二叉树中每一棵子树的根节点都小于他的左右孩子(如果有的话)
- 二叉树的存储方式
- 顺序存储
(1) 按照完全二叉树(无论是那种二叉树)的形式,将二叉树的数据全部存储在数组中.
(2) 顺序表中的每个元素除了存储data,leftchild , rightchild - 链式存储 每个节点都有至少三个域(data,* leftchild,* rightchild)
- 堆仅仅将数据存储在数组中
- 如果父节点的下标为i,则左孩子的下标为 2 * i + 1,右孩子的下标为 2 * i + 2
- 如果孩子的下标为j, 则父节点的下标是: ( j - 1 ) / 2
2.堆的建立过程
- 将数组中的数据调整成堆时,从最后一棵子树开始,向根节点一颗颗调整
每棵子树的调整都是从其根节点开始向下调整的 - 一棵子树的调整过程:
先用i指向子树的根节点,j为根节点的左孩子。将i位置的值保存到tmp中, 然后在左右孩子中找到较大的哪一个,j指向较大值,(1)如果较大的哪一个比临时变量中的值小,则直接退出;(2)如果较大的哪一个比临时变量中的值大,则将较大的值保存到i位置上,然后i=j,j=2*i+1,直到j越界则退出。退出以后,还需要将tmp的值保存到退出时的i位置上。
代码实现
void OneAdjust(int *arr, int len, int root)
{
int tmp = arr[root];
int i = root;
int j = 2 * i + 1;
while (j < len)
{
// j+1 < len 成立 则有右孩子
// arr[j] < arr[j+1] 左孩子的值小于右孩子
if (j + 1 < len && arr[j] < arr[j + 1]) j++;
if (arr[j] < tmp) break;
arr[i] = arr[j];
i = j;
j = 2 * i + 1;
}
arr[i] = tmp;
}
- 将数组调整成堆的过程
先找到最后一棵子树的根节点,向根节点递减,循环调用上面的调整方法。
代码实现
void CreateHeap(int *arr, int len)
{
int lastRoot = (len - 2) / 2; // lastRoot就是最后一棵子树的根节点下标
for (int i = lastRoot; i >= 0; --i) // 从lastRoot开始,循环到整颗数的根节点,调整
{
OneAdjust(arr, len, i);
}
}
3.堆排序
- 先将数组中的数据调整成一棵最大堆, 然后将根节点的数据和最后位置的节点交换, 接着除过最后一个位置的数据外,在将剩余的数据调整成
最大堆。 - 重复上述过程,直到只剩下一个数据没有交换。就完成了堆排序。
// 时间复杂度: O(nlog(n))
// 空间复杂度: O(1)
// 稳定性: 不稳定
void HeapSort(int *arr, int len)
{
CreateHeap(arr, len);
for (int i = 0; i < len - 1; ++i)
{
Swap(&arr[0], &arr[len - 1 - i]);
OneAdjust(arr, len - i - 1, 0);
}
}