堆(数据结构),这看了,还能忘?_review_面试

Posted 黑桃_K_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了堆(数据结构),这看了,还能忘?_review_面试相关的知识,希望对你有一定的参考价值。


一、堆得概念

如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储。
在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
理解:每一个父节点小于(大于)子节点,为最小堆(最大堆)。如下图:

**堆的性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树;

下面介绍的两种调整算法,对于建堆,插入,删除等十分重要

文章中的堆以小堆为例:

二、堆向下调算法

概念:

现在我们给出一个数组逻辑上看做一颗完全二叉树。我们通过从根节点开始的向下调整算法可以把它调整成一个小堆。
向下调整算法有一个前提:左右子树必须是一个堆,才能调整。

图:(小堆)

int a[] = {27,15,19,18,28,34,65,49,25,37};

找到最大子节点,进行交换,然后继续向下进行上述;

代码:

//向下调整(小堆)
void shiftDown(int* arr, int n, int cur){
	//找到孩子的位置
	//左孩子
	int child = 2 * cur + 1;
	while (child < n){
		//从左孩子中找一个最小的
		if (child + 1 < n && arr[child] > arr[child + 1])
			++child;
		//和当前数据比较
		//1.调整
		if (arr[child] < arr[cur]){
			int tmp = arr[child];
			arr[child] = arr[cur];
			arr[cur] = tmp;
			//更新
			cur = child;
			child = cur * 2 + 1;
		}
else//不调整,孩子》=当前
		break;
	}
 }

三、堆向上调整算法

概念:

将一个节点插入堆时,需要根据大小向调整位置,将插入节点,与父节点比较大小,小则交换,依次向上进行判断是否交换操作,不小与则不动;

图:(大堆)

代码:

//向上更新(小堆)
void shiftUp(int* arr, int n, int cur){
	//父节点下标
	int parent = (cur - 1) / 2;
	while (cur > 0){
		//交换
		if (arr[cur]<arr[parent]){
			int tmp = arr[cur];
			arr[cur] = arr[parent];
			arr[parent] = tmp;

		//更新
			cur = parent;
			parent = (cur - 1) / 2;
		}
		//不交换
		else{
			break;
		}
	}
}

四、建堆

概念:

下面我们给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但是还不是一个堆,现在我们通过算法,把它构建成一个堆。根节点左右子树不是堆,我们怎么调整呢?
这里我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆。

图:(大堆)

int a[] = {1,5,3,8,7,6};

代码:

//建堆
void test(){
	int arr[] = {10,5,8,3,2,1};
	int n=sizeof(arr) / sizeof(arr[0]);
//构建一个小堆
	for (int i = ((n - 2) / 2); i >= 0; --i)
		shiftDown(arr,n, i);
	}

五、堆插入:

概念:插入一个数据放在数组的尾部,然后向上调整

图:

先插入一个80到数组的尾上,再进行向上调整算法,直到满足堆。

代码:

//尾插
void heapPush(heap* hp, HDataType val){
	//检查空间
	checkCapacity(hp);
	//尾插
	hp->_data[hp->_size++] = val;
	//向上调整
	shiftUp(hp->_data, hp->_size, hp->_size - 1);
}

六、堆的删除

概念:

删除堆是删除堆顶的数据,将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。

图:

代码:

//删除
void heapPop(heap* hp){
	if (hp->_data == NULL)
		return;
	//交换
	int tmp = hp->_data[0];
	hp->_data[0]=hp->_data[hp->_size - 1];
	hp->_data[hp->_size - 1] = tmp;
	//尾删
	--hp->_size;
	//向下调整
	shiftDown(hp->_data, hp->_size,0);
}

七、全部代码:

#include<stdlib.h>
#include<stdbool.h>
//假设小堆
typedef int HDataType;
void shiftDown(int* arr, int n, int cur);
void shiftUp(int* arr, int n, int cur);
typedef struct heap{
	HDataType* _data;
	int _size;
	int _capacity;
}heap;

//初始化
void heapInit(heap* hp){
	if (hp == NULL)
		return;
	//空堆
	hp->_data = NULL;
	hp->_size = 0;
	hp->_capacity = 0;
}
//检查空间
void checkCapacity(heap* hp){
	if (hp->_size == hp->_capacity){
		int newC = hp->_capacity == 0 ? 1 : 2 * hp->_capacity;
		hp->_data = (HDataType*)realloc(hp->_data, sizeof(HDataType)*newC);
		hp->_capacity = newC;
	}
}
//小堆顶点
HDataType heapTop(heap* hp)
{
	return hp->_data[0];
}
//尾插
void heapPush(heap* hp, HDataType val){
	//检查空间
	checkCapacity(hp);
	//尾插
	hp->_data[hp->_size++] = val;
	//向上调整
	shiftUp(hp->_data, hp->_size, hp->_size - 1);
}
//删除
void heapPop(heap* hp){
	if (hp->_data == NULL)
		return;
	//交换
	int tmp = hp->_data[0];
	hp->_data[0]=hp->_data[hp->_size - 1];
	hp->_data[hp->_size - 1] = tmp;
	//尾删
	--hp->_size;
	//向下调整
	shiftDown(hp->_data, hp->_size,0);
}
//判断为空
bool heapEmpty(heap* hp){
	if (hp == NULL || hp->_data == NULL)
		return true;
	return false;
}
//向下调整
void shiftDown(int* arr, int n, int cur){
	//找到孩子的位置
	//左孩子
	int child = 2 * cur + 1;
	while (child < n){
		//从左孩子中找一个最小的
		if (child + 1 < n && arr[child] > arr[child + 1])
			++child;
		//和当前数据比较
		//1.调整
		if (arr[child] < arr[cur]){
			int tmp = arr[child];
			arr[child] = arr[cur];
			arr[cur] = tmp;

			//更新
			cur = child;
			child = cur * 2 + 1;
		}
else//不调整,孩子》=当前
		break;
	}
 }
//向上更新(小堆)
void shiftUp(int* arr, int n, int cur){
	//父节点下标
	int parent = (cur - 1) / 2;
	while (cur > 0){
		//交换
		if (arr[cur]<arr[parent]){
			int tmp = arr[cur];
			arr[cur] = arr[parent];
			arr[parent] = tmp;

		//更新
			cur = parent;
			parent = (cur - 1) / 2;
		}
		//不交换
		else{
			break;
		}
	}
}
//堆排序
void test(){
	int arr[] = {16,27,4,3,5,16};
	int n = sizeof(arr) / sizeof(arr[0]);
	//建堆
	for (int i = (n - 2) / 2; i >=0; --i){
		shiftDown(arr, n, i);
	}
	int end = n - 1;
	while (end > 0){
		//交换
		int tmp = arr[end];
		arr[end] = arr[0];
		arr[0] = tmp;
		//下调整
		shiftDown(arr, end, 0);
		--end;
	}
}
/*

void test(){
	int arr[] = { 1, 5, 10, 7, 8, 6,3,9,4,2 };
	int n=sizeof(arr) / sizeof(arr[0]);
	heap hp;
	heapInit(&hp);
	for (int i = 0; i < n; ++i){
		heapPush(&hp, arr[i]);
	}
	for (int i = 0; i < n - 1; ++i){
		heapPop(&hp);
	}
}
*/
建堆
//void test(){
//	int arr[] = {10,5,8,3,2,1};
//	int n=sizeof(arr) / sizeof(arr[0]);
//构建一个小堆
//	for (int i = ((n - 2) / 2); i >= 0; --i)
//		shiftDown(arr,n, i);
//	}
	

int main(){
	test();
	return 0;
}

以上是关于堆(数据结构),这看了,还能忘?_review_面试的主要内容,如果未能解决你的问题,请参考以下文章

Java Review - 并发编程_PriorityBlockingQueue原理&源码剖析

2016年3月31日_应化所群体Review

Java Review - 并发编程_LinkedBlockingQueue原理&源码剖析

Java Review - 并发编程_LinkedBlockingQueue原理&源码剖析

Java Review - 并发编程_锁的分类

Java Review - 并发编程_锁的分类