[C][数据结构][排序][下][快速排序][归并排序]详细讲解

news2024/12/24 20:59:34

文章目录

  • 1.快速排序
    • 1.基本思想
    • 2.hoare版本
    • 3.挖坑法
    • 4.前后指针版本
    • 5.非递归版本改写
  • 2.归并排序


1.快速排序

1.基本思想

  • 任取待排序元素序列的某元素作为基准值,按照该排序码将待排序集合分割成两子序列,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止

2.hoare版本

请添加图片描述

  • 选key – 一般是最左边或者最右边的值
    • 左作key,让右边先走
    • 右作key,让左边先走
  • 为什么左边做key,要让右边先走?
    • 要保证相遇位置的值,比key小/就是key
      • R先走,R停下,L去遇到R
        • 相遇位置就是R停下来的位置,必定比key小
      • R先走,R没有找到比key小的值,R去遇到了L
        • 相遇位置就是L上一轮停下来的位置,比key小/就是key
  • left向前找大
  • right向后找小
  • 单趟排完以后:
    • 左边都比key小
    • 右边都比key大
  • 实现:
    void QuickSort1(int* a, int begin, int end)
    {
    	//结束条件  --  只有一个数 --> begin == end || 区间不存在 --> begin > end
    	if (begin >= end)
    	{
    		return;
    	}
     
    	if (end - begin > 0)
    	{
    		//hoare版本
    		int left = begin, right = end;
    		int keyi = left;
     
    		while (left < right)
    		{
    			//右边先走,找小
    			while (left < right && a[right] >= a[keyi])  //left < right是为了防止越界
    			{
    				right--;
    			}
     
    			//左边再走,找大
    			while (left < right && a[left] <= a[keyi])
    			{
    				left++;
    			}
     
    			//交换
    			Swap(&a[left], &a[right]);
    		}
     
    		Swap(&a[keyi], &a[left]);
    		keyi = left;
     
    		//key已经正确的位置上,左边都比key小,右边都比key大
    		//递归,分治 --  [begin,keyi - 1]  keyi  [keyi + 1,end]
    		QuickSort1(a, begin, keyi - 1);
    		QuickSort1(a, keyi + 1, end);
    	}
    	else
    	{
    		//直接插入排序
    		InsertSort(a + begin, end - begin + 1);
    	}
    }
    

3.挖坑法

请添加图片描述

  • 本质同hoare,但是好理解些
  • 右边找小,填到左边的坑里去,这个位置形成新的坑
  • 左边找大,填到右边的坑里去,这个位置形成新的坑
  • 实现:
    void QuickSort2(int* a, int begin, int end)
    {
    	//结束条件  --  只有一个数 --> begin == end || 区间不存在 --> begin > end
    	if (begin >= end)
    	{
    		return;
    	}
     
    	if (end - begin > 0)
    	{
    		//挖坑法
    		int key = a[begin];
    		int piti = begin;
    		int left = begin, right = end;
     
    		while (begin < end)
    		{
    			//右边找小,填到左边的坑里去,这个位置形成新的坑
    			while (begin < end && a[right] >= key)
    			{
    				right--;
    			}
     
    			a[piti] = a[right];
    			piti = right;
     
    			//左边找大,填到右边的坑里去,这个位置形成新的坑
    			while (begin < end && a[left] <= key)
    			{
    				left++;
    			}
     
    			a[piti] = a[left];
    			piti = left;
    		}
     
    		a[piti] = key;
     
    		//key已经正确的位置上,左边都比key小,右边都比key大
    		//递归,分治 --  [begin,keyi - 1]  keyi  [keyi + 1,end]
    		QuickSort2(a, begin, piti - 1);
    		QuickSort2(a, piti + 1, end);
    	}
    	else
    	{
    		//直接插入排序
    		InsertSort(a + begin, end - begin + 1);
    	}
    }
    

4.前后指针版本

请添加图片描述

  • cur向前找小,遇到小,则prev++,且交换cur、prev
  • 实现:
    void QuickSort3(int* a, int begin, int end)
    {
    	//结束条件  --  只有一个数 --> begin == end || 区间不存在 --> begin > end
    	if (begin >= end)
    	{
    		return;
    	}
     
    	if (end - begin > 0)
    	{
    		//前后指针版本
    		int prev = begin;
    		int cur = begin + 1;
    		int keyi = begin;
     
    		//加入三数取中的优化
    		int midi = GetMidIndex(a, begin, end);
    		Swap(&a[keyi], &a[midi]);
     
    		while (cur <= end)  //一直往后走,遇到小则停下来处理
    		{
    			//cur找小
    			if (a[cur] < a[keyi] && ++prev != cur)  //防止prev和cur重合时,重复交换
    			{
    				Swap(&a[prev], &a[cur]);
    			}
     
    			cur++;
    		}
     
    		Swap(&a[keyi], &a[prev]);
    		keyi = prev;
     
    		//key已经正确的位置上,左边都比key小,右边都比key大
    		//递归,分治 --  [begin,keyi - 1]  keyi  [keyi + 1,end]
    		QuickSort3(a, begin, keyi - 1);
    		QuickSort3(a, keyi + 1, end);
    	}
    	else
    	{
    		//直接插入排序
    		InsertSort(a + begin, end - begin + 1);
    	}
    }
    

5.非递归版本改写

  • 为何需要改写非递归?
    • 极端场景下,若深度太深,会出现栈溢出
  • 方法:用数据结构模拟递归过程
  • 实现:
    //用栈模拟递归 -- 先进后出
    void QuickSortNonR(int* a, int begin, int end)
    {
    	Stack st;
    	StackInit(&st);
    	
    	StackPush(&st, end);
    	StackPush(&st, begin);
     
    	while (!isStackEmpty(&st))
    	{
    		//从栈中取出两个数,作为区间
    		int left = StackTop(&st);
    		StackPop(&st);
    		int right = StackTop(&st);
    		StackPop(&st);
     
    		//排序,取keyi
    		int keyi = PartSort3(a, left, right);
     
    		//此时分成了两个区间 [left, keyi-1] keyi [keyi+1, right]
    		//继续压栈
    		if (keyi + 1 < right)
    		{
    			StackPush(&st, right);
    			StackPush(&st, keyi + 1);
    		}
     
    		if (left < keyi - 1)
    		{
    			StackPush(&st, keyi - 1);
    			StackPush(&st, left);
    		}
    	}
     
    	StackDestroy(&st);
    }
    
  • 特性总结:
    • 综合性能&使用场景比较好
    • 时间复杂度:O(N*logN)
    • 空间复杂度:O(logN)
    • 稳定性:不稳定
  • [快速排序]优化方案
    • 三数取中法选key
      • 防止key都是极端数字 --> 递归过深 --> 栈溢出
    • 递归到小的子区间时,考虑使用插入排序
      • 大幅减少递归次数,提高效率
      • 例:最后一层递归占了总递归次数的50%,但可能只有极少量的数

2.归并排序

请添加图片描述

  • 基本思想:

  • 分治思维

    • 将已有序的子序列合并,得到完全有序的序列
    • 即先使每个子序列有序,再使子序列段间有序。
    • 若将两个有序表合并成一个有序表,称为二路归并
  • 归并排序核心步骤:
    请添加图片描述

  • 实现:

    //函数名前加_表示这个函数是内部函数,不对外提供接口 - 子函数
    //后序思想
    void _MergeSort(int* a, int begin, int end, int* tmp)
    {
    	if (begin >= end)
    	{
    		return;
    	}
     
    	int mid = (begin + end) / 2;
     
    	//[begin, mid] [mid+1, end] 分治递归,让子区间有序
    	_MergeSort(a,begin,mid,tmp);
    	_MergeSort(a,mid+1,end,tmp);
     
    	//归并 [begin, mid] [mid+1, end]
    	int begin1 = begin, end1 = mid;
    	int begin2 = mid + 1, end2 = end;
    	int i = begin1;
    	while (begin1 <= end1 && begin2 <= end2)  //有一组结束则结束
    	{
    		if (a[begin1] < a[begin2])
    		{
    			tmp[i++] = a[begin1++];
    		}
    		else
    		{
    			tmp[i++] = a[begin2++];
    		}
    	}
     
    	//已经有一组结束了,拷贝另一组剩余的
    	while (begin1 <= end1)
    	{
    		tmp[i++] = a[begin1++];
    	}
     
    	while (begin2 <= end2)
    	{
    		tmp[i++] = a[begin2++];
    	}
     
    	//把归并数据拷贝回原数组
    	memcpy(a + begin, tmp + begin, (end - begin + 1) * sizeof(int));
    }
     
    //时间复杂度:O(N*logN)
    //空间复杂度:O(N)
    void MergeSort(int* a, int n)
    {
    	int* tmp = (int*)malloc(sizeof(int) * n);
    	if (tmp == NULL)
    	{
    		perror("MergeSort");
    		exit(-1);
    	}
     
    	_MergeSort(a, 0, n - 1, tmp);
     
    	free(tmp);
    }
    
  • 非递归版本改写

  • 实现:

  • void MergeSortNonR(int* a, int n)
    {
    	int* tmp = (int*)malloc(sizeof(int) * n);
    	if (tmp == NULL)
    	{
    		perror("malloc:");
    		exit(-1);
    	}
     
    	//手动归并
    	int gap = 1;  //每次归并的元素个数
    	while (gap < n)
    	{
    		for (int i = 0; i < n; i += 2 * gap)
    		{
    			//[i, i+gap-1] [i+gap,i+2*gap-1]
    			int begin1 = i, end1 = i + gap - 1;
    			int begin2 = i + gap, end2 = i + 2 * gap - 1;
     
    			//越界处理 - 修正边界
    			if (end1 >= n)
    			{
    				end1 = n - 1;
    				//[begin2, end2]修正为不存在区间
    				begin2 = n;
    				end2 = n - 1;
    			}
    			else if (begin2 >= n)
    			{
    				// [begin2, end2]修正为不存在区间
    				begin2 = n;
    				end2 = n - 1;
    			}
    			else if (end2 >= n)
    			{
    				end2 = n - 1;
    			}
     
     
    			//归并
    			int j = begin1;
    			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, tmp, sizeof(int) * n);
     
    		gap *= 2;
    	}
     
    	free(tmp);
    }
    
  • void MergeSortNonR(int* a, int n)
    {
    	int* tmp = (int*)malloc(sizeof(int) * n);
    	if (tmp == NULL)
    	{
    		perror("malloc:");
    		exit(-1);
    	}
     
    	//手动归并
    	int gap = 1;  //每次归并的元素个数
    	while (gap < n)
    	{
    		for (int i = 0; i < n; i += 2 * gap)
    		{
    			//[i, i+gap-1] [i+gap,i+2*gap-1]
    			int begin1 = i, end1 = i + gap - 1;
    			int begin2 = i + gap, end2 = i + 2 * gap - 1;
     
    			//越界处理
    			//end1越界或者begin2越界,则可以不归并了
    			if (end1 >= n || begin2 >= n)
    			{
    				break;
    			}
    			else if (end2 >= n)
    			{
    				end2 = n - 1;
    			}
     
    			//归并
    			int m = end2 - begin1 + 1;
    			int j = begin1;
    			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) * m);
    		}
     
    		gap *= 2;
    	}
     
    	free(tmp);
    }
    
  • 特性总结:

    • 更多解决在磁盘中的外排序问题
    • 时间复杂度:O(N*logN)
    • 空间复杂度:O(N) – 缺点
    • 稳定性:稳定

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

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

相关文章

自然语言处理领域的重大挑战:解码器 Transformer 的局限性

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Undertow学习

Undertow介绍 Undertow是一个用java编写的灵活、高性能的web服务器&#xff0c;提供基于NIO的阻塞和非阻塞API。 Undertow有一个基于组合的体系结构&#xff0c;允许您通过组合小型单用途处理程序来构建web服务器。为您提供了在完整的Java EE servlet 4.0容器或低级别非阻塞处…

【JavaEE精炼宝库】多线程(5)单例模式 | 指令重排序 | 阻塞队列

目录 一、单例模式&#xff1a; 1.1 饿汉模式&#xff1a; 1.2 懒汉模式&#xff1a; 1.2.1 线程安全的懒汉模式&#xff1a; 1.2.2 线程安全的懒汉模式的优化&#xff1a; 二、指令重排序 三、阻塞队列 3.1 阻塞队列的概念&#xff1a; 3.2 生产者消费者模型&#xf…

计算机网络之网络层知识总结

网络层功能概述 主要任务 主要任务是把分组从源端传到目的端&#xff0c;为分组交换网上的不同主机提供通信服务。网络层传输单位是数据报。 分组和数据报的关系&#xff1a;把数据报进行切割之后&#xff0c;就是分组。 主要功能&#xff1a; 路由选择与分组转发 路由器…

ResNet——Deep Residual Learning for Image Recognition(论文阅读)

1.什么是ResNet ResNet是一种残差网络&#xff0c;咱们可以把它理解为一个子网络&#xff0c;这个子网络经过堆叠可以构成一个很深的网络。下面是ResNet的结构。 2.为什么要引入ResNet 理论上来说&#xff0c;堆叠神经网络的层数应该可以提升模型的精度。但是现实中真的是这…

SwiftUI中UIViewRepresentable的使用(UIKit与SwiftUI的桥梁)

UIViewRepresentable是一个协议&#xff0c;用于创建一个SwiftUI视图&#xff0c;该视图包装了一个UIKit视图。通过实现UIViewRepresentable协议&#xff0c;我们可以在SwiftUI中使用自定义的UIKit视图&#xff0c;并与SwiftUI进行交互。 实现UIViewRepresentable 创建一个遵…

DT浏览器很好用

简单的浏览器&#xff0c;又是强大的浏览器&#xff0c;界面简洁大方&#xff0c;操作起来非常流畅&#x1f60e;&#xff0c;几乎不会有卡顿的情况。 搜索功能也十分强大&#x1f44d;&#xff0c;能够快速精准地找到想要的信息。 而且还有出色的兼容性&#xff0c;各种网页都…

qt 实现模拟实际物体带速度的移动(水平、垂直、斜角度)——————附带完整代码

文章目录 0 效果1 原理1.1 图片旋转1.2 物体带速度移动 2 完整实现2.1 将车辆按钮封装为一个类&#xff1a;2.2 调用方法 3 完整代码参考 0 效果 实现后的效果如下 可以显示属性&#xff08;继承自QToolButton&#xff09;: 鼠标悬浮显示文字 按钮显示文字 1 原理 类继承…

单链表经典算法题 1

前言 学习了单链表&#xff0c;我们就做一些题来巩固一下。还有就是解题方法不唯一&#xff0c;我就只讲述为自己的方法。 目录 前言 1.移除链表元素 思路 代码 2.反转链表 思路 代码 3.链表的中间节点 思路 代码 总结 1.移除链表元素 思路 我们创建一个新的表…

FM全网自动采集聚合影视搜索源码

源码介绍 FM 全网聚合影视搜索(响应式布局)&#xff0c;基于 TP5.1 开发的聚合影视搜索程序&#xff0c;本程序无数据库&#xff0c;本程序内置P2P 版播放器&#xff0c;承诺无广告无捆绑。片源内部滚动广告与本站无关,谨防上当受骗&#xff0c;资源搜索全部来自于网络。 环境…

Java面向对象之static关键字,可变参数,递归,数组常见算法,对象数组,方法参数

第一章.static关键字 1.static的介绍以及基本使用 1.概述:static是一个静态关键字 2.使用:a.修饰一个成员变量:static 数据类型 变量名b.修饰一个方法:修饰符 static 返回值类型 方法名(形参){方法体return 结果}3.调用静态成员:类名直接调用(不用new对象)4.静态成员特点:a.静…

智慧守护 畅游无忧——北斗应急呼叫柱,为景区安全加码

在大自然的怀抱中&#xff0c;中型及大型公园、景区以其壮丽风光吸引着成千上万的游客前来探索&#xff0c;成为了人们休闲娱乐的好去处。然而&#xff0c;广袤的区域、复杂的地形和分散的人流也给安全保障带来了前所未有的挑战。传统的巡逻方式难以覆盖每一个角落&#xff0c;…

2.nginx常用命令

使用nginx命令需要进入nginx目录里面执行。 /usr/local/nginx/sbin/ 查看nginx的版本号 启动nginx ./nginx 关闭nginx ./nginx -s stop 查看nginx的是否运行的命令 重新加载nginx 针对配置目录中配置文件nginx.cnf修改后需要重新加载 /usr/local/nginx/conf/nginx.cnf …

WebSocket 详解--spring boot简单使用案例

一、什么是WebSocket WebSocket 是一种网络通信协议&#xff0c;专为在单个 TCP 连接上进行全双工通信而设计。WebSocket 允许客户端和服务器之间的消息能够实时双向传输。这与传统的 HTTP 请求-响应模式有很大的不同。 二、WebSocket 的关键特性 双向通信&#xff1a;WebSocke…

vi/vim使用命令

你是否在编辑文件时以为键盘坏了&#xff0c;为什么不能删除呢&#xff0c;为什么不能敲代码呢&#xff0c;等你初识vi&#xff0c;会觉得这个东西为什么设计得这么难用&#xff0c;这篇教程带你熟练得用上这款经典的工具 Vi 是在 Unix 系统上广泛使用的编辑器&#xff0c;Vim …

java原子变量

在Java中&#xff0c;原子变量是一种特殊的变量&#xff0c;它们提供了一种不需要显式加锁的情况下进行线程安全的操作。Java.util.concurrent.atomic包提供了原子变量类&#xff0c;如AtomicInteger&#xff0c;AtomicLong等&#xff0c;它们利用底层硬件的原子操作来保证线程…

VRChat 2024年裁员原因与背景深度分析

VRChat&#xff0c;作为2022年元宇宙/VR社交领域的巨头&#xff0c;近期在2024年宣布裁员计划&#xff0c;其背后原因和背景值得业界尤其是仍在纯元宇宙虚拟空间创业的同仁们重点关注。 一、创始人决策失误 根据CEO的邮件披露&#xff0c;VRChat的创始人因缺乏经验和过度自信…

HTTP 概述

HTTP 概述 HTTP 是一种用于获取资源&#xff08;如 HTML 文档&#xff09;的协议。 它是 Web 上任何数据交换的基础&#xff0c;它是一种客户端-服务器协议&#xff0c;这意味着请求由接收方&#xff08;通常是 Web 浏览器&#xff09;发起。 一个完整的文档是从获取的不同子文…

10 SpringBoot 静态资源访问

我们在开发Web项目的时候&#xff0c;往往会有很多静态资源&#xff0c;如html、图片、css等。那如何向前端返回静态资源呢&#xff1f; 以前做过web开发的同学应该知道&#xff0c;我们以前创建的web工程下面会有一个webapp的目录&#xff0c;我们只要把静态资源放在该目录下…

N32G45XVL-STB之移植LVGL(8.4.0)

目录 概述 1 系统软硬件 1.1 软件版本信息 1.2 ST7796-LCD 1.3 MCU IO与LCD PIN对应关系 2 认识LVGL 2.1 LVGL官网 2.2 下载V8.4.0 3 移植LVGL 3.1 硬件驱动实现 3.2 添加LVGL库文件 3.3 移植和硬件相关的代码 3.3.1 驱动接口相关文件介绍 3.3.2 重新接口函数 3…