算法导论 第2章 算法基础(插入排序,归并排序,冒泡排序,逆序对查找)

终于还是看《算法导论》这一本大厚书了,今天粗略看了看第2章的内容,讲的主要是几个排序问题,于是我就想着把里面伪代码用C/C++实现一下,一来可以加深我对算法的理解,同时也可以分享一下我的想法,可能对初看《算法导论》这本书的初学者有所帮助。废话不多说,直接开始。


插入排序(Insertion Sort)

顾名思义,在进行插入排序的时候,就像打扑克牌一样,将一个“牌”插入到已有的、排好序的牌中,使其仍有顺序。这个书上应该讲的挺详细了,就直接实现了:

#include <bits/stdc++.h>
using namespace std;

void insertion_sort(int *a)
{
    int n = a[0];
    for (int j = 2; j <= n; j++)
    {
        int key = a[j];
        // Insert a[j] into the sorted sequence a[1: j - 1]
        int i = j - 1;
        while (i > 0 && a[i] > key)
        {
            a[i + 1] = a[i];
            i = i - 1;
        }
        a[i + 1] = key;
    }
}

int main(int argc, char *argv[])
{
    int n = 5;
    int *a = new int[n + 1];
    a[0] = n, a[1] = 2, a[2] = 1, a[3] = 4, a[4] = 5, a[5] = 3;

    insertion_sort(a);

    for (int i = 1; i <= n; i++)
        cout << a[i] << " ";
    cout << endl;

    return 0;
}

这里涉及到一点数据结构。如果我想要保存n个数字到一个数组,那么我可以声明一个n+1长度的数组(记为a),其中 a[0] 表示数组长度,这样在每一次函数传参的时候就不用单独的用一个参数表示数组长度。


归并排序(Merge Sort)

其中的思想主要是分治递归,简言之就是将一个大问题通过二分(或其他)分解为若干小问题,直到分解为一个足够小(小到可以直接得出结果,标准可以自己确定)的问题,然后将小问题的结果慢慢合并成大问题直到原问题。

#include <bits/stdc++.h>
using namespace std;

const int inf = 0x3f3f3f3f;

void merge(int *A, int p, int q, int r)
{
    int n1 = q - p + 1; //length of left subsequence
    int n2 = r - q;     //length of right subsequence
    int *L = new int[n1 + 2];
    int *R = new int[n2 + 2];
    L[0] = n1 + 1, R[0] = n2 + 1;
    for (int i = 1; i <= n1; i++)
        L[i] = A[p - 1 + i];
    for (int i = 1; i <= n2; i++)
        R[i] = A[q + i];
    L[n1 + 1] = inf;
    R[n2 + 1] = inf;
    int i = 1, j = 1;
    for (int k = p; k <= r; k++)
    {
        if (L[i] <= R[j])
        {
            A[k] = L[i];
            i = i + 1;
        }
        else
        {
            A[k] = R[j];
            j = j + 1;
        }
    }
}

void merge_sort(int *A, int p, int r)
{
    if (p < r)
    {
        int q = (p + r) >> 1; // floor
        merge_sort(A, p, q);
        merge_sort(A, q + 1, r);
        merge(A, p, q, r);
    }
}

int main(int argc, char *argv[])
{
    int n = 9;
    int *A = new int[n + 1];
    // A = [n, 4, 1, 9, 5, 3, 7, 2, 6, 8]
    A[0] = n, A[1] = 4, A[2] = 1, A[3] = 9, A[4] = 5, A[5] = 3, A[6] = 7, A[7] = 2, A[8] = 6, A[9] = 8;

    merge_sort(A, 1, n);

    for (int i = 1; i <= n; i++)
        cout << A[i] << " ";
    cout << endl;

    return 0;
}

其中,上述代码merge(int *, int, int, int)函数在之后的思考题中也有扩展:就是能不能不利用一个无穷大来判断是不是读到尾。我们可以直接判断两个子列的index(也就是i和j)是不是到达了相应子列的长度,如果有一个已经读完了(i == n1 + 1 || j == n2 + 1),这时候就可以跳出循环,进入一个小循环,将还没有读完的子列全部移到A中即可。代码如下:

void merge(int *A, int p, int q, int r)
{
    int n1 = q - p + 1; //length of left subsequence
    int n2 = r - q;     //length of right subsequence
    int *L = new int[n1 + 1];
    int *R = new int[n2 + 1];
    L[0] = n1, R[0] = n2;
    for (int i = 1; i <= n1; i++)
        L[i] = A[p - 1 + i];
    for (int i = 1; i <= n2; i++)
        R[i] = A[q + i];
    int i = 1, j = 1, k;
    for (k = p; k <= r; k++)
    {
        if (i == n1 + 1 || j == n2 + 1) // overflow the length of subsequence
            break;
        if (L[i] <= R[j])
        {
            A[k] = L[i];
            i = i + 1;
        }
        else
        {
            A[k] = R[j];
            j = j + 1;
        }
    }
    if (i == n1 + 1)
    {
        for (; k <= r; k++)
        {
            A[k] = R[j];
            j = j + 1;
        }
    }
    else
    {
        for (; k <= r; k++)
        {
            A[k] = L[i];
            i = i + 1;
        }
    }
    // for (; k <= r; k++)         // same as the function of if-else above
    //     A[k] = i == n1 + 1 ? R[j++] : L[i++];
}

通过分析,我们又可以看出,最后的那个if-else语句可以直接使用两行(也就是最后注释掉的两行代码)包含三元运算符的过程实现。


冒泡排序(Bubble Sort)

冒泡排序恐怕是所有排序算法中最简单的一个了,思想也很简单,不做赘述,放代码:

#include <bits/stdc++.h>
using namespace std;

void bubble_sort(int *A)
{
    int n = A[0];
    for (int i = 1; i <= n - 1; i++)
        for (int j = n; j >= i + 1; j--)
            if (A[j] < A[j - 1])
                swap(A[j], A[j - 1]);
}

int main(int argc, char *argv[])
{
    int n = 9;
    int *A = new int[n + 1];
    // A = [n, 4, 1, 9, 5, 3, 7, 2, 6, 8]
    A[0] = n, A[1] = 4, A[2] = 1, A[3] = 9, A[4] = 5, A[5] = 3, A[6] = 7, A[7] = 2, A[8] = 6, A[9] = 8;

    bubble_sort(A);

    for (int i = 1; i <= n; i++)
        cout << A[i] << " ";
    cout << endl;

    return 0;
}

逆序对(Reverse Pair)

这个是第二章最后的那个思考题,就是能不能对归并排序算法稍加改造,使其能够统计数列中逆序对的个数。这就要求我们对归并排序的过程有更深的思考了,要想一想在什么时候发生了大小比较,很明显,就是在merge(int*, int, int, int)中。因此,需要对merge(int*, int, int, int)函数稍加改造,在进行合并的时候顺便把逆序对数统计了。
在进行合并的时候,需要对L[]和R[]中的元素进行比较,如果有L[i]<=R[j],就会将L[i]添加到A[]中,否则,将R[j]添加到A[]中。注意到在将R[j]添加到A[]的情况下,由于R[]中元素的index一定大于L[]中元素的index,因此这种情况下满足逆序对条件,即i<j&&A[i]>A[j],此时,L[i … q]与R[j]一定可以组成逆序对(L和R中均已在子问题中被排好序),因此直接将逆序对个数加上(q-i)即可。具体如下图所示:
在这里插入图片描述
简单画了一个图表示,大致意思就是这。代码如下:

#include <bits/stdc++.h>
using namespace std;

void merge_variation(int *A, int p, int q, int r, int &res)
{
    int n1 = q - p + 1;
    int n2 = r - q;
    int *L = new int[n1 + 1];
    int *R = new int[n2 + 1];
    L[0] = n1, R[0] = n2;
    for (int i = 1; i <= n1; i++)
        L[i] = A[p - 1 + i];
    for (int i = 1; i <= n2; i++)
        R[i] = A[q + i];
    int i = 1, j = 1, k;
    for (k = p; k <= r; k++)
    {
        if (i == n1 + 1 || j == n2 + 1)
            break;
        if (L[i] <= R[j])
            A[k] = L[i++];
        else
        {
            A[k] = R[j++];
            res += (q - i); // this is the crucial key
        }
    }
    for (; k <= r; k++)
        A[k] = i == n1 + 1 ? R[j++] : L[i++];
}

void merge_sort_variation(int *A, int p, int r, int &res)
{
    if (p < r)
    {
        int q = (p + r) >> 1;
        merge_sort_variation(A, p, q, res);
        merge_sort_variation(A, q + 1, r, res);
        merge_variation(A, p, q, r, res);
    }
}

int main(int argc, char *argv[])
{
    int n = 5, res = 0;
    int *A = new int[n + 1];
    A[0] = n, A[1] = 2, A[2] = 3, A[3] = 1, A[4] = 6, A[5] = 1;

    merge_sort_variation(A, 1, n, res);

    cout << res << endl;

    return 0;
}

好啦,这就是我的分享了,如果读者朋友发现有错误之处还请在评论区指出噢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A91A981E

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值