数据结构 - 堆

本文详细介绍了堆的概念,包括小堆和大堆,并展示了如何用C语言实现堆的基本操作,如初始化、插入、删除、调整和排序。重点讨论了向上调整和向下调整的过程,以及堆排序的实现步骤。堆排序通过初始化堆,将数组元素逐个插入堆中,然后不断取出堆顶元素,达到排序的目的。

1. 堆的概念及结构

堆就是用数组实现的二叉树,所以它没有使用父指针或者子指针。

堆根据“堆属性”来排序,“堆属性”决定了树中节点的位置。

堆属性:

parent = (child-1)/ 2

lchild = parent*2 + 1

rchild = parent*2 + 2

父节点小于子节点的称则称为小堆,父节点大于子节点的称为大堆

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆

2. 堆的基本实现

首先创建头文件 - Heap.h

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int HeapDataType;

typedef struct Heap{
	HeapDataType *a;
	int size;
	int capacity;
}HP;

然后实现基本操作:

//初始化堆
void HeapInit(HP*php){
	assert(php);
	php->a=NULL;
	php->size=php->capacity=0;
}
//交换数据
void swap(HeapDataType*a,int parent,int child){
	int temp=a[parent];
	a[parent]=a[child];
	a[child]=temp;
}
void AdjustUp(HeapDataType*a,int size){
	
	//该算法逻辑思想是二叉树,物理中操作思想是数组 
	int child=size;
	int	parent;
    for(parent=(child-1)/2;child>0; parent=(parent-1)/2){      //向上调整  
		if(a[parent]>=a[child]){
			break;
		}
	    swap(a,parent,child);
	    child=parent;
	}
}
//插入
void HeapPush(HP*php,HeapDataType x){
	assert(php);
	int child,parent,new_capacity;
	if(php->size==php->capacity){
		new_capacity ==0 ? 4 : php->capacity*2;
		HeapDataType *tmp=(int*)realloc(php->a,sizeof(int)*new_capacity);
		if(tmp==NULL){
			perror("realloc");
			exit(-1);
		}
		php->a=tmp;
		php->capacity=new_capacity;
	}
	php->a[php->size]=x;
    AdjustUp(php->a,php->size);
	++php->size;
	
}
//销毁
void HeapDestroy(HP*php){
	assert(php);
	free(php->a);
	php->a=NULL;
	php->size=php->capacity=0;
}
//向下调整
void AdjustDown(HeapDataType*a,int s,int length){
	
	int parent=s,child;	   
	for(child=parent*2+1;child<length;child=child*2+1){   
	    int rc=a[parent];
		if(child+1<length&&a[child]<a[child+1]){  //找出左右子树中较小数 
			child++;
		}
		if(rc>=a[child]){     //与父节点进行比较 
			break;
		}
		a[parent]=a[child]; //交换数据 
		a[child]=rc;
		parent=child;           //直到叶子结点 
	}
}
//删除最小/最大的数据 
void HeapPop(HP*php){
	assert(php);
	if(php->size==0){
		return;
	}
	swap(php->a,0,php->size-1);  //交换第一个和最后一个 
	php->size--;   //长度减一 
	AdjustDown(php->a,0,php->size);   //向下调整 
}
//打印
void HeapPrint(HP*php){
	assert(php);
	for(int i=0;i<php->size;i++){
		printf("%d ",php->a[i]);
	}
}
//判空
bool HeapEmpty(HP*php){
	assert(php);
	return php->size==0;
}
//获取堆顶数据
HeapDataType HeapTop(HP*php){
	assert(php);
	if(php->size==0){
		return -1;
	}
	return php->a[0];
}
//返回堆中元素个数
int HeapSize(HP*php){
	assert(php);
	return php->size;
}
//堆排序
void HeapSort(int*a,int length){
	HP php;
	HeapInit(&php);
	for(int i=0;i<length;i++){
		HeapPush(&php,a[i]);
	}
	int j=0;
	while(!HeapEmpty(&php)){
		a[j]=HeapTop(&php);
		HeapPop(&php);
		j++;
	}
}

那么在这些操作中比较重要的有三个:

1. 向上调整

void AdjustUp(HeapDataType*a,int size){
	
	//该算法逻辑思想是二叉树,物理中操作思想是数组 
	int child=size;
	int	parent;
    for(parent=(child-1)/2;child>0; parent=(parent-1)/2){      //向上调整  
		if(a[parent]>=a[child]){
			break;
		}
	    swap(a,parent,child);
	    child=parent;
	}
}

当我们在堆中插入一个元素,在实际操作中相当于在数组尾部插入一个元素,但是不仅仅是插入一个元素在数组尾部,同时我们要满足特定的要求,那这时我们就需要利用完全二叉树用数组实现的一些特点:parent = (child-1)/ 2 。

实现步骤:

1 .  当插入一个数以后,我们首先要做的是要判断此位置是否是正确的位置。

2.   如果当前位置符合则结束,如果不符合则需要进行交换数据。

3.   进行迭代操作,重复上述操作,直到条件终止

 由于是从底部向上的所以被称为向上调整

 2. 向下调整

void AdjustDown(HeapDataType*a,int s,int length){
	
	int parent=s,child;	   
	for(child=parent*2+1;child<length;child=child*2+1){   
	    int rc=a[parent];
		if(child+1<length&&a[child]<a[child+1]){  //找出左右子树中较小数 
			child++;
		}
		if(rc>=a[child]){     //与父节点进行比较 
			break;
		}
		a[parent]=a[child]; //交换数据 
		a[child]=rc;
		parent=child;           //直到叶子结点 
	}
}

如果理解了向上调整,向下调整其实也是很容易理解的。

实现步骤:

1 .  首先我们要找到该父亲结点的左子树,然后令左子树和右子树进行比较,根据需求选取(child是否++)。

2.  判断此位置是否满足堆的特点,如果满足则退出,不满足则交换。

3.    进行迭代操作,重复上述操作,直到条件终止。

 由于此操作是由上到下,所有被称为向下调整

3. 排序

void HeapSort(int*a,int length){
	HP php;
	HeapInit(&php);
	for(int i=0;i<length;i++){
		HeapPush(&php,a[i]);
	}
	int j=0;
	while(!HeapEmpty(&php)){
		a[j]=HeapTop(&php);
		HeapPop(&php);
		j++;
	}
}

实现步骤:

1. 初始化堆 

2. 将传入的数组赋值在堆中

3. 利用堆的堆顶是最大/最小值 来进行排序,每获取一次就要重新建堆,直到堆中元素为空。

 此排序的空间复杂度是 0(n),那我们有没有什么办法让其空间复杂度是0(1)呢?


这个问题我们下期再见!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值