【Java基础】——Java排序(上)

简介: 【Java基础】——Java排序(上)

待排序的元素需要实现 Java 的 Comparable 接口,该接口有 compareTo() 方法,可以用它来判断两个元素的大小关系。

研究排序算法的成本模型时,计算的是 比较和交换的次数。

使用辅助函数 less() 和 swap() 来进行比较和交换的操作,使得代码的可读性和可移植性更好。


public abstract class Sort<T extends Comparable<T>> {
    public abstract void sort(T[] nums);
    protected boolean less(T v, T w) {
        return v.compareTo(w) < 0;
    } 
    protected void swap(T[] a, int i, int j) {
        T t = a[i];
        a[i] = a[j];
        a[j] = t;
    }
}


1、选择排序(Select Sort)


20210506221249385.gif


常规的方法:


import java.util.Arrays;
//选择排序:先定义一个记录最小元素的下标,然后循环一次后面的,找到最小的元素,最后将他放到前面排序好的序列。
public class SelectSort_02 {
  public static void main(String[] args) {
    int a[]={3,44,38,5,47,15,36,26,27,2,46,4,19,50,48};
    for (int i = 0; i < a.length-1; i++) {
      int index=i;//标记第一个为待比较的数
      for (int j = i+1; j < a.length; j++) { //然后从后面遍历与第一个数比较
        if (a[j]<a[index]) {  //如果小,就交换最小值
          index=j;//保存最小元素的下标
        }
      }
      //找到最小值后,将最小的值放到第一的位置,进行下一遍循环
      int temp=a[index];
      a[index]=a[i];
      a[i]=temp;
    }
    System.out.println(Arrays.toString(a));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
  }
}

使用辅助函数:


public class Selection<T extends Comparable<T>> extends Sort<T> {
    @Override
    public void sort(T[] nums) {
        int N = nums.length;
        for (int i = 0; i < N; i++) {
            int min = i;
            for (int j = i + 1; j < N; j++)
                if (less(nums[j], nums[min]))
                    min = j;
            swap(nums, i, min);
        }
    }
}


选择排序需要 ~N2/2 次比较和 ~N 次交换,它的运行时间与输入无关,这个特点使得它对一个已经排序的数组也需要这么多的比较和交换操作。


2、冒泡排序(Bubble Sort)


通过从左到右不断交换相邻逆序的相邻元素,在一轮的交换之后,可以让未排序的元素上浮到右侧。

在一轮循环中,如果没有发生交换,就说明数组已经是有序的,此时可以直接退出。

20210506221229133.gif


import java.util.Arrays;
//冒泡排序
public class BubbleSort_01 {
  public static void main(String[] args) {
    int a[]={3,44,38,5,47,15,36,26,27,2,46,4,19,50,48};
    //记录比较次数
    int count=0;
    //i=0,第一轮比较
    for (int i = 0; i < a.length-1; i++) {
      //第一轮,两两比较
      for (int j = 0; j < a.length-1-i; j++) {
        if (a[j]>a[j+1]) {
          int temp=a[j];
          a[j]=a[j+1];
          a[j+1]=temp;
        }
        count++;
      }
    }
    System.out.println(Arrays.toString(a));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
    System.out.println("一共比较了:"+count+"次");//一共比较了:105次
  }
}


冒泡排序的优化:


import java.util.Arrays;
public class BubbleSort1_01 {
  public static void main(String[] args) {
    int a[]={3,44,38,5,47,15,36,26,27,2,46,4,19,50,48};
    int count=0;
    for (int i = 0; i < a.length-1; i++) {
      boolean flag=true;
      for (int j = 0; j < a.length-1-i; j++) {
        if (a[j]>a[j+1]) {
          int temp=a[j];
          a[j]=a[j+1];
          a[j+1]=temp;
          flag=false;
        }
        count++;
      }
      if (flag) {
        break;
      }
    }
    System.out.println(Arrays.toString(a));// [2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
    System.out.println("一共比较了:"+count+"次");//一共比较了:95次
  }
}


辅助函数方法:


public class Bubble<T extends Comparable<T>> extends Sort<T> {
    @Override
    public void sort(T[] nums) {
        int N = nums.length;
        boolean hasSorted = false;
        for (int i = 0; i < N && !hasSorted; i++) {
            hasSorted = true;
            for (int j = 0; j < N - i - 1; j++) {
                if (less(nums[j + 1], nums[j])) {
                    hasSorted = false;
                    swap(nums, j, j + 1);
                }
            }
        }
    }
}


3、插入排序(Insert Sort)


插入排序从左到右进行,每次都将当前元素插入到左侧已经排序的数组中,使得插入之后左部数组依然有序。

第 j 元素是通过不断向左比较并交换来实现插入过程:当第 j 元素小于第 j - 1 元素,就将它们的位置交换,然后令 j指针向左移动一个位置,不断进行以上操作。


2021050622130485.gif


import java.util.Arrays;
//插入排序:定义一个待插入的数,再定义一个待插入数的前一个数的下标,然后拿待插入数与前面的数组一一比较,最后交换。
public class InsertSort_03 {
  public static void main(String[] args) {
    int a[]={3,44,38,5,47,15,36,26,27,2,46,4,19,50,48};
    for (int i = 0; i < a.length; i++) {  //长度不减1,是因为要留多一个位置方便插入数
      //定义待插入的数
      int insertValue=a[i];
      //找到待插入数的前一个数的下标
      int insertIndex=i-1;
      while (insertIndex>=0 && insertValue <a[insertIndex]) {//拿a[i]与a[i-1]的前面数组比较
        a[insertIndex+1]=a[insertIndex];
        insertIndex--;
      }
      a[insertIndex+1]=insertValue;
    }
    System.out.println(Arrays.toString(a));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
  }
}


辅助函数方法:


public class Insertion<T extends Comparable<T>> extends Sort<T> {
    @Override
    public void sort(T[] nums) {
        int N = nums.length;
        for (int i = 1; i < N; i++)
            for (int j = i; j > 0 && less(nums[j], nums[j - 1]); j--)
                swap(nums, j, j - 1);
    }
}


对于数组 {3, 5, 2, 4, 1},它具有以下逆序:(3, 2), (3, 1), (5, 2), (5, 4), (5, 1), (2, 1), (4, 1),插入排序每次只能交换相

邻元素,令逆序数量减少 1,因此插入排序需要交换的次数为逆序数量。

插入排序的复杂度取决于数组的初始顺序,如果数组已经部分有序了,逆序较少,那么插入排序会很快。


平均情况下插入排序需要 ~N2/4 比较以及 ~N2/4 次交换;

最坏的情况下需要 ~N2/2 比较以及 ~N2/2次交换,最坏的情况是数组是倒序的;

最好的情况下需要 N-1 次比较和 0 次交换,最好的情况就是数组已经有序了。


4、希尔排序(Shell Sort)


对于大规模的数组,插入排序很慢,因为它只能交换相邻的元素,每次只能将逆序数量减少 1。

希尔排序的出现就是为了改进插入排序的这种局限性,它通过交换不相邻的元素,每次可以将逆序数量减少大于1。

希尔排序使用插入排序对间隔 h 的序列进行排序。通过不断减小 h,最后令 h=1,就可以使得整个数组是有序的。


106a75bb50e842f180504903184257a5.png

public class Shell<T extends Comparable<T>> extends Sort<T> {
    @Override
    public void sort(T[] nums) {
        int N = nums.length;
        int h = 1;
        while (h < N / 3)
            h = 3 * h + 1; // 1, 4, 13, 40, ...
        while (h >= 1) {
            for (int i = h; i < N; i++)
                for (int j = i; j >= h && less(nums[j], nums[j - h]); j -= h)
                    swap(nums, j, j - h);
            h = h / 3;
        }
    }
}


希尔排序的运行时间达不到平方级别,使用递增序列 1, 4, 13, 40, … 的希尔排序所需要的比较次数不会超过 N 的若干倍乘于递增序列的长度。后面介绍的高级排序算法只会比希尔排序快两倍左右。

常规代码:


import java.util.Arrays;
//希尔排序:插入排序的升级
public class ShellSort_04 {
  public static void main(String[] args) {
    int a[]={3,44,38,5,47,15,36,26,27,2,46,4,19,50,48};
    int count=0;//比较次数
    for (int gap=a.length / 2; gap > 0; gap = gap / 2) {
      //将整个数组分为若干个子数组
      for (int i = gap; i < a.length; i++) {
        //遍历各组的元素
        for (int j = i - gap; j>=0; j=j-gap) {
          //交换元素
          if (a[j]>a[j+gap]) {
            int temp=a[j];
            a[j]=a[j+gap];
            a[j+gap]=temp;
            count++;
          }
        }
      }
    }
    System.out.println(count);//16
    System.out.println(Arrays.toString(a));//[2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
  }
}


5、归并排序(Merge Sort)



归并排序的思想是将数组分成两部分,分别进行排序,然后归并起来。


3bc116118c6a42ba9832bd49beada198.png

cdc20c35c15842e5b62275b8daa23b0b.png


202105062211506.gif


常规代码:


import java.util.Arrays;
//归并排序
public class MergeSort_06 {
  public static void main(String[] args) {
    int a[]={3,44,38,5,47,15,36,26,27,2,46,4,19,50,48};
    //int a[]={5,2,4,7,1,3,2,2};
    int temp[]=new int[a.length];
    mergesort(a,0,a.length-1,temp);
    System.out.println(Arrays.toString(a));
  }
  private static void mergesort(int[] a, int left, int right, int[] temp) {
    //分解
    if (left<right) {
      int mid=(left+right)/2;
      //向左递归进行分解
      mergesort(a, left, mid, temp);
      //向右递归进行分解
      mergesort(a, mid+1, right, temp);
      //每分解一次便合并一次
      merge(a,left,right,mid,temp);
    }
  }
  /**
   *
   * @param a  待排序的数组
   * @param left  左边有序序列的初始索引
   * @param right 右边有序序列的初始索引
   * @param mid 中间索引
   * @param temp  做中转的数组
   */
  private static void merge(int[] a, int left, int right, int mid, int[] temp) {
    int i=left; //初始i,左边有序序列的初始索引
    int j=mid+1;//初始化j,右边有序序列的初始索引(右边有序序列的初始位置即中间位置的后一位置)
    int t=0;//指向temp数组的当前索引,初始为0
    //先把左右两边的数据(已经有序)按规则填充到temp数组
    //直到左右两边的有序序列,有一边处理完成为止
    while (i<=mid && j<=right) {
      //如果左边有序序列的当前元素小于或等于右边的有序序列的当前元素,就将左边的元素填充到temp数组中
      if (a[i]<=a[j]) {
        temp[t]=a[i];
        t++;//索引向后移
        i++;//i后移
      }else {
        //反之,将右边有序序列的当前元素填充到temp数组中
        temp[t]=a[j];
        t++;//索引向后移
        j++;//j后移
      }
    }
    //把剩余数据的一边的元素填充到temp中
    while (i<=mid) {
      //此时说明左边序列还有剩余元素
      //全部填充到temp数组
      temp[t]=a[i];
      t++;
      i++;
    }
    while (j<=right) {
      //此时说明左边序列还有剩余元素
      //全部填充到temp数组
      temp[t]=a[j];
      t++;
      j++;
    }
    //将temp数组的元素复制到原数组
    t=0;
    int tempLeft=left;
    while (tempLeft<=right) {
      a[tempLeft]=temp[t];
      t++;
      tempLeft++;
    }
  }
}


1. 归并方法


归并方法将数组中两个已经排序的部分归并成一个。


public abstract class MergeSort<T extends Comparable<T>> extends Sort<T> {
    protected T[] aux;
    protected void merge(T[] nums, int l, int m, int h) {
        int i = l, j = m + 1;
        for (int k = l; k <= h; k++)
            aux[k] = nums[k]; // 将数据复制到辅助数组
        for (int k = l; k <= h; k++) {
            if (i > m)
                nums[k] = aux[j++];
            else if (j > h)
                nums[k] = aux[i++];
            else if (aux[i].compareTo(nums[j]) <= 0)
                nums[k] = aux[i++]; // 先进行这一步,保证稳定性
            else
                nums[k] = aux[j++];
        }
    }
}


2. 自顶向下归并排序


9377e31ff0af426ab70462ec449a7581.png

public class Up2DownMergeSort<T extends Comparable<T>> extends MergeSort<T> {
    @Override
    public void sort(T[] nums) {
        aux = (T[]) new Comparable[nums.length];
        sort(nums, 0, nums.length - 1);
    } p
    rivate void sort(T[] nums, int l, int h) {
        if (h <= l)
            return;
        int mid = l + (h - l) / 2;
        sort(nums, l, mid);
        sort(nums, mid + 1, h);
        merge(nums, l, mid, h);
    }
}


因为每次都将问题对半分成两个子问题,而这种对半分的算法复杂度一般为 O(NlogN),因此该归并排序方法的时间复杂度也为 O(NlogN)


3. 自底向上归并排序


先归并那些微型数组,然后成对归并得到的微型数组。


public class Down2UpMergeSort<T extends Comparable<T>> extends MergeSort<T> {
    @Override
    public void sort(T[] nums) {
        int N = nums.length;
        aux = (T[]) new Comparable[N];
        for (int sz = 1; sz < N; sz += sz)
            for (int lo = 0; lo < N - sz; lo += sz + sz)
                merge(nums, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, N - 1));
    }
}


目录
打赏
0
0
0
0
272
分享
相关文章
重学Java基础篇—Java类加载顺序深度解析
本文全面解析Java类的生命周期与加载顺序,涵盖从加载到卸载的七个阶段,并深入探讨初始化阶段的执行规则。通过单类、继承体系的实例分析,明确静态与实例初始化的顺序。同时,列举六种触发初始化的场景及特殊场景处理(如接口初始化)。提供类加载完整流程图与记忆口诀,助于理解复杂初始化逻辑。此外,针对空指针异常等问题提出排查方案,并给出最佳实践建议,帮助开发者优化程序设计、定位BUG及理解框架机制。最后扩展讲解类加载器层次与双亲委派机制,为深入研究奠定基础。
149 0
|
12天前
|
Java语言按文件创建日期排序及获取最新文件的技术
这段代码实现了文件创建时间的读取、文件列表的获取与排序以及获取最新文件的需求。它具备良好的效率和可读性,对于绝大多数处理文件属性相关的需求来说足够健壮。在实际应用中,根据具体情况,可能还需要进一步处理如访问权限不足、文件系统不支持某些属性等边界情况。
57 14
2025 年最新 40 个 Java 基础核心知识点全面梳理一文掌握 Java 基础关键概念
本文系统梳理了Java编程的40个核心知识点,涵盖基础语法、面向对象、集合框架、异常处理、多线程、IO流、反射机制等关键领域。重点包括:JVM运行原理、基本数据类型、封装/继承/多态三大特性、集合类对比(ArrayList vs LinkedList、HashMap vs TreeMap)、异常分类及处理方式、线程创建与同步机制、IO流体系结构以及反射的应用场景。这些基础知识是Java开发的根基,掌握后能为后续框架学习和项目开发奠定坚实基础。文中还提供了代码资源获取方式,方便读者进一步实践学习。
112 2
【Java基础-环境搭建-创建项目】IntelliJ IDEA创建Java项目的详细步骤
IntelliJ IDEA创建Java项目的图文详细步骤,手把手带你创建Java项目
258 10
【Java基础-环境搭建-创建项目】IntelliJ IDEA创建Java项目的详细步骤
Java 基础知识点全面梳理包含核心要点及难点解析 Java 基础知识点
本文档系统梳理了Java基础知识点,涵盖核心特性、语法基础、面向对象编程、数组字符串、集合框架、异常处理及应用实例,帮助初学者全面掌握Java入门知识,提升编程实践能力。附示例代码下载链接。
11 0
Java 基础知识面试题汇总 最全面的 Java 基础面试题整理
本文全面解析Java基础知识面试题,涵盖Java基础概念、面向对象编程、异常处理、集合框架等核心内容。通过实际应用场景,提供技术方案与应用实例,如JDK与JRE区别、==与equals()差异、String类特性、final与static关键字用法、多继承替代方案及接口与抽象类对比。帮助开发者夯实基础,高效备考,提升实战能力。附带完整代码示例,可供下载学习。
131 3
重学Java基础篇—Java对象创建的7种核心方式详解
本文全面解析了Java中对象的创建方式,涵盖基础到高级技术。包括`new关键字`直接实例化、反射机制动态创建、克隆与反序列化复用对象,以及工厂方法和建造者模式等设计模式的应用。同时探讨了Spring IOC容器等框架级创建方式,并对比各类方法的适用场景与优缺点。此外,还深入分析了动态代理、Unsafe类等扩展知识及注意事项。最后总结最佳实践,建议根据业务需求选择合适方式,在灵活性与性能间取得平衡。
210 3
|
4月前
|
重学Java基础篇—Java泛型深度使用指南
本内容系统介绍了Java泛型的核心价值、用法及高级技巧。首先阐述了泛型在**类型安全**与**代码复用**中的平衡作用,解决强制类型转换错误等问题。接着详细讲解了泛型类定义、方法实现、类型参数约束(如边界限定和多重边界)、通配符应用(PECS原则)以及类型擦除的应对策略。此外,还展示了泛型在通用DAO接口、事件总线等实际场景的应用,并总结了命名规范、边界控制等最佳实践。最后探讨了扩展知识,如通过反射获取泛型参数类型。合理运用泛型可大幅提升代码健壮性和可维护性,建议结合IDE工具和单元测试优化使用。
88 1
|
4月前
|
重学Java基础篇—Java Object类常用方法深度解析
Java中,Object类作为所有类的超类,提供了多个核心方法以支持对象的基本行为。其中,`toString()`用于对象的字符串表示,重写时应包含关键信息;`equals()`与`hashCode()`需成对重写,确保对象等价判断的一致性;`getClass()`用于运行时类型识别;`clone()`实现对象复制,需区分浅拷贝与深拷贝;`wait()/notify()`支持线程协作。此外,`finalize()`已过时,建议使用更安全的资源管理方式。合理运用这些方法,并遵循最佳实践,可提升代码质量与健壮性。
127 1
|
10月前
|
java基础(3)安装好JDK后使用javac.exe编译java文件、java.exe运行编译好的类
本文介绍了如何在安装JDK后使用`javac.exe`编译Java文件,以及使用`java.exe`运行编译好的类文件。涵盖了JDK的安装、环境变量配置、编写Java程序、使用命令行编译和运行程序的步骤,并提供了解决中文乱码的方法。
325 2
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问