1.堆基础


堆:完全二叉树 或者是近似完全二叉树  


大根堆:每个结点的值都大于或等于其左右孩子结点的值。(从前至后头序)


小根堆:每个结点的值都小于或等于其左右孩子结点的值。(从后至前头序)



堆排序要解决的问题:


【1】如何由一个无序序列构建成一个堆。=>堆的调整其实就是从下往上,从右到左的调整。


【2】如果在输出堆顶元素后,调整剩余元素成为一个新的堆??? 


2.堆的存储


一般都用数组来表示堆


i的父结点下标: (i – 1) / 2


i的左结点下标: 2 * i + 1 i的右结点下标: 2 * i + 2


如第0个结点左右子结点下标分别为1和2。





2.da根堆排序思路



逻辑思路: 首先可以看到堆建好之后堆中第0个数据是堆中最大的数据。取出这个数据再执行下堆的删除操作。这样堆中第0个数据又是堆中最大的数据,重复上述步骤直至堆中只有一个数据时就直接取出这个数据。


void buildHeap(int *arr, int len)  //建立大根堆堆 
 
{
 
for (int i = len / 2; i >= 0; --i)
 
{
 
heapAdjustUp(arr, i, len); //大根堆调整
 
}
 
}


物理操作: 由于堆也是用数组模拟的,故堆化数组后,第一次将A[0]与A[n - 1]交换,再对A[0…n-2]重新恢复堆。第二次将A[0]与A[n – 2]交换,再对A[0…n - 3]重新恢复堆,重复这样的操作直到A[0]与A[1]交换。由于每次都是将最大的数据并入到后面的有序区间,故操作完成后整个数组就有序了。有点类似于 直接选择排序


void HeapSortMax(int *arr, int len)  //大根堆排序
 
{
 
if (arr == NULL || len<1) return; //检错
 
buildHeap(arr, len); //建立大顶堆(根结点元素已经是最大元素)
 
for (int i=len-1; i>0; --i) //进行排序
 
{
 
swap( arr[0],arr[i]); //第一元素和最后一个元素进行交换(将最大元素后置)
 
heapAdjustUp(arr, 0, i-1); //将剩下的无序元素继续调整为大顶堆
 
}
 
}


大 根堆调整思路: 堆中每次都只能删除第0个数据。为了便于重建堆,实际的操作是将最后一个数据的值赋给根结点,然后再从根结点开始进行一次从上向下的调整。调整时先在左右儿子结点中找最大的,如果父结点比这个最大的子结点还大说明不需要调整了,反之将父结点和它交换后再考虑后面的结点。相当于从根结点将一个数据的“下沉”过程。



void heapAdjustUp(int *ar, int start, int end) //  大根堆调整 
 既把每棵树的大根建好
 
{
 
int temp = ar[start];
 
for (int i = 2*start+1; i <= end; i = i*2+1)
 
{
 
if ( i+1<=end && ar[i] < ar[i+1]) i += 1; //在左右孩子中找最大的
 
if (temp < ar[i]) //左右孩子的最大值同其父比较
 
{
 
ar[start] = ar[i]; //将大值上移
 
start = i; //参考根start顺势下移
 
}
 
else break; //左右孩子的最大值小于根,大根堆已调整好
 
}
 
ar[start] = temp; //将参考根填入适当位置
 
}
 
void Swap(int &a, int &b)
 
{
 
int tmp = a;
 
a = b;
 
b = tmp;
 
}
 
/


大根堆排序完整代码:


void HeapSortMax(int *arr, int len)  //大根堆排序
 
{
 
if (arr == NULL || len<1) return; //检错
 
buildHeap(arr, len); //建立大顶堆(根结点元素已经是最大元素)
 
for (int i=len-1; i>0; --i) //进行排序
 
{
 
swap( arr[0],arr[i]); //第一元素和最后一个元素进行交换(将最大元素后置)
 
heapAdjustUp(arr, 0, i-1); //将剩下的无序元素继续调整为大顶堆
 
}
 
}
 
void buildHeap(int *arr, int len)  //建立大根堆堆 
 
{
 
for (int i = len / 2; i >= 0; --i)
 
{
 
heapAdjustUp(arr, i, len); //大根堆调整
 
}
 
}
 
void heapAdjustUp(int *ar, int start, int end) //  大根堆调整 
 既把每棵树的大根建好
 
{
 
int temp = ar[start];
 
for (int i = 2*start+1; i <= end; i = i*2+1)
 
{
 
if ( i+1<=end && ar[i] < ar[i+1]) i += 1; //在左右孩子中找最大的
 
if (temp < ar[i]) //左右孩子的最大值同其父比较
 
{
 
ar[start] = ar[i]; //将大值上移
 
start = i; //参考根start顺势下移
 
}
 
else break; //左右孩子的最大值小于根,大根堆已调整好
 
}
 
ar[start] = temp; //将参考根填入适当位置
 
}
 
/
 
/  /大根堆排序取巧的一个
 
void MaxHeapFixUp(int *a, int n)
 
{
 
for (int i = n-1; i>0; i--)
 
{
 
int j = (i-1) / 2;
 
if (a[i] > a[j]) Swap(a[i], a[j]);//父和左比较大的上移,左和右比较,大的换左
 
}
 
}
 
void HeapSort(int *a, int n)
 
{
 
for (int i = n; i > 0; i--)
 
{
 
MaxHeapFixUp(a, i); //先堆调整等于把建堆的一次包含了进去
 
Swap(a[0], a[i-1]);
 
}
 
}
 
/


//小根堆排序完整代码:


void min_heap(int *a, int i, int size) //  小根堆调整
 
{
 
if (size <= 1) return; //只有一个元素时终止
 
int left = 2 * i + 1; //置好左右孩子
 
int right = 2 * i + 2;
 

 
int min = -1; //挑出父、左、右最小的上移
 
if ( left < size && a[left] < a[i]) min = left;//挑父和左子最小的上移
 
else min = i;
 
if (right < size && a[right] < a[min]) min = right;//右子和父与左子最小求最小
 
if (min != i) //若已是最小跳过
 
{
 
Swap(a[i], a[min]); //否则交换
 
min_heap(a, min, size); //调整点下移继续调整
 
}
 
}
 
void buildHeapMin(int *arr, int len)  //建立小根堆 
 
{
 
for (int i = len/2; i >= 0; --i)
 
{
 
min_heap(arr, i, len); //小根堆调整
 
}
 
}
 
void HeapSortMin(int *arr, int len)  //小根堆排序
 
{
 
if (arr == NULL || len<1) return; //检错
 
buildHeapMin(arr, len); //建立小顶堆(根结点元素已经是最小元素)
 
for (int i = len - 1; i>0; --i) //进行排序
 
{
 
if (arr[0] < arr[i]) //注意和大根堆调整多了判断,防止已调整好的再次调整
 
Swap(arr[0], arr[i]); //第一元素和最后一个元素进行交换(将最小元素后置)
 
min_heap(arr, 0, i - 1); //将剩下的无序元素继续调整为小顶堆
 
}
 
}


带左右孩子判断的小根堆排序


void adjustMinHeap(int *a, int len, int pa) // 小根堆调整


{
 
if (len <= 1) return; //若只有一个元素,那么只能是堆顶元素,也没有必要再排序了
 
  
 
int leftIndex = 2 * pa + 1;//获取左孩子的索引
 
int rightIndex = 2 * pa + 2;//获取右孩子的索引
 
  
 
int index = -1; //记录比父节点小的左孩子或者右孩子的索引
 
if (leftIndex >= len) return; //没有左孩子
 
if (rightIndex >= len) index = leftIndex; //有左孩子,但是没有右孩子
 
else //有左孩子和右孩子
 
{ // 取左、右孩子两者中较小的一个
 
index = a[leftIndex] < a[rightIndex] ? leftIndex : rightIndex;
 
}
 
if (a[index] < a[pa]) //只有孩子比父节点的值还要小,才需要交换
 
{
 
Swap(a[index], a[pa]);
 
//交换完成后,有可能会导致a[targetIndex]结点所形成的子树不满足堆的条件
 
adjustMinHeap(a, len, index);//若不满足堆的条件,则调整之使之也成为堆
 
}
 
}
 
void initHeapMin(int a[], int len) // 初始化堆(  建立小根堆 
 )
 
{
 
for (int i = (len - 1) / 2; i >= 0; --i)// 从完全二叉树最后一个非子节点(n - 1) / 2开始
 
{
 
adjustMinHeap(a, len, i); //调整堆求得最小值放入a[0]
 
}
 
}
 
void heapSortMIn(int a[], int len)  //小根堆排序
 
{
 
if (len <= 1) return; //排错
 
initHeapMin(a, len); // 初始堆成无序最小堆
 
for (int i = len - 1; i > 0; --i)
 
{
 
if (a[0] < a[i]) Swap(a[0], a[i]); //防止有序的再去交换
 
adjustMinHeap(a, i-1,0); // 其中,0是堆顶
 
}
 
}
 
//  //小根堆排序迭代
 
void heapSortMIn(int arr[], int len)  //小根堆排序
 
{
 
if (len <= 1) return; //排错
 
buildHeap(arr, len); //初始堆成无序最小堆
 
for (int i = len-1; i>= 0; --i)
 
{
 
if (arr[0] < arr[i]) //注意和大根堆调整多了判断,防止已调整好的再次调整
 
Swap(arr[0], arr[i]);//第一元素和最后一个元素进行交换(将最小元素后置)
 
heapDown(arr, i-1, 0); // //将剩下的无序元素继续调整为小顶堆
 
}
 
}
 
  
 
void buildHeap(int a[], int n)  /*构造初始堆*/
 
{
 
for (int j = (n-1)/2; j >= 0; j--) /*从第一个有子节点的节点逐级向上调整*/
 
{
 
heapDown(a, n, j);
 
}
 
}
 
void heapDown(int a[], int n, int j) //  小根堆调整
 
{
 
while (1)
 
{
 
int left = 2 * j + 1; //左子
 
int right = 2 * j + 2; //右子
 

 
int min = j; //当前元素初始为最小
 
int index = 0; //记录左右子最小的下标
 
  
 
if (left >= n && right >= n) break; //左右儿子都不存在,调整完成
 
if (right >= n) index = left; //右子不存在左子存在
 
else //左右都在,找其最小
 
{
 
index = a[left] > a[right] ? right : left; //最小为两子的最小
 
}
 
if (a[min] > a[index]) //只有孩子比父节点的值还要小,才需要交换
 
{
 
Swap(a[index], a[min]);
 
j = index; //记录调整点
 
continue; //从调整后的新元素开始继续向下调整
 
}
 
break; //否则不需要调整,调整完成
 
}
 
}
 
///


每次调整时间复杂度O(logN),共N - 1次重新恢复堆操作,所以


时间复杂度: O(N * logN) 堆排序是就地排序,辅助空间为 O(1) , 不稳定 的排序方法