三种基础排序算法及其拓展应用

三种基础排序算法及其拓展应用

排序是在算法竞赛中经常用到的操作,排序的算法有很多,大多数人的入门算法大多都是冒泡排序,插入排序等 O(n2) 的算法,当数据量比较大时,这个复杂度是不能容忍的。我们在竞赛中用到的最多的三种排序算法,分别是归并排序, 快速排序和堆排序,它们的复杂度都是 O(nlogn) ,这是基于交换的排序算法所能达到的复杂度下限。

一、归并排序

归并排序是《算法导论》中分治法的入门算法。
分治法,就是把大问题分解成小问题,解决小问题,然后再通过小问题的解得出大问题的解
归并排序:

分解:分解待排序的n个元素的序列各成具n/2个元素的两个子序列。
解决:使用归并排序递归地排序两个子序列。
合并:合并两个已排序的子序列以产生已排序的答案

我们先来解释如何合并两个已经排好序的子序列:
因为两个子已经有序,所以最小值一定是这两个序列的第一个元素中较小的那一个。一直这样取就可以了。当其中一个序列已经取空,则直接依次取另一个序列的值。

再来看如何分解并解决子问题:
举个栗子:

a[]16a[015]a[07]a[815]a[07]a[815]
a[07]a[03]a[47]a[03]a[47]
a[03]a[01]a[23]a[01]a[23]
a[01]a[0]a[1]a[0]a[1]

代码:

#include <iostream>
#define MAXN 100000
using namespace std;

int n, a[MAXN], tmp[MAXN];

void merge(int l, int m, int r) {
    int k = l, i = l, j = m+1;
    while (i <= m && j <= r) {
        if (a[i] < a[j]) tmp[k++] = a[i++];
        else tmp[k++] = a[j++];
    }
    while (i <= m) tmp[k++] = a[i++];
    while (j <= r) tmp[k++] = a[j++];
    for (i = l; i <= r; i++) a[i] = tmp[i];
}

void mergeSort(int l, int r) {
    if (l < r) {
        int m = (l + r) / 2; //分解成两个子问题
        mergeSort(l, m);  //解决子问题
        mergeSort(m+1, r);
        merge(l, m, r); //合并
    }
}

int main() {
    cin >> n;
    for (int i = 0; i < n; i++)
        cin >> a[i];

    mergeSort(0, n-1);

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

应用:1. 逆序对

冒泡排序每次只能交换相邻的两个数字的位置,求用冒泡排序算法排序这个序列所需要的交换次数。
比如 9 1 0 5 4, 排序后为 1 0 4 5 9, 用冒泡排序最少需要交换6次。

限定数据范围 n <= 500000 , 即直接用冒泡排序 O(n2) 模拟是超时的。

思路:

可以分而治之,把长度为n的序列分成两个长度为n/2的子序列,分别求出这两个子序列内部需要的交换次数,再加上合并这两个子序列需要的交换次数。

代码:
POJ-2299 Ultra-QuickSort 逆序对,冒泡排序的交换次数

#include <iostream>
#include <cstring>
#define MAXN 500010
using namespace std;

long long n, a[MAXN], tmp[MAXN];
long long ans = 0;

void merge(int l, int m, int r) {
    int k = l, i = l, j = m+1;
    while (i <= m && j <= r) {
        if (a[i] <= a[j]) tmp[k++] = a[i++];
        else {
            ans += m - i + 1; // 添加一行,逆序对计数
            tmp[k++] = a[j++];
        }
    }
    while (i <= m) tmp[k++] = a[i++];
    while (j <= r) tmp[k++] = a[j++];
    for (int i = l; i <= r; i++) a[i] = tmp[i];
}

void mergeSort(int l, int r) {
    if (l < r) {
        int m = (l + r) / 2;
        mergeSort(l, m);
        mergeSort(m+1, r);
        merge(l, m, r);
    }
}

int main() {
    while (cin >> n) {
        if (n == 0) break;
        ans = 0;
        memset(a, 0, sizeof(a));
        memset(tmp, 0, sizeof(tmp));
        for (int i = 0; i < n; i++)
            cin >> a[i];
        mergeSort(0, n-1);
        cout << ans << endl;
    }
    return 0;
}

二、快速排序

快速排序用的也是分治法。

快速排序:

a[l…r] a[l…p-1] a[p+1…r] 使 a[l…p-1] a[p] ,a[p] a[p+1…r] p a[l...p-1] a[p+1...r] : a[l...r]$ 已经有序。

具体解释和证明参考《算法导论》

代码:

#include <iostream>
#include <cstdlib>
#define MAXN 500000
using namespace std;

int n, a[MAXN];

void swap(int &a, int &b) {
    int t = a;
    a = b;
    b = t;
}

int partition(int l, int r) {
    int x = a[r];
    int i = l - 1;
    for (int j = l; j < r; j++) {
        if (a[j] <= x) {
            i++;
            swap(a[i], a[j]);
        }
    }
    swap(a[i+1], a[r]);
    return i+1;
}

int random_partition(int l, int r) {
    int i = rand() % (r - l) + l;
    swap(a[i], a[r]);
    return partition(l, r);
}

void quickSort(int l, int r) {
    if (l < r) {
        int p = random_partition(l, r);
        quickSort(l, p-1);
        quickSort(p+1, r);
    }
}

int main() {
    cin >> n;
    for (int i = 0; i < n; i++)
        cin >> a[i];
    quickSort(0, n-1);
    for (int i = 0; i < n; i++)
        cout << a[i] << " ";
    cout << endl;
    return 0;
}

1. 第k大数字

代码:

#include <iostream>
#include <cstdlib>
#define MAXN 1000000
using namespace std;

int n, a[MAXN];

void swap(int &a, int &b) {
    int t = a; a = b; b = t;
}

int partition(int l, int r) {
    int x = a[r];
    int i = l - 1;
    for (int j = l; j < r; j++) {
        if (a[j] < x) {
            i++;
            swap(a[i], a[j]);
        }
    }
    swap(a[i+1], a[r]);
    return i+1;
}

int random_partition(int l, int r) {
    int i = rand() % (l - r) + l;
    swap(a[i], a[r]);
    return partition(l, r);
}

int random_select(int l, int r, int k) {
    if (l == r)
        return a[l];
    int p = random_partition(l, r);
    int t = p - l + 1;
    if (t == k)
        return a[p];
    else if (k < t) return random_select(l, p-1, k);
    else return random_select(p+1, r, k - t);
}

int main() {
    cin >> n;
    for (int i = 0; i < n; i++)
        cin >> a[i];
    int k;
    cin >> k;
    cout << random_select(0, n-1, k) << endl;
}

三、堆排序

代码:

/*
ID: zachery1
PROG: sort3
LANG: C++
*/
#include <iostream>
#include <fstream>
#include <cstring>
#define MAXN 1010
#define LEFT(x) (x<<1)
#define RIGHT(x) ((x<<1)+1)
#define PARENT(x) (x>>1)
using namespace std;



int N;
int heap[MAXN];
int heapsize;

void swap(int &a, int &b) {
    int t;
    t = a; a = b; b = t;
}

void maxHeapify(int i) {
    int l = LEFT(i);
    int r = RIGHT(i);
    int largest = i;
    if (l <= heapsize && heap[l] > heap[largest])
        largest = l;
    if (r <= heapsize && heap[r] > heap[largest])
        largest = r;
    if (largest != i) {
        swap(heap[i], heap[largest]);
        maxHeapify(largest);
    }
}

void buildHeap() {
    for (int i = PARENT(heapsize); i > 0; i--) {
        maxHeapify(i);
    }
}

void heapSort() {
    heapsize = N;
    buildHeap();
    for (int i = N; i > 0; i--) {
        swap(heap[1], heap[i]);
        heapsize--;
        maxHeapify(1);
    }
}


int main() {
    cin >> N;
    memset(heap, 0, sizeof(heap));
    for (int i = 1; i <= N; i++) {
        cin >> heap[i];
    }
    heapSort();
    for (int i = 1; i <= N; i++)
        cout << i << " "<< heap[i].v << " " << heap[i].idx << endl;

    cout << ans << endl;
}

1. top-N

heapSort()的过程,只排序前N个就好了。
C++ STL 的实现: priority_queue(), 即优先队列。

算法可视化:http://www.cs.usfca.edu/~galles/visualization/Algorithms.html

待整理 排序的交换次数

POJ-2299 Ultra-QuickSort 逆序对 冒泡排序的交换次数
POJ-1674(n的一个全排列中要变成顺序需要几次交换)
USACO-Section2.1 Sorting A Three-Valued Sequence 含有重复元素的交换 简化版

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值