当前位置: 首页 > article >正文

Java中的排序算法:探索与比较

在Java编程中,排序算法是数据处理和分析的基本工具之一。无论是处理简单的整数数组,还是复杂的对象集合,排序算法都能帮助我们高效地组织数据。本文将深入探讨Java中的几种常见排序算法,包括它们的原理、实现方式以及性能特点,并对它们进行比较。

一、冒泡排序(Bubble Sort)

冒泡排序是一种简单直观的排序算法。它重复地遍历要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复进行的,直到没有再需要交换的元素为止,这意味着数列已经排序完成。

实现原理

  • 比较相邻的元素,如果第一个比第二个大,就交换它们两个。
  • 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。
  • 针对所有的元素重复以上的步骤,除了最后一个。
  • 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

性能特点

  • 时间复杂度:O(n^2),其中n是待排序元素的数量。
  • 空间复杂度:O(1),因为冒泡排序是原地排序算法。
  • 稳定性:冒泡排序是稳定的排序算法。
public class BubbleSort {
    public static void bubbleSort(int[] arr) {
        int n = arr.length;
        for (int i = 0; i < n - 1; i++) {
            for (int j = 0; j < n - i - 1; j++) {
                if (arr[j] > arr[j + 1]) {
                    // 交换元素
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
    }

    public static void main(String[] args) {
        int[] arr = {64, 34, 25, 12, 22, 11, 90};
        bubbleSort(arr);
        for (int num : arr) {
            System.out.print(num + " ");
        }
    }
}
二、选择排序(Selection Sort)

选择排序是一种简单直观的排序算法。它的工作原理是:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。

实现原理

  • 在未排序序列中找到最小(大)元素,存放到排序序列的起始位置。
  • 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
  • 重复第二步,直到所有元素均排序完毕。

性能特点

  • 时间复杂度:O(n^2)。
  • 空间复杂度:O(1)。
  • 稳定性:选择排序不是稳定的排序算法。
public class SelectionSort {
    public static void selectionSort(int[] arr) {
        int n = arr.length;
        for (int i = 0; i < n - 1; i++) {
            int minIdx = i;
            for (int j = i + 1; j < n; j++) {
                if (arr[j] < arr[minIdx]) {
                    minIdx = j;
                }
            }
            // 交换元素
            int temp = arr[minIdx];
            arr[minIdx] = arr[i];
            arr[i] = temp;
        }
    }

    public static void main(String[] args) {
        int[] arr = {64, 25, 12, 22, 11};
        selectionSort(arr);
        for (int num : arr) {
            System.out.print(num + " ");
        }
    }
}
三、插入排序(Insertion Sort)

插入排序是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,找到相应位置并插入时,不需要移动其它元素,只需将要插入的元素移到插入点即可。

实现原理

  • 从第一个元素开始,该元素可以认为已经被排序。
  • 取出下一个元素,在已经排序的元素序列中从后向前扫描。
  • 如果该元素(已排序)大于新元素,则将该元素移到下一位置。
  • 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置。
  • 将新元素插入到该位置后。
  • 重复步骤2~5。

性能特点

  • 时间复杂度:O(n^2)(在元素数量较大时)。但在元素数量较小时,插入排序的性能非常好,时间复杂度接近O(n)。
  • 空间复杂度:O(1)。
  • 稳定性:插入排序是稳定的排序算法。
public class InsertionSort {
    public static void insertionSort(int[] arr) {
        int n = arr.length;
        for (int i = 1; i < n; ++i) {
            int key = arr[i];
            int j = i - 1;

            // 将arr[0..i-1]中大于key的元素向后移动一位
            while (j >= 0 && arr[j] > key) {
                arr[j + 1] = arr[j];
                j = j - 1;
            }
            arr[j + 1] = key;
        }
    }

    public static void main(String[] args) {
        int[] arr = {12, 11, 13, 5, 6};
        insertionSort(arr);
        for (int num : arr) {
            System.out.print(num + " ");
        }
    }
}
四、快速排序(Quick Sort)

快速排序是一种分而治之的排序算法。它通过一个枢轴元素将待排序数列分为两个子数列,左边子数列的所有元素都比枢轴元素小,右边子数列的所有元素都比枢轴元素大(或等于枢轴元素)。然后,递归地对两个子数列进行快速排序。

实现原理

  • 选择一个元素作为枢轴(pivot)。
  • 将数列中所有小于枢轴的元素移动到枢轴的左边,所有大于或等于枢轴的元素移动到枢轴的右边。
  • 递归地对枢轴左边和右边的子数列进行快速排序。

性能特点

  • 平均时间复杂度:O(n log n)。
  • 最坏时间复杂度:O(n^2)(当输入数组已经有序或逆序时)。
  • 空间复杂度:O(log n)(递归调用栈的深度)。但在最坏情况下会退化到O(n)。
  • 稳定性:快速排序不是稳定的排序算法。
public class QuickSort {
    static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    static int partition(int[] arr, int low, int high) {
        int pivot = arr[high];
        int i = (low - 1); // 小于pivot的元素的索引

        for (int j = low; j < high; j++) {
            // 如果当前元素小于或等于pivot
            if (arr[j] <= pivot) {
                i++;

                // 交换arr[i]和arr[j]
                swap(arr, i, j);
            }
        }

        // 交换arr[i + 1]和arr[high] (或pivot)
        swap(arr, i + 1, high);
        return (i + 1);
    }

    static void quickSort(int[] arr, int low, int high) {
        if (low < high) {
            int pi = partition(arr, low, high);

            // 分别对左右子数组进行排序
            quickSort(arr, low, pi - 1);
            quickSort(arr, pi + 1, high);
        }
    }

    public static void main(String[] args) {
        int[] arr = {10, 7, 8, 9, 1, 5};
        int n = arr.length;
        quickSort(arr, 0, n - 1);
        for (int num : arr) {
            System.out.print(num + " ");
        }
    }
}
五、归并排序(Merge Sort)

归并排序是一种基于分治法的排序算法。它将一个大的待排序数列分成两个小的子数列,分别进行排序,然后将两个已排序的子数列合并成一个有序的数列。

实现原理

  • 将数列分成两半。
  • 对每一半递归地进行归并排序。
  • 将两个已排序的子数列合并成一个有序的数列。

性能特点

  • 时间复杂度:O(n log n)。
  • 空间复杂度:O(n)(需要额外的空间来存储临时数组)。
  • 稳定性:归并排序是稳定的排序算法。
public class MergeSort {
    static void merge(int[] arr, int left, int mid, int right) {
        int n1 = mid - left + 1;
        int n2 = right - mid;

        int[] L = new int[n1];
        int[] R = new int[n2];

        for (int i = 0; i < n1; ++i)
            L[i] = arr[left + i];
        for (int j = 0; j < n2; ++j)
            R[j] = arr[mid + 1 + j];

        int i = 0, j = 0;

        int k = left;
        while (i < n1 && j < n2) {
            if (L[i] <= R[j]) {
                arr[k] = L[i];
                i++;
            } else {
                arr[k] = R[j];
                j++;
            }
            k++;
        }

        while (i < n1) {
            arr[k] = L[i];
            i++;
            k++;
        }

        while (j < n2) {
            arr[k] = R[j];
            j++;
            k++;
        }
    }

    static void sort(int[] arr, int left, int right) {
        if (left < right) {
            int mid = left + (right - left) / 2;

            sort(arr, left, mid);
            sort(arr, mid + 1, right);

            merge(arr, left, mid, right);
        }
    }

    public static void main(String[] args) {
        int[] arr = {12, 11, 13, 5, 6, 7};
        int arr_size = arr.length;

        sort(arr, 0, arr_size - 1);

        for (int i = 0; i < arr_size; i++) {
            System.out.print(arr[i] + " ");
        }
    }
}
六、堆排序(Heap Sort)

堆排序是一种基于堆数据结构的比较排序算法。它首先将待排序数列构建成一个最大堆(或最小堆),然后依次将堆顶元素(最大值或最小值)与堆的最后一个元素交换,并对堆顶元素重新进行堆调整(使其满足堆的性质),直到整个堆排序完成。

实现原理(以最大堆为例):

  • 构建最大堆。
  • 将堆顶元素(最大值)与堆的最后一个元素交换。
  • 对新的堆顶元素进行堆调整,使其满足最大堆的性质。
  • 重复步骤2和3,直到堆中只剩下一个元素。

性能特点

  • 时间复杂度:O(n log n)。
  • 空间复杂度:O(1)(因为堆排序是原地排序算法)。
  • 稳定性:堆排序不是稳定的排序算法。
public class HeapSort {
    static void heapify(int[] arr, int n, int i) {
        int largest = i; // 将当前节点设为最大值
        int left = 2 * i + 1; // 左子节点
        int right = 2 * i + 2; // 右子节点

        // 如果左子节点大于当前节点
        if (left < n && arr[left] > arr[largest])
            largest = left;

        // 如果右子节点大于当前最大值节点
        if (right < n && arr[right] > arr[largest])
            largest = right;

        // 如果最大值不是当前节点
        if (largest != i) {
            int swap = arr[i];
            arr[i] = arr[largest];
            arr[largest] = swap;

            // 递归堆化受影响的子树
            heapify(arr, n, largest);
        }
    }

    static void heapSort(int[] arr) {
        int n = arr.length;

        // 构建最大堆
        for (int i = n / 2 - 1; i >= 0; i--)
            heapify(arr, n, i);

        // 一个接一个地从堆顶取出元素,并调整堆
        for (int i = n - 1; i >= 0; i--) {
            int temp = arr[0];
            arr[0] = arr[i];
七、算法比较与选择

在选择排序算法时,我们需要考虑数据的规模、数据的分布特性以及对稳定性和额外空间的需求。

  • 对于小规模数据,插入排序和选择排序的性能通常较好。
  • 快速排序在平均情况下性能优异,但最坏情况下会退化到O(n^2)。为了改善最坏情况性能,可以使用随机化快速排序或三数取中法来选择枢轴。
  • 归并排序具有稳定的性能表现,但需要额外的空间来存储临时数组。
  • 堆排序不需要额外的空间(除了递归调用栈的空间),但不稳定。它在处理大规模数据时表现良好。
八、总结

Java中的排序算法种类繁多,每种算法都有其独特的原理和性能特点。在选择排序算法时,我们需要根据具体的应用场景和数据特性来做出合适的选择。通过深入理解和比较这些排序算法,我们可以更好地掌握Java中的数据处理和分析技巧。


http://www.kler.cn/a/394346.html

相关文章:

  • 传奇996_19——常用函数
  • python解析网页上的json数据落地到EXCEL
  • C++单例模式与多例模式
  • HBase使用create创建表时报错ERROR: KeeperErrorCode = NoNode for /hbase/master
  • kafka面试题解答(四)
  • 2024年11月13日
  • 昇思大模型平台打卡体验活动:项目5基于MindSpore实现Transformer机器翻译
  • MacOS 本地生成SSH key并关联Github
  • 【Linux】多线程(概念,控制)
  • 微信小程序自定义tabbar;禁用某个tab;修改某个tab的样式
  • Three.js 原生 实现 react-three-fiber drei 的 磨砂反射的效果
  • I.MX6U 裸机开发9.BEEP蜂鸣器实验
  • leetcode LCR 068 搜索插入位置
  • C++ vector 容器
  • 推荐一款完全开源的多端仓库管理系统
  • 计算机视觉空域处理完整版——超详细图文解
  • Docker安装部署RabbitMQ
  • Android12的ANR解析
  • 防爆增安型电机与防爆无火花型电机的区别
  • Agent熔断:助力构建更健壮的IT监控系统
  • 【代码随想录】刷题记录(29)-用栈实现队列
  • Web性能优化:从基础到高级
  • 引入了JUnit框架 却报错找不到:java.lang.ClassNotFoundException
  • 爬虫如何解决短效代理被封的问题?
  • 基于Spring Boot的电子商务系统设计
  • 海外媒体发稿:聚焦摩洛哥世界新闻 Morocco World News