当前位置: 首页 > news >正文

【数据结构】选择排序(详细)

选择排序

  • 1. 直接选择排序
  • 2. 堆排序
    • 2.1 堆
    • 2.2 堆的实现(以大根堆为例)
    • 2.3 堆排序
  • 3. 堆排序(topK问题)


1. 直接选择排序

  1. 思想
    以排升序为例。以a[i]为最大值(或最小值),从a[i+1]到a[n-1-i]比较选出最大值放在a[n-1-i](或最小值放在a[i])。简单讲就是将以每轮排序的第一个数作为最大值或者最小值,比较剩余的元素,得到最大值或者最小值,将最大值放在剩余数据的最后一位,最小值放在剩余数据的第一位。
    升级版
    以排升序为例。每轮排序中选出最大值和最小值,分别放在数据的左右两端。

  2. 例子(排升序)
    在这里插入图片描述

  3. 代码实现

//选择排序(以排升序为例)
void Swap(int* p1, int* p2)
{int tmp = *p1;*p1 = *p2;*p2 = tmp;
}
void SelectSort(int* a, int n)
{int left = 0;int right = n - 1;while (left < right){int maxi = left, mini = left;for (int i = left + 1; i <= right; i++){if (a[i] > a[maxi]){maxi = i;}if (a[i] < a[mini]){mini = i;}}Swap(&a[maxi], &a[right]);//这里要考虑特殊情况:如果最小值mini==right,那么在交换maxi和right后,//最小值就发生改变,所以要最小值的下标要改变。if (mini == right){mini == maxi;}Swap(&a[mini], &a[left]);right--;left++;}
}
  1. 算法分析
    时间复杂度
    假设有n个元素,第一次排序要遍历n-2个元素,第二次排序要遍历n-4个元素,往后每次排序的元素个数都减2,总的遍历次数是等差数列的前n项和,所以时间复杂度是O(N^2)。
    空间复杂度
    空间复杂度是O(1)。
    稳定性
    是不稳定的排序。如上面的例子中的12,在选出最大值和最小值时,前后顺序发生改变。
    注意
    直接选择排序的最好情况和最坏情况的时间复杂度都是O(N^2)。

2. 堆排序

2.1 堆

  1. 概念
    堆其实是一种树形结构,分为大根堆和小根堆。大根堆是树中所有父节点大于子节点,小根堆是树中所有父节点小于子节点。但父节点的左右孩子谁大谁小没有要求。

  2. 预备知识
    (1)堆的逻辑结构是树,物理结构是数组(即在内存中以数组的形式存储)。
    在这里插入图片描述

(2)父节点和子节点的关系
已知子节点下标,它的父节点下标parent = (child - 1)/2。
已知父节点下标,它的子节点下标leftchild = parent * 2 + 1,rightchild = parent * 2 + 2。
(3)种存储结构只适用于满二叉树和完全二叉树,否则会浪费很多空间。

2.2 堆的实现(以大根堆为例)

  1. 堆的定义
//堆的实现
typedef int HPDataType;
typedef struct Heap
{HPDataType* a;//用来申请空间int size;//记录当前堆的元素个数int capacity;//记录当前堆的最大容量
};
  1. 堆的初始化
//堆的初始化
void HeapInit(HP* ph)
{assert(ph);ph->a = (HPDataType*)malloc(sizeof(HPDataType)*10);//初始化容量设置为10if (ph->a == NULL){perror("malloc failed");return;}ph->capacity = 10;ph->size = 0;
}
  1. 入堆
//入堆
void HeapPush(HP* ph, HPDataType x)
{assert(ph);//首先要检查堆是否已满if (ph->size == ph->capacity){HPDataType* tmp = (HPDataType*)realloc(ph->a, sizeof(HPDataType) * ph->capacity * 2);if (tmp == NULL){perror("realloc failed");return;}ph->a = tmp;ph->capacity *= 2;//每次容量不够时扩容两倍}ph->a[ph->size++] = x;//因为要实现大根堆,如果入堆元素大于前面的元素,就要把它向上调整。AdjustUp(ph->a, ph->size - 1);//第一次参数是要调整的数据元素,第二个参数是入堆元素的下标。
}
  1. 向上调整
//向上调整
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}
void AdjustUp(HPDataType* a, int child)
{//得到父节点的下标int parent = (child - 1) / 2;while (child > 0)//当子节点爬到下标为0的位置,就达到顶部了,此时可以停止循环{//比较父节点和子节点,谁大谁上去//让大的节点不断往上爬if (a[chil] > a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

在这里插入图片描述

  1. 出堆
    疑惑
    (1)
    出堆其实就是弹出根节点。但我们要考虑弹出根节点后,后面的数据要如何处理?将数据往前移动?这样数据间的关系就全乱了,且所有数据往前移效率低(O(N^2))。
    正确的做法是交换堆顶元素和最后一个元素,然后删除最后一个元素,然后将堆顶元素向下调整
    (2)
    为什么要弹出根节点?弹出最后一个元素岂不是更简单?这就涉及到堆的应用。我们所写的是大根堆,根结点是这组数据的最大值,弹出根节点后,向下调整可以得到次大值,然后再弹出根节点。就这样,我们就得到这组数据的前n个最大值,比如得到年纪前50名,得到产品销量前100名。由此可见,弹出根结点是非常有用的。
    代码实现
//堆空
bool HeapEmpty(HP* ph)
{assert(ph);return ph->size == 0;
}//出堆
void HeapPop(HP* ph)
{assert(ph);//首先判断堆是否为空assert(!HeapEmpty(ph));//交换堆顶元素和最后一个元素Swap(&ph->a[0], &ph->a[ph->size - 1]);//删除最后一个元素ph->size--;//向下调整AdjustDown(ph->a, ph->size, 0);//第一个参数是要调整的数据,第二个参数是数据的个数,第三个参数是要调整的位置
}
  1. 向下调整
//向下调整(条件:左右子树都是大根堆或者小根堆)
void AdjustDown(HPDataType* a, int n, int parent)
{int child = 2 * parent + 1;//先得到左孩子while (child < n)//当子节点的下标大于元素个数时停止循环{//如果右孩子存在且大于左孩子,那么就换成右孩子与父节点比较if (child + 1 < n && a[child + 1] > a[child]){++child;}if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = 2 * parent + 1;}else{break;}}
}

在这里插入图片描述

  1. 堆顶
//堆顶
HP* HeapTop(HP* ph)
{assert(ph);//首先判断堆是否为空assert(!HeapEmpty(ph));return ph->a[ph->size - 1];
}
  1. 打印前K个元素
//打印前K个元素
void PrintK(HP* ph, int k)
{while (!HeapEmpty(ph) && k--){printf("%d ", HeapTop(ph));HeapPop(ph);}printf("\n");
}
  1. 销毁
//销毁
void HeapDestroy(HP* ph)
{assert(ph);free(ph->a);ph->a = 0;ph->capacity = 0;ph->size = 0;
}

2.3 堆排序

了解堆的基本操作后,就可以实现堆排序。

//法一
//堆排序(以排升序为例)
void HeapSort(int* a, int n)
{//建堆(大根堆或者小根堆)for (int i = 1; i < n; i++){AdjustUp(a, i);//从下标为1的元素不断插入,从而向上调整}//建大根堆后,就交换根结点和最后一个元素,每次交换都将最大值放在数据的最后面int end = n - 1;while (end > 0){Swap(&a[end], &a[0]);//交换最大值和最后一个元素AdjustDown(a, end, 0);//交换后最后一个元素不参与调整,所有元素个数只有end个//每次调整后都会将最大值放在根节点的位置--end;}
}
//法二
void HeapSort(int* a, int n)
{//从最后一个元素的父节点开始向下调整//目的是建成大根堆for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}int end = n - 1;while (end > 0){Swap(&a[end], &a[0]);AdjustDown(a, end, 0);--end;}
}

时间复杂度
这两种方法唯一的不同就是法一前面是向上调整,法二前面是向下调整。要比较两种方法的时间复杂度就得比较向下调整和向上调整的时间复杂度。

  1. 向下调整的时间复杂度是O(N)
    在这里插入图片描述

  2. 向上调整的时间复杂度是O(NlogN)
    在这里插入图片描述
    咋一看好像第二种方法时间复杂度更牛,但实际上这两种方法的时间复杂度都是O(NlogN)。我们讨论的只是向下调整和向上调整。
    在这里插入图片描述
    总的来说,堆排序的时间复杂度是O(NLogN)。两种方法都在这个量级,但第二种方法稍微高一点点。


3. 堆排序(topK问题)

找出N个数中最大的(或最小的)前K个数。
有两种方法:
法一:建N个大根堆,然后PopK次就可以。
法二:建有K个数的小根堆,遍历剩下的数据,如果数据比堆顶元素大,就替换它,然后向下调整,最后这个小根堆的数据就是最大的前K个。
这里主要用法二

void PrintTopK(const char* file, int k)
{//首先要建立小根堆int* topk = (int*)malloc(sizeof(int) * k);if (topk == NULL){perror("malloc failed");return;}//从文件中读取K个数据FILE* f = fopen(file, "r");if (f == NULL){perror("fopen failed");return;}for (int i = 0; i < k; i++){fscanf(f, "%d", &topk[i]);}//建小堆(之前写的向下调整是调整大根堆,只需修改其中一些>或者<即可)for (int i = (k - 1 - 1) / 2; i >= 0; i--){AdjustDown(topk, k, i);}//将剩下的n-k个元素依次与根节点比较,比它大就入堆且向下调整//这样将大的元素往下沉,小的元素不断换掉,最终只剩下最大的K个元素int val = 0;int ret = fscanf(f, "%d", &val);while (ret != EOF){if (val > topk[0]){topk[0] = val;AdjustDown(topk, k, 0);}ret = fscanf(f, "%d", &val);}for (int i = 0; i <k; i++){printf("%d ", topk[i]);}fclose(f);
}
void CreateData()
{int n = 1000;srand(time(0));const char* file = "TestData.txt";FILE* f = fopen("TestData.txt", "w");//以写的形式打开if (f == NULL){perror("fopen fail");return;}for (int i = 0; i < n; i++){int x = rand() % 1000;//获得0~999的数字fprintf(f, "%d ", x);}fclose(f);
}

相关文章:

【数据结构】选择排序(详细)

选择排序 1. 直接选择排序2. 堆排序2.1 堆2.2 堆的实现&#xff08;以大根堆为例&#xff09;2.3 堆排序 3. 堆排序&#xff08;topK问题&#xff09; 1. 直接选择排序 思想 以排升序为例。以a[i]为最大值&#xff08;或最小值&#xff09;&#xff0c;从a[i1]到a[n-1-i]比较选…...

什么是企业内容管理?

为什么出现企业内容管理&#xff1f; 在数字经济的宏观背景下&#xff0c;企业建立了各种应用系统以满足企业各业务的管理需求&#xff0c;这些系统每天都在产生大量的数据和信息资源&#xff0c;但在企业实践中存在很多数据或资源无法被应用系统获取、处理和共享。 比如发票…...

机器学习:分类、回归、决策树

分类&#xff1a;具有明确的类别 如&#xff1a;去银行借钱&#xff0c;会有借或者不借的两种类别 回归&#xff1a;不具有明确的类别和数值 如&#xff1a;去银行借钱&#xff0c;预测银行会借给我多少钱&#xff0c;如&#xff1a;1~100000之间的一个数值 不纯度&#xff1…...

java常见的异常,下一篇写如何正确处理异常

当我们编写Java程序时&#xff0c;经常会遇到各种异常情况。异常是指在程序执行过程中发生的一些错误或意外情况&#xff0c;它会打断程序的正常执行流程&#xff0c;并且需要被适当地处理。在Java中&#xff0c;异常被分为两种类型&#xff1a;可检查异常&#xff08;Checked …...

C#开发的OpenRA游戏之网络协议打包和解包

C#开发的OpenRA游戏之网络协议打包和解包 OpenRA游戏里,由于这是一个网络游戏,那么与服务器通讯就缺少不了, 既然要通讯,那么就需要协议,有协议就需要对数据进行打包和解包, 这个过程其实就是序列化与反序列化的过程。 游戏里很多命令都需要发送给服务器,以便服务器同…...

K8S通过Ansible安装集群

K8S通过Ansible安装集群 K8S集群安装可参考https://gitee.com/open-hand/kubeadm-ha.git、https://github.com/easzlab/kubeasz.git 安装高可用集群 git clone https://gitee.com/open-hand/kubeadm-ha.git && cd kubeadm-ha升级内核,非必需&#xff0c;默认不升级&…...

ChatGPT辩证观点:“人才不是一个企业的核心竞争力,对人才的管理能力才是一个企业的核心竞争力”

一、问&#xff1a; “人才不是一个企业的核心竞争力&#xff0c;对人才的管理能力才是一个企业的核心竞争力”这句话的理解和误解&#xff0c;这句话有哪个中心论点转移和变化 二、ChatGPT答&#xff1a; 这句话的理解和误解&#xff1a; 理解&#xff1a;这句话的意思是说…...

windows11 永久关闭windows defender的方法

1、按键盘上的windows按键&#xff0c;再点【设置】选项。 2、点击左侧菜单的【隐私和安全性】&#xff0c;再点击列表的【Windows安全中心】选项。 3、点击界面的【病毒和威胁保护】设置项。 4、病毒保护的全部关闭 5、别人的图&#xff08;正常是都开着的&#xff09; 6、终极…...

继承的基本知识

概念 假设基于A类&#xff0c;创建了B类&#xff0c;那么称A为B的父类&#xff0c;B为A的子类 子类会继承父类的成员变量及成员函数&#xff0c;但是不能继承构造、析构、运算符重载 假设又基于B创建了C&#xff0c;那么称B为C的直接基类&#xff0c;A为C的间接基类 继承按…...

【Frida-实战】EA游戏平台的文件监控(PsExec.exe提权)

▒ 目录 ▒ &#x1f6eb; 问题描述环境 1️⃣ 代码编写开源代码搜索自己撸代码procexp确定句柄对应的文件名并过滤 2️⃣ PsExec.exe提权定位找不到EABackgroundService.exe的问题 PsExec.exe提权PsExec.exe原理 &#x1f6ec; 结论&#x1f4d6; 参考资料 &#x1f6eb; 问题…...

可视化和回归分析星巴克咖啡在中国的定价建议

可视化和回归分析星巴克咖啡在中国的定价建议。星巴克的拿铁大杯Tall 在各国的价格。 Claude AI | 代码自动生成的数据可视化代码 选择Claude AI 而非 ChatGPT的理由是前者更懂中文​&#xff01;具体可以参见我前面的两篇文章对比两者的中英文翻译的表现及使用安装等难易程度​…...

热门影片怎么买票比较便宜,低价买电影票的方法,纯攻略!

有时候真的有被自己蠢到&#xff01;看电影看了这么多年&#xff0c;竟然不知道电影票价格才9.9元、19.9元就能买到。之前我看电影动不动就是几十上百块&#xff0c;感觉好亏啊。 其实&#xff0c;我也不敢相信的&#xff0c;通过这些平台&#xff0c;同时在节假日甚至春节档期…...

Python通过SWIG调用C++时出现的ImportError问题解析

摘要 win10系统&#xff0c;编译器为mingw&#xff0c;按照教程封装C的一个类并用python调用&#xff0c;一步步进行直到最后一步运行python代码时&#xff0c;在python代码中import example时报错ImportError: DLL load failed while importing _example: The specified modul…...

3ds Max云渲染有多快,3ds Max云渲染怎么用?

本地渲染效果图和动画3D项目是一个非常耗时的过程&#xff0c;当在场景中使用未优化的几何体或在最终渲染中使用大量多边形模型时&#xff0c;诸如此类的变量最终会增加渲染项目所需的时间和处理器能力。随着提供的渲染服务的云渲染平台出现&#xff0c;越来越多动画师、艺术家…...

Java之线程安全

目录 一.上节回顾 1.Thread类常见的属性 2.Thread类中的方法 二.多线程带来的风险 1.观察线程不安全的现象 三.造成线程不安全现象的原因 1.多个线程修改了同一个共享变量 2.线程是抢占式执行的 3.原子性 4.内存可见性 5.有序性 四.解决线程不安全问题 ---synchroni…...

我有一个方法判断你有没有编程天赋

我有一个方法判断你有没有编程天赋 一 前言 基于知识的诅咒的原理 做一个敲击者很难。问题在于敲击者已拥有的知识&#xff08;歌曲题目&#xff09;让 他们想象不到缺乏这种知识会是什么情形。当他们敲击的时候&#xff0c;他 们不能想象听众听到的是那些独立的敲击声而不是…...

python 生成chart 并以附件形式发送邮件

import requests import json import pandas as pd import numpy as np import matplotlib.pyplot as plt data np.random.randn(5, 3)#生成chart def generate_line_chart(data):df pd.DataFrame(np.abs(data),index[Mon, Tue, Wen, Thir, Fri],columns[A, B, C])df.plot()…...

leetcode-035-搜索插入位置

题目及测试 package pid035; /*35. 搜索插入位置 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。请必须使用时间复杂度为 O(log n) 的算法。示例 1:输入: nums …...

读书笔记--数据治理之法

继续延续上一篇文章&#xff0c;对数据治理之法进行学习。数据治理之法是战术层面的方法&#xff0c;是一套涵盖8项举措的数据治理实施方法论&#xff0c;包括梳理现状与确定目标、能力成熟度评估、治理路线图规划、保障体系建设、技术体系建设、治理策略执行与监控、绩效考核与…...

送了老弟一台 Linux 服务器,它又懵了!

大家好&#xff0c;我是鱼皮。 前两天我学编程的老弟小阿巴过生日&#xff0c;我问他想要什么礼物。 本来以为他会要什么游戏机、Q 币卡、鼠标键盘啥的&#xff0c;结果小阿巴说&#xff1a;我想要一台服务器。 鱼皮听了&#xff0c;不禁称赞道&#xff1a;真是个学编程的好苗…...

CentOS 7(2009) 升级 GCC 版本

1. 前言 CentOS 7 默认安装的 gcc 版本为 4.8&#xff0c;但是很多时候都会需要用到更高版本的 gcc 来编译源码&#xff0c;那么本文将会介绍如何在线升级 CentOS 的 gcc 版本。 2. 升级 GCC (1). 安装 centos-release-scl&#xff1b; [imaginemiraclecentos7 ~]$ sudo yum…...

java非静态代码块和静态代码块介绍

代码块 SE.10.0…02.28 非静态普通代码块&#xff1a;定义在方法内部的代码块&#xff0c;不用任何关键字修饰&#xff0c;又名构造代码块、实例代码块 静态代码块&#xff1a;用static修饰的代码块 非静态代码块 public class Test {public static void main(String[] args…...

Golang中接口类型详解与最佳实践(二)

之前的文章《Golang中的interface(接口)详解与最佳实践》详细介绍了接口类型的定义、使用方法和最佳实践。接口类型使得编写可扩展、可维护和可复用的高质量代码变得更加容易。 如何判断是否实现了某个接口&#xff1f; 还是使用之前文章的例子&#xff0c;例如声明了如下一个…...

ChatGPT 探讨内存屏障的意内存

一、与 ChatGPT 探讨内存屏障的意内存 轻松的氛围&#xff0c;跟 ChatGPT 从内存屏障问题一直扯到CAP原理 我&#xff1a; 2023/4/14 17:48:09 那我可以理解为{ shared_var 1; asm volatile ("sfence" ::: "memory"); asm volatile ("lfence" …...

P1039 [NOIP2003 提高组] 侦探推理

此题难度为&#xff1a;提高/省选- 作者为&#xff1a;CCF_NOI 题目描述 明明同学最近迷上了侦探漫画《柯南》并沉醉于推理游戏之中&#xff0c;于是他召集了一群同学玩推理游戏。游戏的内容是这样的&#xff0c;明明的同学们先商量好由其中的一个人充当罪犯&#xff08;在明…...

模拟电路学习笔记 - 概念与结论

真空二极管&#xff0c;电子管ENIAC发源地&#xff0c;基础方法二极管双极管三极管场向管学习特性&#xff0c;最终运放运方的目的是运用&#xff0c;射频&#xff0c;计算…放大电路大功率元器件和微元器件学习他们的特性分粒 集成设计的角度&#xff0c;不要仅仅分析设计的前…...

Linux驱动开发:I2C子系统

目录 1、I2C简介 1.1 两根线 1.2 信号 1.3 写时序 1.4 读时序 1.5 I2C速率 1.6 I2C驱动框架简介 2、I2C设备驱动 2.1 I2C相关API 2.1.1 i2c_driver 2.1.2 注册&#xff1a;i2c_add_driver 2.1.3 注销&#xff1a;i2c_del_driver 2.1.4 module_i2c_driver&#xff…...

[C++] 动态内存与智能指针

众所周知&#xff0c;C五大内存区&#xff1a;全局数据区(静态区)、代码区、栈区、堆区、常量区。 全局数据区(静态区)&#xff1a;存放全局变量&#xff0c;静态数据和常量&#xff1b; 代码区&#xff1a;存放所有类成员函数和非成员函数代码&#xff0c;函数体的二进制代码。…...

多态的原理

有了虚函数&#xff0c;会在类的对象增加一个指针&#xff0c;该指针就是虚函数表指针_vfptr;虚表本质就是函数指针数组,虚表里面存放着该对象的虚函数的地址&#xff1b; 派生类继承有虚函数基类的对象模型 子类继承父类的虚表指针时&#xff0c;是对父类的虚表指针进行了拷…...

RK3588平台开发系列讲解(内存篇)Linux 伙伴系统数据结构

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、 页二、区三、内存节点沉淀、分享、成长,让自己和他人都能有所收获!😄 📢Linux 系统中,用来管理物理内存页面的伙伴系统,以及负责分配比页更小的内存对象的 SLAB 分配器了。 本篇将介绍伙伴系统相关数据结…...