Java实现十大排序算法

时间/空间复杂度对比:

n表示输入元素的数量,k表示元素的取值范围大小。

稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面。
不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b 的后面。
时间复杂度:对排序数据的总操作次数。反映当n变化时,操作次数呈现什么规律。
空间复杂度:是指算法在计算机内执行时所需存储空间的度量,它也是数据规模n的函数。

排序算法 时间复杂度-最好 时间复杂度-最差 时间复杂度-平均 空间复杂度 稳定性
插入排序 O(n) O(n^2) O(n^2) O(1) 稳定
冒泡排序 O(n) O(n^2) O(n^2) O(1) 稳定
选择排序 O(n^2) O(n^2) O(n^2) O(1) 不稳定
快速排序 O(n log n) O(n^2) O(n log n) O(log n) 不稳定
归并排序 O(n log n) O(n log n) O(n log n) O(n) 稳定
计数排序 O(n+k) O(n+k) O(n+k) O(k) 稳定
希尔排序 O(n) O(n^2) O(n log n) O(1) 不稳定
基数排序 O(n*k) O(n*k) O(n*k) O(n+k) 稳定
堆排序 O(n log n) O(n log n) O(n log n) O(1) 不稳定
桶排序 O(n) O(n^2) O(n^2) O(n+k) 稳定

一、插入排序(Insertion Sort

基本思想:

       通过构造一个有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

时间复杂度:

       最好情况(输入数组已经是有序的):只需遍历一次数组即可完成排序,时间复杂度为 O(n)。

       最坏情况(输入数组完全逆序):需要进行 n*(n-1)/2 次比较和最多同样次数的交换操作,时间复杂度为 O(n^2)。

       平均情况:时间复杂度也为 O(n^2),因为平均情况下,插入排序需要进行接近于 n^2/2 的比较和交换操作。

空间复杂度:

       插入排序是原地排序算法,不需要额外的空间存储数据,仅需要常数级别的临时空间存放中间变量,所以空间复杂度为 O(1)。

public class InsertionSort {
    public static void insertionSort(int[] arr) {
        for (int i = 1; i < arr.length; i++) {
            // 获取当前数组里面的值并赋值给 key
            int key = arr[i];
            int j = i - 1;

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

            // 插入key到正确的位置
            arr[j + 1] = key;
        }
    }

    public static void main(String[] args) {
        int[] array = {49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1};
        insertionSort(array);

        System.out.println("排序后的数组:");
        for (int num : array) {
            System.out.print(num + " ");
        }
    }
}

二、冒泡排序(Bubble Sort)

基本思想:

       通过相邻元素之间的比较和交换来逐渐将最大(或最小)的元素“浮”到数组的一端。 

时间复杂度:

       最好情况(已排序数组):冒泡排序只需遍历一次即可完成排序,因此最好情况下的时间复杂度为 O(n)。

       最坏情况(逆序数组):需要进行 n*(n-1)/2 次比较和交换,时间复杂度为 O(n^2)。

       平均情况:也是 O(n^2),因为在大多数情况下,需要进行接近于 n^2/2 的比较和交换操作。

空间复杂度:

       冒泡排序是原地排序算法,它不需要额外的空间存储数据,只需要常数级别的临时空间存放中间变量,所以空间复杂度为 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 - 1 - i; j++) {
                // 如果当前元素比下一个元素大,则交换它们的位置
                if (arr[j] > arr[j + 1]) {
                    // 交换 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[] array = {64, 34, 25, 12, 22, 11, 90};
        bubbleSort(array);
        System.out.println("Sorted array: ");
        for (int num : array) {
            System.out.print(num + " ");
        }
    }
}

 三、选择排序(Selection Sort)

基本思想:

       每一次从未排序的部分中找到最小(或最大)的元素,并将其放到已排序部分的末尾。

时间复杂度:

       最好情况(输入数组已经是有序的):即使数组已经有序,选择排序仍然需要进行 n-1 轮比较,每轮都需要遍历剩下的元素来确认最小值,所以时间复杂度仍为 O(n^2)。

       最坏情况(输入数组完全逆序):同样需要进行 n*(n-1)/2 次比较和最多同样次数的交换操作,时间复杂度为 O(n^2)。

       平均情况:由于无论初始顺序如何,选择排序都需要进行接近于 n^2/2 的比较,因此平均时间复杂度也是 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++) {
            // 找到[i, n)区间内的最小值的索引
            int minIndex = i;
            for (int j = i + 1; j < n; j++) {
                if (arr[j] < arr[minIndex]) {
                    minIndex = j;
                }
            }

            // 将找到的最小值与当前位置的元素交换
            int temp = arr[minIndex];
            arr[minIndex] = arr[i];
            arr[i] = temp;
        }
    }

    public static void main(String[] args) {
        int[] array = {64, 34, 25, 12, 22, 11, 90};
        selectionSort(array);
        System.out.println("Sorted array: ");
        for (int num : array) {
            System.out.print(num + " ");
        }
    }
}

四、快速排序 (Quick Sort)

基本思想:

       快速排序是一种分治策略的排序算法,通过选取一个“基准”元素将数组划分为两个子序列,左边的元素小于基准,右边的元素大于基准。然后递归地对左右子序列进行同样的操作。 

时间复杂度:

       最好情况(每次划分都很均匀): O(n log n)

       平均情况: O(n log n)

       最坏情况(输入已经有序或逆序): O(n^2)

空间复杂度:

       栈空间消耗: O(log n),递归栈深度在最坏情况下为O(n),但平均情况下是logn。

       原地排序算法,不需要额外空间,辅助空间复杂度: O(1)

public class QuickSort {
    // 快速排序方法,采用分治法策略
    public static void quickSort(int[] arr, int low, int high) {
        if (low < high) {
            // 找到分区点(pivot),将数组分为两部分:小于pivot的元素和大于pivot的元素
            int pivotIndex = partition(arr, low, high);

            // 对左右两边的子数组进行递归排序
            quickSort(arr, low, pivotIndex - 1);
            quickSort(arr, pivotIndex + 1, high);
        }
    }

    // 分区操作,返回pivot的位置
    private static int partition(int[] arr, int low, int high) {
        // 选取最后一个元素作为基准值
        int pivot = arr[high];
        int i = (low - 1);  // index of smaller element

        for (int j = low; j <= high - 1; j++) {
            // 如果当前元素小于或等于pivot,则交换位置并将较小元素索引后移
            if (arr[j] <= pivot) {
                i++;

                // 交换arr[i]和arr[j]
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }

        // 将基准值与i+1位置上的元素交换,保证基准值左边的都比它小,右边的都比它大
        int temp = arr[i + 1];
        arr[i + 1] = arr[high];
        arr[high] = temp;

        return i + 1;
    }

    public static void main(String[] args) {
        int[] array = {9, 7, 5, 11, 12, 2, 14, 3, 10, 6};
        quickSort(array, 0, array.length - 1);

        System.out.println("Sorted array: ");
        for (int num : array) {
            System.out.print(num + " ");
        }
    }
}

 五、归并排序 (Merge Sort)

基本原理:
       归并排序是一种稳定的排序算法,它将待排序的序列分成若干个子序列,分别进行排序,最后再将结果进行合并。其核心步骤是将两个已经排序好的子序列合并成一个新的、更大的已排序序列。

时间复杂度:
       归并排序无论在最好、最坏还是平均情况下,时间复杂度都是 O(n log n)。

空间复杂度:
       归并排序需要额外的空间来存储临时数组,所以空间复杂度为 O(n)。

public class MergeSort {
    // 归并排序方法,采用分治法策略
    public static void mergeSort(int[] arr, int left, int right) {
        if (left < right) {
            int mid = (left + right) / 2;

            // 对左半边数组进行归并排序
            mergeSort(arr, left, mid);

            // 对右半边数组进行归并排序
            mergeSort(arr, mid + 1, right);

            // 合并两个有序的半边数组
            merge(arr, left, mid, right);
        }
    }

    // 合并两个有序数组的方法
    private 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];

        // 复制数据到临时数组
        System.arraycopy(arr, left, L, 0, n1);
        System.arraycopy(arr, mid + 1, R, 0, n2);

        // 初始化指针
        int i = 0, j = 0, k = left;

        // 比较两个数组的元素,并按照升序合并到原数组中
        while (i < n1 && j < n2) {
            if (L[i] <= R[j]) {
                arr[k++] = L[i++];
            } else {
                arr[k++] = R[j++];
            }
        }

        // 将剩余未合并的部分复制回原数组
        while (i < n1) {
            arr[k++] = L[i++];
        }

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

    public static void main(String[] args) {
        int[] array = {9, 7, 5, 11, 12, 2, 14, 3, 10, 6};
        mergeSort(array, 0, array.length - 1);

        System.out.println("Sorted array: ");
        for (int num : array) {
            System.out.print(num + " ");
        }
    }
}

六、堆排序 (Heap Sort)

基本原理:
       堆排序是一种基于比较的排序算法,通过构建一个“大顶堆”或“小顶堆”,使得父节点总是大于(或小于)其子节点。首先构建初始堆,然后把堆顶元素和最后一个元素交换位置,再重新调整堆,重复这个过程直到所有元素都有序。

时间复杂度:
       堆排序在最好、最坏和平均情况下时间复杂度都是 O(n log n),其中 n 是待排序数组的长度。

空间复杂度:
       堆排序是原地排序算法,只需要常数级别的额外空间用于临时存储交换的元素,所以空间复杂度为 O(1)。

public class HeapSort {
    // 建立大顶堆
    public 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) {
            swap(arr, i, largest);
            heapify(arr, n, largest);
        }
    }

    // 交换数组中的两个元素
    private static void swap(int[] arr, int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    // 对整个数组进行堆排序
    public 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--) {
            swap(arr, 0, i);
            heapify(arr, i, 0);
        }
    }

    public static void main(String[] args) {
        int[] array = {9, 7, 5, 11, 12, 2, 14, 3, 10, 6};
        heapSort(array);

        System.out.println("Sorted array: ");
        for (int num : array) {
            System.out.print(num + " ");
        }
    }
}

 七、计数排序 (Counting Sort)

基本原理:

       计数排序是一种非基于比较的排序算法,它适用于整数排序且数值范围不大的情况。通过对每一个输入元素x,确定出不大于x的元素个数,就可以直接得到x在输出序列中的位置。最后根据计数结果重建排序后的序列。

时间复杂度:

       当输入数据范围不是特别大时,计数排序的时间复杂度为 O(n + k),n 是待排序数组的长度,k 是输入数据的最大值与最小值的差加一。

空间复杂度:

       计数排序需要额外的空间来存储计数数组和输出数组,所以空间复杂度为 O(n + k)。但如果k远大于n,则该算法可能不适用。

public class CountingSort {
    public static void countingSort(int[] arr) {
        // 查找数组中的最大值
        int maxVal = Arrays.stream(arr).max().getAsInt();

        // 初始化计数数组,大小为最大值+1
        int[] count = new int[maxVal + 1];

        // 统计每个数出现的次数
        for (int num : arr) {
            count[num]++;
        }

        // 计算累计出现次数
        for (int i = 1; i < count.length; i++) {
            count[i] += count[i - 1];
        }

        // 创建一个新的输出数组
        int[] output = new int[arr.length];

        // 按照顺序将元素放入输出数组
        for (int i = arr.length - 1; i >= 0; i--) {
            output[count[arr[i]] - 1] = arr[i];
            count[arr[i]]--;
        }

        // 将排序好的元素复制回原数组
        System.arraycopy(output, 0, arr, 0, arr.length);
    }

    public static void main(String[] args) {
        int[] array = {9, 7, 5, 11, 12, 2, 14, 3, 10, 6};
        countingSort(array);

        System.out.println("Sorted array: ");
        for (int num : array) {
            System.out.print(num + " ");
        }
    }
}

 八、希尔排序 (Shell Sort)

基本原理:

       希尔排序是插入排序的一种改进版本,通过定义一个间隔序列来分组元素,对每组使用直接插入排序。随着增量逐渐减少,元素变得越来越接近有序,最后当增量为1时,整个数组已经基本有序,此时执行一次插入排序就能得到完全有序的结果。

时间复杂度:

       最好情况下的时间复杂度可以达到O(n log n),最坏情况下和平均情况下的时间复杂度取决于所使用的增量序列。经典的Hibbard增量序列下,希尔排序的时间复杂度为O(n^(3/2))。

空间复杂度:

       希尔排序是原地排序算法,空间复杂度为O(1)。

public class ShellSort {
    // 希尔排序实现
    public static void shellSort(int[] arr) {
        int len = arr.length;

        // 增量序列,这里使用h(n) = 3^k - 1的方式,逐步减小增量
        for (int gap = len / 2; gap > 0; gap /= 2) {
            // 对每一个增量值,进行插入排序
            for (int i = gap; i < len; i++) {
                // 将arr[i]插入到正确的位置(根据当前的增量)
                int temp = arr[i];
                int j;
                for (j = i; j >= gap && arr[j - gap] > temp; j -= gap) {
                    arr[j] = arr[j - gap];
                }
                arr[j] = temp;
            }
        }
    }

    public static void main(String[] args) {
        int[] array = {9, 7, 5, 11, 12, 2, 14, 3, 10, 6};
        shellSort(array);

        System.out.println("Sorted array: ");
        for (int num : array) {
            System.out.print(num + " ");
        }
    }
}

九、桶排序 (Bucket Sort) 

基本原理:

       桶排序是一种分布排序算法,它将要排序的数据分布到有限数量的桶里,对每个桶内的数据进行排序,然后依次合并所有桶中的数据即可完成排序。桶内排序可以采用任何适合的排序算法。

时间复杂度:

       当输入数据均匀分布且桶的数量足够多时,桶排序的时间复杂度可以达到O(n + k),其中n是待排序元素个数,k是桶的数量。但如果元素分布不均匀或桶划分不合理,时间复杂度会变差。

空间复杂度:

       空间复杂度主要取决于桶的数量,若假设每个桶大小固定,则空间复杂度为O(n+k),其中n是待排序元素个数,k是桶的数量。

public class BucketSort {
    // 桶排序实现,假设输入数组为非负整数
    public static void bucketSort(int[] arr, int maxVal) {
        if (arr == null || arr.length == 0) {
            return;
        }

        // 创建足够数量的空桶(这里以最大值 + 1来确定桶的数量)
        List> buckets = new ArrayList<>();
        for (int i = 0; i <= maxVal; i++) {
            buckets.add(new ArrayList<>());
        }

        // 将元素分配到各个桶中
        for (int num : arr) {
            buckets.get(num).add(num);
        }

        // 对每个桶内的元素进行排序,可以使用插入排序等简单算法
        for (List bucket : buckets) {
            Collections.sort(bucket);
        }

        // 合并所有已排序的桶
        int index = 0;
        for (List bucket : buckets) {
            for (Integer value : bucket) {
                // 防止处理空桶
                if (!bucket.isEmpty()) {
                    arr[index++] = value;
                }
            }
        }
    }

    public static void main(String[] args) {
        int[] array = {9, 7, 5, 11, 12, 2, 14, 3, 10, 6};
        int maxVal = Arrays.stream(array).max().getAsInt();
        bucketSort(array, maxVal);

        System.out.println("Sorted array: ");
        for (int num : array) {
            System.out.print(num + " ");
        }
    }
}

十、基数排序(Radix Sort)

基本原理:
       基数排序是一种非比较型整数排序算法,其基本思想是对要排序的数据按位数进行划分,从低位到高位逐次进行排序。它借助了稳定的计数排序,每次针对一位数字进行排序,直到所有位数都排序完成。

时间复杂度:
       在基数排序中,每一趟都是对不同位进行计数排序,所以每一轮的时间复杂度为O(n),由于一共需要log_radix(maxValue)轮,因此总的时间复杂度为O(k * n),其中k通常为radix(如10进制则k=10)。

空间复杂度:
       空间复杂度为O(n + k),其中n是待排序元素个数,k是基数(比如在十进制下k=10)。

public class RadixSort {
    // 基数排序实现,假设输入数组为非负整数
    public static void radixSort(int[] arr) {
        // 获取数组中的最大数,用于确定需要遍历的位数
        int maxNum = Arrays.stream(arr).max().getAsInt();

        // 定义一个基数(例如十进制下基数为10)
        final int RADIX = 10;

        // 从最低有效位开始,对每一位进行排序
        for (int exp = 1; maxNum / exp > 0; exp *= RADIX) {
            // 使用计数排序对当前位进行排序
            countingSortByDigit(arr, exp, RADIX);
        }
    }

    // 计数排序对特定位数进行排序
    private static void countingSortByDigit(int[] arr, int exp, int radix) {
        int[] output = new int[arr.length];
        int[] count = new int[radix];

        // 初始化计数数组
        Arrays.fill(count, 0);

        // 统计每个桶中元素的数量
        for (int i = 0; i < arr.length; i++) {
            count[(arr[i] / exp) % radix]++;
        }

        // 计算每个桶的起始位置
        for (int i = 1; i < radix; i++) {
            count[i] += count[i - 1];
        }

        // 根据每个桶的位置逆序填充输出数组,并更新原数组
        for (int i = arr.length - 1; i >= 0; i--) {
            output[count[(arr[i] / exp) % radix] - 1] = arr[i];
            count[(arr[i] / exp) % radix]--;
            arr[i] = output[i];
        }
    }

    public static void main(String[] args) {
        int[] array = {93, 72, 51, 112, 12, 234, 34, 10, 67};
        radixSort(array);

        System.out.println("Sorted array: ");
        for (int num : array) {
            System.out.print(num + " ");
        }
    }
}

你可能感兴趣的:(程序猿日常,java,排序算法,算法)