数据结构之二叉堆及堆排序(C++)

一、堆定义(Heap)

n个元素的序列{k1,k2,...,kn},当且仅当任意ki 满足以下关系时,称之为堆(也是完全二叉树):

1.当k[i] <= k[2i] && k[i] <= k[2i+1] 时,称为小顶堆,即每个结点的值都小于等于其左右孩子结点的值;

2.当k[i] >= k[2i] && k[i] >= k[2i+1] 时,称为大顶堆,即每个结点的值都大于等于其左右孩子结点的值;

二、堆构建(Build)

1.存储方式:顺序表

2.理论基础:a).此数组元素构成一颗完全二叉树;b).对于一个含有 n 个结点的完全二叉树,其最后 一个结点时第 floor(n/2) 个结点的孩子结点;

3.堆构建过程:从第 floor(n/2) 个结点开始,对此子树根及其孩子结点进行调整,使之成为小根堆(或大根堆);然后依次向前对各个结点子树进行调整;最终完成对整个二叉树的堆化;

4.图解,以序列{49,39,65,97,76,13,27,69}为例:

 

三、堆排序(Sort)

1.排序方式:在构建完成的小根堆基础上,输出根结点(堆顶),并将其与堆尾结点交换位置,同时堆尾指针前移(即将输出结点移除跟堆,如下图中“切断”),重新调整为小根堆;如此,取后序根结点,再交换,再调整,循环往复,直至所有结点都取出。

2.小根堆排序结束后,将输出序列的升序排列{13,27,39,49,65,69,76,97},小根堆最终状态:转化成了大根堆结构

3.小根堆排序结束后,若需再次输出排序,需对其重建根堆。

4.用途:可实现不需要对 M个元素全排序的情况下,输出前 N个最小(大)元素;
    **最小(大)Top N 问题:
        缓存一个 N 长度的数组,遍历 M 个元素;
        当 i < N 时,向数组中插入元素;
        当 i = N 时,向数组中插入元素,并构建 大(小)根堆;
        当 i > N 时,若元素 小于(大于) 根元素,替换根元素,并从根节点处重建根堆;否则,不做处理;
        当 遍历完 M 个元素 后,对根堆排序,输出即是。

5.性质:不稳定排序例如序列 { 55,65,49,97,76,13,27,49 }在排完序后为 { 13,27,49,49,55,65,76,97 },其中下划线49在排序后却比正确序数的49先输出;

6.图解

四、代码(类模板实现)

1.实现了二叉堆的类模板化,可根据需求,创建小根堆或者大根堆;

2.实现了堆元素结点的对象化,结点除关键字Key以外,还可携带其它属性;

3.对外调用还需实现 元素关键字比较接口(函数指针)作为初始化参数 int (*KeyCompare)(ElemType *, ElemType *)

4.实现了堆的元素插入、堆创建、堆调整、移除堆顶、堆排序等方法;

5.代码中包含的"ObjArrayList.h"头文件,代码在之前博文"数据结构之顺序列表(支持对象元素)"中。

//文件名:"BiHeap.h"
#pragma once
#ifndef BIHEAP_H_
#define BIHEAP_H_

#include <iostream>
#include "ObjArrayList.h"
using namespace std;
/*
.	二叉堆 类模板 Binary Heap
.	实现:
.		1.以对象数组形式实现
.		2.小根堆、大根堆,及堆排序
*/

template <typename ElemType>
class BiHeap
{
public:
	enum Type		//堆类型
	{
		MIN,		//小顶堆
		MAX			//大顶堆
	};
private:
	ElemType ** arr;			//对象数组
	int length;					//数组长度
	int num;					//已入堆的元素个数
	int heapTail;				//堆尾指针
	int isBuild;				//是否建堆 0|否 1|已建

	int type;					//堆类型
	int (*KeyCompare)(ElemType *, ElemType *);	//实现关键字比较接口

	void _Heapify(int start, int end);			//堆化(重新调整成堆)
	void _RemoveRoot();							//移除堆顶元素

public:
	BiHeap(int type, int size, int(*KeyCompare)(ElemType *, ElemType *));		//构造函数
	~BiHeap();									//析构函数
	void Insert(ElemType * e);					//顺序插入堆元素
	void Insert(ObjArrayList<ElemType> list);	//顺序列表插入堆元素
	void Build();								//建堆
	ElemType * GetRoot();						//获取堆顶元素
	ObjArrayList<ElemType> * Sort();			//返回排序序列
};

template <typename ElemType>
BiHeap<ElemType>::BiHeap(int type, int size, int(*KeyCompare)(ElemType *, ElemType *))
{
	/*
	.	构造函数
	.	入参:
	.		int type: 堆类型
	.		int size: 堆大小
	.		int (*KeyCompare)(int, int): 堆关键字比较接口
	*/
	//初始化参数
	this->type = type;
	this->length = size;
	this->num = 0;
	this->heapTail = -1;
	this->isBuild = 0;
	this->KeyCompare = KeyCompare;
	//初始化数组,(元素对象存放于堆上,也可存放栈上,此处统一堆上)
	this->arr = (ElemType **) malloc(sizeof(ElemType *) * size);
}

template <typename ElemType>
BiHeap<ElemType>::~BiHeap()
{
	/*
	.	析构函数
	*/
	//释放数组空间,元素空间外部释放
	delete[] this->arr;
	//释放函数指针
	delete this->KeyCompare;
}

template <typename ElemType>
void BiHeap<ElemType>::Insert(ElemType * e)
{
	/*
	.	顺序插入堆元素
	*/
	//1.堆上限判断
	//已入堆的元素个数 小于堆长度时,可插入;否则不可
	if (this->num >= this->length)
		return;
	//2.插入
	this->arr[this->num] = e;
	//3.元素计数,标记堆尾
	this->num++;
	this->heapTail++;
	//4.重置建堆标识
	this->isBuild = 0;
}

template <typename ElemType>
void BiHeap<ElemType>::Insert(ObjArrayList<ElemType> list)
{
	/*
	.	顺序列表插入堆元素
	*/
	for (int i = 0; i < list->Length(); i++)
	{
		Insert(list->Get(i));
	}
}

template <typename ElemType>
void BiHeap<ElemType>::_Heapify(int start, int end)
{
	/*
	.	堆化(重新调整成堆)
	.	算法:
	.		1.游标从子树根结点start 向下调整为指定堆类型
	.		2.比较数根结点与左右孩子结点的 key ,调整其位置
	.		2.1.如(小根堆):
	.				当 Key(arr[root]) > min{ Key(arr[lchild]), Key(arr[rchild]) }时,需交换 arr[root] 与 arr[min{}] 的位置
	.				否则不需交换;
	.		2.2.如(大根堆):
	.				当 Key(arr[root]) < max{ Key(arr[lchild]), Key(arr[rchild]) }时,需交换 arr[root] 与 arr[max{}] 的位置
	.				否则不需交换;
	.		3.调整完位置后,将游标start 向下移,移至与根结点交换的子树结点位置
	.		4.重复上述步骤,直至 end位置 结束。
        .      	时间复杂度:O(log2(n))
	*/
	//1.保存起始根结点元素(以start 为起始位置的子树根结点)
	ElemType * startElem = this->arr[start];
	//2.从子树根结点start 向下调整为指定堆类型
	for (int i = 2 * start + 1; i <= end; i = 2 * i + 1)
	{
		//2.1.选出左右子树较小 或 较大的一个
		//注:"i + 1 < end" 防止数组越界
		if (i + 1 <= end &&
			(this->type == MIN && KeyCompare(this->arr[i], this->arr[i + 1]) > 0 ) 
			|| (this->type == MAX && KeyCompare(this->arr[i], this->arr[i + 1]) < 0 ))
		{
			i = i + 1;	//i++;
		}
		//2.2.1.若为小根堆,根结点key 小于 min{key(lc), key(rc)} 则停止向下调整
		//2.2.2.若为大根堆,根结点key 大于 max{key(lc), key(rc)} 则停止向下调整
		if ((this->type == MIN && KeyCompare(startElem, this->arr[i]) < 0)
			|| (this->type == MAX && KeyCompare(startElem, this->arr[i]) > 0))
		{
			break;
		}
		//2.3.交换结点位置
		this->arr[start] = this->arr[i];
		//2.4.将游标start 移至交换的子结点处
		start = i;
	}
	//3.将起始根结点元素 放到最后游标处
	this->arr[start] = startElem;
}

template <typename ElemType>
void BiHeap<ElemType>::_RemoveRoot()
{
	/*
	.	移除堆顶元素
	*/
	//堆尾指针 < 0 时,表示元素全部排序输出,不可再输出
	if (this->heapTail < 0)
	{
		return;
	}
	//1.将堆顶(根)元素与堆尾元素交换位置
	//cout << "(" << this->heapTail << ")";
	ElemType * root = this->arr[0];
	this->arr[0] = this->arr[this->heapTail];
	this->arr[this->heapTail] = root;
	//2.堆尾指针 向前移动
	this->heapTail--;
}

template <typename ElemType>
void BiHeap<ElemType>::Build()
{
	/*
	.	建堆
	*/
	//建堆前数组输出
	//1.从二叉堆的最后一颗子树(即最后一个结点的父节点处),向上调整
	//注:堆化的对象是子树(有孩子),叶子结点无法堆化
	for (int i = this->num / 2 - 1; i >= 0; i--)
	{
		_Heapify(i, this->num - 1);
	}
	//2.标记为 堆已建
	this->isBuild = 1;
}

template <typename ElemType>
ElemType * BiHeap<ElemType>::GetRoot()
{
	/*
	.	获取堆顶元素
	*/
	//只有建堆完成才可取根结点
	if (this->isBuild == 0)
		return NULL;
	//元素全部排序输出后,若再需重取,需重建堆
	if (this->heapTail < 0)
	{
		this->heapTail = this->num - 1;
		Build();
	}
	//1.取堆顶元素
	ElemType * root = this->arr[0];
	//2.移除堆顶元素(堆尾指针前移)
	_RemoveRoot();
	//3.重新调整堆,使其堆化
	_Heapify(0, this->heapTail);
	//4.返回
	return root;
}

template <typename ElemType>
ObjArrayList<ElemType> * BiHeap<ElemType>::Sort()
{
	/*
	.	返回排序序列
	*/
	ObjArrayList<ElemType> * list = new ObjArrayList<ElemType>();
	for (int i = 0; i < this->num; i++)
	{
		list->Add(GetRoot());
	}
	return list;
}

#endif // !BIHEAP_H_
//文件名:"Heap_Test.cpp"
#include "stdafx.h"
#include <iostream>
#include "BiHeap.h"
using namespace std;

int main()
{
	//1.初始化二叉堆,并实现元素KEY 比较接口(匿名函数)
	BiHeap<int> * h = new BiHeap<int>(BiHeap<int>::MIN, 8, 
		[](int * e1, int *e2) {
		if (*e1 > *e2)
			return 1;
		else if (*e1 == *e2)
			return 0;
		else
			return -1;
	});
	//2.向二叉堆插入元素
	h->Insert(new int(49));
	h->Insert(new int(39));
	h->Insert(new int(65));
	h->Insert(new int(97));
	h->Insert(new int(76));
	h->Insert(new int(13));
	h->Insert(new int(27));
	h->Insert(new int(69));
	//3.构建二叉堆
	h->Build();
	//4.获得排序后列表
	cout << "排序后:" << endl;
	ObjArrayList<int> * sortedList = h->Sort();
	for (int i = 0; i < sortedList->Length(); i++)
	{
		cout << *sortedList->Get(i) << " ";
	}

	//多次排序输出测试(需重建顶堆,内部已实现)
	cout << endl;
	for (int i = 0; i < 32; i++)
	{
		cout << *h->GetRoot() << " ";
		if ((i+1) % 8 == 0)
			cout << endl;
	}
	return 0;
	
}

五、输出结果 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值