数据排序之旅

news2024/11/15 23:18:24

1、排序的概念

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

稳定性:假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

内部排序:数据元素全部放在内存中的排序。

外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不断地在内外存之间移动数据的排序。

平常我们网购的时候,我们有时候会看看销售量最多的是商家或者评论最多的商家等等,这里就需要用到排序。

常见的排序算法:

2、算法实现

2.1插入排序

//插入排序  时间:O(N^2)(最好O(N))
void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (a[end] > a[end + 1])
			{
				Swap(&a[end], &a[end + 1]);
				end--;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
}
 2.2希尔排序

希尔排序就是在插入排序的基础上增加了预排序,目的就是使数组比原先有序一点

预排序,就是设一个gap为大于1的值(不能打过数组长度),先让数据每隔gap的距离进行排序,

然后让不断gap减小,等gap为1时,就是插入排序了。

//希尔排序  时间:O(N^1.3)
void ShellSort(int* a, int n)
{
	//预排序
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		for (int i = 0; i < n - gap; i += gap)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (a[end] > a[end + gap])
				{
					Swap(&a[end], &a[end + gap]);
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

 希尔排序的时间复杂度计算涉及的数学知识比较复杂,记住O(N^1.3)就行。

2.3选择排序

选择排序就是找大和找小,最小放到第一位置和最大放到最后位置,然后设数据来记录数组的长度,减小数组长度。

//时间复杂度O(N^2)
void SelectSort(int*a,int n)
{
    int begin=0;
    int end=n-1;
    while(begin<end)
    {
        int min=begin,max=begin;
        for(int i=begin+1;i<=end;i++)
        {
            if(a[i]<a[min])
                min=i;
            if(a[i]>a[max])
                max=i;
        }
        Swap(&a[begin],&a[min]);
        if(max==begin)
            max=min;
        Swap(&a[begin],&a[max]);
        begin++;
        end--;
    }
}
 2.4堆排序
//堆排序  时间:O(N * log N)
void AdjustDown(int* a, int n, int parent)
{
	int child = 2 * parent + 1;

	while (child < n)
	{
		if (child + 1 < n && a[child] < a[child + 1])
		{
			child++;
		}
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}
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[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}
 2.5冒泡排序

只具有教学意义的排序。核心思想就是两两交换。

//时间复杂度O(N^2)
void BubbleSort(int* a, int n)
{
	for (int j = 0; j < n; j++)
	{
		for (int i = 1; i < n - j; i++)
		{
			if (a[i] < a[i - 1])
				Swap(&a[i], &a[i - 1]);
		}
	}
}
 2.6快排
2.6.1Hoare版

这个版本的快排就是令两个数据分别指向数组的两端,在令key为数组其中一个元素的值,使key的左边都小于对应key位置的值,key右边的值都大于key位置的值,key对应的值就已经排好了,然后数组就被分为两个小数组,这跟二叉树有点相似,直接使用递归。

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

当有很多数据时,我们希望key对应的值比较适中,不希望出现数组分半后,还是只有一个数组。

 为了更快提高效率,我们在数组长度为10时,使用其他排序算法来实现,减少递归次数。

 优化后:

int GetMid(int* a, int left, int right)
{
	int mid = (left + right) / 2;
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
	else
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
}
void QuickSort01(int* a, int left, int right)
{
	if (left >= right)
		return;
	//最小区间优化
	if (right - left + 1 <= 10)
	{
		InsertSort(a+left, right - left + 1);
	}
	else
	{
		//三数取中
		int mid = GetMid(a, left, right);
		Swap(&a[left], &a[mid]);
		int key = left;
		int begin = left, end = right;
		while (begin < end)
		{
			while (begin < end && a[end] >= a[key])
			{
				end--;
			}
			while (begin < end && a[begin] <= a[key])
			{
				begin++;
			}
			Swap(&a[begin], &a[end]);
		}
		Swap(&a[begin], &a[key]);
		key = begin;
		QuickSort01(a, left, key - 1);
		QuickSort01(a, key + 1, right);
	}
}

 为什么要右边先走?

会出现一种情况,L找大停下了,R没有找到小,与L相遇了,与key互换位置后,逻辑就变了

左边作key,可保证相遇位置比key小,右边作key,则相遇位置比key大。

左边作key:L遇R,R先走,停下来,R的值比key小,L没有遇到大的,遇R停下了。

R遇L,R先走,找小,没有,遇L停下了。L停留的位置是上一轮交换的位置,上一轮交换把比key小的值,换到了L的位置。 

 2.6.2挖坑法

没有效率提升,不用分析,左边作key,右边先走的文体,也不用分析相遇位置为什么就是比key小的问题。

void QuickSort02(int* a, int left,int right)
{
	if (left >= right)
		return;
	int key = a[left];
	int begin = left, end = right;
	while (begin < end)
	{
		while ( begin < end &&a[end] >= key)
		{
			end--;
		}
		a[begin] = a[end];
		while (begin < end && a[begin] <= key)
		{
			begin++;
		}
		a[end] = a[begin];
	}
	a[begin] = key;
	QuickSort02(a, left, begin-1);
	QuickSort02(a, begin+1, right);
}
2.6.3前后指针法

//前后指针法
int _QuickSort03(int* a, int left, int right)
{
	int mid = GetMid(a, left, right);
	Swap(&a[left], &a[mid]);
	int key = left;
	int prev = left;
	int pcur = prev + 1;
	while (pcur <= right)
	{
		if (a[pcur] < a[key] && ++prev != pcur)
			Swap(&a[prev], &a[pcur]);
		pcur++;
	}
	Swap(&a[key], &a[prev]);
	return prev;
}
void QuickSort03(int* a, int left, int right)
{
	if (left >= right)
		return;
	int key = _QuickSort03(a, left, right);
	QuickSort03(a, left, key - 1);
	QuickSort03(a, key + 1, right);
}
2.6.4非递归版

非递归,要用栈来帮忙实现。再借用一下前后指针法中子函数。

void QuickSortNonR(int* a, int left,int right)
{
	ST st;
	STInit(&st);
	STPush(&st, right);
	STPush(&st, left);
	while (!STEmpty(&st))
	{
		int begin = STTop(&st);
		STPop(&st);
		int end = STTop(&st);
		STPop(&st);
		int key= _QuickSort03(a, begin, end);
		if (key + 1 < end)
		{
			STPush(&st, end);
			STPush(&st, key + 1);
		}
		if (begin < key - 1)
		{
			STPush(&st, key - 1);
			STPush(&st, begin);
		}
	}
	STDestroy(&st);
}
2.7归并排序 
2.7.1递归版

归并排序的原理就是相当于把两个有序数组合成一个有序数组,而归并就是把一个数组分为很多小数组再进行排序。

/归并排序
//时间复杂度:O(N*logN)
//空间复杂度:O(N)
void _MergeSort(int* a, int* tmp, int begin, int end)
{
	if (begin >= end)
		return;
	int mid = (begin + end) / 2;
	_MergeSort(a,tmp,begin,mid);
	_MergeSort(a,tmp,mid+1,end);

	int begin1 = begin, end1 = mid;
	int begin2 = mid + 1, end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] < a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}
	memcpy(a+begin, tmp+begin,sizeof(int)*(end-begin+1));
}
void MergeSort(int*a,int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	_MergeSort(a, tmp, 0, n-1);
	free(tmp);
	tmp = NULL;
}
2.7.2非递归版
void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			int begin1 = i, end1 = i + gap-1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			//第二组越界不存在,这一组就不需要归并
			if (begin2 >= n)
			{
				break;
			}
			//第二组begin2没越界,end2越界了,需要纠正一下,继续归并
			if (end2 >= n)
			{
				end2 = n - 1;
			}
			int j = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}
			memcpy(a + i, tmp + i, sizeof(int) * (end2-i+1));
		}
		gap *= 2;
	}
	free(tmp);
	tmp = NULL;
}
2.8计数排序
//计数排序
//时间:O(N+range)
//只适合整数/适合范围集中 
//空间:O(range)
void CountSort(int* a, int n)
{
	int min = a[0];
	int max = 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*)calloc(range, sizeof(int));
	if (count == NULL)
	{
		perror("calloc fail");
		return;
	}
	for (int i = 0; i < n; i++)
	{
		count[a[i] - min]++;
	}
	int i = 0;
	for (int j = 0; j < range; j++)
	{
		while (count[j]--)
		{
			a[i++] = j + min;
		}
	}
	free(count);
}

3.排序性能测试

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()+i;
		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);
}

我们随机创建十万个数 ,看看那个排序快。

首先我们看看插入,希尔,堆,选择和冒泡:

 这样来看冒泡和选择太慢了,与其他排序不是一个桌子的。

把选择,冒泡和插入和希尔去掉,我们来比较一下快排。

这么一比,hoare版本和挖坑法的快排性能好。

再看看归并排序:

 

 两种都差不多。

时间复杂度空间复杂度稳定性
插入排序O(N^2)O(1)稳定
希尔排序O(N^1.3)O(1)不稳定
选择排序O(N^2)O(1)不稳定
堆排序O(N*logN)O(1)不稳定
冒泡排序O(N^2)O(1)稳定
快排序O(N*logN)O(logN)不稳定
归并排序O(N*logN)O(N)稳定

 完

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue3 + i18n 实现国际化并动态切换语言

安装 npm install vue-i18n// index.ts import { createI18n } from vue-i18n // 语言包 import ch from ./ch import en from ./enconst lang localStorage.getItem(localeLangD) || ch if (!localStorage.getItem(localeLangD)) {localStorage.setItem(localeLangD, lang) …

linux文本命令:文本处理工具awk详解

目录 一、概述 二、基本语法 1、awk 命令的基本语法 2、常用选项 3、获取帮助 三、工作原理 四、 功能特点 五、分割字段 六、 示例 1. 打印所有行 2. 计算总和 3. 过滤特定模式 4. 使用多个模式 5. 复杂的脚本 6. 自定义分隔符 7. 打印指定列 8. 使用 BEGIN …

微信小程序教程011-1:京西购物商城实战

文章目录 1、起步1.1 uni-app简介1.2 开发工具1.2.1 下载HBuilderX1.2.2 安装HBuilderX1.2.3 安装scss/sass编译1.2.4 快捷键方案切换1.3 创建uni-app项目1.4 目录结构1.5 把项目运行到微信开发者工具1.6 使用Git管理项目1.6.1 本地管理1.6.2 把项目托管到码云1、起步 1.1 uni…

【Unity】3D功能开发入门系列(五)

Unity3D功能开发入门系列&#xff08;五&#xff09; 一、预制体&#xff08;一&#xff09;预制体&#xff08;二&#xff09;预制体的创建&#xff08;三&#xff09;预制体实例&#xff08;四&#xff09;预制体的编辑 二、动态创建实例&#xff08;一&#xff09;动态创建实…

2024/8/4 汇川变频器低压产品分类选型

VF就是通过电压、频率控制 矢量就是通过开环&#xff08;svc&#xff09;和闭环&#xff08;fvc&#xff09; MD310、MD200 开环&#xff0c;不支持闭环&#xff0c;无法接编码器 290 、200s、280、都是VF控制

有哪些供应链管理方法?详解四种常用的供应链管理方法!

在当今复杂多变的商业环境中&#xff0c;供应链管理已成为企业获取竞争优势的关键。有效的供应链策略不仅能提升企业的响应速度和市场适应性&#xff0c;还能显著降低成本、提高效率。本文将深入探讨几种主流的供应链管理方法&#xff0c;包括快速反应、有效客户反应、基于活动…

LeetCode 0572.另一棵树的子树:深搜+广搜(n^2做法就能过,也有复杂度耕地的算法)

【LetMeFly】572.另一棵树的子树&#xff1a;深搜广搜&#xff08;n^2做法就能过&#xff0c;也有复杂度耕地的算法&#xff09; 力扣题目链接&#xff1a;https://leetcode.cn/problems/subtree-of-another-tree/ 给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 s…

DEBUG:sw模板不对

问题 sw自带模板不合适 解决 工具 选项 文件位置 自己新建一个文件夹 放入模板 &#xff08;三维 二维各一个 一般就是统一标准 可以自己新建个模板&#xff09;

深度学习笔记(神经网络+VGG+ResNet)

深度学习 主要参考博客常用英语单词 概念应用神经网络基础神经网络基本结构 超参数超参数是什么常用超参数超参数搜索过程常用超参数调优办法&#xff08;通过问题复杂度和计算资源选择&#xff09; 激活函数介绍为什么要使用激活函数推荐博客 sigmoid激活函数&#xff08;使用…

【教程-时间序列预测】PyTorch 时间序列预测入门

文章目录 from博客: https://zhajiman.github.io/post/pytorch_time_series_tutorial/#%E9%AB%98%E7%BA%A7%E6%96%B9%E6%B3%95%E8%87%AA%E5%9B%9E%E5%BD%92%E6%A8%A1%E5%9E%8B 数据集产生 窗口 也是难点&#xff01;

工作中,如何有效解决“冲突”?不回避,不退让才是最佳方式

职场里每个人都在争取自己的利益&#xff0c;由于立场的不同&#xff0c;“冲突”不可避免。区别在于有些隐藏在暗处&#xff0c;有些摆在了台面上。 隐藏在“暗处”的冲突&#xff0c;表面上一团和气&#xff0c;实则在暗自较劲&#xff0c;甚至会有下三滥的手段&#xff1b;…

常见API(二)

API 应用程序编程接口&#xff0c;提高编程效率。本次学习了Object类&#xff0c;Objects工具类&#xff0c;包装类&#xff0c;StringBuilder&#xff0c;StringBuffer&#xff0c;和StringJoiner。 目录 1.Object 常见方法&#xff1a; 2.Objects 常见方法&#xff1a; 3…

AcWing开放地址法和拉链法

开放地址法&#xff0c;把h数组全部设置为3f&#xff0c;然后设定null为0x3f3f3f3f&#xff0c;find函数设定返回值t&#xff0c;如果h[t]null,那么x在h中不存在&#xff0c;否则为存在 #include<iostream> #include<cstring> #include<string> #define LEN…

在Vue3中如何为路由Query参数标注类型

前言 最近发布了一款支持IOC容器的Vue3框架&#xff1a;Zova。与以往的OOP或者Class方案不同&#xff0c;Zova在界面交互层面仍然采用Setup语法&#xff0c;仅仅在业务层面引入IOC容器。IOC容器犹如一把钥匙&#xff0c;为我们打开了业务工程化的大门&#xff0c;允许我们探索…

Linux网络编程2

TCP编程 顺序图 socket() 函数 socket()函数用于创建一个新的套接字。它是进行网络编程的第一步&#xff0c;因为所有的网络通信都需要通过套接字来进行。 原型&#xff1a; #include <sys/socket.h> int socket(int domain, int type, int protocol); domain&…

使用Go语言绘制饼图的教程

使用Go语言绘制饼图的教程 在本教程中&#xff0c;我们将学习如何使用Go语言及gg包绘制饼图&#xff0c;并将其保存为PNG格式的图片。饼图是一种常用的数据可视化图表&#xff0c;用于展示数据的比例关系和组成部分。 安装gg包 首先&#xff0c;确保你已经安装了gg包。如果还…

前端的学习-CSS(七)

一&#xff1a;定位(position) 1&#xff1a;为什么使用定位。 有一些固定的盒子&#xff0c;并且压在其他盒子上面&#xff0c;一些网页&#xff0c;会有固定的窗口&#xff0c;这些是标准流和浮动无法解决的&#xff0c;比如下面的例子。旁边的红色边框的效果是不会随着页面的…

汇昌联信数字做拼多多运营怎么入行?

拼多多作为中国领先的电商平台之一&#xff0c;近年来在数字运营领域展现出了强大的生命力和创新能力。汇昌联信数字作为一个潜在的新入行者&#xff0c;如何进入拼多多的运营领域&#xff0c;成为业界关注的焦点。本文旨在探讨汇昌联信数字如何通过有效的策略和方法&#xff0…

hal库回调函数机制

1. 第一行就是标准库函数的 在 nvic那个中断向量表里面的函数 以前写的都是 在中断向量表里面把这个中断处理函数重写 2. 第二行 第三行 的 hal库就是 通过中断向量表里面的这个函数 &#xff0c;再一次调用hal自己的中断回调函数&#xff0c;就是相当于多封装了两层 这个图更…

局部整体(二)利用python绘制维恩图

局部整体&#xff08;二&#xff09;利用python绘制维恩图 维恩图&#xff08; Venn Diagram&#xff09;简介 维恩图显示集与集之间所有可能存在的逻辑关系&#xff0c;每个集通常以一个圆圈表示&#xff0c;每个集都是一组具有共同之处的物件或数据。当多个圆圈&#xff08;…