堆的插入、刪除和建立操作,堆排序


1.       

堆:n個元素序列{k1,k2,...,ki,...,kn},當且僅當滿足下列關系時稱之為堆:

(ki <= k2i,ki <= k2i+1)

或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4,...,n/2)

若將和此次序列對應的一維數組(即以一維數組作此序列的存儲結構)看成是一個完全二叉樹,則堆的含義表明,完全二叉樹中所有非終端結點的值均不大於(或不小於)其左、右孩子結點的值。由此,若序列{k1,k2,…,kn}是堆,則堆頂元素(或完全二叉樹的根)必為序列中n個元素的最小值(或最大值)。

一般用數組來表示堆,i結點的父結點下標就為(i–1)/2。它的左右子結點下標分別為2*i+12*i+2。如第0個結點的左右子結點下標分別為12

2.        堆的插入

每次插入都是將先將新數據放在數組最后,由於從這個新數據的父結點到根結點必然為一個有序的序列,現在的任務是將這個新數據插入到這個有序序列中——這就類似於直接插入排序中將一個數據並入到有序區間中。

代碼:

/*
 * 堆插入算法。(小頂堆)
 * 先將num插入堆尾,易知從新數據的父結點到根結點是一個有序的序列,
 * 將num插入到該有序序列當中,該過程為直接插入排序。
 * 未插入前數據長度為n。
 */
int HeapInsert(int *heap, int n, int num)
{
	int i, j;

	heap[n] = num;//num插入堆尾
	i = n;
	j = (n - 1) / 2;//j指向i的父結點
	
	//注意不要漏掉i!=0的條件。因為必須保證i有父結點j。j>=0並不能保證i!=0。
	//如果沒有此條件,當i=0時,j=0,若heap[0]>num,程序就會陷入死循環。
	while (j >= 0 && i != 0)
	{
		if (heap[j] <= num)
			break;
		heap[i] = heap[j];
		i = j;
		j = (i - 1) / 2;
	}
	heap[i] = num;

	return 0;
}

3.        堆的刪除

堆中每次都只能刪除堆頂元素。為了便於重建堆,實際的操作是將最后一個數據的值賦給根結點,然后再從根結點開始進行一次從上向下的調整。調整時先在左右子結點中找最小的,如果父結點比這個最小的子結點還小說明不需要調整了,反之將父結點和它交換后再考慮后面的結點。相當於根結點數據的下沉過程。

代碼:

/*
 * 堆刪除算法。(刪除堆頂元素)
 * n表示未刪除前堆中數據的總數。
 */
int HeapDelete(int *heap, int n)
{
	//使用堆尾元素直接覆蓋堆頂元素。
	heap[0] = heap[n - 1];
	//從堆頂到堆尾(此時堆中只有n-1個元素)進行堆調整。
	HeapAdjust(heap, 0, n - 1);
	return 0;
}

/*
 * 堆調整算法。(小頂堆)
 * 已知heap[top]結點的左右子樹均為堆,調整堆中元素,使以heap[top]為根結點的樹為堆。
 * n為堆中元素總數。
 */
int HeapAdjust(int *heap, int top, int n)
{
	int j = 2 * top + 1;	//左孩子結點
	int temp = heap[top];

	while (j < n)
	{
		if (j + 1 < n&&heap[j + 1] < heap[j])
			j++;	//使j指向左右孩子中較小的結點。
		if (heap[j] >= temp)
			break;
		heap[top] = heap[j];
		top = j;
		j = 2 * top + 1;
	}
	heap[top] = temp;
	return 0;
}

4.        堆的建立

從無序序列建堆的過程就是一個反復調整的過程。若將此序列看成是一個完全二叉樹,則最后一個非終端結點是第(n-2)/2個結點,由此調整過程只需從該結點開始,直到堆頂元素。

代碼:

/*
 * 建堆算法。
 * 將無序數組array[]轉換為堆。
 */
int CreatHeap(int *array, int n)
{
	int i;
	//最后一個結點的編號為n-1,該結點的父節點(n-2)/2為最后一個非終端結點。
	//從結點(n-2)/2到根結點,依次進行堆調整。
	for (i = (n - 2) / 2; i >= 0; i--)
	{
		HeapAdjust(array, i, n);
	}
	return 0;
}

5.        堆排序

若在輸出堆頂的最小值之后,使得剩余n-1個元素的序列重建一個堆,則得到n個元素中的次小值。如此反復執行,便能得到一個有序序列,這個過程稱之為堆排序。

輸出堆頂元素之后,以堆中最后一個元素替代之,此時根結點的左右子樹均為堆,則僅需進行一次從上到下的調整即可重建一個堆。

代碼:

/*
 * 堆排序算法。
 * 形參heap為大頂堆時,實現的是由小到大;
 * 形參heap為小頂堆時,實現的是由大到小;
 */
int HeapSort(int *heap, int n)
{
	int i;
	int temp;

	for (i = n - 1; i > 0; i--)
	{
		//將堆頂元素和未排序的最后一個元素交換。
		temp = heap[0];
		heap[0] = heap[i];
		heap[i] = temp;
		//交換之后進行堆調整
		HeapAdjust(heap, 0, i);
	}
	return 0;
}

6. 測試代碼

/* 
 * 堆的建立、插入、刪除和堆排序算法
 */
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#define TOTAL 20

int HeapInsert(int *heap, int n, int num);
int HeapDelete(int *heap, int n);
int HeapAdjust(int *heap, int top, int n);
int HeapSort(int *heap, int n);
int CreatHeap(int *array, int n);

int main()
{
	int heap[TOTAL];
	int num;
	int i;

	//先輸入一半的數據,對輸入的數組建堆。
	printf("輸入Total/2個數據:\n");
	for (i = 0; i < TOTAL / 2; i++)
		scanf("%d", &heap[i]);

	CreatHeap(heap, TOTAL / 2);

	//檢驗是否建堆成功。
	printf("建堆后:\n");
	for (i = 0; i < TOTAL / 2; i++)
		printf("%-3d", heap[i]);
	putchar('\n');

	//向已建好的堆中插入數據,並重組為堆。
	printf("繼續輸入Total/4個數據:\n");
	for (i = TOTAL / 2; i < TOTAL / 2 + TOTAL / 4; i++)
	{
		scanf("%d", &num);
		HeapInsert(heap, i, num);
	}

	//檢驗是否插入成功。
	printf("重組為堆之后:\n");
	for (i = 0; i < TOTAL / 2 + TOTAL / 4; i++)
		printf("%-3d", heap[i]);
	putchar('\n');

	//刪除堆頂元素Total/4次。
	printf("刪除Total/4個數據:\n");
	for (i = 0; i < TOTAL / 4; i++)
		HeapDelete(heap, TOTAL / 2 + TOTAL / 4 - i);

	//檢驗是否刪除成功。
	for (i = 0; i < TOTAL / 2; i++)
		printf("%-3d", heap[i]);
	putchar('\n');

	//向堆中插滿數據,進行堆排序。
	printf("繼續輸入Total/2個數據:\n");
	for (i = TOTAL / 2; i < TOTAL; i++)
	{
		scanf("%d", &num);
		HeapInsert(heap, i, num);
	}

	HeapSort(heap, TOTAL);
	printf("排序后:\n");
	for (i = 0; i < TOTAL; i++)
		printf("%-3d ", heap[i]);
	putchar('\n');
	return 0;
}

/*
 * 堆插入算法。(小頂堆)
 * 先將num插入堆尾,易知從新數據的父結點到根結點是一個有序的序列,
 * 將num插入到該有序序列當中,該過程為直接插入排序。
 * 未插入前數據長度為n。
 */
int HeapInsert(int *heap, int n, int num)
{
	int i, j;

	heap[n] = num;//num插入堆尾
	i = n;
	j = (n - 1) / 2;//j指向i的父結點
	
	//注意不要漏掉i!=0的條件。因為必須保證i有父結點j。j>=0並不能保證i!=0。
	//如果沒有此條件,當i=0時,j=0,若heap[0]>num,程序就會陷入死循環。
	while (j >= 0 && i != 0)
	{
		if (heap[j] <= num)
			break;
		heap[i] = heap[j];
		i = j;
		j = (i - 1) / 2;
	}
	heap[i] = num;

	return 0;
}

/*
 * 堆刪除算法。(刪除堆頂元素)
 * n表示未刪除前堆中數據的總數。
 */
int HeapDelete(int *heap, int n)
{
	//使用堆尾元素直接覆蓋堆頂元素。
	heap[0] = heap[n - 1];
	//從堆頂到堆尾(此時堆中只有n-1個元素)進行堆調整。
	HeapAdjust(heap, 0, n - 1);
	return 0;
}

/*
 * 堆調整算法。(小頂堆)
 * 已知heap[top]結點的左右子樹均為堆,調整堆中元素,使以heap[top]為根結點的樹為堆。
 * n為堆中元素總數。
 */
int HeapAdjust(int *heap, int top, int n)
{
	int j = 2 * top + 1;	//左孩子結點
	int temp = heap[top];

	while (j < n)
	{
		if (j + 1 < n&&heap[j + 1] < heap[j])
			j++;	//使j指向左右孩子中較小的結點。
		if (heap[j] >= temp)
			break;
		heap[top] = heap[j];
		top = j;
		j = 2 * top + 1;
	}
	heap[top] = temp;
	return 0;
}

/*
 * 堆排序算法。
 * 形參heap為大頂堆時,實現的是由小到大;
 * 形參heap為小頂堆時,實現的是由大到小;
 */
int HeapSort(int *heap, int n)
{
	int i;
	int temp;

	for (i = n - 1; i > 0; i--)
	{
		//將堆頂元素和未排序的最后一個元素交換。
		temp = heap[0];
		heap[0] = heap[i];
		heap[i] = temp;
		//交換之后進行堆調整
		HeapAdjust(heap, 0, i);
	}
	return 0;
}

/*
 * 建堆算法。
 * 將無序數組array[]轉換為堆。
 */
int CreatHeap(int *array, int n)
{
	int i;
	//最后一個結點的編號為n-1,該結點的父節點(n-2)/2為最后一個非終端結點。
	//從結點(n-2)/2到根結點,依次進行堆調整。
	for (i = (n - 2) / 2; i >= 0; i--)
	{
		HeapAdjust(array, i, n);
	}
	return 0;
}

7. 測試結果

無標題_2345看圖王

無標題2_2345看圖王

參考:白話經典算法系列之七 堆與堆排序


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM