Binary Heap(二叉堆) - 堆排序
这篇的主题主要是Heapsort(堆排序),下一篇ADT数据结构随笔再谈谈 - 优先队列(堆)。
首先,我们先来了解一点与堆相关的东西。堆可以实现优先队列(Priority Queue),看到队列,我们马上就想到了队列的一个特点 - 先进先出(FIFO - first in first out),但优先队列有些不同的地方,优先队列是一种具有优先级先出的数据结构。
堆的结构:
typedef int ElemType; typedef struct
{
ElemType * arr;
int size;
}Heap;
和数组栈的结构类似。
初始化堆:
Heap * InitHeap(ElemType * nums, int ArraySize)
{
Heap * heap; heap = (Heap *)malloc(sizeof(Heap));
heap -> arr = (ElemType *)malloc(sizeof(ElemType));
for(heap -> size = 0; heap -> size < ArraySize; heap -> size ++)
heap -> arr[heap -> size] = nums[heap -> size]; return heap;
}
找节点的关系:
/*
* 节点i的双亲
*/
static int HeapParent(int i)
{
return i/2;
} /*
* 节点i的左孩子
*/
static int HeapLeft(int i)
{
return 2*i + 1;
} /*
* 节点i的右孩子
*/
static int HeapRight(int i)
{
return 2*(i + 1);
}
这里要保持的是一个大根堆:
/*
* 维护的最大堆(或最小堆)的性质
* 参数说明:
* 1.接收一个已存在的堆
* 2.节点位置
* 无返回值
*/
void Max_Heapify(Heap * heap, int i)
{
int _L = HeapLeft(i);
int _R = HeapRight(i);
int largest;
int temp; if(_L < heap -> size && heap -> arr[_L] > heap -> arr[i])
largest = _L;
else
largest = i;
if(_R < heap -> size && heap -> arr[_R] > heap -> arr[largest])
largest = _R;
if(largest != i)
{
temp = heap -> arr[i];
heap -> arr[i] = heap -> arr[largest];
heap -> arr[largest] = temp;
Max_Heapify(heap, largest);
}
}
建大根堆:
static void Build_Max_Heap(Heap * heap)
{
int i; for(i = heap -> size/2; i >= 0; i--)
Max_Heapify(heap, i);
}
排序:
void HeapSort(Heap * heap)
{
int i;
int temp; //维护堆,把初始建立的混乱的堆变为大根堆,以便进行排序
Build_Max_Heap(heap);
for(i = heap -> size - 1; i >= 0; i--)
{
temp = heap -> arr[0];
heap -> arr[0] = heap -> arr[i];
heap -> arr[i] = temp;
-- heap -> size; //每次把具有最小值的节点交换到根节点上,则把需要维护的堆的大小减小
Max_Heapify(heap, 0); //交换根节点后的堆的性质变了,需要不断维护
}
}
至此,用二叉堆对一个数组的排序就完成了。
然后,测试一下:
int main()
{
Heap * heap;
ElemType nums[] = {5, 13, 2, 25, 7, 17, 20, 8, 4}, * arr = nums;
int arrSize = sizeof(nums)/sizeof(int);
int i; heap = InitHeap(arr, arrSize); HeapSort(heap); for(i = 0; i < arrSize; i++)
printf("%d ", heap -> arr[i]);
printf("\n"); return 0;
}
运行结果:
2 4 5 7 8 13 17 20 25
完整的堆排序代码:
/**
* 堆排序(Heap Sort)
*/
#include <stdio.h>
#include <stdlib.h> typedef int ElemType; typedef struct
{
ElemType * arr;
int size;
}Heap; Heap * InitHeap();
static int HeapParent();
static int HeapLeft();
static int HeapRight();
void Max_Heapify();
static void Build_Max_Heap();
void HeapSort(); /*
* 初始化堆
*
* 参数说明:
* 1.传入数组
* 2.数组大小
*
* 返回值:堆
*/
Heap * InitHeap(ElemType * nums, int ArraySize)
{
Heap * heap; heap = (Heap *)malloc(sizeof(Heap));
heap -> arr = (ElemType *)malloc(sizeof(ElemType));
for(heap -> size = 0; heap -> size < ArraySize; heap -> size ++)
heap -> arr[heap -> size] = nums[heap -> size]; return heap;
} /*
* 某节点i的双亲
*/
static int HeapParent(int i)
{
return i/2;
} /*
* 某节点i的左孩子
*/
static int HeapLeft(int i)
{
return 2*i + 1;
} /*
* 某节点i的右孩子
*/
static int HeapRight(int i)
{
return 2*(i + 1);
} /*
* 维护堆
*/
void Max_Heapify(Heap * heap, int i)
{
int _L = HeapLeft(i);
int _R = HeapRight(i);
int largest;
int temp; if(_L < heap -> size && heap -> arr[_L] > heap -> arr[i])
largest = _L;
else
largest = i;
if(_R < heap -> size && heap -> arr[_R] > heap -> arr[largest])
largest = _R;
if(largest != i)
{
temp = heap -> arr[i];
heap -> arr[i] = heap -> arr[largest];
heap -> arr[largest] = temp;
Max_Heapify(heap, largest);
}
} /*
* 建堆
*/
static void Build_Max_Heap(Heap * heap)
{
int i; for(i = heap -> size/2; i >= 0; i--)
Max_Heapify(heap, i);
} /*
* 排序
*/
void HeapSort(Heap * heap)
{
int i;
int temp; Build_Max_Heap(heap); //维护堆,把初始建立的混乱的堆变为大根堆,以便进行排序
for(i = heap -> size - 1; i >= 0; i--)
{
temp = heap -> arr[0];
heap -> arr[0] = heap -> arr[i];
heap -> arr[i] = temp;
-- heap -> size; //每次把具有最小值的节点交换到根节点上,则把需要维护的堆的大小减小
Max_Heapify(heap, 0); //交换根节点后的堆的性质变了,需要不断维护
}
} int main(void)
{
Heap * heap;
ElemType nums[] = {5, 13, 2, 25, 7, 17, 20, 8, 4}, * arr = nums;
int arrSize = sizeof(nums)/sizeof(int);
int i; heap = InitHeap(arr, arrSize); HeapSort(heap); for(i = 0; i < arrSize; i++)
printf("%d ", heap -> arr[i]);
printf("\n"); return 0;
}
这里的堆显然是一个二叉堆,因此可以把它想象成二叉树的样子来理解,关于整个堆排序的过程,它的时间复杂度为O(nlgn),因为调用Build_Max_Heap()的代价为O(n),而n - 1次调用Max_Heapify()的代价为O(lgn)。
但至于为什么Build_Max_Heap()函数的时间复杂的只有O(n)...看起来应该是O(nlgn),我也还没完全理解这里的时间复杂度的计算...留个坑慢慢理解...懂了之后再补充。
前面提到了堆的结构型和数组栈相似,实际上,堆与队列和栈有很大的关系,我们知道队列是FIFO,栈是FILO,从上面堆的维护操作等就可以看出,堆还有着具有优先级的先出的特性(注意与栈并不同),优先队列是基于最大/小堆实现的,虽然名字中有个队列,但区别还是很大的。
Binary Heap(二叉堆) - 堆排序的更多相关文章
- 堆(Heap)和二叉堆(Binary heap)
堆(Heap) The operations commonly performed with a heap are: create-heap: create an empty heap heapify ...
- 二叉堆(binary heap)
堆(heap) 亦被称为:优先队列(priority queue),是计算机科学中一类特殊的数据结构的统称.堆通常是一个可以被看做一棵树的数组对象.在队列中,调度程序反复提取队列中第一个作业并运行,因 ...
- 二叉堆(binary heap)—— 优先队列的实现
二叉堆因为对应着一棵完全二叉树,因而可以通过线性数组的方式实现. 注意,数组第 0 个位置上的元素,作为根,还是第 1 个位置上的元素作为根? 本文给出的实现,以数组第 1 个位置上的元素作为根,则其 ...
- 数据结构 之 二叉堆(Heap)
注:本节主要讨论最大堆(最小堆同理). 一.堆的概念 堆,又称二叉堆.同二叉查找树一样,堆也有两个性质,即结构性和堆序性. 1.结构性质: 堆是一棵被完全填满的二叉树,有可能的 ...
- Java实现的二叉堆以及堆排序详解
一.前言 二叉堆是一个特殊的堆,其本质是一棵完全二叉树,可用数组来存储数据,如果根节点在数组的下标位置为1,那么当前节点n的左子节点为2n,有子节点在数组中的下标位置为2n+1.二叉堆类型分为最大堆( ...
- 【425】堆排序方法(二叉堆)优先队列(PQ)
参考:漫画:什么是二叉堆? 大根堆 小根堆 参考:漫画:什么是堆排序? 参考:漫画:什么是优先队列? 参考:[video]视频--第14周10--第8章排序10--8.4选择排序3--堆排序2--堆调 ...
- python下实现二叉堆以及堆排序
python下实现二叉堆以及堆排序 堆是一种特殊的树形结构, 堆中的数据存储满足一定的堆序.堆排序是一种选择排序, 其算法复杂度, 时间复杂度相对于其他的排序算法都有很大的优势. 堆分为大头堆和小头堆 ...
- C# 实现简单的 Heap 堆(二叉堆)
如题,C# 实现简单的二叉堆的 Push() 和 Pop(), 如有不足欢迎指正. 另外,在C#中使用 Heap 的相似功能可以考虑使用:Priority Queues,SortedDictiona ...
- 二叉堆 及 大根堆的python实现
Python 二叉堆(binary heap) 二叉堆是一种特殊的堆,二叉堆是完全二叉树或者是近似完全二叉树.二叉堆满足堆特性:父节点的键值总是保持固定的序关系于任何一个子节点的键值,且每个节点的左子 ...
随机推荐
- 安全文件传输协议之SFTP的使用
一.SFTP概述 在前几篇文章,我们讲到了文件传输协议FTP(File Transfer Protocol),那也是使用比较广泛的文件服务器,但是我们需要知道,Linux系统并不自带FTP程序 如果要 ...
- JVM探秘:VisualVM监控远程Java进程
VisualVM在Java 8中是JDK自带的一个图形化工具,项目主页 VisualVM,在后续版本中可能会从JDK移除. VisualVM可以监控Java进程的CPU与内存占用情况,可以监控Java ...
- 深度学习之tensorflow框架(上)
import tensorflow as tf import os os.environ[' def tensorflow_demo(): #原生python加法运算 a = 2; b=3; c=a+ ...
- Nexus坑人系列-license格式问题
这种情况一般出现在RMA或者新设备使用的时候.这些时候一般需要安装license,在安装完license的时候,例如我们去配置一些三层特性,例如feature eigrp等,可能会出现设备拒绝了你的命 ...
- oracle-数据库被注入恶意攻击程序的案例恢复
问题描述: Oracle数据库由于重启之后无法正常启动,tab$被清空(ORA-600 16703故障解析—tab$表被清空),导致数据库启动异常 ORA-600 16703报错 一.检测方法: 如下 ...
- 并发编程之CyclicBarrier
栅栏类似于闭锁,它能阻塞一组线程直到某个事件的发生.栅栏与闭锁的关键区别在于,所有的线程必须同时到达栅栏位置,才能继续执行.闭锁用于等待事件,而栅栏用于等待其他线程. CyclicBarrier可以使 ...
- 在Docker中使用Microsoft SQL Server数据库
下图中对SQL Server容器创建及数据库创建等操作进行了记录,方便自己日后查看.(文中的 * 仅表示隐藏自己的个人信息,手动马赛克,哈哈-) Docker下载可看上一篇博文mac系统,docker ...
- 寒假安卓app开发学习记录(3)
今天终于开始正式的安卓软件开发学习.开始用了大约一个小时的时间把创建第一个软件的学习视频观看了一下.跟着视频一边学习一边操作. 首先是创建项目,创建的过程和之前创建Java项目的过程相似.先给app起 ...
- jquery实现登录后右下角弹窗提醒(附带简单样式)
页面代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://w ...
- Petr and a Combination Lock
Petr has just bought a new car. He's just arrived at the most known Petersburg's petrol station to r ...