当前位置: 首页 > article >正文

数据结构基础之《(10)—快速排序》

一、快速排序基础

1、Partition过程
给定一个数组arr,和一个整数num。请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。
要求额外空间复杂度O(1),时间复杂度O(N)

2、例子
区分小于等于num的数
(<=区) [5 3 7 2 3 4 1]
num = 3
设计一个区域,叫小于等于区,放在数组前面

遍历数组:
(1)i位置的数<=num
把当前数和小于等于区的下一个数交换,然后小于等于区向右扩一个位置,然后i++
(2)i位置的数>num
直接i++

第一个数5,i直接跳下一个
第二个数3,把3和5交换,小于等于区右移一个位置,i++
  (<=区 3) [5 7 2 3 4 1]
第三个数7,i直接跳下一个
第四个数2,把2和5交换,小于等于区右移一个位置,i++
  (<=区 3 2) [7 5 3 4 1]
第五个数3,把3和7交换,小于等于区右移一个位置,i++
  (<=区 3 2 3) [5 7 4 1]
第六个数4,i直接跳下一个
第七个数1,把1和5交换,小于等于区右移一个位置,i++
  (<=区 3 2 3 1) [7 4 5]

3、荷兰国旗问题,给你一个数组,和给定num,将数组分成三块
(<num区) | (=num区) | (>num区)

[3 5 4 0 4 6 7 2]
num = 4

设计两个区域,小于区域和大于区域
(<区) [3 5 4 0 4 6 7 2] (>区)

遍历数组:
(1)i位置的数==num,i++
(2)i位置的数<num,把i位置的数与<区右一个交换,<区右扩,i++
(3)i位置的数>num,把i位置的数与>区左一个交换,>区左扩,i不动

第一个数3,小于num,自己和自己交换,小于区右移一个位置,i++
  (<区 3) [5 4 0 4 6 7 2] (>区)
第二个数5,大于num,5和2交换,大于区左移一个位置,i不动
  (<区 3) [2 4 0 4 6 7] (5 >区)
i留在原地,第三个数是2,小于num,自己和自己交换,小于区右移一个位置,i++
  (<区 3 2) [4 0 4 6 7] (5 >区)
第三个数4,等于num,i++
  (<区 3 2) [4 0 4 6 7] (5 >区)
第四个数0,大于num,0和4交换,小于区右移一个位置,i++
  (<区 3 2 0) [4 4 6 7] (5 >区)
第五个数4,等于num,i++
  (<区 3 2 0) [4 4 6 7] (5 >区)
第六个数6,大于num,6和7交换,大于区左移一个位置,i不动
  (<区 3 2 0) [4 4 7] (6 5 >区)
第七个数7,大于num,自己和自己交换,大于区左移一个位置,i不动
  (<区 3 2 0) [4 4] (7 6 5 >区)

二、代码

package class03;

/**
 * 快速排序
 */
public class Code03_PartitionAndQuickSort {

	public static void swap(int[] arr, int i, int j) {
		int temp = arr[i];
		arr[i] = arr[j];
		arr[j] = temp;
	}
	
	public static int partition(int[] arr, int L, int R) {
		if (L > R) {
			return -1;
		}
		if (L == R) {
			return L;
		}
		int lessEqual = L - 1;
		int index = L;
		while (index < R) {
			if (arr[index] <= arr[R]) {
				swap(arr, index, ++lessEqual);
			}
			index++;
		}
		swap(arr, ++lessEqual, R);
		return lessEqual;
	}
	
	/**
	 * 在arr[L...R]范围上玩荷兰国旗问题划分,以arr[R]做划分值
	 * 在[L...R]范围内,<arr[R]放左侧,==arr[R]放中间,>arr[R]放在右边
	 * @param arr
	 * @param L
	 * @param R
	 * @return
	 */
	public static int[] netherlandsFlag(int[] arr, int L, int R) {
		if (L > R) { //如果范围不是有效范围
			return new int[] {-1, -1};
		}
		if (L == R) { //说明L到R只有一个数
			return new int[] {L, R};
		}
		int less = L - 1; //<区右边界
		int more = R; //>区左边界
		int index = L;
		while (index < more) {
			if (arr[index] == arr[R]) {
				index++;
			} else if (arr[index] < arr[R]) {
				swap(arr, index++, ++less);
			} else {
				swap(arr, index, --more);
			}
		}
		swap(arr, more, R);
		return new int[] {less + 1, more};
	}
	
	/**
	 * 快排1.0版本
	 * @param arr
	 */
	public static void quickSort1(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		process1(arr, 0, arr.length - 1);
	}
	
	public static void process1(int[] arr, int L, int R) {
		if (L >= R) {
			return;
		}
		int M = partition(arr, L, R);
		process1(arr, L, M - 1); //左侧范围重复玩递归
		process1(arr, M + 1, R); //右侧范围重复玩递归
	}
	
	/**
	 * 快排2.0版本
	 * @param arr
	 */
	public static void quickSort2(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		process2(arr, 0, arr.length - 1);
	}
	
	public static void process2(int[] arr, int L, int R) {
		if (L >= R) {
			return;
		}
		int[] equalArea = netherlandsFlag(arr, L, R);
		process2(arr, L, equalArea[0] - 1);
		process2(arr, equalArea[1] + 1, R);
	}
	
	/**
	 * 快排3.0版本
	 * @param arr
	 */
	public static void quickSort3(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		process3(arr, 0, arr.length - 1);
	}
	
	public static void process3(int[] arr, int L, int R) {
		if (L >= R) {
			return;
		}
		swap(arr, L + (int) (Math.random() * (R - L + 1)), R);
		int[] equalArea = netherlandsFlag(arr, L, R);
		process3(arr, L, equalArea[0] - 1);
		process3(arr, equalArea[1] + 1, R);
	}
	
	// for test
	public static int[] generateRandomArray(int maxSize, int maxValue) {
		int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
		for (int i = 0; i < arr.length; i++) {
			arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
		}
		return arr;
	}
	
	// for test
	public static int[] copyArray(int[] arr) {
		if (arr == null) {
			return null;
		}
		int[] res = new int[arr.length];
		for (int i = 0; i < arr.length; i++) {
			res[i] = arr[i];
		}
		return res;
	}
	
	// for test
	public static boolean isEqual(int[] arr1, int[] arr2) {
		if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
			return false;
		}
		if (arr1 == null && arr2 == null) {
			return true;
		}
		if (arr1.length != arr2.length) {
			return false;
		}
		for (int i = 0; i < arr1.length; i++) {
			if (arr1[i] != arr2[i]) {
				return false;
			}
		}
		return true;
	}
	
	// for test
	public static void printArray(int[] arr) {
		if (arr == null) {
			return;
		}
		for (int i = 0; i < arr.length; i++) {
			System.out.println(arr[i] + " ");
		}
		System.out.println();
	}
	
	// for test
	public static void main(String[] args) {
		int testTime = 500000;
		int maxSize = 100;
		int maxValue = 100;
		boolean succeed = true;
		for (int i = 0; i < testTime; i++) {
			int[] arr1 = generateRandomArray(maxSize, maxValue);
			int[] arr2 = copyArray(arr1);
			int[] arr3 = copyArray(arr1);
			quickSort1(arr1);
			quickSort2(arr2);
			quickSort3(arr3);
			if (!isEqual(arr1, arr2) || !isEqual(arr2, arr3)) {
				succeed = false;
				break;
			}
		}
		System.out.println(succeed ? "Nice!" : "Oops!");
	}
}

1、netherlandsFlag方法说明
假设给个数组,[...3 1 2 6 3...]
L到R上,L是3,R是3,以arr[R]做划分值,所以以3划分

排序后是:1 2 3 3 6

返回等于区域左边界和右边界,就是左边3的位置和右边3的位置

第一次变动和第二次变动,示意图:

2、快排1.0版本说明
(1)在整个数组上,拿最后一个值为划分值,然后做partition
[<=x >x x]
(2)然后把x放到小于区的最后一个
[<=x x >x]
(3)排序的时候x可以不动,接下来左侧范围做递归
(4)每次递归可以至少搞定一个数

3、快排2.0版本说明
利用荷兰国旗问题

4、快排3.0版本说明
原来拿arr[R]做划分值,3.0版本随机选个位置i,拿i位置的数和R位置的数交换之后,以arr[R]做划分值

三、随机快排的时间复杂度

1、通过分析知道,划分值越靠近中间,性能越好:越靠近两边,性能越差
2、随机选一个数进行划分的目的就是让好情况和坏情况都变成概率事件
3、把每一种情况都列出来,会有每种情况下的时间复杂度,但概率都是1/N
4、那么所有情况都考虑,时间复杂度就是这种概率模型下的长期期望

时间复杂度O(N*logN),额外空间复杂度O(logN)都是这么来的。
 


http://www.kler.cn/a/420916.html

相关文章:

  • 互联网 Java 面试八股文汇总(2025 最新整理)
  • 计算机网络-网络安全
  • WEB开发: 丢掉包袱,拥抱ASP.NET CORE!
  • 项目搭建+添加
  • Vulnhub靶场 Matrix-Breakout: 2 Morpheus 练习
  • 基数排序(代码+注释)
  • RoBERTa- 稳健优化的 BERT 预训练模型详解
  • AI - 谈谈RAG中的查询分析(2)
  • 《封装、继承与多态》问题一:封装只有类能做吗?结构体如何封装?名空间、文件能实现封装吗?还有没有其他方式?
  • Vue.js 中集成 Socket.IO 实现实时聊天功能
  • Microi 吾码:后端开发的创新引擎与代码艺术
  • Android Studio安装ADB Wi-Fi插件使用WIFI连接终端设备调试程序
  • Java11使用JVM同一日志框架启用日志记录
  • Shire 1.1 发布:更强大的交互支持,升级 AI 智能体与 IDE 的整合体验
  • 【Unity】WebGL全屏问题
  • 在Scala中栈的认识
  • A30 PHP+MYSQL+LW+工厂库存仓储订单销售后台管理系统的设计与实现 源代码 配置 文档
  • ROS2创建 base 包用于其他模块的参数配置和头文件依赖
  • 【计算机网络】实验1:访问WEB服务器
  • DBA面试题-1
  • 【大模型微调】pdf转markdown
  • QT-thread2种方式选择的优劣对比
  • uniapp 生成二维码
  • 量化交易系统开发-实时行情自动化交易-8.9.通达信平台
  • docker部署RustDesk自建服务器
  • 【自用】管材流转项目前端重部署流程 vue2 webpackage4 vuecli4