排序 (插入/选择排序)

目录

一 . 排序概念及运用

1.1 排序的概念

1.2 排序的应用

1.3 常见的排序算法

二 . 插入排序

2.1 直接插入排序

2.1 复杂度分析

 2.3 希尔排序

 2.4 希尔排序时间复杂度分析

三 . 选择排序

3.1 直接选择排序

3.2 堆排序


一 . 排序概念及运用

1.1 排序的概念

排序 : 所谓排序 , 就是使一条记录 , 按照其中的某个或某些关键字的大小,递增 或 递减的排序起来的操作 。 

1.2 排序的应用

排序在生活中无处不在 , 如果没有排序 , 那么许多业务也不会实现

1 . 院校排名

2 .  购物筛选排序

1.3 常见的排序算法

二 . 插入排序

 基本思想 :

直接插入排序是一种简单的插入排序算法 , 其基本思想是 : 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中 , 直到所有记录插入完为止 , 得到一个新的有序序列 。 

 在实际生活中 , 玩扑克牌就使用了   插入排序   的思想 !

2.1 直接插入排序

插入排序是在已经有序序列中 , 插入新的数据 , 形成一个新的有序序列

直接插入排序 , 需要一个变量来存储需要插入的数据(tmp) , 另一个变量初始为有序序列的最后一个位置 (end), arr[end] 与 tmp 比较 , 谁大谁往后排 。 

 这里我们依旧创建三个文件 , 方便文件的管理 , 使用一个test.c 文件也是可以实现的。

test.c

#include "Sort.h"

void PrintArr(int* arr,int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
void test()
{
	int a[] = { 5,3,9,6,2,4,7,1,8 };
	int n = sizeof(a) / sizeof(a[0]);
	printf("排序之前:");
	PrintArr(a, n);

	InsertSort(a, n);
	printf("排序之后:");
	PrintArr(a, n);
}
int main()
{
	test();
	return 0;
}

Sort.c

#include "Sort.h"

//直接插入排序
void InsertSort(int* arr, int n)
{
	for (int i = 0; i < n-1; i++)
	{
		int end = i;
		int tmp = arr[end + 1];
		while (end >= 0)
		{
			if (arr[end] > tmp)
			{
				arr[end + 1] = arr[end];
				end--;
			}
			else
			{
				break;
			}
		}
		arr[end + 1] = tmp;
	}
}

Sort.h 

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

//直接插入排序
void InsertSort(int* arr, int n);

2.1 复杂度分析

直接插入排序的特性总结 :

1 . 元素集合越接近有序 , 直接插入排序算法时间的效率越高

2 . 时间复杂度 : O ( n^2)

3 . 空间复杂度 : O (1)

 

对比之前学过的 冒泡排序 , 堆排序 堆排序和TOP-K问题-CSDN博客

1 .  冒泡排序时间复杂度为 O(n^2) 

2 .  堆排序时间复杂度为 O (n * log n)

3 . 直接插入排序时间复杂度为 O(n^2)

在冒泡排序中 , 如果序列本身为有序 , 时间复杂度最优 O(n) ; 在直接插入排序中 , 如果序列为有序 , 且为降序 , 时间复杂度最差 O (n^2) , 但这种情况的出现概率很小 ,在一定程度上可以说 , 直接插入排序的时间复杂度达不到 O(n^2) , 但也没比冒泡排序好了多少 。 

以下测试以下三个方法运行时所需的时间 , 单位为毫秒

 从上面的测试结果我们可以得出 , 堆排序的算法  时间复杂度相较于  冒泡排序  和  直接插入排序的更优一些  

test.c

#include "Sort.h"
#include <time.h>
#include <stdlib.h>

void PrintArr(int* arr,int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
void test()
{
	int a[] = { 5,3,9,6,2,4,7,1,8 };
	int n = sizeof(a) / sizeof(a[0]);
	printf("排序之前:");
	PrintArr(a, n);

	//InsertSort(a, n);
	//BubbleSort(a, n);
	HeapSort(a, n);

	printf("排序之后:");
	PrintArr(a, n);
}

// 测试排序的性能对⽐
void TestOP()
 {
	srand(time(0));
	const int N = 100000;
	int* a1 = (int*)malloc(sizeof(int) * N);
	//int* a2 = (int*)malloc(sizeof(int) * N);
	//int* a3 = (int*)malloc(sizeof(int) * N);
	int* a4 = (int*)malloc(sizeof(int) * N);
	//int* a5 = (int*)malloc(sizeof(int) * N);
	//int* a6 = (int*)malloc(sizeof(int) * N);
	int* a7 = (int*)malloc(sizeof(int) * N);

	 for (int i = 0; i < N; ++i)
		 {
		 a1[i] = rand();
		 //a2[i] = a1[i];
		 //a3[i] = a1[i];
		 a4[i] = a1[i];
		 //a5[i] = a1[i];
		 //a6[i] = a1[i];
		 a7[i] = a1[i];
		 }

		int begin1 = clock();
		InsertSort(a1, N);
		int end1 = clock();
	
	 //int begin2 = clock();
	 //ShellSort(a2, N);
	 //int end2 = clock();

	/*int begin3 = clock();
	 SelectSort(a3, N);
	 int end3 = clock();*/

		int begin4 = clock();
		HeapSort(a4, N);
		int end4 = clock();
	
		//int begin5 = clock();
	 //QuickSort(a5, 0, N - 1);
	 //int end5 = clock();
	
		//int begin6 = clock();
		//MergeSort(a6, N);
		//int end6 = clock();
	
		int begin7 = clock();
		BubbleSort(a7, N);
		int end7 = clock();
	
		printf("InsertSort:%d\n", end1 - begin1);

	/*	printf("ShellSort:%d\n", end2 - begin2);
		printf("SelectSort:%d\n", end3 - begin3);*/

		printf("HeapSort:%d\n", end4 - begin4);

		//printf("QuickSort:%d\n", end5 - begin5);
		//printf("MergeSort:%d\n", end6 - begin6);

		printf("BubbleSort:%d\n", end7 - begin7);
	
		 free(a1);
	 //free(a2);
	 //free(a3);
	 free(a4);
	 //free(a5);
	 //free(a6);
	 free(a7);
	
}


int main()
{
	//test();
	TestOP();
	return 0;
}

Sort.c

#include "Sort.h"

//直接插入排序
void InsertSort(int* arr, int n)
{
	for (int i = 0; i < n-1; i++)
	{
		int end = i;
		int tmp = arr[end + 1];
		while (end >= 0)
		{
			if (arr[end] > tmp)
			{
				arr[end + 1] = arr[end];
				end--;
			}
			else
			{
				break;
			}
		}
		arr[end + 1] = tmp;
	}
}

void Swap(int* x,int*y )
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

//冒泡排序
void BubbleSort(int* arr, int size)
{
	for (int i = 0; i < size - 1; i++)
	{
		int exchange = 0;
		for (int j = 0; j < size - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1]) {
				exchange = 1;
				Swap(&arr[j], &arr[j + 1]);
			}
		}
		if (exchange == 0)
		{
			break;
		}
	}
}

//向下调整
void AdjustDown(int* arr, int parent, int n)
{

	int child = parent * 2 + 1;
	while (child < n)
	{
		//先找最小孩子
		if (child + 1 < n && arr[child] < arr[child + 1])
		{
			child++;
		}
		if (arr[child] > arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else {
			break;
		}
	}

}

//堆排序
void HeapSort(int* arr, int n)
{
	//向下调整算法建堆
	for (int i = n - 1 - 1; i >= 0; i--)
	{
		AdjustDown(arr, i, n);
	}
	int end = n - 1;
	while (end > 0)
	{
		Swap(&arr[end], &arr[0]);
		AdjustDown(arr, 0, end);
		end--;
	}
}


Sort.h

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

//直接插入排序
void InsertSort(int* arr, int n);

//冒泡排序
void BubbleSort(int* arr, int size);

//堆排序
void HeapSort(int* arr, int n);

 2.3 希尔排序

在数组为降序时 , 直接插入排序的时间复杂度为 O(n^2) ,  那么可以优化吗 ?

-------> 可以 , 将数组划分成多组来进行直接插入排序,降低时间复杂度

希尔排序法又称缩小增量法 。

希尔排序法的基本思想是 :  选定一个整数 ( 通常是gap = n/3 + 1 ) , 把待排序文件所有记录分成各组 所有的距离相等的记录分在同一组内 , 并对每一组内的记录进行排序 ,

然后gap = gap / 3 +1 得到下一个整数 , 再将数组分成各组 , 进行插入排序 , 当 gap = 1 时 , 就相当于直接插入排序 。

它是在直接插入排序算法的基础上进行改进而来的 , 综合来说它的效率肯定是要高于直接插入排序算法的 。

思考 :

1 ) 为什么要分组 ?

 通过分组来降低元素之间的比较次数,优化时间复杂度

2 ) 怎么分组 ?

通过间隔 gap (gap / 3 + 1 ) 的元素  组成一组

3 )为什么要 +1 , 不直接 gap/3 ?

假设gap = 3 , gap/3 = 0 , 此时数据还没有排序好 就终止了排序 , 希望是当gap == 1  时 , 预排序结束 , 然后进行直接插入排序 。

4 ) 可以gap/2 , gap/8 吗?

可以 , 视情况而论 , 一般是 gap/3 , 因为除小了 , 分组过多就过多 ; 除大了 , 比较次数就多了

//希尔排序
void ShellSort(int* arr, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		for (int i = 0; i < n - gap ; i++)
		{
			int end = i;
			int tmp = arr[end + gap];
			while (end >= 0)
			{
				if (arr[end] > tmp)
				{
					arr[end + gap] = arr[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			arr[end + gap] = tmp;
		}
	}
}

 2.4 希尔排序时间复杂度分析

希尔排序时间复杂度不好计算。因为 gap 的取值很多 ,导致很难去计算 , 因此很多书中给出的希尔排序的时间复杂度后不固定 。 《数据结构 (C语言版)》 -- 严蔚敏书中给出的时间复杂度为 

 这里我们大致记住 , 希尔排序的时间复杂度比直接插入排序更优 ,并且大致上为 O(n^1.3)  , 下面是大致对希尔排序的时间复杂度进行估算 :

外层循环 : O(\log_{2}n) 或者 O(\log_{3}n) ,即 O(log n)

内层循环 : 

 

 通过以上分析 , 可以画得如下曲线图 : 

 因此 , 希尔排序在最初和最后的排序的次数都为 n , 即前一阶段排序次数是逐渐上升的状态 , 当达到某顶点时 , 排序次数逐渐下降至 n ,  而该顶点的计算暂时无法给出具体的计算过程 。 

 单位为毫秒 , 通过性能的测试 , 我们发现 , 希尔排序的运行速度 很近似于   堆排序 , 相较于直接插入排序 是一个 较优的算法 !

三 . 选择排序

 选择排序的基本思想 : 

每一次从待排序的数据元素中选出最小(或最大)的一个元素 , 存放在序列的起始位置 ,直到全部待排序的数据元素排完 。  

3.1 直接选择排序

//直接选择排序
void SelectSort(int* arr, int n)
{
	int begin = 0, end = n - 1;
	while (begin < end)
	{
		int mini = begin, maxi = begin;

		//找最大最小
		for (int i = begin + 1; i <= end; i++)
		{	
			if (arr[i] < arr[mini])
			{
				mini = i;
			}
			if (arr[i] > arr[maxi])
			{
				maxi = i;
			}
		}

		//如果maxi == began
		if (maxi == begin)
		{
			maxi = mini;
		}

		Swap(&arr[mini], &arr[begin]);
		Swap(&arr[maxi], &arr[end]);
		begin++;
		end--;
	}
}

 直接选择排序的思路比较好理解,但是 效率不是很高 , 实际中 很少使用

1 ) 时间复杂度 : O( n ^ 2 )

2 )   空间复杂度 : O ( 1 )

3.2 堆排序

堆排序(HeapSort) 是利用堆 这种数据结构所设计的一种排序算法 , 他是选择排序的一种 。 它是通过堆来进行选择数据 。 需要注意的是 排升序要建大堆 , 排降序建小堆 。 

之前的文章详细介绍了堆排序 , 这里不再赘述 

堆排序和TOP-K问题-CSDN博客

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

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

相关文章

Unity网络开发基础(part5.网络协议)

目录 前言 网络协议概述 OSI模型 OSI模型的规则 第一部分 物理层 数据链路层 网络层 传输层 第二部分 ​编辑 应用层 表示层 会话层 每层的职能 TCP/IP协议 TCP/IP协议的规则 TCP/IP协议每层的职能 TCP/IP协议中的重要协议 TCP协议 三次握手 四次挥手 U…

框架学习01-Spring

一、Spring框架概述 Spring是一个开源的轻量级Java开发框架&#xff0c;它的主要目的是为了简化企业级应用程序的开发。它提供了一系列的功能&#xff0c;包括控制反转&#xff08;IOC&#xff09;、注入&#xff08;DI&#xff09;、面向切面编程&#xff08;AOP&#xff09;…

Late Chunking×Milvus:如何提高RAG准确率

01. 背景 在RAG应用开发中&#xff0c;第一步就是对于文档进行chunking&#xff08;分块&#xff09;&#xff0c;高效的文档分块&#xff0c;可以有效的提高后续的召回内容的准确性。而对于如何高效的分块是个讨论的热点&#xff0c;有诸如固定大小分块&#xff0c;随机大小分…

【深度学习】InstantIR:图片高清化修复

InstantIR——借助即时生成参考的盲图像修复新方法 作者:Jen-Yuan Huang 等 近年来,随着深度学习和计算机视觉技术的飞速发展,图像修复技术取得了令人瞩目的进步。然而,对于未知或复杂退化的图像进行修复,仍然是一个充满挑战的任务。针对这一难题,研究者们提出了 Insta…

qt获取本机IP和定位

前言&#xff1a; 在写一个天气预报模块时&#xff0c;需要一个定位功能&#xff0c;在网上翻来翻去才找着&#xff0c;放在这里留着回顾下&#xff0c;也帮下有需要的人 正文&#xff1a; 一开始我想着直接调用百度地图的API来定位&#xff0c; 然后我就想先获取本机IP的方…

(C++回溯算法)微信小程序“开局托儿所”游戏

问题描述 给定一个矩阵 A ( a i j ) m n \bm A(a_{ij})_{m\times n} A(aij​)mn​&#xff0c;其中 a i j ∈ { 1 , 2 , ⋯ , 9 } a_{ij}\in\{1,2,\cdots,9\} aij​∈{1,2,⋯,9}&#xff0c;且满足 ∑ i 1 m ∑ j 1 n a i j \sum\limits_{i1}^m\sum\limits_{j1}^na_{ij} i…

数字隔离器与光隔离器有何不同?---腾恩科技

在电子隔离中&#xff0c;两种常用的解决方案是数字隔离器和光学隔离器。两者都旨在电气隔离电路的各个部分&#xff0c;以保护敏感元件免受高压干扰&#xff0c;但它们通过不同的技术实现这一目标。本文探讨了这些隔离器之间的差异&#xff0c;重点介绍了它们的工作原理、优势…

什么是多因素身份验证(MFA)的安全性?

多因素身份验证(MFA)简介 什么是MFA 多因素身份验证(MFA)是一种安全过程&#xff0c;要求用户在授予对系统、应用程序或账户的访问权限之前提供两种或多种形式的验证。仅使用单个因素&#xff08;通常是用户名和密码&#xff09;保护资源会使它们容易受到泄露&#xff0c;添加…

10天进阶webpack---(2)webpack模块兼容性处理

回顾CMJ和ESM的区别 CMJ的本质可以使用一个函数概括 // require函数的伪代码 function require(path){if(该模块有缓存吗){return 缓存结果;}function _run(exports, require, module, __filename, __dirname){// 模块代码会放到这里}var module {exports: {}}_run.call(mod…

Spring源码学习(五):Spring AOP

免责声明 本人还处于学习阶段&#xff0c;如果内容有错误麻烦指出&#xff0c;敬请见谅&#xff01;&#xff01;&#xff01;Demo <dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.8<…

【全面解析】Stable Diffusion AI绘画入门教程,轻松掌握,让绘画新手也能快速上手!

前言&#xff1a; 随着人工智能技术的飞速发展&#xff0c;AI绘画领域迎来了一场革命。StableDiffusion作为一款强大的AI绘画工具&#xff0c;以其稳定的图像生成能力和卓越的创造力赢得了广泛关注。本文将向您介绍StableDiffusion的核心特点及其在绘画创作中的广泛应用。同时…

stm32使用串口的轮询模式,实现数据的收发

------内容以b站博主keysking为原型&#xff0c;整理而来&#xff0c;用作个人学习记录。 首先在STM32CubeMX中配置 前期工作省略&#xff0c;只讲重点设置。 这里我配置的是USART2的模式。 会发现&#xff0c;PA2和PA3分别是TX与RX&#xff0c;在连接串口时需要TX对RX&…

openapi回调地址请求不通过

目录 1. 验证url接口get请求本地自测报错 2. 测试回调模式成功不返回结果 3. 测试回调模式返回结果带双引号 对接企业微信 产生会话回调事件 接口问题解决 1. 验证url接口get请求本地自测报错 java.lang.IllegalArgumentException: Last encoded character (before the pa…

软件设计师笔记-数据结构

数据结构 数据元素的集合及元素间的相互关系和构造方法。 线性表的存储结构 顺序存储链式存储 单链表节点 typedef struct node { int data; struct node *link; }NODE, *LinkList; 双向链表 每个节点有两个指针&#xff0c;分别指出直接前驱和直接后继。 循环链表 尾…

【javascript】console 对象提供的方法

文章目录 1、 console.dir() 打印对象2、console.table() 打印数组3、 console.clear() 清理控制台4、console.group() 控制打印组5、console.time() 完成计时 console.log 是一个很好的调试方式。但是 如果我们滥用它&#xff0c;效果反而会适得其反&#xff01;大量打印信息堆…

一:时序数据库-Influx应用

目录 0、版本号 1、登录页面 2、账号基本信息 3、数据库案例 4、可视化 5、java案例 0、版本号 InfluxDB v2.4.0 1、登录页面 http://127.0.0.1:8086/signin 账号&#xff1a;自己账号 密码&#xff1a;自己密码 2、账号基本信息 查看用户id和组织id&#xff01;&…

构建一个导航栏web

<!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><style>*{margin: 0;padding: 0;}#menu{background-color:purple;width: 100px;height: 50px;}.item{float: left;/* 浮动标签可以让块标签&#xff0c…

JAVA基础:单元测试;注解;枚举;网络编程 (学习笔记)

单元测试 操作步骤&#xff1a; a.导包import org.junit; b.三个注解 Test Before After c.点击Test 运行就可以了 用在不需要控制台输入的情境下&#xff1a;javaweb&#xff0c;框架项目&#xff0c;微服务项目 供开发人员自己做测试。 package com.page…

Node.js-增强 API 安全性和性能优化

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;node.js篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来node.js篇专栏内容:node.js-增强 API 安全性和性能优化 前言 在前几篇文章中&#xff0c;我们已经构建了一个…

ThingsBoard规则链节点:Push to Edge节点详解

引言 1. Push to Edge 节点简介 2. 节点配置 2.1 基本配置示例 3. 使用场景 3.1 边缘计算 3.2 本地数据处理 3.3 实时响应 4. 实际项目中的应用 4.1 项目背景 4.2 项目需求 4.3 实现步骤 5. 总结 引言 ThingsBoard 是一个开源的物联网平台&#xff0c;提供了设备管…