百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

程序员必知的十大基础实用算法之-堆排序算法

toyiye 2024-06-21 12:02 13 浏览 0 评论

简介


堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

堆排序算法原理和动态图解


将待排序的序列构造成一个大顶堆。此时,整个序列的最大值就是堆顶的根节点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次最大值。如此反复执行,就能得到一个有序序列了。这个过程其实就是先构建一个最大/最小二叉堆,然后不停的取出最大/最小元素(头结点),插入到新的队列中,以此达到排序的目的。

堆的定义以及堆的数据模型图


堆的典型数据模型就是一个二叉堆, 如下图,是一个最大二叉堆:

最大二叉堆的定义:

1、最顶端的肯定是最大的值

2、父节点的值总是大于子节点的值。

3、是一个完全二叉树,它的层高相差不超过1,叶子结点总是从左向右依次填满。

相应的,如果根节点是最小值,就是最小二叉堆。只要满足上面三个条件即可,在上图中,13的层级比17要高一层,这并没有违反规则,是可以的。

为什么使用堆这种数据结构


优先队列是一种很常用的队列,比如在游戏中,游戏角色在自动模式下,如何在周围一堆小怪中自动攻击某一个小怪?可能是判断这一群小怪哪一个比较近,就攻击哪一个,或者哪一个等级低,就攻击哪一个。总之,是会动态的计算周围小怪的优先级,然后攻击优先级最高的那一个小怪。

堆 这一种数据结构,就是典型的动态优先队列。

另外,如果要从1000000个元素中选出排序前100的元素,如何选出来呢?从N个元素中取出前M个元素?如果用前面介绍到的算法,只能先排序(时间复杂度为NlogN),然后取出前一百元素,如果使用堆排序,那么时间复杂度将降为NlogM. 时间效率将极大提升。

堆排序动态示例图


数据的添加


在堆中添加一个元素,相当于在数组的末尾添加了一个元素,这个元素的位置暂时是最后一个,然后判断是否满足最大堆的定义,如果它比父节点要大,则需要把这个数和父节点交换。交换以后,如果在当前位置还要比父节点大,则继续向上交换,直到合适的位置,所以我们需要定义一个向上移动的函数shiftUp();

public void insert(Item item) {
	if(count == capcity) {
		return;//已经填满了,无法再填入数据了
	}
	data[count+1] = item;
	count ++;//记录总共有多少个数据
	ShiftUp(count);//执行向上移动
}
/**
 * 将k这个位置的元素 调整到堆的合适位置,
 * 
 * 最大堆的特点,父节点大于子节点
 * 完全二叉树特点,总是从左到右,依次填满二叉树的子节点
 */
private void ShiftUp(int k) {
	//用数组存储完全二叉堆,如果根节点的序列化声明为1,则左节点的序号都是父节点的二倍,看图理解,右节点是父节点的二倍+1
	//父节点如果小于子节点,就要交换位置, 注意边界问题,所以k>1
	while(k > 1 && data[k/2].compareTo(data[k]) < 0) {
		swap( k, k/2);
		k /= 2;
	}
}
/**
 * 交换数组中两个值的位置
 */
private void swap(int i, int j) {
	Item temp = data[i];
	data[i] = data[j];
	data[j] = temp;
}

最大数据的取出(数据的删除)


当需要取出优先级最高的数时,即需要取出二叉堆最顶端的数时,我们通常的操作就是将最顶端(脚标为1的数)和数组中最后一个数交换位置,然后移除左后一个元素即可。但是,此时,就不满足最顶端的数时最大数这个规则了,因此此时需要把顶端这个数向下移动(shiftDown), 它需要和左右两个孩子比较,和最大的孩子进行交换,如果交换位置后,它的子孩子还要大于它,则继续和左右孩子中的最大值进行交换,直到移动到合适位置。

/**
 * 取出最大元素
 */
public Item extractMax() {
	if(count == 0) {
		return null;
	}
	Item item = data[1];//堆中第一个元素就是最大值
	swap(1, count);//将最后一个元素放到第一个位置
	count --;//总的数量减一
	shiftDown(1);//重新调整整个堆,成为一个新的最大二叉堆
	return item;
}
/**
 * 将元素向下移动,直到找到合适的位置
 */
private void shiftDown(int i) {
	while(i*2 <= count) {//说明有左孩子
		int j = i*2;//左孩子的脚标位置
		if(j+1 <= count && data[j+1].compareTo(data[j]) > 0) {
			//如果有右孩子,并且,右孩子比左孩子的值大
			j = j+1;
		}
		if(data[i].compareTo(data[j]) > 0) {
			break;//如果父节点大于 最大的子节点,则已经是新的最大二叉堆
		}
		swap(i, j);//否则,则交换父节点和最大子节点的位置,继续向下比较
		i = j;//更新这个值的最新位置
	}
	
}

堆化(Heapify)


果我们传入一个整个数组,数组中有一系列数据,那么我们就可以将这个数组形成一个堆的结构,这个过程称为堆化(Heapify)。

传入整个数组Heapify的算法复杂度比直接一个一个插入数据的效率更高

public MaxHeap(Item[] arr) {
	this.capcity = arr.length;
	//warnningValue = (n * 10) / 9;
	data = (Item[]) new Comparable[capcity + 1];
	System.arraycopy(arr, 0, data, 1, arr.length);
	count = arr.length;
	
	for(int i = count/2; i >= 1; i --) {
		shiftDown(i);
	}
}

堆排序Java代码实现


import java.util.Arrays;

/**

* 最大堆, 此处使用了泛型,

* @author admin

*

*/

public class MaxHeap<Item extends Comparable> {

protected Item[] data;

protected int count;

private int capcity;

private int warnningValue;

private byte[] lock = new byte[0];

public MaxHeap(int capcity){

this.capcity = capcity;

warnningValue = (capcity * 10) / 9;

//java不允许直接创建泛型数组,否则编译报错,所以通过强转来通过编译

//第0个位置不存储,从第一个位置开始存储,所以空一个位置

data= (Item[]) new Comparable[capcity+1];

count = 0;

}

public MaxHeap(Item[] arr) {

this.capcity = arr.length;

//warnningValue = (n * 10) / 9;

data = (Item[]) new Comparable[capcity + 1];

System.arraycopy(arr, 0, data, 1, arr.length);

count = arr.length;

for(int i = count/2; i >= 1; i --) {

shiftDown(i);

}

}

public int size() {

return count;

}

public boolean isEmpty() {

return count == 0;

}

public void destory() {

data = null;

count = 0;

}

//此方法可以学习时可以忽略,是insert方法的改良版, 参考了ArrayList插入数据的扩容功能。

public void insert2(Item item) {

//如果数量大于警戒值了,就是快要满了,此时扩容,考虑到多线程,没有使用 == capcity来判断

if(count >= warnningValue) {

synchronized (lock) {

if(count >= warnningValue) {

capcity = capcity * 2;

warnningValue = (capcity * 10) / 9;

//第0个位置不存储,从第一个位置开始存储,所以空一个位置

Item[] temp = (Item[]) new Comparable[capcity+1];

//方法二: System.arraycopy(src, srcPos, dest, destPos, length)

//src: 源数组

//srcPos: 从源数组复制数据的起始位置

//dest: 目标数组

//destPos: 复制到目标数组的起始位置

//length:数组的元素个数

System.arraycopy(data, 0, temp, 0, data.length);

data = temp;

}

}

}

data[count+1] = item;

count ++;

ShiftUp(count);

}

public void insert(Item item) {

if(count == capcity) {

return;//已经填满了,无法再填入数据了

}

data[count+1] = item;

count ++;

ShiftUp(count);

}

/**

* 取出最大元素

*/

public Item extractMax() {

if(count == 0) {

return null;

}

Item item = data[1];//堆中第一个元素就是最大值

swap(1, count);//将最后一个元素放到第一个位置

count --;//总的数量减一

shiftDown(1);//重新调整整个堆,成为一个新的最大二叉堆

return item;

}

/**

* 将元素向下移动,直到找到合适的位置

*/

private void shiftDown(int i) {

while(i*2 <= count) {//说明有左孩子

int j = i*2;//左孩子的脚标位置

if(j+1 <= count && data[j+1].compareTo(data[j]) > 0) {

//如果有右孩子,并且,右孩子比左孩子的值大

j = j+1;

}

if(data[i].compareTo(data[j]) > 0) {

break;//如果父节点大于 最大的子节点,则已经是新的最大二叉堆

}

swap(i, j);//否则,则交换父节点和最大子节点的位置,继续向下比较

i = j;//更新这个值的最新位置

}

}

/**

* 将k这个位置的元素 调整到堆的合适位置,

*

* 最大堆的特点,父节点大于子节点

* 完全二叉树特点,总是从左到右,依次填满二叉树的子节点

*/

private void ShiftUp(int k) {

//用数组存储完全二叉堆,如果根节点的序列化声明为1,则左节点的序号都是父节点的二倍,看图理解,右节点是父节点的二倍+1

//父节点如果小于子节点,就要交换位置, 注意边界问题,所以k>1

while(k > 1 && data[k/2].compareTo(data[k]) < 0) {

swap( k, k/2);

k /= 2;

}

}

/**

* 交换数组中两个值的位置

*/

private void swap(int i, int j) {

Item temp = data[i];

data[i] = data[j];

data[j] = temp;

}

}

堆排序C语言代码实现


#include <stdio.h>
#include <stdlib.h>
 
void swap(int* a, int* b) {
 int temp = *b;
 *b = *a;
 *a = temp;
}
 
void max_heapify(int arr[], int start, int end) {
 //建立父节点指标和子节点指标
 int dad = start;
 int son = dad * 2 + 1;
 while (son <= end) { //若子节点指标在范围内才做比较
 if (son + 1 <= end && arr[son] < arr[son + 1]) //先比较两个子节点大小,选择最大的
 son++;
 if (arr[dad] > arr[son]) //如果父节点大於子节点代表调整完毕,直接跳出函数
 return;
 else { //否则交换父子内容再继续子节点和孙节点比较
 swap(&arr[dad], &arr[son]);
 dad = son;
 son = dad * 2 + 1;
 }
 }
}
 
void heap_sort(int arr[], int len) {
 int i;
 //初始化,i从最後一个父节点开始调整
 for (i = len / 2 - 1; i >= 0; i--)
 max_heapify(arr, i, len - 1);
 //先将第一个元素和已排好元素前一位做交换,再重新调整,直到排序完毕
 for (i = len - 1; i > 0; i--) {
 swap(&arr[0], &arr[i]);
 max_heapify(arr, 0, i - 1);
 }
}
 
int main() {
 int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 };
 int len = (int) sizeof(arr) / sizeof(*arr);
 heap_sort(arr, len);
 int i;
 for (i = 0; i < len; i++)
 printf("%d ", arr[i]);
 printf("\n");
 return 0;
}

堆排序Python语言代码实现


def big_endian(arr, start, end):
 root = start
 while True:
 child = root * 2 + 1 # 左孩子
 if child > end: # 孩子比最后一个节点还大 也就意味着最后一个叶子节点了 就得跳出去一次循环已经调整完毕
 break
 if child + 1 <= end and arr[child] < arr[child + 1]: # 为了始终让其跟子元素的较大值比较 如果右边大就左换右,左边大的话就默认
 child += 1
 if arr[root] < arr[child]: # 父节点小于子节点直接换位置 同时坐标也得换这样下次循环可以准确判断是否为最底层是不是调整完毕
 arr[root], arr[child] = arr[child], arr[root]
 root = child
 else: # 父子节点顺序正常 直接过
 break
 
 
def heap_sort(arr):
 # 无序区大根堆排序
 first = len(arr) // 2 - 1
 for start in range(first, -1, -1): # 从下到上,从右到左对每个节点进调整 循环得到非叶子节点
 big_endian(arr, start, len(arr) - 1) # 去调整所有的节点
 for end in range(len(arr) - 1, 0, -1):
 arr[0], arr[end] = arr[end], arr[0] # 顶部尾部互换位置
 big_endian(arr, 0, end - 1) # 重新调整子节点的顺序 从顶开始调整
 return arr
 
 
def main():
 l = [3, 1, 4, 9, 6, 7, 5, 8, 2, 10]
 print(heap_sort(l)) # 原地排序
 
if __name__ == "__main__":
 main() 

堆排序C++语言代码实现


#include <iostream>
#include <algorithm>
using namespace std;
 
void max_heapify(int arr[], int start, int end) {
 //建立父节点指标和子节点指标
 int dad = start;
 int son = dad * 2 + 1;
 while (son <= end) { //若子节点指标在范围内才做比较
 if (son + 1 <= end && arr[son] < arr[son + 1]) //先比较两个子节点大小,选择最大的
 son++;
 if (arr[dad] > arr[son]) //如果父节点大於子节点代表调整完毕,直接跳出函数
 return;
 else { //否则交换父子内容再继续子节点和孙节点比较
 swap(arr[dad], arr[son]);
 dad = son;
 son = dad * 2 + 1;
 }
 }
}
 
void heap_sort(int arr[], int len) {
 //初始化,i从最後一个父节点开始调整
 for (int i = len / 2 - 1; i >= 0; i--)
 max_heapify(arr, i, len - 1);
 //先将第一个元素和已经排好的元素前一位做交换,再从新调整(刚调整的元素之前的元素),直到排序完毕
 for (int i = len - 1; i > 0; i--) {
 swap(arr[0], arr[i]);
 max_heapify(arr, 0, i - 1);
 }
}
 
int main() {
 int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 };
 int len = (int) sizeof(arr) / sizeof(*arr);
 heap_sort(arr, len);
 for (int i = 0; i < len; i++)
 cout << arr[i] << ' ';
 cout << endl;
 return 0;
}

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码