数据结构之排序(二)

news2024/11/9 10:13:42

 

目录

基本思想:

1.1冒泡排序

​编辑1.1.1代码实现

 1.3冒泡排序的特性总结:

2.1 快速排序

2.1.1基本思想

2.2.2代码实现

1. hoare版本

2.挖坑法

3.前后指针版本

2.2.3 快速排序的优化(三数取中)

实现步骤

 3.1 快速排序非递归

3.1.1代码实现

快速排序的特性总结:

二. 归并排序

基本思想:

 代码实现

非比较排序 (非递归)

归并排序的特性总结:

三.排序算法复杂度及稳定性分析 

四、结尾


根据上文继续后文的排序数据结构之排序(一)-CSDN博客

一.交换排序

基本思想:

所谓交换,就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置,交换排 序的特点是:将键值较大的记录向序列的尾部移动,键值较小的记录向序列的前部移动。

1.1冒泡排序

1.1.1代码实现
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void BubbleSort(int* a, int n)
{
	for (int j = 0; j < n; j++)
	{
		bool exchange = false;
		for (int i = 1; i < n - j; i++)
		{
			if (a[i - 1] > a[i])
			{
				Swap(&a[i - 1], &a[i]);
				exchange = true;
			}
		}

		if (exchange == false)
		{
			break;
		}
	}
}
 1.3冒泡排序的特性总结:

1. 冒泡排序是一种非常容易理解的排序

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

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

4. 稳定性:稳定

2.1 快速排序

2.1.1基本思想

快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其基本思想为:任取待排序元素序列中 的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右 子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。

将区间按照基准值划分为左右两半部分的常见方式有:

2.2.2代码实现

将区间按照基准值划分为左右两半部分的常见方式有:

1. hoare版本

 

//三数取中
int GetMidNumi(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 left;
		}
		else
		{
			return right;
		}
	}
	else // a[left] > a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}
// hoare版本
void QuickSort(int* a, int left, int right)
{
	// 三数取中
	int midi = GetMidNumi(a, left, right);
	if (midi != left)
		swap(&a[midi], &a[left]);

	int key = left;
	while (left < right)
	{
		while (a[right] >= a[key])
			right--;

		while (left < right && a[left] <= a[key])
			left++;

		swap(&a[right], &a[left]);
	}
	swap(&a[key], &a[left]);

}
2.挖坑法

 

//挖坑法
void PartSort2(int* a, int left, int right)
{
	// 三数取中
	int midi = GetMidNumi(a, left, right);
	if (midi != left)
		swap(&a[midi], &a[left]);

	if (left >= right)
		return;
	int key = a[left];
	int hole = left;
	while (left < right)
	{
		while (a[right] >= a[key])
			right--;
		
		a[hole] = a[right];
		hole = right;

		while (left < right && a[left] <= a[key])
			left++;

		a[right] = a[left];
		hole = left;

	}
	a[left] = key;
	hole = left;

	PartSort2(a, 0, hole - 1);
	PartSort2(a, hole + 1, right);

}
3.前后指针版本

 

//前后指针法
void PartSort3(int* a, int left, int right)
{
	// 三数取中
	int midi = GetMidNumi(a, left, right);
	if (midi != left)
		swap(&a[midi], &a[left]);

	if (left >= right)
		return;
	int key = left;
	int prev = left;
	int cur = left + 1;
	while(cur <= right)
	{
		if (a[cur] < a[key])
		{
			prev++;
			swap(&a[cur], &a[prev]);
		}
		cur++;
	}
	swap(&a[prev], &a[key]);
	
	PartSort3(a, 0, prev - 1);
	PartSort3(a, prev + 1, right);
}

2.2.3 快速排序的优化(三数取中)

三数取中的基本思想是:在待排序的数组(或子数组)中,取第一个元素、最后一个元素以及中间位置的元素,然后比较这三个元素的值,取它们的中间值作为基准值(pivot)。这样做的目的是尽可能选择一个位于数据中间的元素作为基准,以减少数据分区的不平衡性。

实现步骤
  1. 定位元素:首先,找到数组(或子数组)的第一个元素、最后一个元素和中间位置的元素。
  2. 比较元素:然后,比较这三个元素的值。
  3. 选择中间值:根据比较结果,将这三个元素按照从小到大的顺序排列(如果需要的话,可以交换它们的位置),然后取中间的元素作为基准值(pivot)。
  4. 进行分区:以这个基准值为依据,对数组进行分区操作,即小于基准值的元素放在基准值的左边,大于基准值的元素放在基准值的右边。
//三数取中
int GetMidNumi(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 left;
		}
		else
		{
			return right;
		}
	}
	else // a[left] > a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

 3.1 快速排序非递归

3.1.1代码实现

非递归运用了栈的压栈和出栈来进行排序

#include <stdio.h>
#include "Stack.h"

// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)
{
	Stack st;
	StackInit(&st);

	int end = right;
	int begin = left;

	StackPush(&st, end);
	StackPush(&st, begin);

	while (!StackEmpty(&st))
	{
		begin = StackTop(&st);
		StackPop(&st);
		end = StackTop(&st);
		StackPop(&st);
		int keyi = PartSort1(a, begin, end);
		//[begin , keyi - 1] [keyi] [keyi + 1 , end]
		if (end > keyi + 1)
		{
			StackPush(&st, end);
			StackPush(&st, keyi + 1);
		}
		if (keyi - 1 > begin)
		{
			StackPush(&st, keyi - 1);
			StackPush(&st, begin);
		}
	}
}

快速排序的特性总结:

1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

2. 时间复杂度:O(N*logN)

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

4. 稳定性:不稳定

二. 归并排序

基本思想:

归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤:

 

 代码实现

void _Catego(int* a, int begin, int end, int* copy)
{
	if (begin >= end)
		return;

		int i = begin;
		int mid = (begin + end) / 2;
		_Catego(a, begin, mid, copy);
		_Catego(a, mid+1, end, copy);

		int begin1 = begin; int end1 = mid;
		int begin2 = mid+1;  int end2 = end;
		while (begin2 <= end2 && begin1 <= end1)
		{
			if (a[begin1] > a[begin2])
			{
				copy[i++] = a[begin2++];
			}
			else
			{
				copy[i++] = a[begin1++];
			}
		}

		while (begin2 <= end2)
		{
			copy[i++] = a[begin2++];
		}

		while (begin1 <= end1)
		{
			copy[i++] = a[begin1++];
		}
	memcpy(a + begin, copy + begin, sizeof(int) * (end - begin + 1));
}


void CategoSort(int* a, int n)
{
	int* copy = (int*)malloc(sizeof(int) * n);
	if (copy == NULL)
	{
		perror("malloc");
		return;
	}

	_Catego(a, 0, n-1, copy);
	free(copy);
}

非比较排序 (非递归)

在非递归中需要考虑越界的问题:end1,begin2,end2,分别进行导论

 

void CatagoSort(int *a,int n)
{
	int* copy = (int*)malloc(sizeof(int) * n);
	if (copy == NULL)
	{
		perror("malloc");
		return;
	}


	int gap = 1;


	while (gap < n)
	{
		int j = 0;
		int i = 0;
		for (int i = 0; i < n; i += 2*gap)
		{
			int begin1 = i; int end1 = i + gap -1;
			int begin2 = i + gap; int end2 = i + 2 * gap -1;

			// 间距为gap的多组数据,归并完以后,一把拷贝(梭哈)

			if (end1 >= n)
			{
				end1 = n - 1;
				begin2 = n;
				end2 = n-1;
			}
			else if (begin2 >= n)
			{
				begin2 = n;
				end2 = n - 1;
			}
			else if (end2 >= n)
			{
				end2 = n - 1;
			}

			printf("[%d] [%d] [%d] [%d]", begin1, end1, begin2, end2);
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] > a[begin2])
				{
					copy[j++] = a[begin2++];
				}
				else
				{
					copy[j++] = a[begin1++];
				}
			}
			while (begin1 <= end1)
			{
				copy[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				copy[j++] = a[begin2++];
			}
		}
		printf("\n");
		memcpy(a, copy, sizeof(int) * n);
		gap *= 2;
	}
	memcpy(a, copy, sizeof(int) * n);
	free(copy);
}

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail\n");
		return;
	}

	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			// [begin1,end1][begin2, end2]
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;
			//printf("[%d,%d][%d,%d] ", begin1, end1,begin2,end2);

			if (end1 >= n || begin2 >= n)
			{
				break;
			}

			if (end2 >= n)
			{
				end2 = n - 1;
			}

			printf("[%d,%d][%d,%d] ", begin1, end1, begin2, end2);

			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));
		}

		printf("\n");

		gap *= 2;
	}

	free(tmp);
}

 

归并排序的特性总结:

1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。

2. 时间复杂度:O(N*logN)

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

4. 稳定性:稳定

三.排序算法复杂度及稳定性分析 

 

四、结尾

如果有什么建议和疑问,或是有什么错误,希望大家可以在评论区提一下。
希望大家以后也能和我一起进步!!
如果这篇文章对你有用的话,希望能给我一个小小的赞!

 

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

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

相关文章

链表--随机链表复制

给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成&#xff0c;其中每个新节点的值都设为其对应的原节点的值。新节点的 n…

太阳方向角/高度角/赤纬角/太阳时角/真平太阳时差/理论计算方法(matlab)

1. 理论学习 方向角&#xff0c;高度角计算公式 如图&#xff0c;直观地描述了方位角(圆盘上红色夹角)与高度角(黄色线与圆盘的夹角) 赤纬角计算公式 地球赤道平面与太阳和地球中心的连线之间的夹角 如图所示&#xff0c;23度那个. 时角计算公式 太阳时角是指日面中心的时角…

博客园OpenApi管理平台

简介 博客园&#xff08;Cnblogs&#xff09;提供了OpenAPI服务&#xff0c;允许开发者通过API来获取博客园中的数据。使用这个API&#xff0c;可以实现从博客园抓取文章、评论等信息的功能&#xff0c;这对于想要集成博客园内容到自己网站或应用的开发者来说是非常有用的。 …

【hot100篇-python刷题记录】【二叉树的最大深度】

R6-二叉树篇 最简单的方法&#xff1a; 循环len(root)次&#xff0c;每次循环执行以下操作&#xff1a; 循环pow(2,i)次&#xff0c;每次都root.pop(0) 如果为空&#xff0c;立即退出&#xff0c;返回i1 class Solution:def maxDepth(self, root: Optional[TreeNode]) ->…

C语言基础(十七)

C语言中的结构体&#xff08;Struct&#xff09;是一种用户自定义的数据类型&#xff0c;允许将不同类型的数据项组合成一个单一的类型&#xff1a; 测试代码1&#xff1a; #include "date.h" #include <stdio.h> #include <string.h> // 定义结构…

【数据分享】1999—2022年地级市市政公用事业和邮政、电信业发展情况相关指标(Shp/Excel格式)

在之前的文章中&#xff0c;我们分享过基于2000-2023年《中国城市统计年鉴》整理的1999-2022年地级市的人口相关数据、各类用地面积数据、污染物排放和环境治理相关数据、房地产投资情况和商品房销售面积、社会消费品零售总额和年末金融机构存贷款余额、一般公共预算收支状况、…

[NeurIPS 2024] Self-Refine: Iterative Refinement with Self-Feedback

Contents TL;DRReferences TL;DR 通过让 LLM 生成 feedback 不断 refine 自身的回答&#xff0c;可以提升回答效果&#xff0c;但也会带来不可忽视的推理开销 References Madaan, Aman, et al. “Self-refine: Iterative refinement with self-feedback.” Advances in Neura…

Openssl Infinite Loop 漏洞(CVE-2022-0778)

Openssl Infinite Loop 漏洞&#xff08;CVE-2022-0778&#xff09; 1. 漏洞详情 在该漏洞中由于证书解析时使用的 BN_mod_sqrt() 函数存在一个错误&#xff0c;它会导致在非质数的情况下永远循环。可通过生成包含无效的显式曲线参数的证书来触发无限循环。由于证书解析是在验…

word技巧:如何在Word中轻松实现文档内容的左右分栏排版

你是否曾遇到过需要让Word文档的内容更加整洁、易于阅读&#xff0c;却苦于不知如何将其巧妙分为左右两栏的困扰&#xff1f;别担心&#xff0c;今天我们就来详细解析这一实用技巧&#xff0c;让你的文档排版瞬间提升一个档次&#xff01; 方法如下&#xff1a; 首先&#xff0…

uniapp h5手机如何打开本地跑的前端项目进行本地调试

本地调试使用 vConsole是一个轻量级的移动端调试工具&#xff0c;可以在iOS设备上直接调试Uniapp H5应用。下面是具体的步骤&#xff1a; 在Uniapp项目中安装vConsole依赖&#xff1a;npm install vconsole。 在项目的main.js文件中引入vConsole库&#xff1a;import VConso…

python实现简单中文词元化、词典构造、时序数据集封装等

文章目录 简述代码词元化词典构造时序数据生成data.TensorDataset生成 简述 中文词元化、删除非中文字符、构建索引词典&#xff0c;以便于为训练提供向量化数据。 待处理文本&#xff0c;以朱自清的《背影》为例&#xff0c;图中是给句子手动换行了&#xff0c;不换也是没问…

【网络安全】服务基础第一阶段——第二节:网络测试与用户

一、Windows网络测试工具 CMD&#xff08;命令提示符&#xff09;中&#xff0c;ping和tracert是两个非常有用的网络诊断工具 1.1.ping命令 ping命令是Windows和其他操作系统中用于测试主机之间网络连接是否可达的基本命令行工具。它通过发送ICMP&#xff08;Internet Contr…

LLM agentic模式之规划能力(planning)

文章目录 任务分解分解优先方法交替分解方法 多计划选择外部规划器辅助规划反思和改进记忆增强规划评估 2024年2月的综述《 Understanding the planning of LLM agents: A survey》提供了基于LLM的的agent的规划(planning)能力的系统视角&#xff0c;总结了近年来提高规划能力…

如何申请 Midjourney API ,看这篇文章就够了

Midjourney Imagine API 的申请与应用 Midjourney&#xff0c;这一杰出的 AI 绘图工具&#xff0c;凭借输入几个关键字&#xff0c;便能在短短一两分钟内生成极为精美的图像&#xff0c;展现出令人惊叹的创作能力。它以独特的绘画技术在行业中独树一帜&#xff0c;现今在各个领…

墨刀基础篇(一) :6.常用组件(动态组件)

一&#xff1a;动态组件 动态组件是除了文件和矩形之外最重要的一个组件。文本和矩形是一切组件的基础&#xff0c;而动态组件是复杂组件的根本。动态组件就是可以拥有多个状态&#xff0c;每个状态可以放不同的内容&#xff0c;每个状态之间可以互相切换&#xff0c;每个状态…

springboot系列--springboot前置知识

一、spring相关知识 一、spring能干什么 二、spring生态 spring生态覆盖了&#xff1a;web开发、数据访问、安全控制、分布式、消息服务、移动开发、批处理等等。 官网链接&#xff1a;Spring | Home 二、springboot相关知识 一、springboot作用 Spring Boot makes it easy …

Ansys Rocky在电池制造行业应用

Ansys Rocky在电池制造行业应用 对于电池电极制造的理解 干湿混合应用 砑光应用 微尺度电极干燥应用 更多应用 材料生产 电极和电池生产

prolog 基础 - 关系和属性

首先进入环境&#xff1b; 看一下一开始的提示符是 ?- &#xff0c;现在可以用write语句输出一些东西&#xff1b; 根据资料&#xff0c;在prolog中&#xff0c; 两个对象之间的关系&#xff0c;使用括号表示。比如&#xff0c;jack的朋友是peter&#xff0c;写成friend(ja…

论文笔记:Large Language Models are Zero-Shot Next LocationPredictors

1 intro 下一个地点预测&#xff08;NL&#xff09;包括基于个体历史访问位置来预测其未来的位置。 NL对于应对各种社会挑战至关重要&#xff0c;包括交通管理和优化、疾病传播控制以及灾害响应管理NL 问题已经通过使用马尔可夫模型、基于模式的方法以及最近的深度学习&#x…

突破传统,GCOM80-2NET的创新性边缘计算方式

GCOM80-2NET边缘计算网关&#xff0c;突破传统Modbus协议仅能进行数据读采的限制&#xff0c;直接在设备终端进行复杂的数据点运算&#xff0c;减少冗余数据传输&#xff0c;释放软件人员针对不同Modbus设备协议解析的时间。 GCOM80-2NET&#xff0c;是ZLG致远电子推出的一款高…