堆【最大堆】构建及其操作【数据结构笔记】

写在开头:

一般用数组来表示堆,i结点的父结点下标就为(i–1)/2。它的左右子结点下标分别为2i+1和2i+2。如第0个结点的左右子结点下标分别为1和2。

1、堆的定义

n个元素的序列{k1,k2,…,kn}当且仅当满足下列关系之一时,称之为堆。

情形1:ki <= k2i 且ki <= k2i+1 (最小化堆或小顶堆)

情形2:ki >= k2i 且ki >= k2i+1 (最大化堆或大顶堆)

其中i=1,2,…,n/2向下取整;

2,堆的插入

每次插入都是将先将新数据放在数组最后,由于从这个新数据的父结点到根结点必然为一个有序的序列,现在的任务是将这个新数据插入到这个有序序列中——这就类似于直接插入排序中将一个数据并入到有序区间中。

3. 堆的删除

堆中每次都只能删除堆顶元素。为了便于重建堆,实际的操作是将最后一个数据的值赋给根结点,然后再从根结点开始进行一次从上向下的调整。调整时先在左右子结点中找最小的,如果父结点比这个最小的子结点还小说明不需要调整了,反之将父结点和它交换后再考虑后面的结点。相当于根结点数据的“下沉”过程。

4. 堆的建立

从无序序列建堆的过程就是一个反复调整的过程。若将此序列看成是一个完全二叉树,则最后一个非终端结点是第(n-2)/2个结点,由此调整过程只需从该结点开始,直到堆顶元素。

5. 堆排序

若在输出堆顶的最小值之后,使得剩余n-1个元素的序列重建一个堆,则得到n个元素中的次小值。如此反复执行,便能得到一个有序序列,这个过程称之为堆排序。

输出堆顶元素之后,以堆中最后一个元素替代之,此时根结点的左右子树均为堆,则仅需进行一次从上到下的调整即可重建一个堆。

代码
#include<stdio.h>
#include<stdlib.h>
#define INIT_ARRAY_SIZE 50
 
int heap_size; //堆大小
int heap_cap_size;  //堆容量大小
 
/*函数声明*/  
//构建堆
void build_heap(int array[], int length);
//堆的调整
void max_heap_adjust(int array[], int index);
//堆的删除
void heap_delete(int array[], int value);
//堆的插入
void heap_insert(int** array, int value);
//堆排序
void heap_sort(int array[], int length);
 
/*返回以index为根的完全二叉树的左子树的索引,整个二叉树索引以0为开始*/  
int left(int index) {
	return ((index << 1) + 1);
}
/*返回以index为根的完全二叉树的右子树的索引,整个二叉树索引以0为开始*/ 
int right(int index) {
	return ((index << 1) + 2);
}
/*两个数的交换*/ 
void swap(int* a ,int* b) {
	int temp = *a;
		*a = *b;
		*b = temp;
	return;
}
void build_heap(int array[], int length) {
	heap_size = length;
	for (int i = ((heap_size - 1) >> 1); i >= 0; --i) {
		max_heap_adjust(array, i);
	}
}
 
void max_heap_adjust(int array[], int index) {
	int left_index = left(index);
	int right_index = right(index);
	int largest = index;
	//左子树和父节点进行对比
	if (left_index <= (heap_size-1) && array[left_index] > array[largest]) {
		largest = left_index;
	}
	//右子树和父节点进行对比
	if (right_index <= (heap_size-1) && array[right_index] > array[largest] ) {
		largest = right_index;
	}
	if (largest == index) {
		return;
	} else {
		//需要交换
		swap(&array[index], &array[largest]);
		//递归调用
		max_heap_adjust(array, largest);
	}
}
 
void heap_delete(int array[], int value) {
	int index = 0;
	for (index = 0; index < heap_size; index++) {
		if (array[index] == value) {
			break;
		}
	}
	array[index] = array[heap_size - 1];
	--heap_size;
	max_heap_adjust(array, index);
}
void heap_insert(int** array, int value) {
	int index = 0;
	int parent_index = 0;
	if (heap_size+1 > heap_cap_size) {
		*array = (int*) realloc(*array, 2*INIT_ARRAY_SIZE*sizeof(int));
	}
	(*array)[heap_size] = value; //一定要记得加上()既(*array)[heap_size] 如果写出*array[heap_size]肯定会出问题
	index = heap_size;
	heap_size++;//要记得这里堆大小变大了
	//和父节点对比,哪个大就往上移动
	while (index) {
		parent_index = ((index-1) >> 1);
		if ((*array)[parent_index] < (*array)[index]) {
			swap(&((*array)[parent_index]), &((*array)[index]));	
		}
		index = parent_index;
	}
}
void heap_sort(int array[], int length) {
	int old_heap_size  = heap_size;
	int i;
	for (i = length-1; i >= 1; --i) {
		swap(&array[i], &array[0]);
		--heap_size;
		max_heap_adjust(array, 0);
	}
	//恢复堆的大小
	heap_size = old_heap_size;
}
 
void print_array(int* a , int length) {
	for (int i =0; i < length; i++) {
		printf("%d\t", a[i]);
	}
	printf("\n");
}
int main() {
	int i = 0;
	int a[] = {9, 3, 7, 6, 5, 1, 10, 2};
	int *array = NULL;
	array = (int *)malloc(INIT_ARRAY_SIZE*sizeof(int));
	int length = sizeof(a)/sizeof(int);
	printf("数组的长度是:%d\n", length);
	for (i = 0; i < length; ++i) {
		array[i] = a[i];
	}
	printf("原始数组为\n");
	print_array(array, length);
	printf("堆的建立后的数组\n");
	build_heap(array, length);
	print_array(array, length);
	printf("堆排序后的数组为\n");
	heap_sort(array, length);
	print_array(array, length);
    //这个地方一定要记得先构建堆,不然下面执行删除和插入有问题
	build_heap(array, length);
	printf("删除数据10后的数组\n");
	heap_delete(array, 10);
	length--;
	print_array(array, length);
	printf("插入数据10后的数组\n");
	length++;
	heap_insert(&array, 10);
	print_array(array, length);
	printf("堆排序后的数组为\n");
	heap_sort(array, length);
	print_array(array, length);
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值