【算法】---快速排序

news2024/10/4 7:05:38

参考

左神和神书算法导论.

学习前置

  • 了解并实现过快速排序。 笔者曾经在数据结构篇写过快速排序,现在面向算法篇快排。

快速排序

输入数据所有排列是等概率的, 这种情况对于实际工程上不会总是成立。朴素快速排序对于特定的输入很糟糕, 数组越是接近有序,那么朴素快速排序的效率就更低。最坏情况是 O ( n 2 ) O(n^2) O(n2)
实际中,有人提出在快排中引入随机性,对抗特定输入的影响,转而研究期望运行时间。比如可以通过数组打乱算法,用随机化方法打乱数组,使得快排避免了输入序列的影响。
更简单的方法, 快速排序糟糕的时间复杂度究其原因在于 固定取数。
先前的快速排序篇说明了用三数取中法和过短序列穿插插入排序的过程优化。不过对于力扣上的sort an array这道题, 始终是很低的时间复杂度。
下面介绍经典的随机化快速排序写法,和左神介绍的荷兰化国旗法。

经典随机化快排

假设选中值5为枢轴, i遍历数组,如果发现小于等于5的元素,那么与a所处元素进行交换, a进行往后挪动一位。 a左边的[l,a-1]区间维护的是≤5的元素,那么循环结束时,a指向的就是>5的位置, 要返回a-1处的下标位置。
还需注意,由于我们选择值为枢轴,而不是数组下标。我们还需要找到满足值得对应下标,来与a-1处得元素进行交换,使得a-1处元素左右分别≤x,≥x。
在这里插入图片描述
可以修改下面代码中得MAX,和随机数生成范围来测试更多数据。
或者自行编写IO代码, 自主输入测试用例。

package class_2;

import java.util.Arrays;
import java.util.Random;
public class Coding_quickSort1 {
	public static void quickSort(int[] arr) {
		//处理特殊情况。
		if(arr==null || arr.length<2) {
			return ;
		}
		//主方法首次调用
		quick(arr,0, arr.length-1);
	}
	public static void quick(int[] arr, int l,int r) {
		//l==r, l>r直接返回, 不用处理。
		if(l>=r) {
			return ;
		}
		//随机取[l,r]的一个数nums[?]
		int x = arr[l+(int)(Math.random()*(r-l+1))];
		int pivot = partition(arr,l,r,x);
		quick(arr,l,pivot-1);
		quick(arr,pivot,r);
	}
	public static int partition(int[] arr,int l,int r,int x) {
		int i=l,xi=0;
		int a = i;
		//[l...a-1]为<=x的区间
		//[a...r]为>x的区间
		for(i=l;i<=r;i++) {
			if(arr[i]<=x) {
				//进行交换。
				swap(arr,a,i);
				if(arr[a]==x) {
					xi=a;//标记xi的坐标
				}
				a++;
			}
		}
		//将xi的元素作为分割点。
		swap(arr,a-1,xi);
		return a-1;
	}
	public static void swap(int[] arr,int i,int j) {
		int temp = arr[i];
		arr[i] = arr[j];
		arr[j] = temp;
	}
	public static int MAX = 25;
	public static void main(String[] args) {
		Random random = new Random(10000);
		int[] arr = new int[MAX];
		for(int i=0;i<MAX;i++) {
			arr[i] = random.nextInt(1000);
		}
		System.out.println("排序前:"+Arrays.toString(arr));
		quickSort(arr);
		System.out.println("排序后:"+Arrays.toString(arr));
	}
}

荷兰国旗快排

经典随机化快速排序的缺点是什么?
给你一个测试main函数,看看结果吧。

	public static int MAX = 3000;
	public static void main(String[] args) {
		  Random random = new Random(100);
	        int[] arr = new int[MAX];
	        System.out.printf("输入规模:%d\n",MAX);
	        // 测试重复数字的情况
	        for (int i = 0; i < MAX; i++) {
	            arr[i] = random.nextInt(10000);
	        }
	        System.out.println("排序前:" + Arrays.toString(arr));
	        long startTime = System.nanoTime();
	        quickSort(arr);
	        long endTime = System.nanoTime();
	        System.out.println("排序后:" + Arrays.toString(arr));
	        System.out.println("出现重复数字的情况的执行时间: " + (endTime - startTime) + " 纳秒");

	        // 测试无重复数字的情况
	        random.setSeed(0);
	        Set<Integer> set = new HashSet<>();
	        while (set.size() < MAX) {
	            set.add(random.nextInt(10000));
	        }
	        int i = 0;
	        for (int x : set) {
	            arr[i++] = x;
	        }
	        System.out.println("--------------------------------");
	        System.out.println("排序前:" + Arrays.toString(arr));
	        startTime = System.nanoTime();
	        quickSort(arr);
	        endTime = System.nanoTime();
	        System.out.println("排序后:" + Arrays.toString(arr));
	        System.out.println("不出现重复数字的情况的执行时间: " + (endTime - startTime) + " 纳秒");

	        // 测试完全相同的情况
	        Arrays.fill(arr, 5);
	        System.out.println("--------------------------------");
	        System.out.println("排序前:" + Arrays.toString(arr));
	        startTime = System.nanoTime();
	        quickSort(arr);
	        endTime = System.nanoTime();
	        System.out.println("排序后:" + Arrays.toString(arr));
	        System.out.println("出现完全相同的重复数字的情况的执行时间: " + (endTime - startTime) + " 纳秒");
	        System.out.println("--------------------------------");
	}
  • 第一种会出现重复数字。
  • 第二组测试,借助set去重。完全不重复的数字。
  • 第三组测试,完全相同的数字。
  • 随便看一组用例输出:
出现重复数字的情况的执行时间: 1377800 纳秒
不出现重复数字的情况的执行时间: 488300 纳秒
出现完全相同的重复数字的情况的执行时间: 6230400 纳秒

事实是,完全重复>部分重复>完全不重复。
完全重复的时间复杂度是 O ( n 2 ) O(n^2) O(n2), 因为上面经典算法,会将规模为n的数组序列,一直转化为n-1和1的规模。
怎么优化呢?
传统的快速排序,总是选择一个枢轴x,分为<=x,x,>=x的区间。问题在于x可能会重复, 为什么不能将x统一连续的放一起后续就不用处理了呢?
如下修改partition过程, 使其返回一个数对, 这里以一个自定义的内部类Tuple举例实现, 可以通过全局静态变量其它等实现。

public class Coding_quickSort2 {
	public static void quickSort(int[] arr) {
		if(arr==null || arr.length<2) {
			return ;
		}
		quick(arr,0, arr.length-1);
	}
	static class Tuple{
		int first;
		int last;
		public Tuple(int first,int last) {
			this.first = first;
			this.last = last;
		}
		public Tuple() {
			
		}
	}
	//考虑多线程可以放到主方法内实例化对象, 当作参数传递。
	public static Tuple tuple = new Tuple();//辅助partition。
	public static void quick(int[] arr, int l,int r) {
		//l==r, l>r直接返回, 不用处理。
		if(l>=r) {
			return ;
		}
		//随机取[l,r]的一个数 nums[?]
		int x = arr[l+(int)(Math.random()*(r-l+1))];
		
		partition(arr,l,r,x);
		quick(arr,l,tuple.first-1);
		quick(arr,tuple.last,r);
	}
	/**
	 * partition 方法的目的是根据给定的基准(pivot)将数组分成两部分:
	 * 一部分包含所有小于基准的元素,另一部分包含所有大于基准的元素。
	 * 该方法的返回结果是新的分区边界,以便在快速排序中递归调用。
	 * @param arr 数组
	 * @param l 左端点
	 * @param r 右端点
	 * @param x 随机选取的枢轴
	 */
	public static void partition(int[] arr, int l, int r, int x) {
	    tuple.first = l; // 小于区域的右边界
	    tuple.last = r;  // 大于区域的左边界
	    int i = l;       // 当前元素指针

	    while (i <= tuple.last) {
	        if (arr[i] == x) {
	            // 当前元素等于主元,移动指针
	            i++;
	        } else if (arr[i] < x) {
	            // 当前元素小于主元,交换到小于区域
	            swap(arr, tuple.first++, i++);
	        } else {
	            // 当前元素大于主元,交换到大于区域
	        	// 注意这里i不变。
	            swap(arr, tuple.last--, i);
	        }
	    }
	}
	public static void swap(int[] arr,int i,int j) {
		int temp = arr[i];
		arr[i] = arr[j];
		arr[j] = temp;
	}
	public static int MAX = 3000;
	public static void main(String[] args) {
		  Random random = new Random(100);
	        int[] arr = new int[MAX];
	        System.out.printf("输入规模:%d\n",MAX);
	        // 测试重复数字的情况
	        for (int i = 0; i < MAX; i++) {
	            arr[i] = random.nextInt(10000);
	        }
	        System.out.println("排序前:" + Arrays.toString(arr));
	        long startTime = System.nanoTime();
	        quickSort(arr);
	        long endTime = System.nanoTime();
	        System.out.println("排序后:" + Arrays.toString(arr));
	        System.out.println("出现重复数字的情况的执行时间: " + (endTime - startTime) + " 纳秒");

	        // 测试无重复数字的情况
	        random.setSeed(0);
	        Set<Integer> set = new HashSet<>();
	        while (set.size() < MAX) {
	            set.add(random.nextInt(10000));
	        }
	        int i = 0;
	        for (int x : set) {
	            arr[i++] = x;
	        }
	        System.out.println("--------------------------------");
	        System.out.println("排序前:" + Arrays.toString(arr));
	        startTime = System.nanoTime();
	        quickSort(arr);
	        endTime = System.nanoTime();
	        System.out.println("排序后:" + Arrays.toString(arr));
	        System.out.println("不出现重复数字的情况的执行时间: " + (endTime - startTime) + " 纳秒");

	        // 测试完全相同的情况
	        Arrays.fill(arr, 5);
	        System.out.println("--------------------------------");
	        System.out.println("排序前:" + Arrays.toString(arr));
	        startTime = System.nanoTime();
	        quickSort(arr);
	        endTime = System.nanoTime();
	        System.out.println("排序后:" + Arrays.toString(arr));
	        System.out.println("出现完全相同的重复数字的情况的执行时间: " + (endTime - startTime) + " 纳秒");
	        System.out.println("--------------------------------");
	}
}

出现重复数字的情况的执行时间: 1616100 纳秒
不出现重复数字的情况的执行时间: 7351300 纳秒
出现完全相同的重复数字的情况的执行时间: 28300 纳秒
经过·处理, 效率直接颠倒了, 重复数字越多处理越快。

sort an array
在这里插入图片描述

补充中ing, 再见, 感谢您的观看。

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

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

相关文章

PASCAL VOC 2012数据集 20类物体,这些物体包括人、动物(如猫、狗、鸟等)、交通工具(如车、船、飞机等)以及家具(如椅子、桌子、沙发等)。

VOC2012数据集是PASCAL VOC挑战赛官方使用的数据集之一&#xff0c;主要包含20类物体&#xff0c;这些物体包括人、动物&#xff08;如猫、狗、鸟等&#xff09;、交通工具&#xff08;如车、船、飞机等&#xff09;以及家具&#xff08;如椅子、桌子、沙发等&#xff09;。每个…

ultralytics-yolo-webui :Detect 目标检测 工具-先行版本 >> DataBall

通过webui 方式对ultralytics 的 detect 检测任务 进行&#xff1a; 1&#xff09;数据预处理&#xff0c;2&#xff09;模型训练&#xff0c;3&#xff09;模型推理。 本项目提供了 示例数据集&#xff0c;用 labelImage标注&#xff0c;标注文件为 xml 文件。 项目地址&…

数据结构(栈和队列的实现)

1. 栈&#xff08;Stack&#xff09; 1.1 栈的概念与结构 栈是一种特殊的线性表&#xff0c;其只允许固定的一段插入和删除操作&#xff1b;进行数据插入和删除的一段叫做栈顶&#xff0c;另一端叫栈底&#xff1b;栈中的元素符合后进先出LIFO&#xff08;Last In First Out&…

PCL 点云半径滤波

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 半径滤波实现 2.1.2 可视化函数 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算法与项目实战案例汇总&#xff08;长期更新&#xf…

MFC有三个选项:MFC ActiveX控件、MFC应用程序、MFC DLL,如何选择?

深耕AI&#xff1a;互联网行业 算法研发工程师 ​ 目录 MFC ActiveX 控件 控件的类型 标准控件 自定义控件 ActiveX控件 MFC ActiveX控件 标准/自定义控件 MFC ActiveX控件分类 3种MFC如何选择&#xff1f; MFC ActiveX控件 MFC 应用程序 MFC DLL 总结 举例说明…

【JAVA开源】基于Vue和SpringBoot的周边产品销售网站

本文项目编号 T 061 &#xff0c;文末自助获取源码 \color{red}{T061&#xff0c;文末自助获取源码} T061&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

申请免费或试用VPS服务

申请免费或试用VPS服务 有时候我们特别希望能够找到一台像 Oracle Cloud 一样的永久免费 VPS&#xff08;需要满足一定的条件&#xff09;&#xff0c;可相对于其它厂商申请相对比较难&#xff0c;可能需要多次申请才能得到。其实&#xff0c;除了 Oracle Cloud 之外&#xff0…

阿里云对象存储OSS 速学

目录 1.创建一个Bucket 2.创建密钥AccessKey 3.在文档中心打开阿里云对象存储OSS 4.参考上传文件示例 以官网的文档为主&#xff0c;我的文章教学为辅 官网有详细的视频介绍&#xff1a; OSS快速入门_对象存储(OSS)-阿里云帮助中心 (aliyun.com)https://help.aliyun.com/…

Linux: network: 典型网络延迟图,CPU导致;

接上回说&#xff0c;https://mzhan017.blog.csdn.net/article/details/142689870&#xff1b; 其中在debug的过程中&#xff0c;看到下面这个IO图&#xff0c;这个图比较经典&#xff0c;是一个典型的网络延迟图&#xff0c;可用作为分析问题的一个参考。 如下图&#xff1a;黑…

C++ | Leetcode C++题解之第454题四数相加II

题目&#xff1a; 题解&#xff1a; class Solution { public:int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {unordered_map<int, int> countAB;for (int u: A) {for (int v: B) {count…

ZYNQ: GPIO 之 EMIO 按键控制 LED 实验

GPIO 之 EMIO 按键控制 LED 实验目的 使用启明星 ZYNQ 底板上的两个用户按键分别控制 PS 端两个 LED 的亮灭 其中一个按键 PL_KEY0 连接到了 PL 端&#xff0c;需要通过 EMIO 进行扩展&#xff0c;另外一个按键是底板上 PS 端的用户按键PS_KEY0&#xff0c;这两个按键分别控制…

堆的向上和向下调整

堆的物理结构和逻辑结构是什么&#xff1f; 堆如何插入数据和删除数据&#xff1f;为什么&#xff1f; 向上调整和向下调整的要求是啥&#xff1f; 文中不理解的可以先看堆的代码和基础知识-CSDN博客 也欢迎评论区一起讨论 1.堆的物理结构和逻辑结构 我们的堆是用数组实…

计算机毕业设计 视频点播系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

【AI知识点】维度灾难(curse of dimensionality)

维度灾难&#xff08;curse of dimensionality&#xff09; 是指在处理高维数据时&#xff0c;随着维度的增加&#xff0c;数据的性质和空间结构变得越来越复杂&#xff0c;导致许多常见的算法和技术在高维空间中效率低下或效果变差的问题。 这个概念最早是由Richard Bellman在…

RabbitMQ篇(基本介绍)

目录 一、MQ 1. 什么是MQ 2. 为什么要用MQ【业务场景】 2.1. 异步 2.2. 应用解耦 2.3. 流量削峰 3. MQ的分类 &#xff08;1&#xff09;ActiveMQ &#xff08;2&#xff09;Kafka &#xff08;3&#xff09;RocketMQ &#xff08;4&#xff09;RabbitMQ 4. MQ 的选…

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-02

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-02 1. APM: Large Language Model Agent-based Asset Pricing Models Authors: Junyan Cheng, Peter Chin https://arxiv.org/abs/2409.17266 APM: 基于大型语言模型的代理资产定价模型&#xff08;LLM Agent-b…

2、项目配置设计(上)

文章目录 前言一、配置文件功能需求二、web工程设计思路三、Config实现思路 前言 配置文件作用&#xff1a;把需要经常修改的参数&#xff0c;从代码中分离出来,单独管理&#xff0c;方便后期维护。 开发一个web应用&#xff0c;肯定需要一些基础性的配置信息&#xff0c;这些信…

骨架屏 (懒加载优化)

骨架屏 &#xff08;懒加载优化&#xff09; 即便通过 Webpack 的按需加载、CDN 静态资源缓存 和 代码分割 等技术来减少首屏的代码体积&#xff0c;首屏加载时的白屏时间&#xff08;也称为首屏等待时间&#xff09;仍然可能存在&#xff0c;尤其在网络条件较差或页面内容复杂…

【设计模式-解释模式】

定义 解释器模式是一种行为设计模式&#xff0c;用于定义一种语言的文法&#xff0c;并提供一个解释器来处理该语言的句子。它通过为每个语法规则定义一个类&#xff0c;使得可以将复杂的表达式逐步解析和求值。这种模式适用于需要解析和执行语法规则的场景。 UML图 组成角色…

基于Springboot vue应急物资供应管理系统设计与实现

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm 等开发框架&#xff09; vue .net php python(flask Django) 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找…