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

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

我的《恋上数据结构》源码(第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语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
63 1
|
2天前
|
存储 算法 测试技术
【C++数据结构——树】二叉树的遍历算法(头歌教学实验平台习题) 【合集】
本任务旨在实现二叉树的遍历,包括先序、中序、后序和层次遍历。首先介绍了二叉树的基本概念与结构定义,并通过C++代码示例展示了如何定义二叉树节点及构建二叉树。接着详细讲解了四种遍历方法的递归实现逻辑,以及层次遍历中队列的应用。最后提供了测试用例和预期输出,确保代码正确性。通过这些内容,帮助读者理解并掌握二叉树遍历的核心思想与实现技巧。
16 2
|
2月前
|
存储 算法 搜索推荐
Python 中数据结构和算法的关系
数据结构是算法的载体,算法是对数据结构的操作和运用。它们共同构成了计算机程序的核心,对于提高程序的质量和性能具有至关重要的作用
|
2月前
|
数据采集 存储 算法
Python 中的数据结构和算法优化策略
Python中的数据结构和算法如何进行优化?
|
2月前
|
算法
数据结构之路由表查找算法(深度优先搜索和宽度优先搜索)
在网络通信中,路由表用于指导数据包的传输路径。本文介绍了两种常用的路由表查找算法——深度优先算法(DFS)和宽度优先算法(BFS)。DFS使用栈实现,适合路径问题;BFS使用队列,保证找到最短路径。两者均能有效查找路由信息,但适用场景不同,需根据具体需求选择。文中还提供了这两种算法的核心代码及测试结果,验证了算法的有效性。
123 23
|
2月前
|
算法
数据结构之蜜蜂算法
蜜蜂算法是一种受蜜蜂觅食行为启发的优化算法,通过模拟蜜蜂的群体智能来解决优化问题。本文介绍了蜜蜂算法的基本原理、数据结构设计、核心代码实现及算法优缺点。算法通过迭代更新蜜蜂位置,逐步优化适应度,最终找到问题的最优解。代码实现了单链表结构,用于管理蜜蜂节点,并通过适应度计算、节点移动等操作实现算法的核心功能。蜜蜂算法具有全局寻优能力强、参数设置简单等优点,但也存在对初始化参数敏感、计算复杂度高等缺点。
66 20
|
1月前
|
存储 算法 程序员
C 语言递归算法:以简洁代码驾驭复杂逻辑
C语言递归算法简介:通过简洁的代码实现复杂的逻辑处理,递归函数自我调用解决分层问题,高效而优雅。适用于树形结构遍历、数学计算等领域。
|
2月前
|
传感器 算法
数据结构之环境监测系统(深度优先搜索)
环境监测系统采用深度优先搜索(DFS)算法,实现实时监测和分析环境参数,如温度、湿度等。系统通过构建传感器网络图结构,利用DFS遍历网络,检测异常数据。当温度超过预设阈值时,系统将发出警告。此系统适用于工业生产、室内空调控制、农业温室管理等多种场景,提供高效的环境监测解决方案。
54 12
|
2月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
72 1
|
2月前
|
存储 缓存 算法
通过优化算法和代码结构来提升易语言程序的执行效率
通过优化算法和代码结构来提升易语言程序的执行效率