【恋上数据结构】排序算法前置知识及代码环境准备

简介: 【恋上数据结构】排序算法前置知识及代码环境准备
感谢小码哥的 恋上数据结构,记录课程笔记。

我的《恋上数据结构》源码(第1季 + 第2季):https://github.com/szluyu99/Data_Structure_Note

建议先看一下这篇博客: 复杂度知识以及LeetCode刷题指南

在这里插入图片描述

何为排序?

  • 排序前:$3, 1, 6, 9, 2, 5, 8, 4, 7$
  • 升序排序:$1, 2, 3, 4, 5, 6, 7, 8, 9$
  • 降序排序: $9, 8, 7, 6, 5, 4, 3, 2, 1$

何为稳定性?

如果相等的 2 个元素,在排序前后的相对位置保持不变那么这是稳定的排序算法。

  • 排序前:$5, 1, 3_a, 7, 3_b$
  • 稳定的排序: $1, 3_a, 3_b, 4, 5, 7$
  • 不稳定的排序: $1, 3_b, 3_a, 4, 5, 7$

自定义对象进行排序时,稳定性会影响最终的排序效果。

何为原地算法?

  • 不依赖额外的资源或者依赖少数的额外资源,仅依靠输出来覆盖输入
  • 空间复杂度为$O(1)$的都可以认为是原地算法
  • 非原地算法,称为 Not-in-place 或者 Out-of-place

时间复杂度的知识

请看这个:复杂度知识以及LeetCode刷题指南

写排序算法前的准备

项目结构

创建一个项目,大体结构是这样的:
在这里插入图片描述
以下包名省略 com.mj

  • sort:各种排序类

    • cmp:比较类排序
    • sort.java 排序抽象类
  • tools:排序中用到的工具

    • Asserts.java 单元测试
    • Inegers.java 生成测试数据
    • Time.java 计算时间
  • Main 主函数,写排序测试
  • Student.java 利用自定义类测试稳定性(sort.java中使用)

Sort.java

写一个抽象类 Sort.java

  • 实现自定义对象的比较(cmp),交换(swap)方法
  • 同时可以用来计算比较次数(cmpCount),交换次数(swapCount),花费时间(time)
  • 排序是否稳定()
@SuppressWarnings("unchecked")
public abstract class Sort<T extends Comparable<T>> implements Comparable<Sort<T>> {
    protected T[] array;
    private int cmpCount;
    private int swapCount;
    private long time;
    private DecimalFormat fmt = new DecimalFormat("#.00");
    
    public void sort(T[] array) {
        if (array == null || array.length < 2) return;
        
        this.array = array;
        
        long begin = System.currentTimeMillis();
        sort();
        time = System.currentTimeMillis() - begin;
    }
    
    @Override
    public int compareTo(Sort<T> o) {
        int result = (int)(time - o.time);
        if (result != 0) return result;
        
        result = cmpCount - o.cmpCount;
        if (result != 0) return result;
        
        return swapCount - o.swapCount;
    }
    
    protected abstract void sort();
    
    /*
     * 返回值等于0,代表 array[i1] == array[i2]
     * 返回值小于0,代表 array[i1] < array[i2]
     * 返回值大于0,代表 array[i1] > array[i2]
     */
    protected int cmp(int i1, int i2) {
        cmpCount++;
        return array[i1].compareTo(array[i2]);
    }
    
    protected int cmp(T v1, T v2) {
        cmpCount++;
        return v1.compareTo(v2);
    }
    
    protected void swap(int i1, int i2) {
        swapCount++;
        T tmp = array[i1];
        array[i1] = array[i2];
        array[i2] = tmp;
    }
    
    @Override
    public String toString() { 
        String timeStr = "耗时:" + (time / 1000.0) + "s(" + time + "ms)";
        String compareCountStr = "比较:" + numberString(cmpCount);
        String swapCountStr = "交换:" + numberString(swapCount);
        String stableStr = "稳定性:" + isStable();
        return "【" + getClass().getSimpleName() + "】\n" 
                + stableStr + " \t"
                + timeStr + " \t"
                + compareCountStr + "\t "
                + swapCountStr + "\n"
                + "------------------------------------------------------------------";
    }
    
    private String numberString(int number) {
        if (number < 10000) return "" + number;
        
        if (number < 100000000) return fmt.format(number / 10000.0) + "万";
        return fmt.format(number / 100000000.0) + "亿";
    }
    
    private boolean isStable() {
        if (this instanceof RadixSort) return true;
        if (this instanceof CountingSort) return true;
        if (this instanceof ShellSort) return false;
        if (this instanceof SelectionSort) return false;
        
        Student[] students = new Student[20];
        for (int i = 0; i < students.length; i++) {
            students[i] = new Student(i * 10, 10);
        }
        sort((T[]) students);
        for (int i = 1; i < students.length; i++) {
            int score = students[i].score;
            int prevScore = students[i - 1].score;
            if (score != prevScore + 10) return false;
        }
        return true;
    }
}

Asserts.java

public class Asserts {
    public static void test(boolean value) {
        try {
            if (!value) throw new Exception("测试未通过");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Integers.java

public class Integers {
    public static Integer[] random(int count, int min, int max) {
        if (count <= 0 || min > max) return null;
        Integer[] array = new Integer[count];
        int delta = max - min + 1;
        for (int i = 0; i < count; i++) {
            array[i] = min + (int)(Math.random() * delta);
        }
        return array;
    }
    
    public static Integer[] combine(Integer[] array1, Integer[] array2) {
        if (array1 == null || array2 == null) return null;
        Integer[] array = new Integer[array1.length + array2.length];
        for (int i = 0; i < array1.length; i++) {
            array[i] = array1[i];
        }
        for (int i = 0; i < array2.length; i++) {
            array[i + array1.length] = array2[i];
        }
        return array;
        
    }
    
    public static Integer[] same(int count, int unsameCount) {
        if (count <= 0 || unsameCount > count) return null;
        Integer[] array = new Integer[count];
        for (int i = 0; i < unsameCount; i++) {
            array[i] = unsameCount - i;
        }
        for (int i = unsameCount; i < count; i++) {
            array[i] = unsameCount + 1;
        }
        return array;
    }
    
    public static Integer[] headTailAscOrder(int min, int max, int disorderCount) {
        Integer[] array = ascOrder(min, max);
        if (disorderCount > array.length) return array;
        
        int begin = (array.length - disorderCount) >> 1;
        reverse(array, begin, begin + disorderCount);
        return array;
    }
    
    public static Integer[] centerAscOrder(int min, int max, int disorderCount) {
        Integer[] array = ascOrder(min, max);
        if (disorderCount > array.length) return array;
        int left = disorderCount >> 1;
        reverse(array, 0, left);
        
        int right = disorderCount - left;
        reverse(array, array.length - right, array.length);
        return array;
    }
    
    public static Integer[] headAscOrder(int min, int max, int disorderCount) {
        Integer[] array = ascOrder(min, max);
        if (disorderCount > array.length) return array;
        reverse(array, array.length - disorderCount, array.length);
        return array;
    }
    
    public static Integer[] tailAscOrder(int min, int max, int disorderCount) {
        Integer[] array = ascOrder(min, max);
        if (disorderCount > array.length) return array;
        reverse(array, 0, disorderCount);
        return array;
    }
    
    public static Integer[] ascOrder(int min, int max) {
        if (min > max) return null;
        Integer[] array = new Integer[max - min + 1];
        for (int i = 0; i < array.length; i++) {
            array[i] = min++;
        }
        return array;
    }
    
    public static Integer[] descOrder(int min, int max) {
        if (min > max) return null;
        Integer[] array = new Integer[max - min + 1];
        for (int i = 0; i < array.length; i++) {
            array[i] = max--;
        }
        return array;
    }
    
    /**
     * 反转一个数组,索引范围是[begin, end)
     */
    private static void reverse(Integer[] array, int begin, int end) {
        int count = (end - begin) >> 1;
        int sum = begin + end - 1;
        for (int i = begin; i < begin + count; i++) {
            int j = sum - i;
            int tmp = array[i];
            array[i] = array[j];
            array[j] = tmp;
        }
    }
    
    public static Integer[] copy(Integer[] array) {
        return Arrays.copyOf(array, array.length);
    }
    
    public static boolean isAscOrder(Integer[] array) {
        if (array == null || array.length == 0) return false;
        for (int i = 1; i < array.length; i++) {
            if (array[i - 1] > array[i]) return false;
        }
        return true;
    }
    
    public static void println(Integer[] array) {
        if (array == null) return;
        StringBuilder string = new StringBuilder();
        for (int i = 0; i < array.length; i++) {
            if (i != 0) string.append("_");
            string.append(array[i]);
        }
        System.out.println(string);
    }
}

Times.java

public class Times {
    private static final SimpleDateFormat fmt = new SimpleDateFormat("HH:mm:ss.SSS");
    
    public interface Task {
        void execute();
    }
    
    public static void test(String title, Task task) {
        if (task == null) return;
        title = (title == null) ? "" : ("【" + title + "】");
        System.out.println(title);
        System.out.println("开始:" + fmt.format(new Date()));
        long begin = System.currentTimeMillis();
        task.execute();
        long end = System.currentTimeMillis();
        System.out.println("结束:" + fmt.format(new Date()));
        double delta = (end - begin) / 1000.0;
        System.out.println("耗时:" + delta + "秒");
        System.out.println("-------------------------------------");
    }
}

Student.java

public class Student implements Comparable<Student> {
    public int score;
    public int age;
    
    public Student(int score, int age) {
        this.score = score;
        this.age = age;
    }
    
    @Override
    public int compareTo(Student o) {
        return age - o.age;
    }
}

排序算法测试

把上面的工具代码配置好,我们就可以来写排序算法并且测试啦。

首先写一个简单的冒泡排序

由于冒泡排序属于比较排序,我们将它放到cmp包下。
在这里插入图片描述

BubbleSort1.java

/**
 * 冒泡排序-无优化
 */
public class BubbleSort1<T extends Comparable<T>> extends Sort<T>{
    @Override
    protected void sort() {
        for (int end = array.length - 1; end > 0; end--) {
            for (int begin = 1; begin <= end; begin++) {
                if (cmp(begin, begin - 1) < 0) {
                    swap(begin, begin - 1);
                }
            }
        }
    }
}

此时目录结构应该与我一开始发的完全一样了,可以来测试排序代码了。

Main.java

@SuppressWarnings({ "rawtypes", "unchecked" })
public class Main {
    // 在 main 函数里写测试代码
    public static void main(String[] args) {
        // 产生 20000 个数据,每个数据的范围是 1~10000
        Integer[] array = Integers.random(20000, 1, 10000);    
        // 在这里面写要测试的代码
        testSorts(array, 
                new BubbleSort1()        // 冒泡排序
                );
    }
    
    // 下面这个复制就可以
    static void testSorts(Integer[] array, Sort... sorts) {
        for (Sort sort : sorts) {
            Integer[] newArray = Integers.copy(array);
            sort.sort(newArray);
            Asserts.test(Integers.isAscOrder(newArray));
        }
        Arrays.sort(sorts);
        for (Sort sort : sorts) {
            System.out.println(sort);
        }
    }

}

目前只有一个冒泡排序,运行结果如下:
在这里插入图片描述

以后我们写了多个排序算法做测试,效果是这样的:

@SuppressWarnings({ "rawtypes", "unchecked" })
public class Main {
    public static void main(String[] args) {
        // 产生 20000 个数据,每个数据的范围是 1~10000
        Integer[] array = Integers.random(20000, 1, 10000);    

        testSorts(array, 
                new BubbleSort1(),        // 冒泡排序
//                new BubbleSort2(),        // 冒泡排序-优化1
//                new BubbleSort3(),        // 冒泡排序-优化2
                new SelectionSort(),     // 选择排序
                new HeapSort()          // 堆排序
//                new InsertionSort1(),   // 插入排序
//                new InsertionSort2(),     // 插入排序-挪动优化
                new InsertionSort3(),    // 插入排序-二分查找优化
                new MergeSort(),        // 归并排序
                new QuickSort(),        // 快速排序
                new ShellSort(),        // 希尔排序
                new CountingSort(),        // 计数排序
                new RadixSort()            // 基数排序
                );
    }
    
    static void testSorts(Integer[] array, Sort... sorts) {
        for (Sort sort : sorts) {
            Integer[] newArray = Integers.copy(array);
            sort.sort(newArray);
            Asserts.test(Integers.isAscOrder(newArray));
        }
        Arrays.sort(sorts);
        for (Sort sort : sorts) {
            System.out.println(sort);
        }
    }

}

在这里插入图片描述

相关文章
|
2天前
|
存储 算法 C语言
数据结构基础详解(C语言):单链表_定义_初始化_插入_删除_查找_建立操作_纯c语言代码注释讲解
本文详细介绍了单链表的理论知识,涵盖单链表的定义、优点与缺点,并通过示例代码讲解了单链表的初始化、插入、删除、查找等核心操作。文中还具体分析了按位序插入、指定节点前后插入、按位序删除及按值查找等算法实现,并提供了尾插法和头插法建立单链表的方法,帮助读者深入理解单链表的基本原理与应用技巧。
|
2天前
|
存储 C语言 C++
数据结构基础详解(C语言) 顺序表:顺序表静态分配和动态分配增删改查基本操作的基本介绍及c语言代码实现
本文介绍了顺序表的定义及其在C/C++中的实现方法。顺序表通过连续存储空间实现线性表,使逻辑上相邻的元素在物理位置上也相邻。文章详细描述了静态分配与动态分配两种方式下的顺序表定义、初始化、插入、删除、查找等基本操作,并提供了具体代码示例。静态分配方式下顺序表的长度固定,而动态分配则可根据需求调整大小。此外,还总结了顺序表的优点,如随机访问效率高、存储密度大,以及缺点,如扩展不便和插入删除操作成本高等特点。
|
2天前
|
存储 C语言
数据结构基础详解(C语言): 栈与队列的详解附完整代码
栈是一种仅允许在一端进行插入和删除操作的线性表,常用于解决括号匹配、函数调用等问题。栈分为顺序栈和链栈,顺序栈使用数组存储,链栈基于单链表实现。栈的主要操作包括初始化、销毁、入栈、出栈等。栈的应用广泛,如表达式求值、递归等场景。栈的顺序存储结构由数组和栈顶指针构成,链栈则基于单链表的头插法实现。
|
3天前
|
机器学习/深度学习 存储 算法
经典算法代码
这段代码展示了多个经典算法,包括:穷举法解决“百钱买百鸡”问题;递推法计算“猴子吃桃”问题;迭代法求解斐波那契数列及折纸高度超越珠峰的问题。同时,还提供了希尔排序算法实现及披萨票务订购系统和汉诺塔问题的链表存储解决方案。每部分通过具体案例解释了算法的应用场景与实现方法。
15 3
|
2天前
|
存储 算法 C语言
C语言手撕数据结构代码_顺序表_静态存储_动态存储
本文介绍了基于静态和动态存储的顺序表操作实现,涵盖创建、删除、插入、合并、求交集与差集、逆置及循环移动等常见操作。通过详细的C语言代码示例,展示了如何高效地处理顺序表数据结构的各种问题。
|
14天前
|
人工智能 算法 数据可视化
DBSCAN密度聚类算法(理论+图解+python代码)
DBSCAN密度聚类算法(理论+图解+python代码)
|
22天前
|
数据采集 搜索推荐 算法
【高手进阶】Java排序算法:从零到精通——揭秘冒泡、快速、归并排序的原理与实战应用,让你的代码效率飙升!
【8月更文挑战第21天】Java排序算法是编程基础的重要部分,在算法设计与分析及实际开发中不可或缺。本文介绍内部排序算法,包括简单的冒泡排序及其逐步优化至高效的快速排序和稳定的归并排序,并提供了每种算法的Java实现示例。此外,还探讨了排序算法在电子商务、搜索引擎和数据分析等领域的广泛应用,帮助读者更好地理解和应用这些算法。
14 0
|
23天前
|
算法
【初阶数据结构篇】二叉树算法题
二叉树是否对称,即左右子树是否对称.
|
23天前
|
算法 索引
【初阶数据结构篇】单链表算法题进阶
深拷贝应该正好由 n 个全新节点组成,其中每个新节点的值都设为其对应的原节点的值。
|
6天前
|
算法 BI Serverless
基于鱼群算法的散热片形状优化matlab仿真
本研究利用浴盆曲线模拟空隙外形,并通过鱼群算法(FSA)优化浴盆曲线参数,以获得最佳孔隙度值及对应的R值。FSA通过模拟鱼群的聚群、避障和觅食行为,实现高效全局搜索。具体步骤包括初始化鱼群、计算适应度值、更新位置及判断终止条件。最终确定散热片的最佳形状参数。仿真结果显示该方法能显著提高优化效率。相关代码使用MATLAB 2022a实现。