您的位置:首页 > 其它

排序总结系列七:堆排序

2016-10-06 10:23 274 查看
1.堆基础
堆:完全二叉树或者是近似完全二叉树 
大根堆:每个结点的值都大于或等于其左右孩子结点的值。(从前至后头序)
小根堆:每个结点的值都小于或等于其左右孩子结点的值。(从后至前头序)
堆排序要解决的问题:
【1】如何由一个无序序列构建成一个堆。=>堆的调整其实就是从下往上,从右到左的调整。
【2】如果在输出堆顶元素后,调整剩余元素成为一个新的堆??? 
2.堆的存储
一般都用数组来表示堆
i的父结点下标:(i – 1) / 2
i的左结点下标:2 * i + 1i的右结点下标: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 (
b23c
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),不稳定的排序方法
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐