堆排序 原理与实现

参考一

堆排序

估计还要问问:什么是堆,什么是堆排序?堆与计算机分配内存的堆相同吗?
很多资料给出:堆的定义是
(1)、n个关键字序列(Kl,K2,…,Kn)称为(Heap),当且仅当该序列满足如下性质(简称为堆性质):
ki≤K2i且ki≤K2i+1 或  Ki≥K2i且ki≥K2i+1 (1≤i≤ n) //ki相当于二叉树的非叶结点,K2i则是左孩子,k2i+1是右孩子 

(2)若将此序列所存储的向量R[1..n]看做是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:

  树中任一非叶结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。

(3)、根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者的堆称为小根堆,又称最小堆。根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆,又称最大堆。

(4)、堆中任一子树亦是堆。
(5)、堆可以视为一棵完全的二叉树,完全二叉树的一个“优秀”的性质是,除了最底层之外,每一层都是满的,这使得堆可以利用数组来表示(普通的一般的二叉树通常用链表作为基本容器表示),每一个结点对应数组中的一个元素。

那么堆排序呢?
堆排序其实就是将一组无序的序列变成堆的过程、下面我们一起看看堆排序的算法。
2、堆排序
以最大堆为例,步骤为:
  ① 先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区

  ② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key

  ③由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,同样要将R[1..n-2]调整为堆。

  ……

  直到无序区只有一个元素为止。  
将一个无序的序列建成堆的过程实际上是一个反复筛选的过程。若将此序列看作是一棵完全二叉树,则最后一个非终端结点是[n/2](不大于n/2的整数),因此筛选过程只需从[n/2]开始。要建成一个大顶堆,即先选择一个值最大的元素并与序列中的最后一个元素交换,然后对序列前n-1元素进行筛选,从新调整为一个大顶堆,直到结束。 
算法描述如下: 
  
typedef   int[]   Elem;//采用顺序存储表示完全二叉树 

void   HeapAdjust(Elem   &e,int   s,int   m) 
{//e中s...m的元素除e[s]之外已经满足堆的定义(e[i] <=e[2*i]&&e[i] <=e[2*i+1] 
  //或e[i]> =e[2*i]&&e[i]> =e[2*i+1]),调整e[s]使的e[s...m]成为一个大顶堆。 
      selem=e[s]; 
      for(i=2*s;i <=m;i*=2)//沿着值较大的元素(结点)向下筛选 
      { 
            if(i <m   &&   e[i] <e[i+1])++i;//i为值较大的元素下标 
            if(selem> =e[i])break; 
            e[s]=e[i]; 
            s=i; 
        } 
      e[s]=selem; 


void   HeapSort(Elem   &e) 
{//对顺序表排序 
    for(i=e.length/2;i> 0;--i) 
            HeapAdjust(e,i,e.length); 
    for(i=e.length;i> 1;--i) 
    { 
          temp=e[1];                     //将堆中的最后一个元素与第一个元素交换 
          e[1]=e[i]; 
          e[i]=temp; 
          HeapAdjust(H,1,i-1);//调整1..i-1的元素成为大顶堆 
      }     
}


/
使用堆排序注意的问题:::
1、序列是从1开始的。,注意定义,R[1..n],故要空出R[0]。
2、堆排序算法分为两部分:建立大顶堆和排序。
3、排序的最坏时间复杂度为O(nlogn)。堆序的平均性能较接近于最坏性能。
4、由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。

5、 堆排序是就地排序,辅助空间为O(1), 它是不稳定的排序方法。

问题:当序列空间为R[0,1....n];时,该怎么去使用堆排序呢?元素后移一位,这明显不是一个好方法。

 

参考二

堆排序(C/C++):  

2011-05-19 15:06:52|  分类: 代码分享 |  标签:堆排序   |字号 订阅

“堆”定义
  n个关键字序列Kl,K2,…,Kn称为(Heap),当且仅当该序列满足如下性质(简称为堆性质):   
  (1) ki≤K2i且ki≤K2i+1 或(2)Ki≥K2i且ki≥K2i+1(1≤i≤ n) //ki相当于二叉树的非叶结点,K2i则是左孩子,k2i+1是右孩子

  大根堆和小根堆:
  根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最小者的堆称为小根堆,又称最小堆。
  根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆,又称最大堆

  堆排序利用了大根堆(或小根堆)堆顶记录的关键字最大(或最小)这一特征,
  使得在当前无序区中选取最大(或最小)关键字的记录变得简单。   
  (1)用大根堆排序的基本思想   
  ① 先将初始文件R[1..n]建成一个大根堆,此堆为初始的无序区   
  ② 再将关键字最大的记录R[1](即堆顶)和无序区的最后一个记录R[n]交换,
  由此得到新的无序区R[1..n-1]和有序区R[n],且满足R[1..n-1].keys≤R[n].key   
  ③由于交换后新的根R[1]可能违反堆性质,故应将当前无序区R[1..n-1]调整为堆。
  然后再次将R[1..n-1]中关键字最大的记录R[1]和该区间的最后一个记录R[n-1]交换,
  由此得到新的无序区R[1..n-2]和有序区R[n-1..n],且仍满足关系R[1..n-2].keys≤R[n-1..n].keys,
  同样要将R[1..n-2]调整为堆。   
  ……   
  直到无序区只有一个元素为止。

 

代码:

堆排序: - 王者归来 - df19861017@126.com

 

堆排序: - 王者归来 - df19861017@126.com

 

参考三

C++堆排序

    堆排序堆排序是使用堆这个数据结构作为基础的,是一种在最坏情况下时间复杂度可以达到O(nlogn)的排序算法,堆是优先队列的基础,应用情况很广泛,比如可以把爬虫URL队列存放在一个优先队列中,每次计算每个URL的权值,下一次抓取的网页对应的是当前队列中权值最大的URL。
 
为了简单起见,本文只对整型数处理,其它的可以通过模板进行扩展。这里实现的是最大堆,最小堆同理。
 
/*************************** 头文件 Heap.h ***************************/

#ifndef _HEAP_H

#define _HEAP_H

#include <memory.h>

class CHeap

{

public:

CHeap(int heapCapacity) : _mHeapCapacity(heapCapacity)

{

_mHeap = new int[_mHeapCapacity];

memset(_mHeap, 0, _mHeapCapacity);

}

~CHeap() { delete[] _mHeap; }

void BuildMaxHeap(const char *filePath);

void MaxHeapify(int i);

void HeapSort();

void PrintResult();

private:

int _mHeapSize;

int _mRealSize;

int _mHeapCapacity;

int *_mHeap;

int LeftChild(int i) { return 2 * i + 1; }

int RightChild(int i) { return 2 * (i + 1); }

void Swap(int &a, int &b) { int tmp = a; a = b; b = tmp; }

};

#endif

 

/*********************** Heap.cpp ***********************/

#include "Heap.h"

#include <iostream>

#include <fstream>

#include <string>

using namespace std;

int main()

{

const char *filePath = "data.txt";

CHeap heap(20);

heap.BuildMaxHeap(filePath);

heap.HeapSort();

heap.PrintResult();

return 0;

}

void CHeap::BuildMaxHeap(const char *filePath)

{

ifstream infile(filePath);

if (infile)

{

//cout << "Open file succeed." << endl;

string line;

int i = 0;

while (infile >> line)

{

_mHeap[i] = atoi(line.c_str());

++i;

if (i > _mHeapCapacity)

break;

}

_mHeapSize = _mRealSize = i;

infile.close(); // close the file descriptor

for (i = (_mHeapSize - 2) / 2; i >= 0; --i)

{

MaxHeapify(i);

}

}

}

void CHeap::MaxHeapify(int i)

{

int left = LeftChild(i);

int right = RightChild(i);

int max = i;

if (left < _mHeapSize && _mHeap[max] < _mHeap[left])

{

max = left;

}

if (right < _mHeapSize && _mHeap[max] < _mHeap[right])

{

max = right;

}

if (max != i)

{

Swap(_mHeap[i], _mHeap[max]);

MaxHeapify(max);

}

}

void CHeap::HeapSort()

{

for (int i = _mHeapSize - 1; i > 0; --i)

{

Swap(_mHeap[0], _mHeap[i]);

--_mHeapSize;

MaxHeapify(0);

}

}

void CHeap::PrintResult()

{

cout << "Total number of digits is: " << _mRealSize << endl;

for (int i = 0; i < _mRealSize; ++i)

{

cout << _mHeap[i] << endl;

}

}

 

/// data.txt //

23

34

56

...

...

 

堆排序的算法原理很简单,但实际应用却很广泛。

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值