堆排序算法

堆排序算法介绍:

堆排序算法:是指利用堆这种数据结构所设计的一种排序算法

堆介绍:堆是一个数组,它可以被看成一个近似的**完全二叉树**,数上的每一个结点对应于数组中的一个元素。所以我们可以将堆理解为将一个完全二叉树按照从上到下,

从左到右将元素逐个放置于一个一维的数组,而这个数组我们就可以称为堆。如下:

堆的种类:*堆分为两种分别叫做最大堆和最小堆,所谓最大堆就是除了根结点以外的所有结点i都要满足完全二叉树中的双亲结点要大于任何一个孩子结点,而最小堆恰好

相反。下面我使用的是最大堆来研究堆排序算法。
 

在学习堆排序之前我们需要先学习二叉树,作为准备知识

堆排序设计步骤:

1. 建堆:既然堆排序算法采用的是堆数据结构,因此在实施算法之前我们需要建立这个数据结构。而建立堆我们就首先需要准备一个数组,这个数组就是我们最后结果所

需要的堆,而对于大顶堆来说我们在建堆的过程中就需要时刻维护它本身的性质特点,即双亲结点

要大于任何一个孩子结点,因为在建堆过程中我们需要时刻维护这个性质,所以我们不妨将这个性质维护的过程写成一个函数,在维护性质的时候调用即可,而函数的思

路如下:(如图中左上的堆就不满足大最大堆的性质,我们维护的方法就是拿这个不满足的结点,

与其左右孩子结点做比较,取最大的孩子结点与它交换,然后逐层下去,很显然这里就要用到递归的思路)

建堆的思路就是将每个元素结点都进行一次调整,而对于叶子结点可以看成是只有一个元素的堆,因此不需要调整(叶子结点的下标开始于a.length/2-1的地方),只需要对

非叶子结点进行调整即可。具体代码如下:

class Node {
 public:
	 int left;
	 int right;
	 int val;
	 Node(int left, int right,int val):left(left),right(right),val(val) {
	 }
};

void   Max_Heapify(Node* a, int i,int size) //这里的i表示在数组中出现问题的元素坐标,size表示数组(堆)的大小
{
	int l = a[i].left;
	int r = a[i].right;
	int v = a[i].val;
	int max=0;
	if (l <= size &&l!=-1&& a[l].val > a[i].val)
	{
		max = l;
	}
	else max = i;
	if (r <= size &&r!= -1 && a[r].val > a[max].val)
	{
		max = r;
	}
	if (max != i)
	{
		Node midNode = a[max];
		a[max].val=a[i].val;
		a[i].val =midNode.val;
		Max_Heapify(a, max, size);
	}
}
void Build_Max_Heapify(Node*  a,int size) {
	for (int i = size / 2 - 1; i >=0; i--)
	{
		Max_Heapify(a, i, size);

	}
}

2. 堆排序算法:

堆排序算法是这么一种算法,利用前面已经建立好的堆,我们可以把堆顶的元素,也就是最大堆(二叉树)的最大元素与最后一个叶子结点进行交换,并将这个叶子结点作为这个新二叉树的根结点,然后将原来的根节点作为这个堆的

最大值踢出这个堆。由于新形成的堆已经不是一个最大堆,所以我们只需要对这个新堆的堆顶元素也就是交换过来的叶子结点调用前面定义的维持堆性质的函数即可。因为这个新堆只有堆顶相对于其它结点来说是新来的,其它的都是

原来不变的,因此仅仅需堆顶作调整即可。调整完后我们发现新的堆顶又会是堆的最大值,依次重复,直到排序成功。堆排序算法具体实现函数如下:

#include <iostream>
using namespace std;
 class Node {
 public:
	 int left;
	 int right;
	 int val;
	 Node(int left, int right,int val):left(left),right(right),val(val) {
	 }
};
void   Max_Heapify(Node* a, int i,int size) //这里的i表示在数组中出现问题的元素坐标,size表示数组(堆)的大小
{
	int l = a[i].left;
	int r = a[i].right;
	int v = a[i].val;
	int max=0;
	if (l <= size &&l!=-1&& a[l].val > a[i].val)
	{
		max = l;
	}
	else max = i;
	if (r <= size &&r!= -1 && a[r].val > a[max].val)
	{
		max = r;
	}
	if (max != i)
	{
		Node midNode = a[max];
		a[max].val=a[i].val;
		a[i].val =midNode.val;
		Max_Heapify(a, max, size);
	}
}
void Build_Max_Heapify(Node*  a,int size) {
	for (int i = size / 2 - 1; i >=0; i--)
	{
		Max_Heapify(a, i, size);

	}
}
void coutarr(Node* a, int size)
{
	for (int i = 0; i < size; i++)
	{
		cout << a[i].val << "-";
	}
	cout << endl;
}
void Build_Max_Heap(Node * a,int size) {
	Build_Max_Heapify(a, size);
	coutarr(a, 10);
	for (int i = 9; i >= 0; i--)
	{
		Node midNode = a[0];
		a[0].val = a[i].val;
		cout << midNode.val << endl;
		size--;
		Max_Heapify(a, 0, size);
	}
}
int main()
{
	Node node1=Node(1, 2, 4);
	Node node2 = Node(3, 4, 1);
	Node node3 = Node(5, 6, 3);
	Node node4 = Node(7, 8, 2);
	Node node5 = Node(9, -1, 16);
	Node node6 = Node(-1, -1, 9);
	Node node7 = Node(-1, -1, 10);
	Node node8 = Node(-1, -1, 14);
	Node node9 = Node(-1, -1, 8);
	Node node10 = Node(-1, -1, 7);
	Node* a =new Node[10]{ node1,node2,node3,node4,node5,node6,node7,node8,node9,node10};
	Build_Max_Heap(a, 10);
}

算法时间复杂度分析:

(1)对于维护树性质的函数的时间代价而言,首先我们需要调整根结点与子结点进行交换,这个的时间复杂度为1,然后是递归运行Max_Heapify()(维护树的性质的函数)最坏的情况是要调整的元素是位于根节点上,因为每个孩子

的子树至多为2n/3(证明如下),我们可以用下面递归式表示Max_Heapify()运行时间:T(n)<=T(2n/3)+1解出来是T(n)=O(lgn)

问题:一棵以节点i为根的、大小为n的子树上,i节点的子树大小至多为2n/3(最坏情况:底层恰好半满的时候),怎么证明?

证明:

这里应该是讨论完全二叉树,规定总size为n,要求根的左子树的最大size。(由于右子树size总是<=左子树size)。

那么显然,观察最底层节点数目为0, 1, 2...的情况,显然半满的时候左子树达到了最大。以下求此时左子树的大小:

设底层半满时节点数为x,则再加x个节点,就是满树:n + x = 2x * 2 - 1 = 满树size
可得n = 3x - 1。
满树时,左子树节点数 = (满树size - 1) / 2 = 2x -1 ~= 2n / 3

(2) 建堆的时间复杂度:由于建堆里面有个for循环所以时间复杂度为n,由于还调用了前面的Max_Heapify()函数,所以时间复杂度为T(n)=O(nlgn)

  (3)   堆排序算法的时间:在代码中直到,在堆排序函数中首先调用了Build_Max_Heapify(a, size)函数,由二知时间复杂度为O(nlgn),而堆排序函数中也有个for循环,并同样调用了Max_Heapify()函数,所以for循环的时间复杂度为T(n)=O(nlgn),

所以总的时间复杂度为2O(nlgn)=O(nlgn)

综上堆排序算法的时间复杂度为O(nlgn)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值