5. 归并排序
归并排序是利用归并的思想实现的排序方法,该算法采用经典的分治策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
算法核心逻辑如下
- 分割数组
首先,把数组分成两半,然后分别对这两半继续进行分割,直到每一部分只有一个元素。每次分割都通过计算中间索引mid = (left + right) / 2
来进行。 - 排序
当数组被分到最小部分后,实际上这些小部分天然是有序的。接着,开始将这些有序的小部分合并起来。合并时,比较两个子数组的元素,把小的放到临时数组中。 - 合并
合并的过程就是把两个已经有序的子数组合并成一个大的有序数组。我们通过两个指针分别扫描两个子数组,比较它们的元素,取小的放入临时数组中。直到一个子数组遍历完,再将另一个子数组剩下的元素直接放入临时数组。 - 拷贝回原数组
最后,将临时数组中的元素复制回原数组。这样,原数组的内容就变成了排好序的。
private static void MergeSort(int[] arr,int left,int right,int []temp){
if(left<right){
int mid = (left+right)/2;
MergeSort(arr,left,mid,temp);//左边归并排序,使得左子序列有序
MergeSort(arr,mid+1,right,temp);//右边归并排序,使得右子序列有序
merge(arr,left,mid,right,temp);//将两个有序子数组合并操作
}
}
private static void merge(int[] arr,int left,int mid,int right,int[] temp){
int i = left;//左序列指针
int j = mid+1;//右序列指针
int t = 0;//临时数组指针
while (i<=mid && j<=right){
if(arr[i]<=arr[j]){
temp[t++] = arr[i++];
}else {
temp[t++] = arr[j++];
}
}
while(i<=mid){//将左边剩余元素填充进temp中
temp[t++] = arr[i++];
}
while(j<=right){//将右序列剩余元素填充进temp中
temp[t++] = arr[j++];
}
t = 0;
//将temp中的元素全部拷贝到原数组中
while(left <= right){
arr[left++] = temp[t++];
}
}
算法分析:稳定时间复杂度的一个算法,但是会造成额外的空间开销int [] temp
6. 快速排序
快速排序的基本思想:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均比另一部分的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序。
算法核心逻辑
- 选择一个基准元素(通常是数组的第一个元素或最后一个元素)。
- 重新排列数组,将小于基准的元素放在左边,大于基准的元素放在右边。
- 递归地对基准元素左侧和右侧的子数组进行快速排序。
- 直到子数组的长度为1或0时,排序完成。
// 快速排序
public static void quickSort(int[] arr, int left, int right) {
if (left >= right) {
return;
}
int pivot = arr[left]; // 基准元素
int i = left, j = right;
while (i < j) {
// 从右边开始,找到小于基准的元素
while (i < j && arr[j] >= pivot) {
j--;
}
// 从左边开始,找到大于基准的元素
while (i < j && arr[i] <= pivot) {
i++;
}
// 交换两个元素
if (i < j) {
swap(arr, i, j);
}
}
// 将基准元素放到正确位置
arr[left] = arr[i];
arr[i] = pivot;
// 递归排序左右部分
quickSort(arr, left, i - 1);
quickSort(arr, i + 1, right);
}
// 交换数组中的两个元素
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
get不到这段代码逻辑的问问GPT就好了~
算法分析:快速排序适合大数据量并且数据不均匀的情况下用, 通过选择一个合理的分区点(pivot),每次都能将数据集分成相对均匀的两部分,这样快速排序能够利用分治思想高效地排序 。并且不用额外内存空间(原地移动)
7. 堆排序
原理:堆排序是一种基于完全二叉树的排序算法,时间复杂度为 O(n log n),是一种不稳定的排序方法。堆排序的基本思想是:首先将待排序数组构建成一个大根堆(或小根堆),然后将堆顶元素与最后一个元素交换,接着重新调整堆结构,使得新的堆顶元素仍然是最大(或最小)的,再重复这一过程直到所有元素都排序完毕。
算法核心逻辑
- 构建最大堆:
在heapSort
方法的第一个for
循环中,调用adjustHeap
函数将数组调整成最大堆。这里从arr.length / 2 - 1
开始向前调整,确保每个节点的子树都符合最大堆的性质。 - 交换堆顶元素和末尾元素:
在第二个for
循环中,堆顶元素(即arr[0]
)和当前堆的最后一个元素交换,这使得最大值被放置到数组的末端。 - 重新调整堆:
交换后,调用adjustHeap
来重新调整堆顶元素,使得剩余的元素仍然满足堆的性质。这里的adjustHeap
主要负责维护堆的结构。 - 重复步骤2和3:
第二个for
循环会继续执行,直到数组的所有元素都被排好序为止。每次adjustHeap
都是对堆顶元素进行调整,确保最大堆的性质持续保持。
public static void heapSort(int[] arr) {
int temp = 0;
// 1. 构建最大堆
for (int i = arr.length / 2 - 1; i >= 0; i--) {
adjustHeap(arr, i, arr.length);
}
// 2. 将最大堆的根元素与末尾元素交换,然后重新调整堆
for (int j = arr.length - 1; j > 0; j--) {
// 交换根元素和当前末尾元素
temp = arr[j];
arr[j] = arr[0];
arr[0] = temp;
// 重新调整堆,排除已排好序的部分
adjustHeap(arr, 0, j);
}
}
// 调整堆,使得以i为根节点的子树满足大根堆的性质
public static void adjustHeap(int[] arr, int i, int length) {
int temp = arr[i]; // 保存父节点的值
// 从i的左子节点开始
for (int k = i * 2 + 1; k < length; k = k * 2 + 1) {
// 如果右子节点比左子节点大,则选择右子节点
if (k + 1 < length && arr[k] < arr[k + 1]) {
k++;
}
// 如果子节点比父节点大,交换它们
if (arr[k] > arr[i]) {
arr[i] = arr[k];
i = k; // i移到交换后的子节点位置
} else {
break; // 如果已经满足堆的性质,就不需要继续调整
}
}
// 将父节点的值放到新的位置
arr[i] = temp;
}
算法分析:
- 数据量较大时: 堆排序适用于需要排序大数据集的情况,特别是当你希望保证最坏情况下的性能为 O(n log n) 时。相比快速排序在最坏情况下可能退化成 O(n²),堆排序始终保持 O(n log n) 的时间复杂度。
- 内存受限的情况: 堆排序是一种原地排序算法,只需要常数级的额外空间,因此适合内存受限的场景。相比归并排序需要额外的 O(n) 空间,堆排序的空间开销要小得多。
- 频繁需要获取最大值或最小值的场景: 堆排序适合那些需要频繁获取最大值或最小值的情况。例如,在优先队列中,堆数据结构被广泛用于管理任务队列,其中优先级最高的任务会被首先处理。堆排序通过不断调整堆的结构,能够有效地提取最大值(或最小值)。
- 不需要稳定性的情况: 堆排序是不稳定的排序算法,意味着相同的元素可能会改变顺序。如果你对排序结果的稳定性没有要求,可以考虑使用堆排序。
8. 桶排序
桶排序的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序
算法核心逻辑伪代码
1. 初始化空桶数组bucket
2. 遍历待排序数组arr,将每个元素按照某种映射规则分配到对应的桶中
bucket[floor(arr[i]/bucketSize)] += arr[i]
3. 遍历桶数组bucket,对每个非空桶内部进行排序
for j = 0 to bucket.length-1 do
if bucket[j] is not empty then
sort(bucket[j])
4. 将各个桶内部排序后的数据合并到待排序数组arr中
index = 0
for j = 0 to bucket.length-1 do
if bucket[j] is not empty then
for k = 0 to bucket[j].length-1 do
arr[index] = bucket[j][k]
index++
5. 返回排好序的数组arr
public static void bucketSort(int[] arr, int bucketSize) {
if (arr.length == 0) {
return;
}
// 寻找数组中的最大值和最小值
int minValue = arr[0];
int maxValue = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] < minValue) {
minValue = arr[i];
} else if (arr[i] > maxValue) {
maxValue = arr[i];
}
}
// 计算桶的数量
int bucketCount = (maxValue - minValue) / bucketSize + 1;
ArrayList<ArrayList<Integer>> buckets = new ArrayList<>(bucketCount);
for (int i = 0; i < bucketCount; i++) {
buckets.add(new ArrayList<>());
}
// 将数组中的元素分配到各个桶中
for (int value : arr) {
int bucketIndex = (value - minValue) / bucketSize;
buckets.get(bucketIndex).add(value);
}
// 对每个非空的桶内部进行排序
int index = 0;
for (ArrayList<Integer> bucket : buckets) {
if (!bucket.isEmpty()) {
Collections.sort(bucket); // 这里使用了Java自带的排序方法
for (int value : bucket) {
arr[index++] = value;
}
}
}
}
算法分析
桶排序的时间复杂度可以达到O(n),但是桶排序对传入的数据有要求,需要传入数据均匀分布的情况下才能达到O(n)