【数据结构与算法】排序

目 录

  • 一.排序的概念及引用
    • 1.1 排序的概念
    • 1.2 常见的排序算法
  • 二.常见排序算法的实现
    • 2.1 插入排序
      • 直接插入排序
      • 希尔排序( 缩小增量排序 )
    • 2.2 选择排序
      • 直接选择排序
      • 堆排序
    • 2.3 交换排序
      • 冒泡排序
      • 快速排序
      • 快速排序优化:
      • 非递归实现快速排序
    • 2.4归并排序
      • 2.4.3 海量数据的排序问题
  • 三.排序算法复杂度及稳定性分析
  • 四.非基于比较排序

一.排序的概念及引用

1.1 排序的概念

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

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

内部排序:数据元素全部放在内存中的排序

外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

1.2 常见的排序算法

在这里插入图片描述


二.常见排序算法的实现

2.1 插入排序

直接插入排序是一种简单的插入排序法,其基本思想是:

把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。类似于玩扑克牌

直接插入排序

当插入第 i(i>=1) 个元素时,前面的 array[0],array[1],…,array[i-1] 已经排好序,此时用 array[i] 的排序码与 array[i-1],array[i-2],… 的排序码顺序进行比较,找到插入位置即将 array[i] 插入,原来位置上的元素顺序后移

public static void insertSort(int[] array) {for(int i = 1;i < array.length;i++) {int tmp = array[i];int j = i-1;for (; j >= 0 ; j--) {if(array[j] > tmp) {array[j+1] = array[j];}else {//array[j+1] = tmp;break;}}array[j+1] = tmp;}
}

直接插入排序的特性总结:

  • 时间复杂度:[对数据敏感]
  • 最坏情况下:O(n^2) 逆序的
  • 最好情况下:数据是有序的 O(n)
  • 直接插入排序的使用场景是:当数据量小,并且已经趋于有序的时候,使用直接插入排序
  • 空间复杂度:O(1)
  • 稳定性:稳定的排序

希尔排序( 缩小增量排序 )

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。当到达=1 时,所有记录在统一组内排好序。

在这里插入图片描述

public static void shell(int[] array,int gap) {for(int i = gap ;i < array.length;i++) {int tmp = array[i];int j = i-gap;for (; j >= 0 ; j -= gap) {if(array[j] > tmp) {array[j+gap] = array[j];}else {break;}}array[j+gap] = tmp;}
}public static void shellSort(int[] array) {int gap = array.length;while (gap > 1) {shell(array,gap);gap /= 2;}shell(array,1);
}

希尔排序的特性总结:

  • 空间复杂度:O(1)
  • 稳定性:不稳定的排序
  • 希尔排序是对直接插入排序的优化
  • 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
  • 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的希尔排序的时间复杂度都不固定

2.2 选择排序

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完 。

直接选择排序

  • 在元素集合array[i]–array[n-1]中选择关键码最大(小)的数据元素
  • 若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换
  • 在剩余的array[i]–array[n-2](array[i+1]–array[n-1])集合中,重复上述步骤,直到集合剩余1个元素
public static void selectSort(int[] array) {for (int i = 0; i < array.length; i++) {int minIndex = i;for (int j = i+1; j < array.length; j++) {if(array[j] < array[minIndex]) {minIndex = j;}}swap(array,minIndex,i);}
}private static void swap(int[] array,int i,int j) {int tmp = array[i];array[i] = array[j];array[j] = tmp;
}

直接选择排序的特性总结:

  • 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用
  • 时间复杂度:O(N^2)
  • 空间复杂度:O(1)
  • 稳定性:不稳定

堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。

private static void shiftDown(int[] array,int root,int len) {int parent = root;int child = (2*parent) + 1;while (child < len) {if(child+1 < len && array[child] < array[child+1]) {child++;}if(array[child] > array[parent]) {swap(array,child,parent);parent = child;child = 2*parent+1;}else {break;}}
}private static void createHeap(int[] array) {for (int p = (array.length-1-1)/2; p >= 0 ; p--) {shiftDown(array,p,array.length);}
}public static void heapSort(int[] array) {createHeap(array);int end = array.length-1;while (end >= 0) {swap(array,0,end);shiftDown(array,0,end);end--;}
}

堆排序的特性总结:

  • 堆排序使用堆来选数,效率就高了很多。
  • 时间复杂度:O(N*logN)
  • 空间复杂度:O(1)
  • 稳定性:不稳定

2.3 交换排序

冒泡排序

public static void bubbleSort(int[] array) {for (int i = 0; i < array.length-1; i++) {for (int j = 0; j < array.length-1-i; j++) {if(array[j] > array[j+1]) {swap(array,j,j+1);}}}
}

优化一下代码让有序性的代码就不进行比较了

public static void bubbleSort2(int[] array) {for (int i = 0; i < array.length-1; i++) {boolean flg = false;for (int j = 0; j < array.length-1-i; j++) {if(array[j] > array[j+1]) {swap(array,j,j+1);flg = true;}}if(!flg) {break;}}
}

冒泡排序的特性总结:

  • 冒泡排序是一种非常容易理解的排序
  • 时间复杂度:O(N^2)
  • 空间复杂度:O(1)
  • 稳定性:稳定

快速排序

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

将区间按照基准值划分为左右两半部分的常见方式有:

  1. Hoare版
public static void quickSort(int[] array) {quick(array,0,array.length-1);
}private static void quick(int[] array,int left,int right) {if(left >= right) return;int pivot = partitionHoare(array,left,right);quick(array,left,pivot-1);quick(array,pivot+1,right);
}private static int partitionHoare(int[] array,int low,int high) {int i = low;int tmp = array[low];while (low < high) {while (low < high && array[high] >= tmp) {high--;}//此时high就是你要找的数字while (low < high && array[low] <= tmp) {low++;}//此时low就是你要找的数字swap(array,low,high);}swap(array,low,i);return low;
}
  1. 挖坑法
private static int partitionHole(int[] array,int low,int high) {int tmp = array[low];while (low < high) {while (low < high && array[high] >= tmp) {high--;}array[low] = array[high];while (low < high && array[low] <= tmp) {low++;}array[high] = array[low];}array[low] = tmp;return low;
}
  1. 前后指针

方法一:

private static int partition1(int[] array,int low,int high) {int prev = low ;int cur = low+1;while (cur <= high) {if(array[cur] < array[low] && array[++prev] != array[cur]) {swap(array,cur,prev);}cur++;}swap(array,prev,low);return prev;
}

方法二:

private static int partition(int[] array, int left, int right) {int d = left + 1;int pivot = array[left];for (int i = left + 1; i <= right; i++) {if (array[i] < pivot) {swap(array, i, d);d++;}}swap(array, d - 1, left);return d - 1;
}

快速排序优化:

  1. 区间优化(插入排序)(不能解决递归深度问题)
public static void insertSortRange(int[] array,int low,int end) {for(int i = low+1;i <= end;i++) {int tmp = array[i];int j = i-1;for (; j >= low ; j--) {if(array[j] > tmp) {array[j+1] = array[j];}else {break;}}array[j+1] = tmp;}
}private static void quick(int[] array,int left,int right) {if(left >= right) return;//1.在某个区间的时候进行优化(插入排序)(不能解决递归深度问题)if(right-left+1 <= 70000) {//这里值的大小看情况而定//进行插入排序:insertSortRange(array, left, right);return;}int pivot = partitionHoare(array,left,right);quick(array,left,pivot-1);quick(array,pivot+1,right);
}
  1. 三数取中法
public static int medianOfThreeIndex(int[] array, int left, int right){int mid = left + (right - left) >>> 1;if(array[left] < array[right]){if(array[mid] < array[left]){return left;}else if(array[mid] < array[right]){return mid;}else if(array[mid] > array[right]){return right;}}else {if(array[mid] < array[right]){return right;}else if(array[mid] > array[left]){return left;}else {return mid;}}return mid;
}private static void quick(int[] array,int left,int right) {if(left >= right) return;//1.在某个区间的时候进行优化(插入排序)(不能解决递归深度问题)if(right-left+1 <= 70000) {//这里值的大小看情况而定//进行插入排序:insertSortRange(array, left, right);return;}//2.三数取中法int index = medianOfThreeIndex(array, left, right);swap(array,left,index);int pivot = partitionHoare(array,left,right);quick(array,left,pivot-1);quick(array,pivot+1,right);
}

非递归实现快速排序

public static void quickSortNor(int[] array) {Stack<Integer> stack = new Stack<>();int left = 0;int right = array.length-1;int piovt = partitionHole(array,left,right);//左边有两个数据及以上if(piovt > left+1){stack.push(left);stack.push(piovt-1);}if(piovt < right-1){stack.push(piovt+1);stack.push(right);}while (!stack.isEmpty()){right = stack.pop();left = stack.pop();piovt = partitionHole(array,left,right);//左边有两个数据及以上if(piovt > left+1){stack.push(left);stack.push(piovt-1);}if(piovt < right-1){stack.push(piovt+1);stack.push(right);}}
}

快速排序总结:

  • 时间复杂度:最好的情况:O(logN) 最坏的情况(有序或逆序):O(N^2)
  • 空间复杂度:最好的情况:logN 最坏的情况:O(N)
  • 稳定性:不稳定
  • 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

2.4归并排序

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

private static void merge(int[] array,int low,int mid,int high){int s1 = low;int e1 = mid;int s2 = mid+1;int e2 = high;int[] tmpArr = new int[high-low+1];int k = 0;//k代表tmpArr数组下标//证明两个段都是有数据的while (s1 < e1 && s2 < e2){if(array[s1] < array[s2]){tmpArr[k++] = array[s1++];}else {tmpArr[k++] = array[s2++];}}while (s1 < e1){tmpArr[k++] = array[s1++];}while (s2 < e2){tmpArr[k++] = array[s2++];}for (int i = 0; i < tmpArr.length;i++){array[i+low] = tmpArr[k];}
}public static void mergeSortInternal(int[] array,int low,int high){if(low >= high) return;int mid = low + ((high-low) >>> 1);mergeSortInternal(array,low,mid);mergeSortInternal(array,mid+1, high);merge(array,low,mid,high);
}public static void mergeSort(int[] array){mergeSortInternal(array,0,array.length-1);
}

非递归实现

public static void mergeSortNor(int[] array){int gap = 1;while (gap < array.length){for(int i = 0;i < array.length;i += 2*gap){int left = i;int mid = left+gap+1;//修正 midif(mid >= array.length){mid = array.length - 1;}int right = mid + gap;//修正rightif(right >= array.length){right = array.length - 1;}merge(array,left,mid,right);}gap *= 2;}
}

归并排序总结:

  • 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  • 时间复杂度:O(N*logN)
  • 空间复杂度:O(N)
  • 稳定性:稳定

2.4.3 海量数据的排序问题

外部排序:排序过程需要在磁盘等外部存储进行的排序
前提:内存只有 1G,需要排序的数据有 100G
因为内存中因为无法把所有数据全部放下,所以需要外部排序,而归并排序是最常用的外部排序

  1. 先把文件切分成 200 份,每个 512 M
  2. 分别对 512 M 排序,因为内存已经可以放的下,所以任意排序方式都可以
  3. 进行 2路归并,同时对 200 份有序文件做归并过程,最终结果就有序了

三.排序算法复杂度及稳定性分析

在这里插入图片描述

在这里插入图片描述


四.非基于比较排序

  1. 计数排序

思想:计数排序又称为鸽巢原理,是对哈希直接定址法的变形应用。 操作步骤:

  • 统计相同元素出现次数
  • 根据统计的结果将序列回收到原来的序列中
public static void countSort(int[] array){//1.获取最大值和最小值int maxVal = array[0];int minVal = array[0];for (int i = 0; i < array.length; i++) {if(array[i] < minVal){minVal = array[i];}if(array[i] > maxVal){maxVal = array[i];}}//2.开始计数int range = maxVal - minVal +1;int[] count = new int[range];for (int i = 0; i < array.length; i++) {count[array[i]-minVal]++;}//3.遍历这个计数数组,把数据返回给 arrayint index = 0;for (int i = 0; i < count.length; i++) {while (count[i] > 0){array[index++] = i + minVal;count[i]--;}}
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

arthas 常用命令详解(dashboard、thread、jad、redefine等)

上文中我们介绍了arthas安装和基本命令查看&#xff0c;本文详细介绍各个命令的使用方式。我们在启动arthas的时候&#xff0c;arthas会将监控的java进程展示出来&#xff0c;我们需要选择一个进程进入监控状态。 1、基础命令 命令说明示例help查看命令帮助信息helpcat 打印文…

目标追踪DeepSort算法原理浅析

前言 背景&#xff1a;DeepSort是基于Sort目标跟踪进行的改进&#xff0c;它引入深度学习模型&#xff0c;在实时目标跟踪过程中&#xff0c;提取目标的外观特征进行最近邻近匹配。 目的&#xff1a;改善有遮挡情况下的目标追踪效果&#xff1b;同时&#xff0c;也减少了目标I…

ubuntu 下git常用指令【持续更新中】

1.下载 sudo apt install git 2. 查看版本 git --version3. 登录git账号 git config --global user.email "youexample.com" git config --global user.name "Your Name"4.生成密钥对 ssh-keygen -t rsa -C "your_emailyouremail.com"复制公…

证券公司如何应对大数据调度系统的高负载挑战

​在金融行业&#xff0c;数据处理和任务调度是日常运营的重要组成部分。随着业务量的激增&#xff0c;日益增长的任务量和复杂的资源管理需求&#xff0c;要求该系统不仅要稳如磐石&#xff0c;还需灵活高效。 本文将探讨某证券公司在应对这些挑战时所采用的策略&#xff0c;并…

拼图小游戏制作教程:用HTML5和JavaScript打造经典游戏

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

Redis 内存的优化

目录 前言 Redis 的内存碎片问题 判断Redis 内存碎片 如何清理内存碎片&#xff1f; 前言 我想讲一下怎么提高Redis 内存的利用率&#xff0c;redis 的数据是保存在内存中。对内存的利用率低&#xff0c;意味着存的数据很少&#xff0c;并不意味着就没有内存了&#xff0c…

Maven深入了解

Maven深入了解 前言一、Maven的核心概念1.1 Maven-Jar包模块化管理1.2 POM1.3 坐标及其命名规范1.4 仓库的概念1.5 生命周期1.6 插件和目标 二、依赖管理2.1 自己写的模块和模块之间也可以互相依赖2.2 依赖的生效范围(scope标签)2.3 依赖的传递性2.4 依赖冲突问题2.5 依赖的排除…

unity3d Animal Controller的Animal组件中General基础部分理解

控制器介绍 动物脚本负责控制动物的所有运动逻辑.它管理所有的动画师和刚体参数,以及所有的状态和模式,动物可以做。 动物控制器 是一个动画框架控制器,根动或到位,为任何生物或人形。它利用刚体与物理世界的互动和动画师的玩动画。 States States 是不互相重叠的动画。例如…

Qt 如何搭建Lua的运行环境

一、Lua简介 Lua 是一种强大的、高效的、轻量级的、可嵌入的脚本语言。它支持过程&#xff08;procedural&#xff09;编程、面向对象编程、函数式编程以及数据描述。Lua 是动态类型的&#xff0c;运行速度快&#xff0c;支持自动内存管理&#xff0c;因此被广泛用于配置、脚本…

探索Java高并发编程之道:理论与实践

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 简介 随着互联网和信息技术的快速发展&#x…

比较两组二维平面结构的演化

假设1个6*6的二维平面空间&#xff0c;这个空间的行和列只能按照1-2-3-4-5-6-1的顺序变换。这个平面上的物体只能平移。在这个空间里有力&#xff0c;在这些力的作用下&#xff0c;两个点按照 1-7的顺序运动。 - - - - - - - - - - - - - - - A - - - - - …

COOH-PEG-Galactose 羧基-聚乙二醇-半乳糖 Galactose 靶向肝肿瘤细胞

在生物体内&#xff0c;正常细胞通过有氧呼吸将糖类等物质分解代谢产生能量&#xff0c;从而供给细胞的增殖和生 长。而癌细胞似乎更为“蛮横”&#xff0c;它们主要依靠糖酵解作用为生&#xff0c;因此癌细胞代谢葡萄糖的速度比正 常细胞要快得多。值得注意的是&#xff0c;…