堆排序算法及其稳定性分析

堆排序算法及其稳定性分析

什么是堆排序?

堆排序是利用数据结构堆而设计的一种排序算法。

堆分为两种,大顶堆小顶堆

所谓大顶堆就是每个节点的值都大于或者等于其左右孩子节点的值。

小顶堆则是相反的,每个节点的值都小于或者等于其左右孩子节点的值。

下面是一个大顶堆的示例,其拥有下面的性质:

arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]

heapsort1

下面是一个小顶堆的实例,其拥有下面的性质:

arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]

heapsort2

大顶堆和小顶堆数据结构是堆排序的基础,下面就看堆排序是如何利用堆来进行排序的。

堆排序的步骤如下:

  • 1.将原始数组转换成一个大顶堆(如果要求升序)或者小顶堆(如果要求降序)。
  • 2.将大顶堆或者小顶堆的首元素与最后一个元素交换
  • 3.剔除尾部元素,将剩下的元素重新构成一个大顶堆或者小顶堆,重复2。

以一个例子来看一下上述过程是怎样的。

原始数组arr = [3,1,4,5,2], 对其进行升序。

步骤1:首先将原数组构建成一个大顶堆。首先从叶子节点开始,将1和5进行对调。

heapsort-demo

步骤2:继续进行调整,将5和3进行对调,此时已经成为了一个大顶堆。

heapsort-demo

步骤3:将堆顶元素5和尾部元素2进行对调。

heapsort-demo

步骤4:重新构建一个大顶堆。将2和4进行对调。

heapsort-demo

步骤5:将堆顶元素4和尾部元素1进行对调。

heapsort-demo

步骤6:重新构建一个大顶堆。将元素1和3进行对调。

heapsort-demo

步骤7:将堆顶元素3和尾部元素2进行对调。

heapsort-demo

步骤8:将堆顶元素2和尾部元素1进行对调。

heapsort-demo

复杂度

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

空间复杂度为: O ( 1 ) O(1) O(1)。没有使用额外的存储空间。

堆排序的代码实现

#include <stdio.h>    
#include <string.h>
#include <ctype.h>      
#include <stdlib.h>   
#include <math.h>  
#include <time.h>

typedef int Status; 


#define MAXSIZE 10000  /* 用于要排序数组个数最大值,可根据需要修改 */
typedef struct
{
	int r[MAXSIZE+1];	/* 用于存储要排序数组,r[0]用作哨兵或临时变量 */
	int length;			/* 用于记录顺序表的长度 */
}SqList;

/* 交换L中数组r的下标为i和j的值 */
void swap(SqList *L,int i,int j) 
{ 
	int temp=L->r[i]; 
	L->r[i]=L->r[j]; 
	L->r[j]=temp; 
}

void print(SqList L)
{
	int i;
	for(i=1;i<L.length;i++)
		printf("%d,",L.r[i]);
	printf("%d",L.r[i]);
	printf("\n");
}


/* 已知L->r[s..m]中记录的关键字除L->r[s]之外均满足堆的定义, */
/* 本函数调整L->r[s]的关键字,使L->r[s..m]成为一个大顶堆 */
void HeapAdjust(SqList *L,int s,int m)
{ 
	int temp,j;
	temp=L->r[s];
	for(j=2*s;j<=m;j*=2) /* 沿关键字较大的孩子结点向下筛选 */
	{
		if(j<m && L->r[j]<L->r[j+1])
			++j; /* j为关键字中较大的记录的下标 */
		if(temp>=L->r[j])
			break; /* rc应插入在位置s上 */
		L->r[s]=L->r[j];
		s=j;
	}
	L->r[s]=temp; /* 插入 */
}

/*  对顺序表L进行堆排序 */
void HeapSort(SqList *L)
{
	int i;
	for(i=L->length/2;i>0;i--) /*  把L中的r构建成一个大根堆 */
		 HeapAdjust(L,i,L->length);

	for(i=L->length;i>1;i--)
	{ 
		 swap(L,1,i); /* 将堆顶记录和当前未经排序子序列的最后一个记录交换 */
		 HeapAdjust(L,1,i-1); /*  将L->r[1..i-1]重新调整为大根堆 */
	}
}

/* **************************************** */


#define N 9
int main()
{
   int i;
   
   /* int d[N]={9,1,5,8,3,7,4,6,2}; */
   int d[N]={50,10,90,30,70,40,80,60,20};
   /* int d[N]={9,8,7,6,5,4,3,2,1}; */

   SqList l0,l1,l2,l3,l4,l5,l6,l7,l8,l9,l10;
   
   for(i=0;i<N;i++)
     l0.r[i+1]=d[i];
   l0.length=N;
   l1=l2=l3=l4=l5=l6=l7=l8=l9=l10=l0;
   printf("排序前:\n");
   print(l0);
	
   printf("堆排序:\n");
   HeapSort(&l6);
   print(l6);

	return 0;
}

稳定性分析

稳定性就是指对于两个关键字相等的记录,它们在序列中的相对位置,在排序之前和排序之后没有发生改变。通俗地讲就是有两个关键字相等的数据A、B,排序前,A的位置是 i ,B的位置是 j,此时 i < j,则如果在排序后A的位置还是在B之前,那么称它是稳定的。

那么堆排序是一个稳定排序吗?

堆排序的稳定性分析

直接上答案堆排序并不是一个稳定排序。

堆排序的会将原始的数组转化成一个大顶堆或一个小顶堆,在输出堆顶后,此时需要维护堆,操作如下:

(1)堆顶与堆尾交换并删除堆尾,被删除的堆尾的元素就是输出过的元素

(2)把当前堆顶向下调整,直到满足构成堆的条件,重复(1)步骤

在堆顶与堆尾交换的时候两个相等的记录在序列中的相对位置就可能发生改变,这就影响其稳定性了。

下面看一个实际的例子, [5A,6,5B,7,8] ,A和B用于区分相同元素。

数组的原始的状态如下所示:

heapsort-stable1

首先调整下方的子树[6,7,8],将8和6的位置调换。

heapsort-stable2

接着调整下方的子树[5A,8,5B],将8和5A的位置调换。

heapsort-stable3

由于5A和8位置的调换,需要重新调整下方的子树[5A,7,6],将5A和7的位置调换。这个时候5A和5B的顺序就出现了一次乱序。

heapsort-stable4

至此,第一轮排序完毕,将8和数组尾部元素6交换。

heapsort-stable5

接着调整顶部的子树[6,7,5B],将7和6的位置调换。

heapsort-stable6

这个时候第二轮排序已经结束,此时可以将7和数组尾部元素5A进行调整。

heapsort-stable7

接着调整顶部的子树[5A,6,5B],将6和5A的位置调换。

heapsort-stable8

这个时候第三轮排序已经结束,此时可以将6和数组尾部元素5B进行调整。

heapsort-stable9

剩下的元素已经满足了排序的要求,于是直接输出结果。

heapsort-stable10

至此[5A,6,5B,7,8] 排序为 [5B,5A,6,7,8]。可以看到5A和5B的关系发生了变化。

通过这个例子也证明了堆排序不是一个稳定排序。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/34892.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

单片机STM32看门狗详解(嵌入式学习)

单片机STM32看门狗 什么是看门狗为什么需要看门狗&#xff1f;STM32CubeMX配置和应用示例独立看门狗&#xff08;IWDG&#xff09;窗口看门狗&#xff08;WWDG&#xff09; 注意事项 什么是看门狗 单片机STM32的看门狗&#xff08;Watchdog&#xff09;是一种硬件定时器&#…

Android中级——IPC

IPC IPC是什么&#xff1f;多进程带来的问题IPC前提SerializableParcelableBinder Android中的IPCBundle文件共享MessengerAIDLContentProviderSocket不同IPC优缺点 Binder连接池 IPC是什么&#xff1f; Inter-Process Communcation&#xff0c;含义为进程间通信或者跨进程通信…

【FFMPEG】AVFilter使用流程

流程图 核心类 AVFilterGraph ⽤于统合这整个滤波过程的结构体 AVFilter 滤波器&#xff0c;滤波器的实现是通过AVFilter以及位于其下的结构体/函数来维护的 AVFilterContext ⼀个滤波器实例&#xff0c;即使是同⼀个滤波器&#xff0c;但是在进⾏实际的滤波时&#xff0c;也…

易模为真人3D手办制作带来了创新

3d打印技术是一项近年来迅速发展的先进制造技术&#xff0c;逐渐在各个领域展现出无限的潜力。其中&#xff0c;3d打印真人手办成为了一个备受关注的领域。在市面上&#xff0c;我们常常可以看到一些热门动漫角色或明星的真人3d手办&#xff0c;逼真的细节和完美的再现度让人们…

实验室仪器管理系统/基于微信小程序的实验室仪器管理系统

摘 要 随着当今网络的发展&#xff0c;时代的进步&#xff0c;各行各业也在发生着变化&#xff0c;于是网络已经逐步进入人们的生活&#xff0c;给我们生活或者工作提供了新的方向新的可能。 本毕业设计的内容是设计实现一个实验室仪器管理系统。使用微信开发者是以java语言…

【机器学习】主成分分析实现案例 (PCA)

一、说明 这篇文章的目的是提供主成分分析&#xff08;PCA&#xff09;的完整和简化的解释。我们将逐步介绍它是如何工作的&#xff0c;这样每个人都可以理解并使用它&#xff0c;即使是那些没有强大数学背景的人。 PCA是网络上广泛覆盖的机器学习方法&#xff0c;并且有一些关…

3.Hive SQL数据定义语言(DDL)

1. 数据定义语言概述 1.1 常见的开发方式 &#xff08;1&#xff09; Hive CLI、Beeline CLI Hive自带的命令行客户端 优点&#xff1a;不需要额外安装 缺点&#xff1a;编写SQL环境恶劣&#xff0c;无有效提示&#xff0c;无语法高亮&#xff0c;误操作率高 &#xff08;2&…

LangChain大型语言模型(LLM)应用开发(一):Models, Prompts and Output Parsers

LangChain是一个基于大语言模型&#xff08;如ChatGPT&#xff09;用于构建端到端语言模型应用的 Python 框架。它提供了一套工具、组件和接口&#xff0c;可简化创建由大型语言模型 (LLM) 和聊天模型提供支持的应用程序的过程。LangChain 可以轻松管理与语言模型的交互&#x…

ipad手写笔一定要买苹果的吗?苹果平板平替电容笔排行

苹果原装Pencil&#xff0c;无疑是一个性能很出色的电容笔&#xff0c;但是&#xff0c;由于其的价格也很高&#xff0c;如果丢失了&#xff0c;或者弄坏&#xff0c;那就太可惜了。而且购买如此昂贵的苹果原装电容笔&#xff0c;仅仅只用于书写笔记方面&#xff0c;显得有点浪…

【开发问题】sqlserver怎么开启cdc

怎么开启 执行sql1、创建cdc​2.如上执行完毕之后&#xff0c;会在<database_name>数据库下的“系统表”中创建如下六个系统表&#xff1a;3.验证SQLServer库级别CDC是否启用4.启用SQLServer表级别CDC功能&#xff08;针对某一张表&#xff09;5、验证SQLServer表级别是否…

本地部署开源大模型的完整教程:LangChain + Streamlit+ Llama

在过去的几个月里&#xff0c;大型语言模型(llm)获得了极大的关注&#xff0c;这些模型创造了令人兴奋的前景&#xff0c;特别是对于从事聊天机器人、个人助理和内容创作的开发人员。 大型语言模型(llm)是指能够生成与人类语言非常相似的文本并以自然方式理解提示的机器学习模型…

【STM32智能车】小车状态

【STM32智能车】小车状态 搭建智能车 65MM轮径小车所选材料安装说明直行测试智能车可能存在的状态 智能车功能丰富&#xff0c;我们从最基础的开始&#xff0c;先来搭建一个智能车吧~。 搭建智能车 我们之前用了一个测试板子去学习调试电机&#xff0c;是时候拼装一个简单的车来…

Leetcode-每日一题【92.反转链表Ⅱ】

题目 给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right 。请你反转从位置 left 到位置 right 的链表节点&#xff0c;返回 反转后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], left 2, right 4输出&#xff1a;…

详细解释lvs的工作原理

vsl用于集群中的直接路由它的原理如下 如果在公司并发太高了怎么解决 1.加配置cpu 内存 带宽 ssd高效硬盘 2.加服务器 为用户提供服务 横向扩展 集群是什么 由的多台主机构成,相当于一台大型计算机,只提供一个访问入口(域名与ip地址) 集群用在那个场景 高并发场景 vrrp是…

服务无法注册进Eureka

相同的配置&#xff0c;在demo里能注册&#xff0c;在自己项目的无法注册&#xff0c;眼睛都快盯出老花眼了&#xff0c;还是不行&#xff0c;果然出现的问题只有在发现问题以后才觉得简单&#xff08;虽然确实是小问题&#xff0c;但是排查了一整天&#xff0c;值得记录一下&a…

论文阅读:Segment Anything之阅读笔记

目录 引言整体结构介绍论文问答代码仓库中&#xff0c;模型哪部分转换为了ONNX格式&#xff1f;以及如何转的&#xff1f;Mask decoder部分 Transformer decoder block?如何整合image_embedding&#xff0c;image_pe, sparse_prompt_embedding和dense_prompt_embedding的&…

ue4:Dota总结—HUD篇

1.绘制ui&#xff1a; DrawMoney&#xff1a; DrawPower&#xff1a; 点击ui响应事件&#xff1a; 点击响应显示对应的模型&#xff1a; 点击ui拖动模型跟随鼠标移动&#xff1a; 显示ui&#xff1a;PlayerContrler&#xff1a;

内网IP怎么用域名让外网访问,域名动态解析和静态区别?

域名解析是将域名与公网IP进行对应关系&#xff0c;实现访问域名即访问到对应IP应用的方式。域名解析分静态域名解析和动态域名解析的区别&#xff0c;它们的区别在哪&#xff1f;内网IP服务器怎么用域名让外网连接访问&#xff1f;这些都是需要我们有所了解掌握的。 这里不但…

如何基于GeoToolKit/INT实现矢量流线的聚集动画效果示例

继续在上一篇文章的基础上&#xff0c;利用相同的数据处理方法统一了不同年代地层的数据格式&#xff08;目前js解析支持的格式有ZMap、TS、XYZ和XYZA等&#xff09;&#xff0c;本文主要基于GeoToolKit/INT组件&#xff0c;针对地质研究经常在二维等值线基础上模拟计算地层中物…

Quiz 14_2-2: Using Web Services | Python for Everybody 配套练习_解题记录

文章目录 Python for Everybody课程简介Quiz 14_2-2: Using Web Services单选题&#xff08;1-15&#xff09;操作题Autograder 1: Extract Data from JSONAutograder 2: Calling a JSON API Python for Everybody 课程简介 Python for Everybody 零基础程序设计&#xff08;P…