排序算法之堆排序

    堆排序引入了另外一种算法设计技巧:使用一种称之为堆的数据结构来进行信息管理。(因为形式类似二叉树所以被称为二叉堆)二叉堆是一个数组,它可以被看作一个近似的完全二叉树,树上的每个节点对应一个数组元素。标识堆的数组有两个属性: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);
}

测试结果:

排序算法之堆排序_第1张图片
   再把前面提到的测试模块跑一遍,基本可以排除基本的错误了,注意,堆排序算法还没有进行抽象封装,实际应用还需要进一步完善它。
——————The End。

你可能感兴趣的:(排序,算法,C语言,堆排序)