[C语言实现]码林盟主秘籍——《手撕八大排序》


🥰作者: FlashRider

🌏专栏: 初阶数据结构

🍖知识概要:详解八大排序的原理、时间复杂度分析、以及代码实现。

目录

八大排序

插入排序

直接插入排序

希尔插入排序

选择排序

冒泡排序

计数排序

堆排序

快速排序

霍尔法

挖坑法

前后指针法及优化快排

归并排序


八大排序

八大排序分别是:插入排序、希尔排序、选择排序、冒泡排序、快速排序、归并排序、计数排序以及堆排序。

我们很多算法和数据结构的实现都离不开排序,也有很多实际应用离不开排序,如何去选择一个合适的排序是我们需要熟知并掌握的知识。

插入排序

直接插入排序

假设我们有一个已经有序的序列,那么新插入的数只需要和序列中的数依次比较,找到合适的位置插入即可。

那么对于一个待排序的序列,我们只需要遍历这个数列,将每一个数当作新插入的数,插入到之前已经有序的序列中即可(第一个数插入到空序列,空序列必定有序)。

过程如下:

void InsertSort(int* a, int n)
{
	for(int i = 1; i < n; i++)
	{
		int end = i-1;
		int tmp = a[i];
		while(end >= 0)
		{
			if(a[end] > tmp)
			{
				a[end+1] = a[end];
				end--;
			}
			else break;
		}
		a[end+1] = tmp;
	}
}

希尔插入排序

从上方插入排序的过程可以看出来,如果待排序的序列是有序或者接近有序的时候,我们的每次插入都只需要遍历寥寥几个数,希尔排序的思路就是,利用步长把一个序列排为接近有序,逐步将步长缩短,这样能优化插入排序的效率。

void ShellSort(int* a, int n)
{
	for(int j = 0; j < gap; j++)//分为gap组数据 分别进行插入排序
	{
		for(int i = j; i < n - gap; i += gap)//插入排序逻辑
		{
			int end = i;
			int tmp = a[end + gap];
			while(end >= 0)
			{
				if(a[end] > tmp)
				{
					a[end+gap] = a[end];
					end -= gap;
				}
				else break;
			}
			a[end + gap] = tmp;
		}
	}
}

选择排序

对于一个待排序的序列,我们选择其中的最小值,和第一个位置的数交换,再选择次小值,和第二个位置的数交换,以此类推......

最后就能得到一个升序的序列。(降序同理)

void SelectSort(int* a, int n)//同时选择最大值和最小值,可以提高小部分效率
{
	int begin = 0, end = n - 1;
	while(begin < end)
	{
		int maxi = begin, mini = begin;
		for(int i = 0; i < n; i++)
		{
			if(a[maxi] < a[i]) maxi = i;
			if(a[mini] > a[i]) mini = i;
		}
		Swap(a[mini], a[begin]);
		if(maxi == begin) maxi = mini;//修正
		Swap(a[maxi], a[end])
		begin++, end--;
	}
}

冒泡排序

冒泡排序的主要思想就是相邻交换,比如我要将一个序列变成升序序列,一个数比右边的数大,那么他就要和右边的数进行交换,这样不断交换下去,一趟遍历的交换就能把最大的值放在倒数第一位,第二趟遍历就可以把第二大的值放在倒数第二位,以此类推。

当某一次遍历所有数后也没发生交换,代表已经有序,就可以退出排序了。

void BubbleSort(int* a, int n)
{
	for(int j = 0; j < n; j++)//确定n个数的位置
	{
		int exchange = 0;//是否发生交换
		for(int i = 1; i < n - j; i++)
		{
			if(a[i - 1] > a[i])
			{
				Swap(a[i - 1], a[i]);
				exchange = 1;
			}
		}
		if(exchange == 0) break;
	}
}

计数排序

我们开一个数组全部初始化为0,下标对应数字,来统计每个数出现的次数,从小到大遍历数组,把出现过的数依次拿出来即可。

步骤如下:

1. 统计数字出现的次数

2. 按照次数有序归回原数组

局限性:浮点数与字符串无法排序。耗费大量空间。

void CountSort(int* a, int n)
{
	int max = a[0], min = a[0];
	for(int i = 0; i < n; i++)
	{
		if(a[i] > max) max = a[i];
		if(a[i] < min) min = a[i];
	}
	int range = max - min + 1;
	int* count = (int*)malloc(sizeof(int) * range);
	memset(count, 0, sizeof(int) * range);
	//统计
	for(int i = 0; i < n; i++)
	{
		count[a[i] - min]++;
	}
	int j = 0;
	//排序
	for(int i = 0; i < range; i++)
	{
		while(count[i]--)
		{
			a[j++] = i + min;
		}
	}
}

堆排序

堆排序有两种方式,一种是将一个序列建成堆,再不断打印堆顶元素并pop,就能满足有序,但实际上这个序列本身是堆,而非有序序列,只是打印的时候有序罢了。

这里推荐第二种方式,直接将一个待排序序列建堆,比如我们建大堆,这样堆顶就是最大的元素,让堆顶和堆尾进行swap,再将堆的size减少1,这样不断做下去就能得到有序序列。

void HeapSort(int* a, int n)
{
	int i;
	//向下调整建堆
	for(i = (n - 1 - 1) / 2; i >= 0; i--) //因为叶子结点都可以看做一个堆 所以从倒数第一个非叶结点开始建堆 也就是最后一个叶结点的父亲
	{
		AdjustDown(a, n, i);//倒着调整 保证每次向下调整时左右子树都是堆
		//类似于头插
	}

	int end = n - 1;
	while(end > 0)
	{
		//交换首尾元素 把最小的数放在末尾
		Swap(&a[0], &a[end]);
		//向下调整 选出次小的数
		AdjustDown(a, end, 0);
		//剔除最后一个数 把其他数当成堆继续筛选
		end--;
	}
	//最后数组里的数将会是降序 大~小
}

快速排序

霍尔法

快排的思想主要是选择一个key作为关键值,我们让比key小的数都在key左边,比key大的都跑去key右边,再将左右两个区间递归进行此过程即可。

每一次过程有两个指针left和right,当left遇到比key大的数停下,right遇到比key小的停下。

最后left和right的位置相互交换即可。

当left和right相遇时,用key和它们的位置交换,就能保证key左比key小,key右比key大。

void QuickSort(int* a, int begin, int end)
//快排 霍尔版 不需要考虑边界问题 用key做了下标 不需要考虑key本身的位置
{
	if(begin >= end) return;//保证区间存在
	int left = begin, right = end;
	int key = left; //left为key right先走 保证交换key后永远左边最小右边最大
	while(left < right)
	{
		while(left < right && a[right] >= a[key]) right--;
		while(left < right && a[left] <= a[key]) left++;
		Swap(a[left], a[right]);
	}
	Swap(a[left], a[key]);
	key = left;//修正key
	QuickSort(a, begin, key - 1);
	QuickSort(a, key + 1, end);
}

挖坑法

挖坑法就是把key的位置挖一个坑(未填充数字),不再是交换left和right了,而是将满足条件的数填充到坑中,并把它原本所在的位置当作新的坑。

void QuickSort(int* a, int begin, int end)
{
	if(begin >= end) return;
	int left = begin, right = end;
	int key = a[left];
	int pit = left;
	while(left < right)
	{
		while(left < right && a[right] >= key) right--;
		a[pit] = a[right];
		pit = right;
		while(left < right && a[left] <= key) left++;
		a[pit] = a[left];
		pit = left;
	}
	a[pit] = key;
	QuickSort(a, begin, pit - 1);
	QuickSort(a, pit + 1, end);
}

前后指针法及优化快排

两个指针,cur指向的数如果大于了key,那么cur++。

一旦当cur小于了key,那么让cur和prev指向的数交换,并且cur++,prev++。

这样就能保证比key小的数都交换到前面去了。

快排优化:

快速排序的过程里面最蛋疼的一点就是,当你还剩一小部分元素的时候,它依然会去递归,开辟函数栈帧,这样消耗的资源其实远远大于直接将这部分元素排序的资源的。

所以我们当排序元素剩下一小部分的时候,就直接用插入排序(因为插入排序效率算比较高的了)进行排序即可。

void QuickSort(int* a, int begin, int end)
{
	if(begin >= end) return;
	int prev = begin, cur = begin + 1;
	int key = begin;
	while(cur <= end)
	{
		if(a[cur] < a[key] && ++prev != cur)
			Swap(a[cur], a[prev]);
		cur++;
	}
	//递归优化
	if(end - begin > 10)
	{
		Swap(a[prev], a[key]);
		key = prev;
		QuickSort(a, begin, key - 1);
		QuickSort(a, key + 1, end);
	}
	else InsertSort(a+begin, end - begin + 1);
}

归并排序

首先我们要知道,如何将两个有序序列合并为一个新的有序序列。

其实很简单,我们将两个序列当前遍历到的数比完大小一个个摘下来放到新序列中即可。

那么一个待排序的序列,我们把不断对半对半分下去,最后序列会被分为一个个单独的数,那么对于单独的数来说,它本身就是一个有序序列,这样我们就可以使用上面的方法去排序了。

void _MergeSort(int* a, int begin, int end, int* tmp)
{
	//递归条件
	if(begin >= end) return;
	int mid = begin + end >> 1;
	//划分区间
	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid + 1, end, tmp);
	//归并
	int k = begin;
	int l1 = begin, r1 = mid;
	int l2 = mid + 1, r2 = end;
	while(l1 <= r1 && l2 <= r2)
	{
		if(a[l1] < a[l2])
		{
			tmp[k++] = a[l1];
			l1++;
		}
		else
		{
			tmp[k++] = a[l2];
			l2++;
		}
	}
	while(l1 <= r1)
	{
		tmp[k++] = a[l1];
		l1++;
	}
	while(l2 <= r2)
	{
		tmp[k++] = a[l2];
		l2++;
	}
	memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));						
}
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	_MergeSort(a, 0, n-1, tmp);
	free(tmp);
}

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

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

相关文章

Gather:基于 DePIN 体系构建的 Web3 社交生态

“Gather 通过搭建一套基于 DePIN 的 Web3 社交体系&#xff0c;正在成为 SocialFi 革命的早期推动者。” 基于互联网的社交&#xff0c;正在为人们提供了远距离沟通和连接的便利&#xff0c;打破了地理障碍&#xff0c;这种具备包容性、广泛性的线上连接&#xff0c;加速了信息…

Git禁止松散对象loose objects弹窗

打开仓库时&#xff0c;弹窗如图 This repository currently has approximately XXXX loose objects.解决办法&#xff1a;见How to skip “Loose Object” popup when running ‘git gui’ Git v1.7.9 或以上版本&#xff0c;执行git config --global gui.gcwarning false

什么是XXE攻击?如何进行防护

安全性很难做到正确&#xff0c;即使在当今具有安全意识的世界中&#xff0c;也存在一些严重的漏洞&#xff0c;例如 XML 外部实体 (XXE)&#xff0c;它们被忽视并最终成为破坏的原因。 XML 外部实体 (XXE) 攻击是一种计算机安全漏洞&#xff0c;通常存在于 Web 应用程序中&…

Uds诊断协议的请求和响应的寻址

一根总线上挂载着很多ECU&#xff0c;那么基于CAN协议UDS的诊断请求报文&#xff0c;诊断仪是如何发给ECU的&#xff1f;如何精准的找到想要诊断的那个ECU&#xff1f;ECU又是如何将诊断响应的报文返回给诊断仪&#xff1f; 在UDS协议中&#xff0c;规定了诊断请求和响应报文发…

kerberos:适配华为FI

文章目录 一、hive1、hive thrift连接方式 一、hive 1、hive thrift连接方式 kerberos认证失败信息 缺少配置&#xff1a;{“hadoop.rpc.protection”:“privacy”}&#xff0c;具体可参考&#xff1a;kerbros认证相关问题 华为FI参考资料&#xff1a; https://github.com…

Hive 解决数据倾斜方法

数据倾斜问题&#xff0c; 通常是指参与计算的数据分布不均&#xff0c; 即某个 key 或者某些 key 的数据量远超其他 key&#xff0c; 导致在 shuffle 阶段&#xff0c; 大量相同 key 的数据被发往同一个 Reduce&#xff0c; 进而导致该 Reduce 所需的时间远超其他 Reduce&…

easyui datagrid单元格点击进入编辑时,行会自动向上错位

现象描述&#xff0c;点击第20行可编辑的单元格进入编辑状态时&#xff0c;滚动条自动滚动到第19行了。导致第20行被分页遮挡&#xff0c;看不到无法编辑。 排查了一天百度AI说是滚动定位问题&#xff0c;最后发现是自己设置的列有问题&#xff0c;表格总共五列&#xff0c;全…

mysql面试题八(SQL语句)

目录 1.SQL 基本组成部分 常用操作示例 创建表 插入数据 查询数据 更新数据 删除数据 创建索引 授予用户权限 2.常见的聚合查询 1. 计数&#xff08;COUNT&#xff09; 2. 求和&#xff08;SUM&#xff09; 3. 平均值&#xff08;AVG&#xff09; 4. 最大值&…

4套java智慧型管理系统源码-智慧校园-智慧工地-智慧城管-智慧3D导诊

第一套&#xff1a;Java智慧校园系统源码 智慧学校源码 微信小程序电子班牌 智慧校园系统简介&#xff1a; 智慧校园的建设逐渐被师生、家长认可接受&#xff0c;智慧校园通过对在校师生、教务等所有人员的信息以及各种信息搜集与储存&#xff0c;进行数据优化与管理&#xf…

Formik:让用户体验更加出色的表单解决方案

hi, 大家好&#xff0c;我是徐小夕&#xff0c; 今天又到了我们的博学时间。今天和大家分享一款非常有价值的开源项目——Formik。 这款开源项目也是我研究零代码搭建平台——H5-Dooring 时参考的项目之一&#xff0c;它可以提高表单渲染引擎的性能和效率&#xff0c;构建出性能…

弱口令之暴力破解

目录 前言 弱口令与暴力破解介绍 漏洞挖掘实战专栏 个人介绍 第一关:基于表单的暴力破解 绕过步骤 1.第一步抓包观察 2.使用burp的攻击模块 3.选择攻击模式以及爆破字典 ​编辑 4.进行爆破 第二关 验证码绕过(on server) 绕过步骤 1.观察输入错误观察返回结果 2…

MATLAB中左边的大括号最后一行为什么会留很大的空白——解决

看了一些帖子说改字体&#xff0c;但是并没有什么用&#xff0c;在此给出亲测有效的方法&#xff1a;改变矩阵的行间距 先说一下问题 上图中留有大块空白 **解决办法&#xff1a;**光标放在矩阵上 格式——矩阵——更改矩阵&#xff0c;在矩阵设置中选中“行高相等”&#xff…

网络IO模型 select poll epoll的区别

epoll与select、poll的对比 1. 用户态将文件描述符传入内核的方式 select&#xff1a;创建3个文件描述符集并拷贝到内核中&#xff0c;分别监听读、写、异常动作。这里受到单个进程可以打开的fd数量限制&#xff0c;默认是1024。 poll&#xff1a;将传入的struct pollfd结构…

基于Springboot的社区防疫物资申报系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的社区防疫物资申报系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系…

插入排序的可视化实现(Python)

插入排序的Python代码 import tkinter as tk import random import timeclass InsertionSortVisualizer:def __init__(self, root, canvas_width800, canvas_height400, num_bars10):self.root rootself.canvas_width canvas_widthself.canvas_height canvas_heightself.nu…

MySQL学习笔记1(MySQL基础)

1.MySQL基础 1.数据库相关概念 ​ *数据库&#xff1a;存储数据的仓库&#xff0c;数据是有组织的进行存储 DtaBase(DB) ​ *数据管理系统&#xff1a;操纵和管理数据库的大型软件 DataBase Management System (DBMS) ​ *SQL&#xff1a;操作关系型数据库的编程语言&#…

抖音ip地址怎么换位置

抖音&#xff0c;作为一款短视频分享平台&#xff0c;已经成为了许多人展示生活、分享才艺的重要舞台。然而&#xff0c;在抖音的使用过程中&#xff0c;你是否想过更换自己的IP地址位置呢&#xff1f;更换IP地址不仅可以帮助你访问一些地域限制的内容&#xff0c;还可以为你的…

3D开发工具HOOPS助力CAM软件优化制造流程

在现代制造业中&#xff0c;计算机辅助制造&#xff08;CAM&#xff09;软件的发展已成为提高生产效率和产品质量的关键。为了满足不断增长的需求和日益复杂的制造流程&#xff0c;CAM软件需要具备高效的CAD数据导入、云端协作、移动应用支持以及丰富的文档生成能力。 Tech So…

node.js-模块化

定义&#xff1a;CommonJS模块是为Node.js打包Javascript代码的原始方式。Node.js还支持浏览器和其他Javascript运行时使用的ECMAScript模块标准。 在Node.js中&#xff0c;每个文件都被视为一个单独的模块。 概念&#xff1a;项目是由很多个模块文件组成的 好处&#xff1a…

09 JavaScript学习:对象

对象的概念 在计算机科学中&#xff0c;对象是一种数据结构&#xff0c;用于存储数据和方法&#xff08;也称为函数&#xff09;。对象可以包含属性&#xff08;也称为成员变量&#xff09;和方法&#xff08;也称为成员函数&#xff09;&#xff0c;通过这些属性和方法可以描述…