重点算法排序之快速排序、归并排序(上篇)

news2024/10/4 9:27:01

文章目录

一、排序的概念及常见的排序算法

二、快速排序的思想及代码详解

2、1 快速排序的思想 

2、2 挖坑法

2、2、1 挖坑法实现思想

2、2、2 挖坑法举例

2、2、3 挖坑法代码实现

2、3 左右指针法

2、3、1 左右指针法实现思想

2、3、2 左右指针法举例

2、3、3  左右指针法代码实现

2、4 前后指针法

2、4、1 前后指针发实现思想

2、4、2  前后指针法举例

2、4、3 前后指针代码实现

三、归并排序的思想及代码详解

3、1 归并排序的思想实现

3、2 归并排序的代码实现

总结


标题:重点算法排序之快速排序、归并排序(上篇)

作者:@Ggggggtm

寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景

 

一、排序的概念及常见的排序算法

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

  在排序中我们还经常讨论这个排序是否稳定?那到底怎么来判断一个潘旭是否稳定呢?我们先看一下排序稳定的概念。

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

   我们比较常见的排序有六种:插入排序、希尔排序、选择排序、堆排序、快速排序、归并排序。应用较多的,也是相对来说比较重要的有快速排序和归并排序。我们本篇文章先来详细介绍一下快速排序和归并排序的思想及代码详解,下篇文章会写出插入排序、希尔排序、选择排序、堆排序的思想及代码详解。

二、快速排序的思想及代码详解

2、1 快速排序的思想 

 任取待排序元素序列中的某元素作为基准值(我们这里一般选择数组的第一个元素或者最后一个元素或者中间的元素),将数组的元素与基准值比较分成左、右子序列左子序列中所有元素均小于或等于基准值右子序列中所有元素均大于或等于基准值,然后对左右子序列重复该过程(递归实现排序),直到所有元素都排列在相应位置上为止。 

  我们举一个例子一起理解一下。加入我们给出如下数组: 

int arr[]={49,38,65,97,76,13,27,49};

  对上面的数组进行快排,我们按照快排的思想分为三步骤进行排序,如下:

  1. 我们先选出基准值,我们在这里选择数组的最左边的数。
  2. 按照基准直对数组进行初次排序,分成左、右子序列。左子序列中所有元素均小于或等于基准值,右子序列中所有元素均大于或等于基准值。
  3. 递归排序左右子序列,当子序列中只有一个元素时我们就不在递归(递归的停止条件)。

   当递归结束时,此时的序列已经为有序的了。我们不难发现,快速排序递归实现有序是用了分治的思想。我们刚开始是把一个无序的数组分成了左、右子序列数组,进而分别对左、右子序列再分,直到分成左、右子序列只有一个数据时我们可以把一个数据看成有序的),就不会再分。这时递归结束后就已经是有序的了。 

   那么具体显现的思路有几种呢?在这里我给出三种是实现方法:挖坑法、左右指针法、前后指针法。

2、2 挖坑法

2、2、1 挖坑法实现思想

  我们先来了解一下挖坑法的主要思想:

  1. 先选出一个坑的位置(基准值),我们选择坑的位置一般会选最左边或者最右边。
  2. 第一次排序:当我们选择坑的位置为最左边时,我们再从数组的最右边依次往前找找比基准直小的数放到坑的位置。更新坑的位置为从右边找到比基准直小的位置。我们再从左边依次找比基准直大的数字放到新坑的位置。反复此操作,当左右相遇时(此时该地方为坑),把基准值放到坑里就行。
  3. 递归重复第二步骤的操作达到有序。

2、2、2 挖坑法举例

  通过上述思想我们举一个例子具体看一下怎么实现的。我们给出如下数组:

   我们按照上述挖坑法思想来看一下具体实现:

  1. 选出坑的位置(基准直);
  2. 第一次排序;
  3. 递归排序左、右子序列。

2、2、3 挖坑法代码实现

void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void QuickSort(int arr[], int left, int right)
{
	if (left >= right)
	{
		return;
	}
	int index = GetMid(arr, left, right);
	Swap(&arr[left], &arr[index]);
	int begin = left;
	int end = right;
	int pivot = begin;
	int key = arr[begin];
	while (begin < end)
	{
		while (begin<end && arr[end] >= key)
		{
			end--;
		}
		arr[pivot] = arr[end];
		pivot = end;
		while (begin < end && arr[begin] <= key)
		{
			begin++;
		}
		arr[pivot] = arr[begin];
		pivot = begin;
	}
	arr[begin] = key;
	QuickSort(arr, left, begin - 1);
	QuickSort(arr, begin + 1, right);
}
void Print(int arr[], int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
}
void TestSort()
{
	int arr[10] = { 0,9,8,7,6,5,4,3,2,1 };
	int n = 10;
	QuickSort(arr, 0, n - 1);
	Print(arr, n);
}
int main()
{
	TestSort();
	return 0;
}

2、3 左右指针法

2、3、1 左右指针法实现思想

  我们先来了解一下左右指针法的主要思想:

  1. 先选出一个基准值,我们选择坑的位置一般会选最左边或者最右边或者中间的数据。
  2. 第一次排序:我们定义两个指针。初始时,一个指针(begin指针)指向最左边的数据,另一个指针(end指针)指向最后一个数据。begin指针向右找比基准值大的数据,找到了就停下来。end指针向左找比基准值小的数据,找到了就停下来。然后交换两个指针的指向的数据,直到begin指针与end指针相遇,最后把基准值与begin与end相遇位置的数据交换即可。
  3. 递归左右子区重复第二步骤的操作达到有序。

2、3、2 左右指针法举例

  同样,我们先给出一个数组,如下:

  我们用上述的左右指针法的思想来实现一下:

  1. 选出一个基准直(数组最左边的值);
  2. 定义左右指针,依次往中间找;
  3. 递归左右子区间重复第二步骤达到有序。

2、3、3  左右指针法代码实现

#include<stdio.h>
void Print(int arr[], int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
}
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void QuickSort(int arr[], int left, int right)
{
	if (left >= right)
		return;
	int begin = left;
	int end = right;
	int keyi = begin;
	while (begin < end)
	{
		while (begin < end && arr[end] >= arr[keyi])
		{
			end--;
		}
		while (begin < end && arr[begin] <= arr[keyi])
		{
			begin++;
		}
		Swap(&arr[begin], &arr[end]);
	}
	Swap(&arr[begin], &arr[keyi]);
	QuickSort(arr, left, begin - 1);
	QuickSort(arr, begin + 1, right);
}
void TestSort()
{
	int arr[] = { 3,5,1,2,7,6,4,5,8,0,9,-1 };
	int n = 12;
	QuickSort(arr, 0, n - 1);
	Print(arr,n);
}
int main()
{
	TestSort();
	return 0;
}

2、4 前后指针法

2、4、1 前后指针发实现思想

  我们先来了解一下前后指针法的主要思想:

  1. 先选出一个基准值,我们一般只会选择第一个数据作为基准值;
  2. 第一次排序:我们定义两个指针(前后指针)。初始时,前指针(prev指针)指向最左边的数据,后指针(cur指针)指向第二个数据。cur指针向右找比基准值小的数据,找到了就停下来。然后perv指针加一,cur指针与prev指针指向的数据交换。直到cur指针找到最右边停止。最后再把基准值与prev数据交换一下即可。
  3. 递归左右子区重复第二步骤的操作达到有序。

2、4、2  前后指针法举例

 老样子,我们先给出一个数组,如下:

  我们用上述的前后指针法的思想来实现一下:

  1. 先选出一个基准值;
  2. 定义前后指针,进行第一次排序;
  3. 递归实现左右子序列。

2、4、3 前后指针代码实现

#include<stdio.h>
void Print(int arr[], int n)
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
}
void Swap(int* p1, int* p2)
{
	int tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void QuickSort(int arr[], int left, int right)
{
	int keyi = left;
	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
		while (arr[cur] < arr[keyi] && ++prev != cur)
		{
			Swap(&arr[cur], &arr[prev]);
		}
		cur++;
	}
	Swap(&arr[keyi], &arr[prev]);
	QuickSort(arr, left, prev - 1);
	QuickSort(arr, prev + 1, right);
}
void TestSort()
{
	int arr[] = { 3,5,1,2,7,6,4,5,8,0,9,-1 };
	int n = 12;
	QuickSort(arr, 0, n - 1);
	Print(arr,n);
}
int main()
{
	TestSort();
	return 0;
}

  以上就是快速排序的三种不同的实现方法,但是实现的基本思想是大同小异的。接下来我们看一下归并排序。

三、归并排序的思想及代码详解

  归并排序的思想与快速排序的思想有一点相似之处,但又有些不同。当我们理解了快速排序后,归并排序理解起来也就是跟简单了。我们来看一下归并排序的思想实现。

3、1 归并排序的思想实现

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

  我们同样将归并排序分为三大步骤:

  1.  找出数组个数的中间值,将数组非为左右子区间;
  2. 递归找到最小的左右区间
  3. 依次将最小的左右(有序)区间进行归并操作得到有序数组。

  我们结合下面的举例图片更容易理解。

3、2 归并排序的代码实现

void MergeSort(int arr[], int left, int right,int tmp[])
{
	if (left >= right)
		return;
	int mid = left + right >> 1;
	MergeSort(arr, left, mid, tmp);
	MergeSort(arr, mid + 1, right, tmp);

	int begin1 = left;
	int end1 = mid;
	int begin2 = mid + 1;
	int end2 = right;
	int index = left;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (arr[begin1] < arr[begin2])
			tmp[index++] = arr[begin1++];
		else
			tmp[index++] = arr[begin2++];
	}
	while (begin1 <= end1)
	{
		tmp[index++] = arr[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[index++] = arr[begin2++];
	}
	for (int i = left; i <= right; i++)
	{
		arr[i] = tmp[i];
	}
}
void TestSort()
{
	int arr[] = { 3,5,1,2,7,6,4,5,8,0,9,-1 };
	int n = 12;
	int tmp[12];
	QuickSort(arr, 0, n - 1,tmp);
	Print(arr,n);
}
int main()
{
	TestSort();
	return 0;
}

总结

   通过上面的学习,我们发现快速排序和归并排序都用了分治的思想来实现的。分支的思想就是把一个复杂的问题分解成很多相对较容易的问题,通过实现相对较容易的小问题后,一步一步返回完成复杂的大问题。 分支的思想在快速排序和归并排序中就得到了很好的体现。快速排序和归并排序在排序中相对来说是十分重要的,我们应该反复练习,达到熟能生巧的地步。

  快速排序和归并排序就讲到这里,希望本篇文章对你有所帮助,后续会持续更新插入排序、希尔排序、选择排序、堆排序的详解。

  感谢阅读ovo~

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

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

相关文章

类的成员之四:代码块

文章目录一、代码块静态代码块非静态代码块注意总结&#xff1a;由父及子&#xff0c;静态先行属性赋值总结一、代码块 1、代码块的作用&#xff1a;用来初始化类、对象 2、代码块如果有修饰的话&#xff0c;只能使用static 3、分类&#xff1a;静态代码块 vs 非静态代码块 静…

Ubuntu自动登录脚本

Ubuntu自动登录脚本一、!/usr/bin/expect -f的意义二、spawn命令行&#xff1a;三、send命令&#xff1a;四、expect五、interact命令&#xff1a;本人用xshell的SSH登录云服务器&#xff0c;需要从用户Ubuntu切换到root&#xff0c;再切换到lighthouse。编辑了一个自动切换用户…

2-1进程管理-进程与线程

文章目录1.进程2.进程控制块&#xff08;PCB&#xff09;3.进程的状态与转换4.进程通信8.线程9.线程和进程的比较10.线程的实现方式11.多线程模型20.线程的状态与转换1.进程 &#xff08;1&#xff09;进程是程序的一次执行过程 &#xff08;2&#xff09;进程是进程实体的运行…

JavaScript篇.day09-数据类型,表达式,运算符,作用域,语句,严格模式

目录1.数据类型(1)原始数据类型(2)隐式转换(3)逻辑语句中的类型转换(4)数据类型检测2.表达式3.运算符(1)分类(2)其他4.作用域5.语句6.严格模式1.数据类型(1)原始数据类型number, string, boolean, null, undefined, object object对象包含: Array, Function, Date...function f…

【微服务笔记03】微服务组件之Eureka注册中心高可用集群环境搭建

这篇文章&#xff0c;主要介绍微服务组件之Eureka注册中心高可用集群环境搭建。 目录 一、Eureka集群环境 二、搭建Eureka高可用集群环境 2.1、前提准备 &#xff08;1&#xff09;修改hosts配置文件 &#xff08;2&#xff09;创建父工程项目 2.2、搭建Eureka注册中心 …

数据可视化大屏Echarts高级开发散点图实战案例分析(地图扩展插件bmap.min.js、散点图、百度地图控件、柱图、涟漪动图、条件判断颜色)

系列文章目录 燃&#xff0c;拿来即用&#xff01;Echarts动态排名柱状图(自适应电脑和手机端)漏刻有时数据可视化Echarts组件开发(27)&#xff1a;盒须图(箱线图)前后端php交互的实战案例漏刻有时数据可视化Echarts组件开发(26):全国地图三级热力图下钻和对接api自动调用数据…

高压功率放大器在高校实验室的实际应用领域介绍

功率放大器的应用领域是很多电子工程师都关心的问题&#xff0c;那么功率放大器的使用场景又有哪些呢&#xff0c;下面来介绍一下安泰高压功率放大器在各个高校的实际应用情况。 图&#xff1a;激光切割 一、ATA-1000系列宽带功率放大器 应用领域&#xff1a;激光切割 适用高校…

Power BI 可视化修改配色

示例数据&#xff1a; 一、Power BI 按列排序 当把文本字段放在坐标轴&#xff0c;显示的顺序都乱了&#xff0c;完全不是自己想要的&#xff0c;就像下图所示&#xff1a; 默认按照Y轴的数值降序排序&#xff0c;如果选择按照month 以升序排序&#xff0c;就会如下所示&am…

【CDP】CDP集群如何通过Cloudera Manager配置使用SNMP方式转发告警

前言 这篇文章参考了大神的文章&#xff0c;如何在CDH平台上集成SNMP服务&#xff0c;然后实现了CDP集群集成SNMP服务&#xff0c;这里描述下&#xff0c;如何集成步骤&#xff0c;在CDP集群中告警是一个很重要的信息&#xff0c;最直观的衡量一个集群的健康状况&#xff0c;那…

[深度学习] 基于切片辅助超推理库SAHI优化小目标识别

对象检测是迄今为止计算机视觉中最重要的应用领域。然而&#xff0c;小物体的检测和大图像的推理仍然是实际使用中的主要问题&#xff0c;这是因为小目标物体有效特征少&#xff0c;覆盖范围少。小目标物体的定义通常有两种方式。一种是绝对尺度定义&#xff0c;即以物体的像素…

【小程序】自定义组件

文章目录组件的创建与引用自定义组件的样式自定义组件的数据、方法和属性data数据methods 方法properties 属性数据监听器纯数据字段组件的生命周期组件所在页面的生命周期插槽单个插槽多个插槽父子组件之间的通信behaviors组件的创建与引用 创建一个组件的步骤&#xff1a; …

发票额度报销最优排列组合问题

1、问题描述 因为我的公司每个月给员工会有一定的交通费额度&#xff0c;需要拿发票去报销才能获得的。要求的是发票总金额不能大于报销的额度。因此在实际报销的时候&#xff0c;你要一张张发票去排列组合经可能的把报销金额往报销额度那里去凑。比如你有1000元额度&#xff0…

Unity 3D JavaScript 脚本基础||Unity 3D C# 脚本基础

变量 数值变量。var a1000;var b3.1415926;字符串变量。 是由单引号或双引号括起来的 Unicode 字符序列。 布尔值。 只有 true 和 false 。用来描述某个事物为真或为假。 数组 是数据的集合&#xff0c;数组中的每一个元素都有自己独有的下标&#xff0c;下标从0开始计数。 表…

《收获,不止Oracle》读书笔记之第三章逻辑体系世界

1.数据库有序的逻辑体系 2.逻辑体系从老余养殖细细说起 推出的逻辑结构是&#xff1a;表空间&#xff08;tablespace&#xff09;、段&#xff08;segment&#xff09;、区&#xff08;extent&#xff09;、块&#xff08;block&#xff09;。Oracle server正是有条理地通过表空…

基础数学(七)——线性方程组的数值解法

文章目录考试要求基础知识一般求解思路消元法高斯消元法小主元导致的计算失误高斯列主元素消去法&#xff08;期末考试要求之一&#xff09;高斯消元法例题&#xff08;期末必考&#xff09;高斯消元法的优缺点减少fill-in现象&#xff08;了解&#xff09;矩阵分解Doolittle分…

【ROS】——常用命令(五)

文章目录前言1. rosnode1.1 rosnode list 列出活动节点1.2 rosnode ping 测试到节点的连接状态1.3 rosnode info 打印节点信息1.4 rosnode machine 列出指定设备上节点1.5 rosnode kill 杀死某个节点1.6 rosnode cleanup 清除不可连接的节点2. rostopic2.1 rostopic list(-v)2.…

【学习】深度强化学习

李宏毅深度学习一、深度强化学习Deep Reinforcement Learning (RL)什么是RL&#xff1f;&#xff08;三步骤&#xff09;计算loss计算A版本0版本1版本2版本32、梯度方法收集训练数据:探索3、actor criticMCTD版本3.5一、深度强化学习Deep Reinforcement Learning (RL) 什么是R…

【回答问题】ChatGPT上线了!给我推荐20个比较流行的图像语义分割模型

给我推荐20个比较流行的图像语义分割模型 在这里列出20个比较流行的图像语义分割模型&#xff1a;U-Net Mask R-CNN Fully Convolutional Network (FCN) SegNet DeepLab v3 PSPNet ENet ICNet RefineNet DenseASPP DenseNet LinkNet FPN BiSeNet ENAS Deeplab v3 GCN CRF-RNN …

Linux环境变量

文章目录什么是环境变量&#xff1f;什么是本地变量&#xff1f;如何获取环境变量&#xff1f;在shell中在程序中如何添加/删除环境变量&&本地变量&#xff1f;在shell中在程序中为什么环境变量能够被子进程继承&#xff1f;什么是环境变量&#xff1f; 环境变量是进程…

Vue初识系列【1】

文章目录一 前端体系1.1 前端三要素1.2 JavaScript 框架1.3 JavaScript 构建工具1.4 前端所需后端技术1.5 UI框架二 前后端分离史2.1 后端为主的 MVC 时代&#xff08;web1.0&#xff09;2.2 基于 AJAX 带来的 SPA 时代(web2.0)2.3 前端为主的 MV* 时代2.4 NodeJS 带来的全栈时…