堆排序引入了另外一种算法设计技巧:使用一种称之为堆的数据结构来进行信息管理。(因为形式类似二叉树所以被称为二叉堆)二叉堆是一个数组,它可以被看作一个近似的完全二叉树,树上的每个节点对应一个数组元素。标识堆的数组有两个属性:lenngth(数组元素个数),heap-size(表示有多少个堆元素存储在数组中)。也就是说数组length都可能存有数据,但是只有[0-heap-size]存储的是有效数据。这里,[0<=heap-size<=length]。树的根节点为A[i],得出它的父节点坐标为:(i/2)。左孩子为:(2*i)。右孩子为:(2*i + 1)。
关于堆的几个性质。
1>二叉堆可以分为两种形式,最大堆和最小堆。在最大堆中,除了根节点外,其他节点需要满足:A[PARENT(i)] >= A[i]。PARENT(i)表示父节点坐标。同理最小堆满足:A[PARENT[i]] <= A[i]。
2>两种堆使用场景不同,最小堆通常用于构造优先队列。使用的时候需要根据实际情况选择。
堆的几个基本操作:
.MAX-HEAPIFY:时间复杂度O(lgn),用于维护堆的基本性质,即最大以及最小。
.BUILD-MAX-HEAP:功能是从无序输入数据数组中构建一个最大堆。
.HEAPSORT过程:功能是对一个数组进行原址排序。
.MAX-HEAP-INSERT/HEAP-EXTERACT-MAX/HEAP-INCREASE-KEY:利用堆构建一个优先队列。
(一).MAX-HEAP操作
MAX-HEAP输入为一个需要排序的数组以及一个数组下标(index)。在进行MAX-HEAP的时候前提是假设只有index及其只节点可能存在比index节点大的情况。这个时候我们让A[index]的值逐步下降的方式,重塑数组使得满足最大堆的性质。
算法描述:
int is_leaf(size_t nHeapSize, int nIndex)
{
return (nIndex >= (LEAF_START(nHeapSize)) ? 1 : 0);
}
void max_heapify(int* pnArray, size_t nHeapSize, int nIndex)
{
int nLindex = LEFT_INDEX(nIndex);
int nRindex = RIGHT_INDEX(nIndex);
int nLargestindex = nIndex;
if((nLindex <= nHeapSize) && (pnArray[nLindex] > pnArray[nIndex]))
{
nLargestindex = nLindex;
}
else
{
nLargestindex = nIndex;
}
if((nRindex <= nHeapSize) && (pnArray[nRindex] > pnArray[nLargestindex]))
{
nLargestindex = nRindex;
}
if(nLargestindex != nIndex)
{
_exchange_val(pnArray, nLargestindex, nIndex);
max_heapify(pnArray, nHeapSize, nLargestindex);
}
return;
}交换数据:
#define PARENT_INDEX(idx) (idx / 2)
#define LEFT_INDEX(idx) (2 * (idx + 1) - 1)
#define RIGHT_INDEX(idx) (2 * (idx + 1))
#define LEAF_START(heap_size) (heap_size / 2)
_inline void max_heapify_exchange(int* pnArray, int nIndex1, int nIndex2)
{
int nTempVal = pnArray[nIndex1];
pnArray[nIndex1] = pnArray[nIndex2];
pnArray[nIndex2] = nTempVal;
return;
}
(二).BUILD-MAX-HEAP操作有个上面一个MAX-HEAP维护堆性质的操作,建堆就简单多了。我们知道二叉数组长度为length,那么叶子节点的坐标范围为[length/2 + 1.....n]。那么非叶节点的坐标为[1.....length/2]。我们只需要维护每个非叶子节点保持堆的性质就可以完成BUILD-MAX-HEAP操作。
算法描述:
void build_max_heap(int* pnArray, size_t nHeapSize)
{
int nIndex = 0;
if(pnArray == NULL || nHeapSize == 0)
{
return;
}
for(nIndex = (nHeapSize / 2) - 1; nIndex >= 0 ; nIndex--)
{
max_heapify(pnArray, nHeapSize, nIndex);
}
return;
}
现在写一个简单的测试程序验证下前面的思路。
#define LEAF_START(heap_size) (heap_size / 2)
int main(int argc, char** argv)
{
int anTest[] = {4, 1, 3, 2, 16, 9, 10, 14, 8, 7};
int nIndex = 0;
build_max_heap( anTest, sizeof(anTest) / sizeof(anTest[0]));
for(nIndex = 0; nIndex < LEAF_START(sizeof(anTest) / sizeof(anTest[0])); nIndex++)
{
assert(anTest[nIndex] >= anTest[LEFT_INDEX(nIndex + 1) - 1] && anTest[nIndex] >= anTest[RIGHT_INDEX(nIndex + 1) - 1]);
}
return (0);
}三).MAX-SORT操作前面我们实现了建堆操作,把数组建立成最大堆,这仅仅是满足了最大堆的性质,下面我们要做的是对其排序,这才是我们的目的。排序的原理大致为:经过前面的一些操作步骤(BUILD-MAX-HEAP),根节点存放着最大元素,这个数也就是排序结果的最大数,我们把它移出二叉堆,用最后一个节点填充到根的位置,此时二叉堆可能不满足最大堆的性质,这个时候我们需要max_heapify操作,把最大数重新提取到根节点的位置,重复操作便完成了整个操作过程。
算法描述:
void heap_sort(int* pnArray, size_t nLength)
{
int nIndex = 0;
if(pnArray == NULL || nLength == 0)
{
return (0);
}
build_max_heap(pnArray, nLength);
for(nIndex = nLength - 1; nIndex >=1; nIndex--)
{
_exchange_val(pnArray, 0, nIndex);
max_heapify(pnArray, nIndex -1, 0);
}
}最后写一个测试程序:
int main(int argc, char** argv)
{
int anTest[] = {4, 1, 3, 2, 16, 9, 10, 14, 8, 7};
int nIndex = 0;
build_max_heap(anTest, sizeof(anTest) / sizeof(anTest[0]));
heap_sort( anTest, sizeof(anTest) / sizeof(anTest[0]));
for(nIndex = 1; nIndex < (sizeof(anTest) / sizeof(anTest[0])) ; nIndex++)
{
assert(anTest[nIndex - 1] <= anTest[nIndex]);
printf("%d ", anTest[nIndex - 1]);
}
printf("\n");
return (0);
}测试结果:
再把前面提到的测试模块跑一遍,基本可以排除基本的错误了,注意,堆排序算法还没有进行抽象封装,实际应用还需要进一步完善它。
——————The End。
31万+

被折叠的 条评论
为什么被折叠?



