用 Java 实现常见的 8 种内部排序算法
一、插入类排序
插入类排序就是在一个有序的序列中,插入一个新的关键字。从而达到新的有序序列。插入排序一般有直接插入排序、折半插入排序和希尔排序。
1. 插入排序
1.1 直接插入排序
/**
* 直接比较,将大元素向后移来移动数组
*/
public static void InsertSort(int[] A) {
for(int i = 1; i < A.length; i++) {
int temp = A[i]; //temp 用于存储元素,防止后面移动数组被前一个元素覆盖
int j;
for(j = i; j > 0 && temp < A[j-1]; j--) { //如果 temp 比前一个元素小,则移动数组
A[j] = A[j-1];
}
A[j] = temp; //如果 temp 比前一个元素大,遍历下一个元素
}
}
/**
* 这里是通过类似于冒泡交换的方式来找到插入元素的最佳位置。而传统的是直接比较,移动数组元素并最后找到合适的位置
*/
public static void InsertSort2(int[] A) { //A[] 是给定的待排数组
for(int i = 0; i < A.length - 1; i++) { //遍历数组
for(int j = i + 1; j > 0; j--) { //在有序的序列中插入新的关键字
if(A[j] < A[j-1]) { //这里直接使用交换来移动元素
int temp = A[j];
A[j] = A[j-1];
A[j-1] = temp;
}
}
}
}
/**
* 时间复杂度:两个 for 循环 O(n^2)
* 空间复杂度:占用一个数组大小,属于常量,所以是 O(1)
*/
1.2 折半插入排序
/*
* 从直接插入排序的主要流程是:1.遍历数组确定新关键字 2.在有序序列中寻找插入关键字的位置
* 考虑到数组线性表的特性,采用二分法可以快速寻找到插入关键字的位置,提高整体排序时间
*/
public static void BInsertSort(int[] A) {
for(int i = 1; i < A.length; i++) {
int temp = A[i];
//二分法查找
int low = 0;
int high = i - 1;
int mid;
while(low <= high) {
mid = (high + low)/2;
if (A[mid] > temp) {
high = mid - 1;
} else {
low = mid + 1;
}
}
//向后移动插入关键字位置后的元素
for(int j = i - 1; j >= high + 1; j--) {
A[j + 1] = A[j];
}
//将元素插入到寻找到的位置
A[high + 1] = temp;
}
}
2. 希尔排序
希尔排序又称缩小增量排序,其本质还是插入排序,只不过是将待排序列按某种规则分成几个子序列,然后如同前面的插入排序一般对这些子序列进行排序。因此当增量为 1 时,希尔排序就是插入排序,所以希尔排序最重要的就是增量的选取。
主要步骤是:
- 将待排序数组按照初始增量 d 进行分组
- 在每个组中对元素进行直接插入排序
- 将增量 d 折半,循环 1、2 、3步骤
- 待 d = 1 时,最后一次使用直接插入排序完成排序
/**
* 希尔排序的实现代码还是比较简洁的,除了增量的变化,基本上和直接插入序列没有区别
*/
public static void ShellSort(int[] A) {
for(int d = A.length/2; d >= 1; d = d/2) { //增量的变化,从 d = "数组长度一半"到 d = 1
for(int i = d; i < A.length; i++) { //在一个增量范围内进行遍历[d,A.length-1]
if(A[i] < A[i - d]) { //若增量后的元素小于增量前的元素,进行插入排序
int temp = A[i];
int j;
for(j = i - d; j >= 0 && temp < A[j-d]; j -= d) { //对该增量序列下的元素进行排序
A[j + d] = A[j]; //这里要使用i + d 的方式来移动元素,因为增量 d 可能大于数组下标
} //造成数组序列超出数组的范围
A[j + d] = temp;
}
}
}
}
复杂度分析
排序方法 | 空间复杂度 | 最好情况 | 最坏情况 | 平均时间复杂度 |
---|---|---|---|---|
直接插入排序 | O(1) | O(n^2) | O(n^2) | O(n^2) |
折半插入排序 | O(1) | O(nlog2n) | O(n^2) | O(n^2) |
希尔排序 | O(1) | O(nlog2n) | O(nlog2n) | O(nlog2n) |
二、交换类排序
交换,指比较两个元素关键字大小,来交换两个元素在序列中的位置,最后达到整个序列有序的状态。主要有冒泡排序和快速排序
3. 冒泡排序
冒泡排序就是通过依次比较序列中两个相邻元素的值,根据需要的升降序来交换这两个元素。最终达到整个序列有序的结果。
/**
* 冒泡排序
*/
public static void BubbleSort(int[] A) {
for (int i = 0; i < A.length - 1; i++) { //冒泡次数,遍历数组次数,有序元素个数
for(int j = 0; j < A.length - i - 1; j++) { //对剩下无序元素进行交换排序
if(A[j] > A[j + 1]) {
int temp = A[j];
A[j] = A[j + 1];
A[j + 1] = temp;
}
}
}
}
4. 快速排序
快速排序实际上也是属于交换类的排序,只是它通过多次划分操作实现排序。这就是分治思想,把一个序列分成两个子序列它每一趟选择序列中的一个关键字作为枢轴,将序列中比枢轴小的移到前面,大的移到后边。当本趟所有子序列都被枢轴划分完毕后得到一组更短的子序列,成为下一趟划分的初始序列集。每一趟结束后都会有一个关键字达到最终位置。
/**
* 快速排序算是在冒泡排序的基础上的递归分治交换排序
* @param A 待排数组
* @param low 数组起点
* @param high 数组终点
*/
public static void QuickSort(int[] A, int low, int high) {
if(low >= high) { //递归分治完成退出
return;
}
int left = low; //设置左遍历指针 left
int right = high; //设置右遍历指针 right
int pivot = A[left]; //设置枢轴 pivot, 默认是数组最左端的值
while(left < right) { //循环条件
while(left < right && A[right] >= pivot) {//若右指针所指向元素大于枢轴值,则右指针向左移动
right--;
}
A[left] = A[right]; //反之替换
while (left < right && A[left] <= pivot) {//若左指针所指向元素小于枢轴值,则左指针向右移动
left++;
}
A[right] = A[left]; //反之替换
}
A[left] = pivot; //将枢轴值放在最终位置上
QuickSort(A, low, left - 1); //依此递归枢轴值左侧的元素
QuickSort(A, left + 1, high); //依此递归枢轴值右侧的元素
}
复杂度分析
排序方法 | 空间复杂度 | 最好情况 | 最坏情况 | 平均时间复杂度 |
---|---|---|---|---|
冒泡排序 | O(1) | O(n^2) | O(n^2) | O(n^2) |
快速排序 | O(log2n) | O(nlog2n) | O(n^2) | O(nlog2n) |
三、选择排序
选择排序就是每一趟从待排序列中选择关键字最小的元素,直到待排序列元素选择完毕。
5. 简单选择排序
/**
* 简单选择排序
* @param A 待排数组
*/
public static void SelectSort(int [] A) {
for (int i = 0; i < A.length; i++) {
int min = i; //遍历选择序列中的最小值下标
for (int j = i + 1; j < A.length; j++) { //遍历当前序列选择最小值
if (A[j] < A[min]) {
min = j;
}
}
if (min != i) { //选择并交换最小值
int temp = A[min];
A[min] = A[i];
A[i] = temp;
}
}
}
6.堆排序
堆是一种数据结构,可以把堆看成一颗完全二叉树,而且这棵树任何一个非叶结点的值都不大于(或不小于)其左右孩子结点的值。若父结点大子结点小,则这样的堆叫做大顶堆;若父结点小子结点大,则这样的堆叫做小顶堆。
堆排序的过程实际上就是将堆排序的序列构造成一个堆,将堆中最大的取走,再将剩余的元素调整成堆,然后再找出最大的取走。这样重复直至取出的序列有序。
排序主要步骤可以分为(以大顶堆为例):
(1) 将待排序列构造成一个大顶堆:BuildMaxHeap()
(2) 对堆进行调整排序:AdjustMaxHeap()
(3) 进行堆排序,移除根结点,调整堆排序:HeapSort()
/**
* 堆排序(大顶堆)
* @param A 待排数组
*/
public static void HeapSort(int [] A) {
BuildMaxHeap(A); //建立堆
for (int i = A.length - 1; i > 0; i--) { //排序次数,需要len - l 趟
int temp = A[i]; //将堆顶元素(A[0])与数组末尾元素替换,更新待排数组长度
A[i] = A[0];
A[0] = temp;
AdjustMaxHeap(A, 0, i); //调整新堆,对未排序数组再次进行调整
}
}
/**
* 建立大顶堆
* @param A 待排数组
*/
public static void BuildMaxHeap(int [] A) {
for (int i = (A.length / 2) -1; i >= 0 ; i--) { //对[0,len/2]区间中的的结点(非叶结点)从下到上进行筛选调整
AdjustMaxHeap(A, i, A.length);
}
}
/**
* 调整大顶堆
* @param A 待排数组
* @param k 当前大顶堆根结点在数组中的下标
* @param len 当前待排数组长度
*/
public static void AdjustMaxHeap(int [] A, int k, int len) {
int temp = A[k];
for (int i = 2*k + 1; i < len; i = 2*i + 1) { //从最后一个叶结点开始从下到上进行堆调整
if (i + 1 < len && A[i] < A[i + 1]) { //比较两个子结点大小,取其大值
i++;
}
if (temp < A[i]) { //若结点大于父结点,将父结点替换
A[k] = A[i]; //更新数组下标,继续向上进行堆调整
k = i;
} else {
break; //若该结点小于父结点,则跳过继续向上进行堆调整
}
}
A[k] = temp; //将结点放入比较后应该放的位置
}
复杂度分析
排序方法 | 空间复杂度 | 最好情况 | 最坏情况 | 平均时间复杂度 |
---|---|---|---|---|
简单选择排序 | O(1) | O(n^2) | O(n^2) | O(n^2) |
堆排序 | O(1) | O(log2n) | O(nlog2n) | O(nlog2n) |
四、其他内部排序
7. 归并排序
归并排序是将多个有序表组合成一个新的有序表,该算法是采用分治法的一个典型的应用。即把待排序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为一个整体有序的序列。这里主要以二路归并排序来进行分析。
该排序主要分为两步:
- 分解:将序列每次折半拆分
- 合并:将划分后的序列两两排序并合并
private static int[] aux;
/**
* 初始化辅助数组 aux
* @param A 待排数组
*/
public static void MergeSort(int [] A) {
aux = new int[A.length];
MergeSort(A,0,A.length-1);
}
/**
* 将数组分成两部分,以数组中间下标 mid 分为两部分依此递归
* 最后再将两部分的有序序列通过 Merge() 函数 合并
* @param A 待排数组
* @param low 数组起始下标
* @param high 数组末尾下标
*/
public static void MergeSort (int[] A, int low, int high) {
if (low < high) {
int mid = (low + high) / 2;
MergeSort(A, low, mid);
MergeSort(A, mid + 1, high);
Merge(A, low, mid, high);
}
}
/**
* 将 [low, mid] 有序序列和 [mid+1, high] 有序序列合并
* @param A 待排数组
* @param low 数组起始下标
* @param mid 数组中间分隔下标
* @param high 数组末尾下标
*/
public static void Merge (int[] A, int low, int mid, int high) {
int i, j, k;
for (int t = low; t <= high; t++) {
aux[t] = A[t];
}
for ( i = low, j = mid + 1, k = low; i <= mid && j <= high; k++) {
if(aux[i] < aux[j]) {
A[k] = aux[i++];
} else {
A[k] = aux[j++];
}
}
while (i <= mid) {
A[k++] = aux[i++];
}
while (j <= high) {
A[k++] = aux[j++];
}
}
8. 基数排序
基数排序比较特别,它是通过关键字数字各位的大小来进行排序。它是一种借助多关键字排序的思想来对单逻辑关键字进行排序的方法。
它主要有两种排序方法:
- 最高位优先法(MSD):按照关键字位权重高低依此递减来划分子序列
- 最低位优先法(LSD) :按照关键字位权重低高依此增加来划分子序列
基数排序的思想:
- 分配
- 回收
/**
* 找出数组中的最长位数
* @param A 待排数组
* @return MaxDigit 最长位数
*/
public static int MaxDigit (int [] A) {
if (A == null) {
return 0;
}
int Max = 0;
for (int i = 0; i < A.length; i++) {
if (Max < A[i]) {
Max = A[i];
}
}
int MaxDigit = 0;
while (Max > 0) {
MaxDigit++;
Max /= 10;
}
return MaxDigit;
}
/**
* 将基数排序的操作内化在一个二维数组中进行
* @param A 待排数组
*/
public static void RadixSort(int [] A) {
//创建一个二维数组,类比于在直角坐标系中,进行分配收集操作
int[][] buckets = new int[10][A.length];
int MaxDigit = MaxDigit(A);
//t 用于提取关键字的位数
int t = 10;
//按排序趟数进行循环
for (int i = 0; i < MaxDigit; i++) {
//在一个桶中存放元素的数量,是buckets 二维数组的y轴
int[] BucketLen = new int[10];
//分配操作:将待排数组中的元素依此放入桶中
for (int j = 0; j < A.length ; j++) {
//桶的下标值,是buckets 二维数组的x轴
int BucketIndex = (A[j] % t) / (t / 10);
buckets[BucketIndex][BucketLen[BucketIndex]] = A[j];
//该下标下,也就是桶中元素个数随之增加
BucketLen[BucketIndex]++;
}
//收集操作:将已排好序的元素从桶中取出来
int k = 0;
for (int x = 0; x < 10; x++) {
for (int y = 0; y < BucketLen[x]; y++) {
A[k++] = buckets[x][y];
}
}
t *= 10;
}
}
复杂度分析
排序方法 | 空间复杂度 | 最好情况 | 最坏情况 | 平均时间复杂度 |
---|---|---|---|---|
归并排序 | O(n) | O(nlog2n) | O(nlog2n) | O(nlog2n) |
基数排序 | O(rd) | O(d(n+rd)) | O(d(n+rd)) | O(d(n+rd)) |
备注:基数排序中,n 为序列中的关键字数,d为关键字的关键字位数,rd 为关键字位数的个数
参考文章:
- Java 实现八大排序算法
- 《 2022王道数据结构》
- 《算法》
- 八种排序算法模板
- 基数排序就这么简单
用 Java 实现常见的 8 种内部排序算法的更多相关文章
- Java中常见的5种WEB服务器介绍
这篇文章主要介绍了Java中常见的5种WEB服务器介绍,它们分别是Tomcat.Resin.JBoss.WebSphere.WebLogic,需要的朋友可以参考下 Web服务器是运行及发布Web应用的 ...
- Java实现各种内部排序算法
数据结构中常见的内部排序算法: 插入排序:直接插入排序.折半插入排序.希尔排序 交换排序:冒泡排序.快速排序 选择排序:简单选择排序.堆排序 归并排序.基数排序.计数排序 直接插入排序: 思想:每次将 ...
- 7种基本排序算法的Java实现
7种基本排序算法的Java实现 转自我的Github 以下为7种基本排序算法的Java实现,以及复杂度和稳定性的相关信息. 以下为代码片段,完整的代码见Sort.java 插入排序 /** * 直接插 ...
- 常见内部排序算法对比分析及C++ 实现代码
内部排序是指在排序期间数据元素全部存放在内存的排序.外部排序是指在排序期间全部元素的个数过多,不能同时存放在内存,必须根据排序过程的要求,不断在内存和外存之间移动的排序.本次主要介绍常见的内部排序算法 ...
- 七种经典排序算法及Java实现
排序算法稳定性表示两个值相同的元素在排序前后是否有位置变化.如果前后位置变化,则排序算法是不稳定的,否则是稳定的.稳定性的定义符合常理,两个值相同的元素无需再次交换位置,交换位置是做了一次无用功. 下 ...
- 七内部排序算法汇总(插入排序、Shell排序、冒泡排序、请选择类别、、高速分拣合并排序、堆排序)
写在前面: 排序是计算机程序设计中的一种重要操作,它的功能是将一个数据元素的随意序列,又一次排列成一个按keyword有序的序列.因此排序掌握各种排序算法很重要. 对以下介绍的各个排序,我们假定全部排 ...
- php四种基础排序算法的运行时间比较
/** * php四种基础排序算法的运行时间比较 * @authors Jesse (jesse152@163.com) * @date 2016-08-11 07:12:14 */ //冒泡排序法 ...
- PHP四种基本排序算法
PHP的四种基本排序算法为:冒泡排序.插入排序.选择排序和快速排序. 下面是我整理出来的算法代码: 1. 冒泡排序: 思路:对数组进行多轮冒泡,每一轮对数组中的元素两两比较,调整位置,冒出一个最大的数 ...
- php四种基础排序算法的运行时间比较!
/** * php四种基础排序算法的运行时间比较 * @authors Jesse (jesse152@163.com) * @date 2016-08-11 07:12:14 */ //冒泡排序法 ...
随机推荐
- excel vba的inputBox函数
Sub test1() Dim h Dim j As Integer j = 0 Dim n1 As Integer '分行单元格在第几列 Dim m1 As Integ ...
- hive学习笔记之十一:UDTF
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- Docker:redis容器使用redis.conf启动失败,不报错
查看redis.conf配置信息 daemonize no :redis默认是不作为守护进程使用的,这也就是说为什么在你不修改配置文件时直接使用redis-server /redis/redis.co ...
- Quartz:Quartz定时代码实现
1.添加pom.xml <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId> ...
- pod调度
Pod调度 在默认情况下,一个pod在哪个node节点上运行,是由scheduler组件采用相应的算法计算出来的,这个过程是不受人工控制的. 但是在实际过程中,这并不满足需求,因为很多情况下,我们想控 ...
- ARTS第一周
开始进行的第一周. 1.Algorithm:每周至少做一个 leetcode 的算法题2.Review:阅读并点评至少一篇英文技术文章3.Tip:学习至少一个技术技巧4.Share:分享一篇有观点和思 ...
- DNS部署与安全
1.DNS Domain Name Service 域名服务 作用: 为客户机提供域名解析服务器 2.域名组成 2.1 域名组成概述 如"www.baidu.com"是一个域名,从 ...
- QT从入门到入土(四)——多线程
引言 前面几篇已经对C++的线程做了简单的总结,浅谈C++11中的多线程(三) - 唯有自己强大 - 博客园 (cnblogs.com).本篇着重于Qt多线程的总结与实现. 跟C++11中很像的是,Q ...
- 个人博客开发之blog-api 项目整合JWT实现token登录认证
前言 现在前后端分离,基于session设计到跨越问题,而且session在多台服器之前同步问题,肯能会丢失,所以倾向于使用jwt作为token认证 json web token 导入java-jwt ...
- python -- 程序的结构语句
一.顺序结构 顺序结构是python脚本程序中基础的结构,它是按照程序语句出现的先后顺序进行依次执行 二.选择结构 选择结构是通过判断某些特定的条件是否满足来决定程序语句的执行顺序 常见的有单分支选择 ...