冒泡排序和快速排序(分治递归算法)

冒泡排序:

冒泡排序时间复杂度为O(N^2)

直接插入排序比冒泡排序适应性更好,数据接近有序时比直接选择排序更好。

冒泡排序代码:

void PrintArray(int* a, int n)
{
	int i;
	for (i = 0; i < n; i++)
	{
		printf("%d ", *(a + i));
	}
}
void Swap(int* a, int* b)
{
	int tem = *a;
	*a = *b;
	*b = tem;
}
void BubbleSort(int* a, int n)
{
	int i,j;
	for (i = 0; i < n; i++)
	{
		for (j = 0; j < n - i - 1; j++)
		{
			if (a[j] < a[j + 1])
			{
				Swap(&a[j], &a[j + 1]);
			}
		}
	}
}
void TestBubbleSort()
{
	int a[] = { 3,5,2,7,8,6,1,9,4,0 };
	BubbleSort(a, sizeof(a) / sizeof(int));
	PrintArray(a, sizeof(a) / sizeof(int));
}

快速排序:

1.挖坑法

以下为快速排序的一次遍历,即将key放至排序好之后的位置,pivot为坑

之后利用分治递归对6左右两边分别进行一次排序 ,原理如下图:

//快速排序(1.挖坑法)
void QuickSort1(int* a, int left,int right)
{
	if (left >= right)
		return;
	int begin = left, end = right;
	int pivot = begin;
	int key = a[begin];
	while (begin < end)
	{
		//右边找小,放到左边
		while (begin < end &&a[end] >= key)
		{
			end--;
		}
		//小的放在左边的坑里,自己形成一个新的坑
		a[pivot] = a[end];
		pivot = end;
		//左边找大,放到右边
		while (begin < end &&a[begin] <= key)
		{
			begin++;
		}
		//大的放在右边的坑里,自己形成一个新的坑
		a[pivot] = a[begin];
		pivot = begin;
	}
	pivot = begin;
	a[pivot] = key;
	//[left,pivot-1]pivot[pivot+1,right] 分治递归
	QuickSort(a, left, pivot - 1);
	QuickSort(a, pivot + 1, right);
}
void TestQuickSort()
{
	int a[] = { 6,3,5,2,7,8,9,4,1 };
	QuickSort1(a,0, sizeof(a) / sizeof(int)-1);
	PrintArray(a, sizeof(a) / sizeof(int));
}

 

当所排数据有序时,快速排序的时间复杂度最差为O(N^2),因为每次挖的坑都是第一个数或者最后一个数不会有类似二叉树的结构(一次递归左右子树),第一次排用的次数为N,第二次为N-1,第三次为N-2,容易发现此为等差数列,之和近似N^2,故时间复杂度为O(N^2)

为了解决快速排序的最坏情况,可以使用三数取中的方法,找到最左边、最右边以及中间数据的中间值,这样避免了选出的关键数据不会是最大值或者最小值,就避免了一次排序之后没有左边的数据以及右边的数据,导致递归无法同时调用而使时间复杂度增大

int GetMidIndex(int* a, int left, int right)
{
	int mid = (left + right) >> 1;//右移一位,相当于除以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
	{
		if (a[right] > a[left])
			return left;
		else if (a[mid] > a[right])
			return mid;
		else
			return right;
	}
}

同时,由于递归会消耗栈帧,递归次数越多,栈帧消耗越大,而且如果左右每次排序均调用递归,越往后,栈帧会指数性的被消耗,所以在这里我们可以先让让快速排序使其接近有序,之后再利用直接插入排序即可,靠后所创建的栈帧就被消除掉了,在这里我是当左右两边数据个数小于10时,使用直接选择排序。

//三数取中
int GetMidIndex(int* a, int left, int right)
{
	int mid = (left + right) >> 1;//右移一位,相当于除以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
	{
		if (a[right] > a[left])
			return left;
		else if (a[mid] > a[right])
			return mid;
		else
			return right;
	}
}
//快速排序(1.挖坑法)
int PartSort1(int* a, int left, int right)
{
	int index = GetMidIndex(a, left, right);
	Swap(&a[left], &a[index]);
	int begin = left, end = right;
	int pivot = begin;
	int key = a[begin];
	while (begin < end)
	{
		//右边找小,放到左边
		while (begin < end && a[end] >= key)
		{
			end--;
		}
		//小的放在左边的坑里,自己形成一个新的坑
		a[pivot] = a[end];
		pivot = end;
		//左边找大,放到右边
		while (begin < end && a[begin] <= key)
		{
			begin++;
		}
		//大的放在右边的坑里,自己形成一个新的坑
		a[pivot] = a[begin];
		pivot = begin;
	}
	pivot = begin;
	a[pivot] = key;
	return pivot;
}
void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
	
	int keyindex = PartSort1(a, left, right);
	//[left,keyindex-1]keyindex[keyindex+1,right] 分治递归
	/*QuickSort(a, left, keyindex - 1);
	QuickSort(a, keyindex + 1, right);*/
	if (keyindex - 1 - left > 10)//当左边的数据大于10时,再进行递归调用,小于等于10时,直接插入排序
		QuickSort(a, left, keyindex - 1);//利于减小栈帧的消耗(每进行一次递归调用,都会消耗栈帧,左边和右边同时调用每次消耗的栈帧都会指数性增长)
	else
		InsertSort(a + left, keyindex - 1 - left + 1);
	if (right - (keyindex + 1) > 10)
		QuickSort(a, keyindex + 1, right);//右边同理
	else
		InsertSort(a + keyindex+1, right-(keyindex+1)+1);
}

2.左右指针法

取左边和右边数组的下标,左边找大(比提取数据大),右边找小(比提取数据小)。将左边找的大与右边找的小交换即可,设左索引为begin,右索引为end,二者遍历找合适的数据,最后当begin与end重合时,将关键数据与begin交换即可,最后也可以实现关键数据左边的数据比关键数据小,右边的数据比关键数据大的效果。代码如下:

这里只是单趟排序,最后返回关键数据的下标利用递归实现多次排序。多次排序整体思路和挖坑法相同。

//快速排序(2.左右指针法)
int PartSort2(int* a, int left, int right)
{

	int index = GetMidIndex(a, left, right);
	Swap(&a[left], &a[index]);
	int begin = left, end = right;
	int keyi = begin;
	while (begin < end)
	{
		//找小
		while (begin < end && a[end] >= a[keyi])
			end--;
		//找大
		while (begin < end && a[begin] <= a[keyi])
			begin++;
		Swap(&a[begin], &a[end]);
	}
	Swap(&a[begin], &a[keyi]);
	return begin;//相遇的位置
}

3.前后指针法

这个单趟排序的思路为用prev和cur分别索引,cur用来找比关键数据小的数据,prev和cur最初均指向left,当cur找到比关键数据小的数据时,prev++,将prev数据与cur数据进行交换,这样就保证prev所经过的下标索引的数据均比关键数据小,当cur遍历完整个数组时,此时已经将所有比关键数据小的数据放在prev前面,最后将prev指向的数据与关键数据交换即可。

代码如下:

//快速排序3(前后指针法)
int PartSort3(int* a, int left, int right)
{
	int index = GetMidIndex(a, left, right);
	Swap(&a[left], &a[index]);
	int keyi = left;
	int cur = left, prev = left;
	while (cur <= right)
	{
		if (a[cur] < a[keyi])
		{
			prev++;
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	Swap(&a[keyi], &a[prev]);
	return prev;
}

整体快速排序代码如下:

//三数取中
int GetMidIndex(int* a, int left, int right)
{
	int mid = (left + right) >> 1;//右移一位,相当于除以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
	{
		if (a[right] > a[left])
			return left;
		else if (a[mid] > a[right])
			return mid;
		else
			return right;
	}
}
//快速排序(1.挖坑法)
int PartSort1(int* a, int left, int right)
{
	int index = GetMidIndex(a, left, right);
	Swap(&a[left], &a[index]);
	int begin = left, end = right;
	int pivot = begin;
	int key = a[begin];
	while (begin < end)
	{
		//右边找小,放到左边
		while (begin < end && a[end] >= key)
		{
			end--;
		}
		//小的放在左边的坑里,自己形成一个新的坑
		a[pivot] = a[end];
		pivot = end;
		//左边找大,放到右边
		while (begin < end && a[begin] <= key)
		{
			begin++;
		}
		//大的放在右边的坑里,自己形成一个新的坑
		a[pivot] = a[begin];
		pivot = begin;
	}
	pivot = begin;
	a[pivot] = key;
	return pivot;
}
//快速排序(2.左右指针法)
int PartSort2(int* a, int left, int right)
{

	int index = GetMidIndex(a, left, right);
	Swap(&a[left], &a[index]);
	int begin = left, end = right;
	int keyi = begin;
	while (begin < end)
	{
		//找小
		while (begin < end && a[end] >= a[keyi])
			end--;
		//找大
		while (begin < end && a[begin] <= a[keyi])
			begin++;
		Swap(&a[begin], &a[end]);
	}
	Swap(&a[begin], &a[keyi]);
	return begin;//相遇的位置
}
//快速排序3(前后指针法)
int PartSort3(int* a, int left, int right)
{
	int index = GetMidIndex(a, left, right);
	Swap(&a[left], &a[index]);
	int keyi = left;
	int cur = left, prev = left;
	while (cur <= right)
	{
		if (a[cur] < a[keyi])
		{
			prev++;
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	Swap(&a[keyi], &a[prev]);
	return prev;
}
void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
	
	int keyindex = PartSort3(a, left, right);
	//[left,keyindex-1]keyindex[keyindex+1,right] 分治递归
	/*QuickSort(a, left, keyindex - 1);
	QuickSort(a, keyindex + 1, right);*/
	if (keyindex - 1 - left > 10)//当左边的数据大于10时,再进行递归调用,小于等于10时,直接插入排序
		QuickSort(a, left, keyindex - 1);//利于减小栈帧的消耗(每进行一次递归调用,都会消耗栈帧,左边和右边同时调用每次消耗的栈帧都会指数性增长)
	else
		InsertSort(a + left, keyindex - 1 - left + 1);
	if (right - (keyindex + 1) > 10)
		QuickSort(a, keyindex + 1, right);//右边同理
	else
		InsertSort(a + keyindex+1, right-(keyindex+1)+1);
}

三者多次排序代码相同,知识单趟排序思想不同,但都把所选定的关键数据放置排序好之后的位置上。即左边全是比关键数据小的数据,而右边全是比关键数据大的数据。

当然,快速排序也有非递归的做法,下篇博客我再详细介绍吧!

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

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

相关文章

(详解版)创建线程的四种方式

文章目录 Java中创建线程的四种方式1. 继承Thread类并重写 run 方法来创建线程2. 实现Runnable接口并实现 run 方法来创建线程。3. 使用Callable接口创建线程4. 使用Executor框架创建线程 Java中创建线程的四种方式 接下来我会详细解释这四种方式创建线程如何实现. 我们如果要…

7000字详解ERP管理系统!

在当今竞争激烈的商业世界中&#xff0c;中小企业不仅需要保持灵活性&#xff0c;更需要高效管理企业资源。 你可能听说过ERP系统&#xff0c;但它究竟是什么&#xff1f;它为何成为中小企业管理的不二选择&#xff1f;又是如何助力中小企业整合资源、提升效率&#xff0c;并在…

C++ OJ题测试—排序算法效率

目录 OJ链接 一、直接插入排序 二、希尔排序 三、直接选择排序 常规&#xff1a; 第二种&#xff1a; 四、 堆排序 五、冒泡排序 六、快速排序 常规&#xff1a; 三路划分优化效率 七、归并排序 八、计数排序 OJ链接 ​ 一、直接插入排序 class Solution { pub…

Python3.5 中->,即横杠和箭头,用来表示函数的返回值类型

最近在看playwright的源码&#xff0c;在看sync_playwright()方法的源码时发现一个特殊的语法-> 即横杠箭头&#xff0c;跟据如下源码猜测它应该是一个说明函数返回值类型的标识&#xff0c;因为 -> PlaywrightContextManager 与return PlaywrightContextManager() 一致…

算法(2)——滑动窗口

前言&#xff1a; 步骤及算法模板&#xff1a; 确定两个指针变量&#xff0c;left0,right0; 进窗口&#xff1a; 判断&#xff1a; 出窗口 更新结果 接下来我们的所用滑动窗口解决问题都需要以上几个步骤。 一、长度最小的子数组 209. 长度最小的子数组 - 力扣&#xff08;L…

C语言-> 文件操作(函数满屏)

系列文章目录 前言 ✅作者简介&#xff1a;大家好&#xff0c;我是橘橙黄又青&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;橘橙黄又青_C语言,数据结构,函数-CSDN博客 目的&#xff1a;学习文件操作&#xff0c;即…

Halcon深度学习方法

1、异常检测和全局上下文异常检测 图(1)异常检测示例 在图(1)上图中&#xff0c;异常检测示例&#xff1a;为输入图像的每个像素都分配一个分数&#xff0c;表明它显示未知特征(即异常)的可能性&#xff1b;在图(1)下图中&#xff0c;全局上下文异常检测示例&#xff1a;为输入…

Leetcode—859.亲密字符串【简单】

2023每日刷题&#xff08;六十三&#xff09; Leetcode—859.亲密字符串 &#x1f4a9;山实现代码 class Solution { public:bool buddyStrings(string s, string goal) {int len1 s.size(), len2 goal.size();int cnt 0;int flag 0;int flag2 0;int odd -1;int a[26] …

Eclipse_01_如何设置代码文件背景颜色为护眼沙绿色

设置方法 Window --> Preference 参考文档 参考文档 1

软件测试人才稀缺!揭秘为什么你找不到软件测试工作?

最近后台很多粉丝给我留言&#xff1a; 2023年软件测试已经崩盘了吗&#xff0c;为什么都找不到工作了&#xff1f; 确实&#xff0c;今年经济大环境不好&#xff0c;企业也都在降本增效&#xff0c;如果技术能力还在被应届生竞争岗位的阶段&#xff0c;只会越来越难。 找不…

全球首个AI监管法案出炉!

全球首个AI监管法案诞生了&#xff01; 今年来AI领域出现了爆发式的发展&#xff0c;在大模型的浪潮下&#xff0c;全球关于AI监管的讨论也越来越热。近日&#xff0c;经过长时间的讨论&#xff0c;欧洲议会、欧盟成员国和欧盟委员会三方签署了全球首份AI监管法案——《人工智…

关于Android studio新版本和NEW UI显示返回按钮的设置

1.新版Android studio问题 因为在新版本的Android Studio中&#xff0c;默认情况下是没有直接的选项来显示返回上一步按钮在状态栏上的&#xff0c;可以通过以下方法来实现返回上一步的功能&#xff1a; 在Android Studio的顶部菜单栏中&#xff0c;选择"View"。在…

[原创][R语言]股票分析实战[1]:周级别涨幅趋势的相关性

[简介] 常用网名: 猪头三 出生日期: 1981.XX.XX QQ联系: 643439947 个人网站: 80x86汇编小站 https://www.x86asm.org 编程生涯: 2001年~至今[共22年] 职业生涯: 20年 开发语言: C/C、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python 开发工具: Visual Studio、D…

spring MVC概述和土门案例(无配置文件开发)

SpringMVC 1&#xff0c;SpringMVC概述2&#xff0c;SpringMVC入门案例2.1 需求分析2.2 案例制作步骤1:创建Maven项目步骤2:补全目录结构步骤3:导入jar包步骤4:创建配置类步骤5:创建Controller类步骤6:使用配置类替换web.xml步骤7:配置Tomcat环境步骤8:启动运行项目步骤9:浏览器…

Spring MVC 原理(四)

Spring MVC 原理 Spring 的模型-视图-控制器&#xff08;MVC&#xff09;框架是围绕一个 DispatcherServlet 来设计的&#xff0c;这个 Servlet会把请求分发给各个处理器&#xff0c;并支持可配置的处理器映射、视图渲染、本地化、时区与主题渲染等&#xff0c;甚至还能支持文…

还搞不懂虚短与虚断概念?虚断与虚断通俗讲解,几分钟带你搞定

在模拟电路 中&#xff0c;虚短和虚断是两个重要的概念&#xff0c;它们通常与运放电路 有关。这两个术语描述了运放电路中的一些重要现象&#xff0c;认识它们对 于 电子工程师 和电路设计师来说至关重要。本文将深入探讨虚短和虚断的含义&#xff0c;以及它们在电子电路中的应…

Java中线程状态的描述

多线程-基础方法的认识 截止目前线程的复习 Thread 类 创建Thread类的方法 继承Thread类,重写run方法实现Runnable接口,重写run方法使用匿名内部类继承Thread类,重写run方法使用匿名内部类实现Runnable接口,重写run方法使用Lambda表达式 run方法中的所有的代码是当前线程对…

LeetCode刷题---长度最小的子数组

要点&#xff1a;该题属于滑动窗口类型的题目 解法一&#xff1a;暴力破解法 使用两层for循环&#xff0c;i为起始位置&#xff0c;j为终止位置&#xff0c;每次j都要遍历到数组最后一个下标&#xff0c;并且逐个累加。当sum大于等于target时&#xff0c;比较获取最小的长度&am…

51单片机简易出租车计费系统仿真设计

51单片机简易出租车计费系统仿真设计( proteus仿真程序报告讲解视频&#xff09; 仿真图proteus 8.9及以上 程序编译器&#xff1a;keil 4/keil 5 编程语言&#xff1a;C语言 设计编号&#xff1a;S0036 1.主要功能&#xff1a; 出租车计费系统设计内容&#xff1a; 1、…

Burp漏洞扫描指南

burp是web渗透测试中最常用的工具之一。可以通过抓包请求&#xff0c;分析站点漏洞等。是安全爱好者最喜欢的工具。本文让我们一起来学习利用burp进行站点漏洞扫描。 总体而言&#xff0c;burp的扫描方式分为被动扫描和主动扫描两者方式。现对这两种方式进行详细的说明。注意&a…