【算法导论】堆排序和优先级队列


堆是一种数据结构,可以被视为一棵完全二叉树,用数组存储,树中的每个结点与数组中存放该结点的那个元素对应。表示堆的数组arr[]有两个属性,length(arr[])是数组arr[]中的元素个数,heapSize(arr[])是存放在arr[]中堆的元素个数。此处heapSize(arr[]) <= length(arr[])。


堆的性质

堆的根节点为arr[1],给定某个节点node,他的父节点parent(node)和左孩子left(node)和右孩子right(node)结点可以用如下的方法得出:

//返回结点node的父节点
int parent(int node)
{
	return (node / 2);
}

//返回结点node左孩子节点
int left(int node)
{
	return (2 * node);
}

//返回结点node的右孩子结点
int right(int node)
{
	return (2 * node + 1);
}
对于具有n个结点的堆,,其叶子节点为 [n/2]+1,[n/2]+2,[n/2]+3......n


大根堆和小根堆

二叉堆有两种,大根堆和小根堆。

大根堆是指除了根节点以外的所有节点有arr[parent(node)] >= arr[node],最大堆中最大的元素就在根部;

小根堆是指除了根节点以外的所有节点有arr[parent(node)] <= arr[node],最小堆中最小的元素就在根部。


整堆(以大根堆为例)

假设结点node,以他的左右孩子结点(left(node)和right(node))为根的子树已经是大根堆,但是arr[node]小于其孩子节点,则需要对结点node进行整堆,以保持堆的性质。整堆的代码如下:

int maxHeapify(sortArr *sortNum, int node)
{
	int l = 0;
	int r = 0;
	int largest = 0;

	l = left(node);
	r = right(node);

	if((l <= sortNum->heapSize) && (sortNum->arr[l] > sortNum->arr[node]))
	{
		largest = l;
	}
	else
	{
		largest = node;
	}

	if((r <= sortNum->heapSize) && (sortNum->arr[r] > sortNum->arr[largest]))
	{
		largest = r;
	}

	if(largest != node)
	{
		sortNum->arr[0] = sortNum->arr[node]; //sortNum->arr[0]作为备用的存储空间
		sortNum->arr[node] = sortNum->arr[largest];
		sortNum->arr[largest] = sortNum->arr[0];

		maxHeapify(sortNum, largest);
	}

	return OK;
}
maxHeapify()的10-22行从元素arr[node],arr[left(node)],arr[right(node)]中找出最大的,将其下标存到largest中,如果arr[node]是最大的,则说明已经是最大堆了,程序结束。否则node的左右孩子结点有一个是最大的,交换arr[node]和arr[largest]的值,从而使arr[node]及其左右孩子满足了堆的性质,而交换了之后arr[largest]的值是原来arr[node]的值,该值可能又会和它的孩子节点违反最大堆的性质,因而要对他再进行整堆maxHeapify(),如此反复下去。

maxHeapify()的时间复杂度为:O(logn)


建堆

由堆的性质我们指导arr[]的子数组:arr[n/2+1]...arr[n]都是树中的叶子节点,这些叶子结点可以看做是只含一个元素的最大堆,然后我们可以自下而上的对每一个结点遍历一边,对其进行整堆。代码如下:

int buildMaxHeap(sortArr *sortNum)
{
	int i = 0;
	sortNum->heapSize = sortNum->length;
	for(i = (sortNum->length / 2); i >= 1; i--)
	{
		maxHeapify(sortNum, i);
	}
	return OK;
}
buildMaxHeap()的时间复杂度为O(n)。


堆排序算法

堆排序开始时先用buildMaxHeap()将arr[1...n]整理成一个最大堆,这样根结点就是堆中元素值最高的,将他同arr[n]互换来达到他最终的正确的位置,然后再将arr[1...n-1]构成一个最大堆,将arr[1]同arr[n-1]互换,这样以此类推,堆的大小由n一直降到2,这时数组arr[]就实现了元素由小到大的排序。代码如下:

int heapSort(sortArr *sortNum)
{
	int i = 0;
	buildMaxHeap(sortNum);
	for(i = sortNum->length; i >= 2; i--)
	{
		sortNum->arr[0] = sortNum->arr[1];
		sortNum->arr[1] = sortNum->arr[i];
		sortNum->arr[i] = sortNum->arr[0];

		sortNum->heapSize = sortNum->heapSize - 1;
		maxHeapify(sortNum, 1);
	}

	return OK;
}
heapSort()的时间复杂度为O(nlogn),其中调用buildMaxHeap()的时间为O(n),n-1次调用maxHeapify()中每一次的时间代价为O(logn)。


堆排序完整版测试代码:

/*
 * filename: heapSort.c
 *
 * 
 * chapter6  Heapsort
 * editor: vim v7.4
 * compiler: gcc v4.4.1
 * date: 2014-10-5
 * */

#include 
#include 
#include 
#include 

#define MAX 100
#define OK 0
#define ERROR 1

typedef struct
{
	int arr[MAX+1];
	int length;		//数组arr[]中的元素个数
	int heapSize;	//arr中堆的元素个数
}sortArr;

//返回结点node的父节点
int parent(int node)
{
	return (node / 2);
}

//返回结点node左孩子节点
int left(int node)
{
	return (2 * node);
}

//返回结点node的右孩子结点
int right(int node)
{
	return (2 * node + 1);
}

/********************
 * Description: 整堆(大根堆),让arr[i]在最大堆中“下降”,使以i为根的子树成为最大堆。
 * Input:
 * - sortNum: 存放待整堆的数组
 * - node: 待整堆的子树的根的下标
 * Return: 整堆结束返回 OK
 * ******************/
int maxHeapify(sortArr *sortNum, int node)
{
	int l = 0;
	int r = 0;
	int largest = 0;

	l = left(node);
	r = right(node);

	if((l <= sortNum->heapSize) && (sortNum->arr[l] > sortNum->arr[node]))
	{
		largest = l;
	}
	else
	{
		largest = node;
	}

	if((r <= sortNum->heapSize) && (sortNum->arr[r] > sortNum->arr[largest]))
	{
		largest = r;
	}

	if(largest != node)
	{
		sortNum->arr[0] = sortNum->arr[node]; //sortNum->arr[0]作为备用的存储空间
		sortNum->arr[node] = sortNum->arr[largest];
		sortNum->arr[largest] = sortNum->arr[0];

		maxHeapify(sortNum, largest);
	}

	return OK;
}

/********************
 * Description: 建堆(大根堆),自底向上的将数组arr[]变成一个最大堆
 * Input:
 * - sortNum: 存放待建堆的数组
 * Return: 建堆结束返回OK
 * ******************/
int buildMaxHeap(sortArr *sortNum)
{
	int i = 0;
	sortNum->heapSize = sortNum->length;
	for(i = (sortNum->length / 2); i >= 1; i--)
	{
		maxHeapify(sortNum, i);
	}
	return OK;
}

/********************
 * Description: 堆排序
 * Input:
 * - sortNum: 存放待排序的数组
 * - length: 待排序数组的长度
 * - size : 待排序数组的数值范围
 * Return: 排序成功返回OK
 * ******************/
int heapSort(sortArr *sortNum)
{
	int i = 0;
	buildMaxHeap(sortNum);
	for(i = sortNum->length; i >= 2; i--)
	{
		sortNum->arr[0] = sortNum->arr[1];
		sortNum->arr[1] = sortNum->arr[i];
		sortNum->arr[i] = sortNum->arr[0];

		sortNum->heapSize = sortNum->heapSize - 1;
		maxHeapify(sortNum, 1);
	}

	return OK;
}

/********************
 * Description: 随机生成待排序数组
 * Input:
 * - sortNum: 存放待排序的数组
 * - length: 待排序数组的长度
 * - size : 待排序数组的数值范围
 * Return: 打印成功返回OK
 * ******************/
int creatSortArray(sortArr *sortNum, int length, int size)
{
	int i = 0;
	srand((unsigned)time(NULL));
	for(i = 1; i <= length; i++)
	{
		sortNum->arr[i] = rand() % size;
	}

	sortNum->length = length;

	return OK;
}

/********************
 * Description: 获取用户输入数据
 * Input:
 * - length: 待排序数组的长度
 * - size: 待排序数组的数值范围
 * Output:
 * - length: 保存用户指定的待排数组的长度,范围:1-100
 * - size: 保存用户指定的待排数据的数值范围
 * Return:
 * - 用户输入数据成功返回OK
 * - 用户输入数据不合法返回ERROR
 * ******************/
int inputNumber(int *length, int *size)
{
	printf("随机生成待排序数组\n");
	printf("请输入数组长度(1-100):");
	scanf("%d", length);
	if(!((*length>=1)&&(*length<=100)))
	{
		return ERROR;
	}
	printf("请输入数组数据范围(0-x):\nx = ");
	scanf("%d", size);

	return OK;
}

/********************
 * Description: 打印数组
 * Input:
 * - sortNum : 存放待打印的数组
 * Return: 打印成功返回OK
 * ******************/
int printArray(sortArr sortNum)
{
    int i = 0;

    for(i = 1; i <= sortNum.length; i++)
    {
        printf("%d ", sortNum.arr[i]);
    }
    printf("\n");

    return OK;
}

int main()
{

	int size = 0;
	int length = 0;

	sortArr *sortNum = NULL;
	sortNum = (sortArr *)malloc(sizeof(sortArr));
	memset(sortNum, 0, sizeof(sortArr));

	inputNumber(&length, &size);
	creatSortArray(sortNum, length, size);
	printf("待排序的数组为:\n");
	printArray(*sortNum);

	printf("排序之后的数组为:\n");
	heapSort(sortNum);
	printArray(*sortNum);

	free(sortNum);

	return 0;
}



优先级队列

优先级队列是堆的一个重要应用,同最大堆和最小堆一样,优先级队列也有最大优先级队列和最小优先级队列。

优先级队列是一种用来维护一族元素构成集合S的数据结构,队列中的每个元素都有一个关键字key,即优先级,队列中的元素按照优先级的大小依次出队。


优先级队列的操作

1. INSERT(S, x): 把元素x插入到集合S中。

2. MAXIMUM(S):返回S中具有最大关键字的元素。

3. EXTRACT-MAX(S):去掉并返回S中具有最大关键字的元素。

4. INCREASE-KEY(S, x, key):将元素x的关键字的值增加到k,这里k值不能小于x的原关键字的值。


取最大关键字

因为优先级队列arr[]中第一个元素就是关键字最大的元素,所以只要返回第一个元素即可。代码如下:

int heapMaximum(sortArr *sortNum)
{
	return sortNum->arr[1];
}
时间复杂度为O(1)。


删除

1. 取堆顶arr[1];

2. 置arr[1] = arr[heapSize];

3. arr.heapSize = arr.heapSize - 1;

4. 对arr[1]进行整堆。

代码如下:

int heapExtractMax(sortArr *sortNum)
{
	int max = 0;

	if(sortNum->heapSize < 1)
	{
		printf("heap underflow\n");
		return ERROR;
	}

	max = sortNum->arr[1];
	sortNum->arr[1] = sortNum->arr[sortNum->heapSize];
	sortNum->heapSize = sortNum->heapSize - 1;

	maxHeapify(sortNum, 1);

	return max;
}
heapExtractMax()的时间复杂度为O(longn),因为他除了时间代价为O(longn)的maxHeapify()操作外只有很少的固定量的工作。


修改

增加元素关键字的值,即提高优先级。由于元素提高了优先级可能会破坏了最大堆的性质,所以需要整理堆,只要不断地将该元素与其父亲节点比较,一直往根上移动,直到合适他的位置即可。代码如下:

int heapIncreaseKey(sortArr *sortNum, int i, int key)
{
	if(key < sortNum->arr[i])
	{
		printf("new key is smaller than current key!\n");
		return ERROR;
	}

	sortNum->arr[i] = key;

	while((i > 1) && (sortNum->arr[parent(i)] < sortNum->arr[i]))
	{
		sortNum->arr[0] = sortNum->arr[i];
		sortNum->arr[i] = sortNum->arr[parent(i)];
		sortNum->arr[parent(i)] = sortNum->arr[0];

		i = parent(i);
	}

	return OK;
}
heapIncreaseKey()的运行时间为O(logn),即为被更新的结点到其根节点的路经长度。


插入

1. 新元素插入到heapSize+1的位置,将其关键字的值设置为无穷小;

2. 调用heapIncreaseKey()将该元素的关键字增加到要求的位置。

代码如下:

int maxHeapInsert(sortArr *sortNum, int key)
{
	sortNum->heapSize = sortNum->heapSize + 1;
	sortNum->arr[sortNum->heapSize] = INT_MIN;

	heapIncreaseKey(sortNum, sortNum->heapSize, key);

	return OK;
}
运行时间主要即为 heapIncreaseKey()的运行时间,即O(longn)


优先级队列完整版测试代码:

/*
 * filename: priorityQueues.c
 *
 * 
 * chapter6 Heapsort
 * editor: vim v7.4
 * compiler: gcc v4.4.1
 * date: 2014-10-7
 * */

#include 
#include 
#include 
#include 
#include 

#define MAX 100
#define OK 0
#define ERROR -1

typedef struct
{
	int arr[MAX+1];	//arr[]里面存放优先级队列元素的关键字(即优先级)
	int length;		//数组arr[]中的元素个数
	int heapSize;	//arr中堆的元素个数
}sortArr;

//返回结点node的父节点
int parent(int node)
{
	return (node / 2);
}

//返回结点node左孩子节点
int left(int node)
{
	return (2 * node);
}

//返回结点node的右孩子结点
int right(int node)
{
	return (2 * node + 1);
}

//返回arr[]中具有最大关键字的元素
/********************
 * Description: 返回堆arr[]中具有最大关键字的元素
 * Input:
 * - sortNum: 原最大堆
 * Return:
 * - 堆中具有最高关键字的元素的关键字
 * ******************/
int heapMaximum(sortArr *sortNum)
{
	return sortNum->arr[1];
}

/********************
 * Description: 整堆(大根堆),让arr[i]在最大堆中“下降”,使以i为根的子树成为最大堆。
 * Input:
 * - sortNum: 存放待整堆的数组
 * - node: 待整堆的子树的根的下标
 * Return: 整堆结束返回 OK
 * ******************/
int maxHeapify(sortArr *sortNum, int node)
{
	int l = 0;
	int r = 0;
	int largest = 0;

	l = left(node);
	r = right(node);

	if((l <= sortNum->heapSize) && (sortNum->arr[l] > sortNum->arr[node]))
	{
		largest = l;
	}
	else
	{

		largest = node;
	}

	if((r <= sortNum->heapSize) && (sortNum->arr[r] > sortNum->arr[largest]))
	{
		largest = r;
	}

	if(largest != node)
	{
		sortNum->arr[0] = sortNum->arr[node]; //sortNum->arr[0]作为备用的存储空间
		sortNum->arr[node] = sortNum->arr[largest];
		sortNum->arr[largest] = sortNum->arr[0];

		maxHeapify(sortNum, largest);
	}

	return OK;
}

/********************
 * Description: 去掉并返回优先级队列arr[]中具有最大关键字的元素
 * Input:
 * - sortNum: 原最大堆
 * Return:
 * - 操作成功返回 OK
 * - 堆中元素不足返回 ERROR
 * ******************/
int heapExtractMax(sortArr *sortNum)
{
	int max = 0;

	if(sortNum->heapSize < 1)
	{
		printf("heap underflow\n");
		return ERROR;
	}

	max = sortNum->arr[1];
	sortNum->arr[1] = sortNum->arr[sortNum->heapSize];
	sortNum->heapSize = sortNum->heapSize - 1;

	maxHeapify(sortNum, 1);

	return max;
}

/********************
 * Description: 提高优先级,将元素i的关键字的值增加到key
 * Input:
 * - sortNum: 原最大堆
 * - i: 待提高优先级的元素下标
 * - key: 元素i需要提高到的优先级
 * Return:
 * - 操作成功返回 OK
 * - 输入参数key不合法返回 ERROR
 * Others: 参数key的值不能小于arr[i]的原关键字的值
 * ******************/
int heapIncreaseKey(sortArr *sortNum, int i, int key)
{
	if(key < sortNum->arr[i])
	{
		printf("new key is smaller than current key!\n");
		return ERROR;
	}

	sortNum->arr[i] = key;

	while((i > 1) && (sortNum->arr[parent(i)] < sortNum->arr[i]))
	{
		sortNum->arr[0] = sortNum->arr[i];
		sortNum->arr[i] = sortNum->arr[parent(i)];
		sortNum->arr[parent(i)] = sortNum->arr[0];

		i = parent(i);
	}

	return OK;
}

/********************
 * Description: 在最大堆arr[]中插入一个新的元素
 * Input:
 * - sortNum: 原最大堆
 * - key: 待插入的元素的关键字
 * Return:
 * - 插入成功返回OK
 * ******************/
int maxHeapInsert(sortArr *sortNum, int key)
{
	sortNum->heapSize = sortNum->heapSize + 1;
	sortNum->arr[sortNum->heapSize] = INT_MIN;

	heapIncreaseKey(sortNum, sortNum->heapSize, key);

	return OK;
}

/********************
 * Description: 随机生成待排序数组
 * Input:
 * - sortNum: 存放待排序的数组
 * - length: 待排序数组的长度
 * - size : 待排序数组的数值范围
 * Return: 打印成功返回OK
 * ******************/
int creatSortArray(sortArr *sortNum, int length, int size)
{
	int i = 0;
	srand((unsigned)time(NULL));
	for(i = 1; i <= length; i++)
	{
		sortNum->arr[i] = rand() % size;
	}

	sortNum->length = length;

	return OK;
}

/********************
 * Description: 获取用户输入数据
 * Input:
 * - length: 待排序数组的长度
 * - size: 待排序数组的数值范围
 * Output:
 * - length: 保存用户指定的待排数组的长度,范围:1-100
 * - size: 保存用户指定的待排数据的数值范围
 * Return:
 * - 用户输入数据成功返回OK
 * - 用户输入数据不合法返回ERROR
 * ******************/
int inputNumber(int *length, int *size)
{
	printf("随机生成待排序数组\n");
	printf("请输入数组长度(1-100):");
	scanf("%d", length);
	if(!((*length>=1)&&(*length<=100)))
	{
		return ERROR;
	}
	printf("请输入数组数据范围(0-x):\nx = ");
	scanf("%d", size);

	return OK;
}

/********************
 * Description: 打印数组
 * Input:
 * - sortNum : 存放待打印的数组
 * Return: 打印成功返回OK
 * ******************/
int printArray(sortArr sortNum)
{
    int i = 0;

    for(i = 1; i <= sortNum.heapSize; i++)
    {
        printf("%d ", sortNum.arr[i]);
    }
    printf("\n");

    return OK;
}

int main()
{
	int i = 0;
	int max = 0;
	int size = 0;
	int length = 0;
	sortArr *sortNum = NULL;
	sortArr *tempSortNum = NULL;
	sortNum = (sortArr *)malloc(sizeof(sortArr));
	tempSortNum = (sortArr *)malloc(sizeof(sortArr));
	memset(sortNum, 0, sizeof(sortArr));
	memset(tempSortNum, 0, sizeof(sortArr));

	inputNumber(&length, &size);
	creatSortArray(tempSortNum, length, size);
	printf("待建优先级队列的元素优先级为:\n");
	tempSortNum->heapSize = tempSortNum->length;
	printArray(*tempSortNum);
	tempSortNum->heapSize = 0;

	printf("建立优先级队列:\n");
	for(i = 1; i <= length; i++)
	{
		maxHeapInsert(sortNum, tempSortNum->arr[i]);
	}
	printArray(*sortNum);

	printf("\n");

	printf("获取当前优先级队列中最大关键字的元素\n关键字为:");
	max = heapMaximum(sortNum);
	printf("%d\n", max);
	printf("\n");

	printf("从当前队列中去掉关键字最高的元素\n关键字为:");
	max = heapExtractMax(sortNum);
	printf("%d\n", max);
	printf("修改之后的优先级队列:");
	printArray(*sortNum);
	printf("\n");

	free(tempSortNum);
	free(sortNum);
	return 0;
}


你可能感兴趣的:(算法导论,算法)