选择排序:堆排序

堆排序(Heap Sort):使用堆这种数据结构来实现排序。

先看下堆的定义:

最小堆(Min-Heap)是关键码序列{k0,k1,…,kn-1},它具有如下特性:

ki<=k2i+1,

ki<=k2i+2(i=0,1,…)

简单讲:孩子的关键码值大于双亲的。

同理可得,最大堆(Max-Heap)的定义:

ki>=k2i+1,

ki>=k2i+2(i=0,1,…)

同样的:对于最大堆,双亲的关键码值大于两个孩子的(如果有孩子)。

堆的特点:

  1. 堆是一种树形结构,而且是一种特殊的完全二叉树。
  2. 其特殊性表现在:它是局部有序的,其有序性只体现在双亲节点和孩子节点的关系上(树的每一层的大小关系也是可以体现的)。兄弟节点无必然联系。
  3. 最小堆也被称为小顶堆(根节点是最小的),最大堆也被称为大顶堆(根节点是最大的)。我们常利用最小堆实现从小到大的排序,最大堆实现从大到小的排序。

最小堆和最大堆的两个示例图:

要想实现排序,第一个问题:如何建堆?(以最小堆为例,最大堆同理)

建堆:从最后一个内部节点开始,不断地向下调整,直到根节点。

画个流程图,看得更明白:矩形框表示要调整的位置

仔细看上面的流程图,相信你一定可以清楚明白整个调整过程。

建堆代码:我们使用顺序结构存储堆(可不要以为树形结构一定得使用链表来实现),向下调整(heapSiftDown())的方法是关键,建堆的代码如下:

void heapSiftDown(int a[], int n, int pos)   //从pos位置向下调整
{
	int i = pos;
	int j = 2 * i + 1;   //j为i的左孩子
	while (j < n)
	{
		if (j + 1 < n && a[j + 1]<a[j])   //如果右孩子存在,并且右孩子<左孩子
			j++;
		if(a[i] < a[j])   //已满足堆序,不需调整
			break;   //为什么不是continue?因为子树已经排好堆序,结合流程图想想?
		swap(a[i], a[j]);   //交换元素
		i = j;
		j = 2 * i + 1;
	}
}
void createHeap(int a[], int n)   //建堆
{
	if (a && n > 1)
	{
		for (int i = (n - 2) / 2; i >= 0; i--)  //(n-2)/2是最后一个内部节点的下标
			heapSiftDown(a, n, i);
	}
}

建堆时间复杂度:

建堆的时间复杂度是O(n),推导比较复杂。下面粘贴出从资料上找到的推导过程:

对于n个节点的堆,其对应的完全二叉树的层数是logn。若i为层数,则第i层上的节点数最多为2^i(i>=0)。建堆时,每个非叶子节点都调用了一次heapSiftDown()函数,并且每个节点最多调整到最底层,即第i层上的节点调整到最底层的调整次数为logn-i(最大的),则建堆的时间复杂度为

以上复杂度分析参考张铭等《数据结构与算法》,推导过程其实并不重要,关键在于我们可以肯定的是建堆是很快的,最多是线性的。

建好堆之后,如何实现堆排序呢?排序之前,我们先看有关堆的两个操作:插入和删除。理解了这两个操作,排序就自然清楚了。

堆的插入:插入时总是把新节点插入到堆的最后,并从插入位置向上调整,直到根节点或在此之前已满足堆序。

举个例子解释下这个过程:

红色的3是新添的节点。

注意:向上调整的时候,只关注插入位置到根节点的路径,其它路径上的节点是不用调整的。理由很简单:它们已是堆序。这一点可要想清楚了!

向上调整的代码如下:

const int MAX=20;
void heapSiftUp(int a[], int n)   //向上调整
{
	int i,j;
	j = n-1;
	i = (i-1)/2;   //i为j的父节点
	while(i>=0)
	{
		if(a[j] >= a[i])
		break;
		swap(a[i], a[j]);
		j = i;
		i = (j-1)/2;  //更精确的写法: i=j%2?(j-1)/2:(j-2)/2;
	}
}
void addToHeap(int a[], int n, int data)
{
	/*
	前提:数组a已排好堆序且数组还有多余位置存放新节点
	*/
	if(n+1>MAX)
	{
		printf("数组已满!无法插入\n");
		return;
	}
	n++;
	a[n-1]=data;  //把新节点加到最后
	heapSiftUp(a, n);
}

堆的删除:删除操作总是在堆顶进行(也有的说,可以在任意位置删除,但做法一样),我们把最后一个节点填入待删除位置。然后从该位置向下调整

同样给个示例图:

结合上面以给出的向下调整代码,则很好得到堆删除的代码,为了通用性,我们给出指定位置删除的代码:

void deleteAt(int a[], int &n, int pos)  //删除pos位置的节点
{
	if(pos >= n)
	{
		printf("删除的位置不对!\n");
		return;
	}
	a[pos] = a[n-1];  //把最后一个节点填到待删除位置
	n--;
	heapSiftDown(a, n, pos);   //向下调整
} 

特别地,删除堆顶就是 deleteAt(a, n, 0);

有了上面的铺垫,堆排序就呼之欲出了。

堆排序步骤

  1. 先建好堆。
  2. 不断地删除堆顶即可(删除前记得打印堆顶元素),直到只剩下一个元素。

似乎堆的插入操作没有用到。其实,当有新的元素加入到一个已建好堆序的序列中,就用到了。

下面看一个完整的堆排序代码:

#include<iostream>
using namespace std;
void heapSiftDown(int a[], int n, int pos)   //从pos位置向下调整
{
	int i = pos;
	int j = 2 * i + 1;   //j为i的左孩子
	while (j<n)
	{
		if (j + 1 < n && a[j + 1]<a[j])   //如果右孩子存在,并且右孩子<左孩子
			j++;
		if(a[i] < a[j])   //已满足堆序,不需调整
			break;   //为什么不是continue?因为子树已经排好堆序
		swap(a[i], a[j]);   //交换元素
		i = j;
		j = 2 * i + 1;
	}
}
void createHeap(int a[], int n)   //建堆
{
	if (a && n > 1)
	{
		for (int i = (n - 2) / 2; i >= 0; i--)
			heapSiftDown(a, n, i);
	}
}
void deleteAt(int a[], int &n, int pos)  //删除pos位置的节点
{
	if(pos >= n)
	{
		printf("删除的位置不对!\n");
		return;
	}
	a[pos] = a[n-1];  //把最后一个节点填到待删除位置
	n--;
	heapSiftDown(a, n, pos);   //向下调整
}
void HeapSort(int a[], int n)    //堆排序
{
	if (a && n > 1)
	{
		createHeap(a, n);
		while (n > 1)
		{
			printf("%4d", a[0]);
			deleteAt(a, n, 0);
		}
		printf("%4d\n", a[0]);
	}
}
int main()
{
	printf("堆排序演练\n");
	printf("原序列\n");
	const int N = 12;
	int *a = new int[N];
	srand((unsigned)time(NULL));
	for (int i = 0; i < N; i++)
	{
		a[i] = rand() % 100;
		printf("%4d", a[i]);
	}
	printf("\n");
	printf("经过堆排序\n");
	HeapSort(a, N);
	delete[]a;
	system("pause");
	return 0;
}

运行:

转载请注明出处,本文地址:http://blog.csdn.net/zhangxiangdavaid/article/details/30069623

若是有所帮助,顶一个哦!

专栏目录看这里:数据结构与算法目录

选择排序:堆排序,布布扣,bubuko.com

时间: 2024-12-11 18:39:40

选择排序:堆排序的相关文章

排序 选择排序&amp;&amp;堆排序

选择排序&&堆排序 1.选择排序: 介绍:选择排序(Selection sort)是一种简单直观的排序算法.它的工作原理如下.首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾.以此类推,直到所有元素均排序完毕. 步骤:假设数组array长度为N即有数组内有N个数据未排序数据 1.第一趟遍历将这N个数据中最小的数据和array[0]交换. 2.第二趟则遍历N-1个数据,将这N-1个数据中最小的和arra

八大排序算法之四选择排序—堆排序(Heap Sort)

堆排序是一种树形选择排序,是对直接选择排序的有效改进. 基本思想: 堆的定义如下:具有n个元素的序列(k1,k2,...,kn),当且仅当满足 时称之为堆.由堆的定义可以看出,堆顶元素(即第一个元素)必为最小项(小顶堆). 若以一维数组存储一个堆,则堆对应一棵完全二叉树,且所有非叶结点的值均不大于(或不小于)其子女的值,根结点(堆顶元素)的值是最小(或最大)的.如: (a)大顶堆序列:(96, 83,27,38,11,09) (b)  小顶堆序列:(12,36,24,85,47,30,53,91

选择排序—堆排序(Heap Sort) 没看明白,不解释

堆排序是一种树形选择排序,是对直接选择排序的有效改进. 基本思想: 堆的定义如下:具有n个元素的序列(k1,k2,...,kn),当且仅当满足 时称之为堆.由堆的定义可以看出,堆顶元素(即第一个元素)必为最小项(小顶堆). 若以一维数组存储一个堆,则堆对应一棵完全二叉树,且所有非叶结点的值均不大于(或不小于)其子女的值,根结点(堆顶元素)的值是最小(或最大)的.如: (a)大顶堆序列:(96, 83,27,38,11,09) (b)  小顶堆序列:(12,36,24,85,47,30,53,91

选择排序---简单选择排序 堆排序

一.简单选择排序 对于n个数要进行n次排序,第一次,将最小的数放在第一个.第二次,将第二小的树,放在第二个.... 每次都和后面的数做比较,如果是从小到大的排序,当当前的数字比后面的大时,要进行交换. #include <stdio.h> void chosesort(int a[],int length) { int i,j,temp; for(i=0;i<length;i++) for(j=i+1;j<length;j++) { if(a[i]>a[j]) { temp

内部排序-&gt;选择排序-&gt;堆排序

文字描述 堆排序中,待排序数据同样可以用完全二叉树表示, 完全二叉树的所有非终端结点的值均不大于(或小于)其左.右孩子结点的值.由此,若序列{k1, k2, -, kn}是堆,则堆顶元素(或完全二叉树的根)必为序列中n个元素的最小值(或最大值). 若在输出堆顶的最小值之后,使得剩余n-1个元素的序列重又建成一个堆,则得到n个元素中的次小值.如此反复执行,便能得到一个有序序列,这个过程称之为堆排序. 由此,实现堆排序需要解决两个问题:(1)如何由一个无序序列建成一个堆?(2)如何在输出堆顶元素之后

排序01---[排序基本知识&amp;&amp;冒泡排序&amp;&amp;选择排序&amp;&amp;堆排序]

1.排序基本知识 1.1初始排序 1.2十大排序算法 2.冒泡排序(Bubble Sort) 2.1Baseline static void bubbleSort1(Integer[] array) { for (int end = array.length - 1; end > 0; end--) { for (int begin = 1; begin <= end; begin++) { if (array[begin] < array[begin - 1]) { int tmp =

4.3_8种常用排序算法3(选择排序:简单选择排序+堆排序)

[简单选择排序] package com.sort.demo3; import java.util.Arrays; /** * 简单选择排序 */ public class SelectSort { public static void main(String[] args) { int[] arr = new int[]{1,4,5,7,3,9,8,0,2,6}; System.out.println(Arrays.toString(arr)); selectSort(arr); System

选择排序---堆排序算法(Javascript版)

堆排序分为两个过程: 1.建堆. 堆实质上是完全二叉树,必须满足:树中任一非叶子结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字. 堆分为:大根堆和小根堆,升序排序采用大根堆,降序排序采用小根堆. 如果是大根堆,则通过调整函数将值最大的节点调整至堆根. 2.将堆根保存于尾部,并对剩余序列调用调整函数,调整完成后,再将最大跟保存于尾部-1(-1,-2,...,-i),再对剩余序列进行调整,反复进行该过程,直至排序完成. 以下代码在nodejs中执行通过 //调整函数function

选择排序---堆排序算法(Javascript版) 降序排列

//调整函数 function headAdjust(elements, pos, len){ //将当前节点值进行保存 var swap = elements[pos]; //定位到当前节点的左边的子节点 var child = pos * 2 + 1; //递归,直至没有子节点为止 while(child < len){ //如果当前节点有右边的子节点,并且右子节点较小的场合,采用右子节点 //和当前节点进行比较 if(child + 1 < len && elements

排序算法(二)选择排序---堆排序

概念:利用树结构进行排序. 分类:1.大顶堆: 每个小树的根节点都大于子节点   升序排序使用大顶堆 2.小顶堆:每个小树的子节点都大于根节点 降序排序使用小顶堆 1 public class HeapSort { 2 3 public static void main(String[] args){ 4 int[] arr=new int[]{9,6,7,0,1,10,4,2}; 5 System.out.println(Arrays.toString(arr)); 6 heapSort(ar