沥川的算法学习笔记:基础算法(2)----归并排序

1.归并排序

        归并排序是一种常见的排序算法,它的基本思想是将一个数组划分成两个子数组,然后分别对子数组进行排序,最后将两个有序的子数组合并成一个有序的数组。

具体的排序过程如下:

  1. 将待排序数组递归地划分成两个子数组,直到子数组的长度为1。
  2. 对每个子数组进行排序,可以使用递归或其他排序算法进行排序。
  3. 合并两个有序的子数组,得到一个更大的有序数组。

合并两个有序的子数组的过程如下:

  1. 创建一个临时数组,用于存放合并后的结果。
  2. 初始化两个指针,分别指向两个子数组的开头。
  3. 比较两个指针指向的元素,将较小的元素插入临时数组,并将对应的指针后移一位。
  4. 重复步骤3,直到其中一个指针超过了子数组的长度。
  5. 将另一个子数组中剩余的元素依次插入临时数组。
  6. 将临时数组中的元素复制回原数组的对应位置,完成合并。

        归并排序的时间复杂度为O(nlogn),空间复杂度为O(n)。它是一种稳定的排序算法,适用于对大规模数据进行排序。

#include <iostream>  // 包含输入输出流库
#include <cstring>   // 包含字符串处理库
#include <algorithm> // 包含算法库

using namespace std; // 使用标准命名空间

int n; // 数列的长度
const int N = 1000010; // 定义一个常量N,代表数列的最大长度

int q[N]; // 创建一个大小为N的整数数组q,用于存储数列
int tmp[N]; // 创建一个大小为N的整数数组tmp,用于归并排序时的临时存储

// 归并函数,用于合并两个有序数组
void merge(int q[], int l, int mid, int r) {
    int i = l, j = mid + 1, k = 0; // 初始化指针i, j, k
    while (i <= mid && j <= r) { // 当i和j都在各自数组的范围内时
        if (q[i] <= q[j]) { // 如果左边的元素小于等于右边的元素
            tmp[k++] = q[i++]; // 将左边的元素放入临时数组,并移动指针
        } else { // 如果左边的元素大于右边的元素
            tmp[k++] = q[j++]; // 将右边的元素放入临时数组,并移动指针
        }
    }
    while (i <= mid) tmp[k++] = q[i++]; // 将左边剩余的元素放入临时数组
    while (j <= r) tmp[k++] = q[j++]; // 将右边剩余的元素放入临时数组
    for (i = l, k = 0; i <= r; i++, k++) q[i] = tmp[k]; // 将临时数组中的元素复制回原数组
}

// 归并排序函数,用于递归地将数组分成两半并进行排序
void merge_sort(int q[], int l, int r) {
    if (l >= r) return; // 如果左指针大于等于右指针,说明数组只有一个元素,不需要排序
    int mid = l + r >> 1; // 计算中间指针
    merge_sort(q, l, mid); // 对左半部分进行归并排序
    merge_sort(q, mid + 1, r); // 对右半部分进行归并排序
    merge(q, l, mid, r); // 合并左右两部分
}

int main() {
    scanf("%d", &n); // 读取数列的长度
    for (int i = 0; i < n; i++) {
        scanf("%d", &q[i]); // 读取数列的每个元素
    }
    merge_sort(q, 0, n - 1); // 对数列进行归并排序
    for (int i = 0; i < n; i++) {
        printf("%d ", q[i]); // 输出排序后的数列
    }
    return 0; // 程序结束
}

2.逆序对的数量

        逆序对的数量是指在一个数组中,所有满足 i < j 且 nums[i] > nums[j] 的数对 (i, j) 的数量。

        在归并排序的过程中,可以统计逆序对的数量。具体的方法是在合并两个有序子数组的步骤中,当将左子数组的元素放入临时数组时,如果右子数组中还剩余的元素比当前的左子数组元素要小,则说明存在逆序对。

具体步骤如下:

  1. 在合并操作中,将左右子数组的元素依次比较,如果右子数组中当前元素小于左子数组当前元素,则存在逆序对。
  2. 统计逆序对的数量,每次发现逆序对时,将逆序对的数量加上右子数组当前元素的位置与左子数组剩余元素的数量,即逆序对的数量 += len(left) - left_pointer。
  3. 将逆序对的数量与左右子数组的合并操作后得到的逆序对数量相加,得到最终的逆序对数量。

        归并排序的逆序对数量的时间复杂度也是O(nlogn)。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int n;
const int N = 1000010;
int q[N],tmp[N];
long long cnt=0;
long long merge(int q[], int l, int mid, int r) {
    int i = l, j = mid + 1, k = 0;
    while (i <= mid && j <= r) {
        if (q[i] <= q[j]) {
            tmp[k++] = q[i++];
        } else {
            tmp[k++] = q[j++];
            cnt+=mid-i+1;
        }
    }
    while (i <= mid) tmp[k++] = q[i++];
    while (j <= r) tmp[k++] = q[j++];
    for (i = l, k = 0; i <= r; i++, k++) q[i] = tmp[k];
    return cnt;
}

long long merge_sort(int q[], int l, int r) {
    if (l >= r) return 0;
    int mid = l + r >> 1;
    merge_sort(q, l, mid);
    merge_sort(q, mid + 1, r);
    return merge(q, l, mid, r);
}
int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        scanf("%d", &q[i]);   
    }
    cout<<merge_sort(q,0,n-1);

}

猜你喜欢

转载自blog.csdn.net/lys20221113242/article/details/143431833