java随机yujie_排序算法的总结——Java实现

2023-11-09 12:50

本文主要是介绍java随机yujie_排序算法的总结——Java实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

简单归纳一下最近学习的排序算法,如果有什么错误的地方还请大家指教。

本文介绍了七种经典排序算法,包括冒泡排序,选择排序,插入排序,希尔排序,归并排序,快速排序以及堆排序,并且讨论了各种算法的进一步改进,在文章最后还对所有算法的时间和空间复杂度作了一个总结。

用Java语言可以很简洁优雅的实现各种排序算法,我们在写排序算法的时候可以下面这种模板:

public class Example {

public static void sort(Comparable[] a) {

//具体算法

}

//判断v是否小于w

public static boolean less(Comparable[] v, Comparable[] W) {

return v.compareTo(w) < 0;

}

//交换

public static void exch(Comparable[] a, int i, int j) {

Comparable temp = a[i];

a[i] = a[j];

a[j] = temp;

}

}

此种模板适合的是实现了Comparable接口的数据类型,如String, Integer, Date 或是自己创建的实现了compareTo()方法的数据类型。此种模板实际排序的是对象的引用,然而对于没有实现该接口的类型或是一些原始数据类型则不适用(可以利用Comparator比较器来实现)。

为了简洁易懂,下面以int型数据为例来书写代码,不使用less()方法,但仍使用exch()方法来表示交换值的过程。

一、冒泡排序

冒泡排序的思想理解起来很简单,就是依次比较相邻的元素,将大的元素往后移,小的往前移。因此,数列中最大的元素总是随着交换不断浮到顶端,于是有了冒泡排序这个名字。

冒泡排序会经历 n-1 次循环(n 为数组长度),第一次循环将最大的数置于最末端,第二次将第二大的数置于倒数第二的位置,每一次循环待排序的数列长度逐渐减小,直至排序完成。

c0a3c8412687b03a87add8b5839d174f.png

代码:

public class BubbleSort {

public static void sort(int[] a) {

for (int i = a.length - 1; i > 0; i--)

for (int j = 0; j < i; j++)

if (a[j] > a[j + 1])

exch(a, j, j + 1); //交换相邻元素

}

改进:如果在某一次循环中,发现没有进行任何交换,这说明已经完成了排序,即可直接退出。代码如下:

//改进版本

public class BubbleSort {

public static void sort(int[] a) {

for (int i = a.length - 1; i > 0; i--) {

boolean didExch = false;//利用一个变量监控是否交换

for (int j = 0; j < i; j++) {

if (a[j] > a[j + 1]) {

exch(a, j, j + 1);

didExch = true;

}

}

if (didExch == false)//如果没有发生交换,直接返回

return;

}

}

分析:对于改进后的版本,在最坏情况下所需的比较次数为(n - 1) + (n - 2) + .. + 1 = ( n-1) * n /2

~ n^2 /2,时间复杂度为O(n^2),而最好情况下(即已经是有序数列),只需进行n

- 1次比较和0次交换,时间复杂度为O(n)。

二、选择排序

选择排序的思想是:首先找到数组中最小的元素,将它和数组第一个元素交换位置,其次,在剩下元素中找到最小元素,和数组中第二个元素交换位置,以此类推,直至整个数组排序完毕。这种排序方法不断地选择剩余元素中的最小者,故得名选择排序。

ac29e7bbad78a0b978816340e7564686.png

代码:

public class Selection {

public static void sort(int[] a) {

for (int i = 0; i < a.length; i++) {

int min = i;

//找到最小值,并把下标记为 min

for (int j = i + 1; j < a.length; j++) {

if (a[j] < a[min])

min = j;

}

exch(a, i, min);//交换 a[i] 和 a[min]

}

}

分析:对于长度为n的数组,选择排序需要(n - 1)*n/2 ~ n^2 /2次比较和n次交换,时间复杂度为O(n^2)。

三、插入排序

插入排序和人们打牌时整理牌的思想是一致的,将每一张牌插入到其他已经有序的牌中的适当位置。在每一次循环当中,当前索引左边的元素都是有序的,直到当前索引移动到最右端,数组完成排序。

55598714a6e45aee7fc6c3b41c09358d.png

排序的实现有两种方法,第一种是通过不断交换顺序颠倒的相邻元素来找到合适位置:

public class Insertion {

public static void sort(int[] a) {

for (int i = 1; i < a.length; i++)

for (int j = i; j > 0 && a[j] < a[j - 1]; j--)

exch(a, j, j - 1);//交换顺序颠倒的元素

}

另一种是通过在内循环中将较大元素往右移动来找到插入位置,这种方法相比上面的方法访问数组的次数减少了一半

public class Insertion {

public static void sort(int[] a) {

for (int i = 1; i < a.length; i++) {

int j;//注意 j 的声明要在内循环的外面

int temp = a[i]; //保存 a[i] 的值

for (j = i; j > 0 && temp < a[j - 1]; j--) {

a[j] = a[j - 1];//后移较大的元素

}

a[j] = temp;//把原来的 a[i] 值插入空位中

}

}

分析:以方法一为例,插入排序对于长度为n的数组,最坏情况下需要~n^2 /2次比较和~n^2

/2次交换,时间复杂度为O(n^2),最好情况下需要n-1次比较和0次交换,时间复杂度为O(n)。实际上,插入排序的需要的交换与数组中倒置元素的对数一致,因此插入排序对于规模不大,部分有序的数组排序十分有效。

四、希尔排序

希尔排序是一种基于插入排序的快速改进方法,利用的是插入排序处理部分有序数组有很好效果的特点。希尔排序处理不相邻的元素对数组的局部进行排序,并最终用插入排序将局部有序的数组排序。

希尔排序的思想是使数组中间隔为h的元素都是有序的,这样的数组称为h有序数组。下图则是一个4-有序数组:

920dcdf1e886ec613f58ca26c9d757b6.png

下面使用一个递增序列h = 3 * h + 1 来实现希尔排序,在 n = 16 的例子中,h 可以取 1,4 和 13:

4abe7c1f9eefa4e5796d1c8bdf7ef601.png

代码:

public class Shell {

public static void sort(int[] a) {

int N = a.length;

int h = 1;

while (h < N / 3)

h = 3 * h + 1; //h的递增序列

while (h >= 1) {

for (int i = h; i < N; i++) {

int j;//注意 j 的声明要在内循环的外面

int temp = a[i]; //保存 a[i] 的值

for (j = i; j >= h && temp < a[j - h]; j -= h) {

a[j] = a[j - h];//后移较大的元素

}

a[j] = temp;//把原来的 a[i] 值插入空位中

}

h = h / 3;

}

}

分析:希尔排序的性能取决于h的递增序列,上面代码中所用的递增序列并不是最优秀的,但最坏情况下的运行时间仍少于平方级别,算法的时间复杂度为O(n^(2/3))。

五、归并排序

归并排序的思想是利用递归先将数组分成两半分别排序,再归并起来。

我们首先需要实现的是归并,最直接的办法就是将两个不同的有序数组归并到第三个数组中,可以通过不断比较将两个输入数组中的元素一个个按照顺序放入这个数组。

归并方法:先将所有元素赋值到辅助数组aux[]中,再归并回a[]中;

public static void merge(int[] a, int lo, int mid, int hi) {

int i = lo;

int j = mid + 1;

for (int k = lo; k <= hi; k++)

aux[k] = a[k];

for (int k = lo; k <= hi; k++) {

if (i > mid)a[k] = aux[j++];

else if (j > hi) a[k] = aux[i++];

else if (aux[j] < aux[i]) a[k] = aux[j++];

else a[k] = aux[i++];

}

}

自顶向下的排序:

public class Merge {

private static int[] aux;//静态数组

public static void sort(int[] a) {

aux = new int[a.length];//分配空间

sort(a, 0, a.length - 1);

}

private static void sort(int[] a, int lo, int hi) {

if (hi <= lo)return;

int mid = (lo + hi) / 2;

sort(a, lo, mid);

sort(a, mid + 1, hi);

merge(a, lo, mid, hi);

public static void sort(int[] a) {

int[] aux = new int[a.length];//创建辅助数组分配空间

sort(a, aux, 0, a.length - 1);

}

}

自底向下的排序:

private static void sort(int[] a) {

int N = a.length;

aux = new int[N];

for (int sz = 1; sz < N; sz = sz + sz) {

for (int lo = 0; lo < N - sz; lo += sz + sz) {

merge(a, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, N - 1));

}

}

}

改进:归并排序在以上基础上还可以有很多改进空间

改进1:将aux[]辅助数组作为参数传递给递归的sort()方法 (作为类的私有静态变量 和 放入归并方法中都不妥当);

public static void sort(int[] a) {

int[] aux = new int[a.length];//创建辅助数组分配空间

sort(a, aux, 0, a.length - 1);

}改进2:添加判断条件,如果a[mid] <= a[mid + 1],此时数组已经有序,就可以跳过merge(),此时处理已经有序的数组

的运行时间变为线性;

改进3:用插入排序处理小规模的子数组:

if (hi - lo + 1 <= M) {//长度小于等于M的子数组用插入排序

InsertSort(a, lo, hi);

return;

}改进4:快速归并,按降序将a[]的后半部分复制到aux[],然后从两边向中间归并,这样可以省去判断半边是否用尽,

但这样的排序是不稳定的;

public static void merge(int[] a, int[] aux, int lo, int mid, int hi) {

//升序复制前一半

for (int k = lo; k <= mid; k++)

aux[k] = a[k];

//降序复制后一半

for (int k = mid + 1, j = hi; k <= hi; k++, j--)

aux[k] = a[j];

int i = lo, j = hi;

for (int k = lo; k <= hi; k++) {

if (aux[j] < aux[i]) a[k] = aux[j--];

else a[k] = aux[i++];

}

}分析:对于长度为n的数组,归并排序需要~ nlgn次比较,时间复杂度为O(nlgn)。(注:这里lgn表示以2为底的n的对数)

六、快速排序

快速排序是一种分治的排序算法,主要思想是利用切分,切分点左边的元素小于等于切分元素,切分点右边的元素大于等于切分元素,当左右两边的子数组有序时即完成排序,利用递归可以很好的将数组排序。

代码:

public class Quick {

public static void sort(int[] a) {

StdRandom.shuffle(a);//打乱数组

sort(a, 0, a.length - 1);

}

private static void sort(int[] a, int lo, int hi) {

if (hi <= lo)return;

int j = partition(a, lo, hi);

sort(a, lo, j - 1);

sort(a, j + 1, hi);

}

private static int partition(int[] a, int lo, int hi) {

int i = lo, j = hi + 1;

int v = a[lo];

while (true) {

while (a[++i] < v)if (i == hi)break;

while (v < a[--j]);

if (j <= i)break;

exch(a, i, j);

}

exch(a, lo, j);

return j;

}

}

改进:同样,快速排序也有很大改进空间

改进1:对于小数组切换到插入排序,5 ~ 15 之间的任意值即可;

改进2:对于含有大量重复元素的数组,使用三向切分;

//三向切分

private static void sort(int[] a, int lo, int hi) {

//对于小数组用插入排序

if (hi <= lo + 5){InsertSort(a, lo, hi);return;}

int lt = lo, i = lo + 1, gt = hi;

int v = a[lo];

while (i <= gt) {

if (a[i] < v)exch(a, lt++, i++);

else if (v < a[i])exch(a, i, gt--);

elsei++;

}

sort(a, lo, lt - 1);

sort(a, gt + 1, hi);

}

//供小规模数组使用的插入排序

public static void InsertSort(int[] a, int lo, int hi) {

for (int i = lo; i <= hi; i++) {

int j;//注意 j 的声明要在内循环的外面

int temp = a[i]; //保存 a[i] 的值

for (j = i; j > lo && temp < a[j - 1]; j--) {

a[j] = a[j - 1];//后移较大的元素

}

a[j] = temp;//把原来的 a[i] 值插入空位中

}

}

分析:对于长度为n的数组,归并排序平均需要~ 1.39nlgn次比较,最好情况下时间复杂度为O(nlgn),而最坏情况下(有序)时间复杂度为O(n^2)。

七、堆排序

先介绍堆的定义(这里以最大堆举例),在二叉堆的数组中,每个元素都要保证大于另两个特点位置的元素。在堆有序的二叉树中,每个节点都小于等于它的父节点(存在的话)。可以用长度为N+1的私有数组来表示一个大小为N的堆,不使用pq[0],堆元素放在pq[1]至pq[N]中。

当一个节点太大时,需要上浮到堆的更高层:

private void swim(int k) {

while (k > 1 && less(k / 2, k)) {

exch(k / 2, k);

k /= 2;

}

}当一个节点太小,需要下沉到更低层时:

private void sink(int k) {

while (k * 2 <= N) {

int j = 2 * k;

if (j < N && less(j, j + 1))j++;//j表示两个子节点里较大的那个

if (!less(k, j))break;//注意退出循环

exch(k, j);

k = j;

}

}

堆排序的代码如下,接收一个数组,先将其转化为堆有序,然后每一次把最大的元素下沉到数组最后(原有的less()和exch()方法将a[1]~a[N]的元素排序,将这两种方法的索引减一即可得到a[0]~a[N-1]的排序)

public class Heap {

public static void sort(int[] a) {

int N = a.length;

//堆有序

for (int k = N / 2; k >= 1; k--)

sink(a, k, N);

//堆排序

while (N > 1) {

exch(a, 1, N--);

sink(a, 1, N);

}

}

private static void sink(int[] a, int k, int N) {

while (k * 2 <= N) {

int j = k * 2;

if (j < N && less(a, j, j + 1))j++;

if (!less(a, k, j))break;

exch(a, k, j);

k = j;

}

}

private static void exch(int[] a, int i, int j) {

int temp = a[i - 1];

a[i - 1] = a[j - 1];

a[j - 1] = temp;

}

private static boolean less(int[] a, int i, int j) {

return a[i - 1] < a[j - 1];

}

}分析:对于长度为n的数组,堆排序只需要最多2nlgn+2n次比较,时间复杂度为O(nlgn)。

这篇关于java随机yujie_排序算法的总结——Java实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/376189

相关文章

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2