jQuery火箭图标返回顶部代码

十大排序算法

1. 常用排序

1.1 归并排序

归并排序(Merge Sort)是一种基于分治策略的排序算法,它的主要思想是将一个未排序的数组划分为两个子数组,分别对这两个子数组进行排序,然后再将排好序的子数组合并成一个有序数组。归并排序的关键步骤在于"合并"操作,这是通过将两个已排序的子数组按照顺序合并而实现的。

归并排序的过程可以分为以下几个步骤:

  1. 分解:将待排序的数组分成两个子数组,通常是将数组分成相等大小的两部分,直到每个子数组的大小为1或0为止,因为一个元素的数组是有序的。

  2. 排序:对每个子数组进行递归地排序。这个步骤通过将问题逐步分解为更小的子问题,直到达到基本情况(即单个元素的数组),然后逐步合并子问题的解来实现排序。

  3. 合并:将已排序的子数组合并为一个有序数组。这是归并排序的核心步骤,它通过比较两个子数组中的元素,并按照顺序将它们合并到一个新的数组中,直到所有元素都被合并。

归并排序具有稳定性,即相等元素的顺序在排序后仍然保持不变。其时间复杂度为O(NlogN),其中N是数组的大小,这使得归并排序在大规模数据集上表现出色。然而,它需要额外的空间来存储临时数组,所以空间复杂度为O(N)。

复制代码
public static void process(int[] arr, int L, int R) {
    if (L == R) {
        return;
    }
    int mid = L + ((R - L) >> 1);
    //递归过程将数组分成树的结构,可画图演示
    process(arr, L, mid);
    process(arr, mid + 1, R);
    //将树的节点合并
    merge(arr, L, mid, R);
}

private static void merge(int[] arr, int l, int mid, int r) {
    int[] help = new int[r - l + 1];
    int i = 0;
    int p = l;//左侧
    int q = mid + 1;//右侧
    while (p <= mid && q <= r) {
        //相等元素优先合并左侧节点,保证稳定性
        help[i++] = arr[p] <= arr[q] ? arr[p++] : arr[q++];
    }
    while (p <= mid) {
        help[i++] = arr[p++];
    }
    while (q <= r) {
        help[i++] = arr[q++];
    }
    //辅助数组,保存归并结果
    for (i = 0; i < help.length; i++) {
        arr[l + i] = help[i];
    }
}
复制代码

1.2 快速排序

快速排序(Quick Sort)是一种常用的高效的排序算法,它是一种基于比较的排序算法,利用了分治法的思想。快速排序的主要思想是选择一个基准元素,将数组分成两个子数组,一个子数组的所有元素都小于基准元素,另一个子数组的所有元素都大于基准元素,然后递归地对这两个子数组进行排序,最后将它们合并起来。

快速排序的基本步骤如下:

  1. 选择基准元素:从数组中选择一个元素作为基准(pivot)元素。选择合适的基准元素可以影响算法的性能。

  2. 划分:将数组中的其他元素与基准元素进行比较,将小于基准元素的元素放在左边,大于基准元素的元素放在右边。最终基准元素将位于其正确的位置,左边是小于它的元素,右边是大于它的元素。

  3. 递归:递归地对左右两个子数组进行快速排序。

快速排序的平均时间复杂度为O(NlogN),其中N是待排序数组的长度。然而,在最坏情况下,如果选择的基准元素始终是数组中的最小或最大元素,快速排序的时间复杂度可能达到O(N^2),但这种情况相对较少出现。快速排序的实际性能通常比其他基于比较的排序算法(如归并排序和堆排序)好,尤其在大规模数据集上。

复制代码
private int Partition(int A[], int low, int high) {
    int pivot = A[low];
    while (low < high) {
        while (low < high && A[high] >= pivot) {
            high--;
        }
        A[low] = A[high];  // 比枢轴元素小的元素移动到左端
        while (low < high && A[low] <= pivot) {
            low++;
        }
        A[high] = A[low];  // 比枢轴元素大的元素移动到右端
    }
    A[low] = pivot;  // 枢轴元素放到最终位置
    return low;  // 枢轴元素的最终位置,左边元素小于枢轴元素,右边元素大于枢轴元素
}

public void QuickSort(int A[], int low, int high) {
    if (low < high) {
        int pivotPos = Partition(A, low, high);  // 进行一次划分
        QuickSort(A, low, pivotPos - 1);         // 递归划分左边部分
        QuickSort(A, pivotPos + 1, high);        // 递归划分右边部分
    }
}
复制代码

快速排序优化

双指针(三向划分)快速排序,选择基准元素(随机化,三数取中法),小规模子数据使用插入排序。

在快速排序中,如果每次选择基准元素都是数组中的最小或最大元素,那么每次分割都会将数组分成一个元素和 n-1 个元素的子数组,导致算法的时间复杂度退化为 O(n^2)。随机选择基准元素可以降低这种最坏情况发生的概率,从而避免退化。

复制代码
// 基准元素随机化
class Solution {
    private Random random = new Random();

    public int[] sortArray(int[] nums) {
        if (nums.length < 1) {
            return nums;
        }
        quickSort(nums, 0, nums.length - 1);
        return nums;
    }

    private void quickSort(int[] nums, int low, int high) {
        if (low >= high) {
            return;
        }
        int pivot = partition(nums, low, high);
        quickSort(nums, low, pivot - 1);
        quickSort(nums, pivot + 1, high);
    }

    private int partition(int[] nums, int low, int high) {
        int randomIndex = low + random.nextInt(high - low + 1);
        int pivot = nums[randomIndex];
        nums[randomIndex] = nums[low];
        nums[low] = pivot;
        while (low < high) {
            while (low < high && nums[high] >= pivot) {
                high--;
            }
            nums[low] = nums[high];
            while (low < high && nums[low] <= pivot) {
                low++;
            }
            nums[high] = nums[low];
        }
        nums[low] = pivot;
        return low;
    }

}
复制代码

1.3 堆排序

堆排序(Heap Sort)是一种基于二叉堆数据结构的排序算法,它是一种选择排序的一种改进,具有较好的时间和空间复杂度。堆是一种特殊的完全二叉树,分为最大堆和最小堆两种类型,其中最大堆要求父节点的值大于或等于子节点的值,最小堆要求父节点的值小于或等于子节点的值。

堆排序的基本思想是:首先将待排序的数组构建成一个二叉堆(通常使用最大堆),然后不断地将堆顶元素(即最大元素)与堆的最后一个元素交换,并从堆中移除,然后对剩余的元素重新调整为一个合法的堆,重复这个过程直到堆为空,得到一个有序的数组。

以下是堆排序的关键步骤:

  1. 构建最大堆:从最后一个非叶子节点开始,自底向上地将数组调整为一个最大堆。
  2. 交换堆顶元素和末尾元素:将堆顶元素与堆的最后一个元素交换,然后将堆的大小减一。
  3. 调整堆:从堆顶开始,通过逐级下沉(或上浮)操作将堆重新调整为最大堆。
  4. 重复步骤 2 和 3,直到堆为空。

2. 低级排序

2.1 冒泡排序

复制代码
public static void bubbleSort(int[] arr) {
        if(arr == null || arr.length < 2) {
            return;
        }
        for (int i = arr.length - 1; i > 0; i--) {
            for (int j = 0; j < i; j++) {
                if (arr[j] > arr[j + 1]) {
                    swap(arr, j, j + 1);
                }
            }
        }
}
复制代码

2.2 直接插入排序

复制代码
// 插入排序,比较有效地排序方法
public static void insertionSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    for (int i = 1; i < arr.length; i++) {
        // 使0到i范围内有序
        for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
            swap(arr, j, j + 1);
        }
    }
}
// 交换数组中的i, j位置上的元素
private static void swap(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}
复制代码

2.3 希尔排序

希尔排序(Shell Sort)是一种插入排序的一种改进,它通过将数组分成若干个子序列来进行排序,逐步减小子序列的长度,最终完成排序。希尔排序的核心思想是将间隔较大的元素先排好序,然后逐步减小间隔,直到间隔为1,最后进行一次插入排序。

希尔排序的步骤:

  1. 选择一个增量序列(也称为间隔序列),常用的增量序列有希尔增量、Hibbard增量等。
  2. 根据增量序列将数组分成若干个子序列,对每个子序列进行插入排序。
  3. 逐步减小增量,重复步骤 2,直到增量为1,此时进行一次完整的插入排序,完成排序。

希尔增量:最坏O(N^2)平均O(N^1.3)~O(N^1.5)

Hibbard增量:Hibbard增量是希尔排序中使用的一种增量序列,由Donald Hibbard提出。Hibbard增量序列的计算公式是 h_k = 2^k - 1,其中 k 是增量序列的索引。这意味着第一个增量是1,第二个增量是3,第三个增量是7,以此类推。Hibbard增量序列是递增且不互质的,因此在一些情况下可能不够均匀地进行数组分组。最坏时间复杂度可以到O(N^3/2),平均时间复杂度O(N^5/4)。

Sedgewick增量序列<br />

2.4 选择排序

复制代码
public static void selectioinSort(int[] arr) {
        if(arr == null || arr.length < 2) {
            return;
        }
        for (int i = 0; i < arr.length - 1; i++) {
            int minIndex = i;
            for (int j = i + 1; j < arr.length; j++) {
                minIndex = arr[j] < arr[minIndex] ? j : minIndex;
            }
            swap(arr, i, minIndex);
        }
}
复制代码

3. 基于比较的排序算法时间复杂度下限证明

经过k次比较最多能区分的序列个数是2^k,如果有M种序列,需要logM次比较才能区分出大小。那有N个元素的数组右N!中排序可能,需要logN!次比较才能区分出大小,使用斯特林公式可知最低复杂度是NlogN。

4. 排序算法会出现不稳定的状态原因

比较操作不考虑相等情况: 许多排序算法是基于比较操作的,它们只考虑元素的大小关系,而不考虑相等情况。当两个元素的大小相等时,排序算法可能会交换它们的位置,从而破坏了它们在原始序列中的相对顺序,导致排序结果不稳定。

5. 非比较排序

5.1 计数排序

计数排序适用于待排序元素的范围比较小且非负整数的情况。它的基本思想是,统计每个元素出现的次数,然后根据统计信息构建有序的结果序列。

5.2 桶排序

桶排序适用于待排序元素服从均匀分布的情况,它将待排序元素划分为一定数量的桶,然后对每个桶内的元素进行排序,最后将排序后的桶依次合并成有序的结果。

5.3 基数排序

基数排序(Radix Sort)是一种非比较的排序算法,它适用于整数或字符串等具有固定位数的元素。基数排序的基本思想是将待排序的元素从低位到高位依次进行排序,以实现整体的排序。具体来说,基数排序将元素按照各个位上的值进行桶排序,从最低位到最高位依次进行,最终得到有序的结果。

6. 思考:有没有办法实现在O(1)时间复杂度的排序算法?

可以对有限的数字范围内的元素建立一个大哈希表,直接进行映射。

posted @   rongqing2019  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
点击右上角即可分享
微信分享提示