四种基础排序算法

希尔排序

希尔排序(ShellSort) 的思路有点奇特, 或许是书上表述的不清或许是我理解歪了, 真正了解希尔排序是怎样一种排序方法花了了差不多一个小时. 现在理解透了, 觉得真是很妙.

按照所规定的步长, 覆盖全部元素, 跳跃着进行插入排序, 暂时忽略中间的元素. 这也许就是希尔排序的内涵.

大概分为两个过程

  1. 根据所给数据设定步长. (我用的是n不断除2向下取整的策略, 这个策略不是最优的)
  2. 对每个步长, 从开始到数据尾部进行跳跃式的插入排序. (每个元素都会参与)

维基百科-希尔排序

演示
这里写图片描述

注意:

步长的最后一个一定是1, 也就是说, 最后进行的是一个完整的常规的插入排序, 但是因为前面所做的工作, 在这一步中, 所需的操作已经很少了. 这对理解希尔排序有一定帮助.

算法的复杂度在O(n^2)和O(nlog₂n)之间. 占用空间大概就是存储序列的空间+1.

C++代码

#include <iostream>
using namespace std;

const int maxn = 1005;
int n;

void ShellInsert(int *arr, int dk)
{
    for (int i = dk + 1; i <= n; ++i) {
        int j = i - dk;
        arr[0] = arr[i];
        for (; j > 0 && arr[0] < arr[j]; j -= dk) {
            arr[j + dk] = arr[j];
        }
        arr[j + dk] = arr[0];
    }
}

void ShellSort(int *arr, int *dlta, int size)
{
    for (int i = 0; i < size; ++i) {
        ShellInsert(arr, dlta[i]);
        cout << "\n第 " << i + 1 << " 趟排序:\n"; 
        for (int j = 1; j <= n; ++j) {
            cout << arr[j] << ' ';
        }
        cout << endl;
    }
}

int main()
{
    int array[maxn];
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> array[i];
    }
    int tmp = n, k = 0, dlta[maxn];
    while (tmp > 1) {
        dlta[k++] = tmp / 2;
        tmp /= 2;
    }
    ShellSort(array, dlta, k);
}
/*
8
49 39 65 97 76 13 27 69

7
7 1 5 4 3 6 2

10 
49 38 65 97 76 13 27 49 55 04  // 5 3 1
*/ 

归并排序

这里介绍的归并排序是最基础的二路归并. 归并排序的思想很简单, 就是分治的思想.

分而治之, 把一个序列分成两段, 分别对这两个序列再排序. 不断地划分后, 最后的序列就是一个元素. 那么再依次往上合并.

使用递归非常方便实现. 也可以用非递归实现.

维基百科-归并排序

归并排序演示
这里写图片描述

以及

递归版本

有两个重要函数

  1. MergeSort()函数, 这个是归并排序的主体, 它解决的问题是下标从left到right的排序. 在程序中将会调用自身.
  2. Merge()函数, 这个函数被MergeSort()函数调用, 它解决的问题是将left和right的两个分别排好序的序列合并.

通过以上两个函数, 就可以实现归并排序了.

归并排序的时间复杂度最差是O(nlog₂n), 最优是O(n), 且是一种稳定排序

递归代码

#include <iostream>
using namespace std;

const int maxn = 1005;

void merge(int *A, int L1, int R1, int L2, int R2)
{
    int tmp[maxn], index = 0;
    int i = L1, j = L2;
    while (i <= R1 && j <= R2) {
        if (A[i] < A[j]) tmp[index++] = A[i++];
        else tmp[index++] = A[j++];
    }
    while (i <= R1) tmp[index++] = A[i++];
    while (j <= R2) tmp[index++] = A[j++];
    for (int k = L1; k <= R2; ++k) {
        A[k] = tmp[k - L1];
    }
}

void mergeSort(int *A, int left, int right)
{
    if (left < right) {
        int mid = (left + right) / 2;
        mergeSort(A, left, mid);
        mergeSort(A, mid + 1, right);
        merge(A, left, mid, mid + 1, right);
    }
}

int main()
{
    int A[maxn], n;
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> A[i];
    }
    mergeSort(A, 1, n);

    cout << "排序结果:\n";
    for (int i = 1; i <= n; ++i) {
        cout << A[i] << ' ';
    }
    cout << endl;
}

/*
7
7 1 5 4 3 6 2

10 
49 38 65 97 76 13 27 49 55 04
*/ 

非递归版本

思路是基本一样的.

非递归版本在选取的步长上做了些学问

#include <iostream>
#include <algorithm>
using namespace std;

const int maxn = 1005;

void Merge(int *A, int L1, int R1, int L2, int R2)
{
    int i = L1, j = L2;
    int index = 0, tmp[maxn];
    while (i <= R1 && j <= R2) {
        if (A[i] < A[j]) tmp[index++] = A[i++];
        else tmp[index++] = A[j++];
    }
    while (i <= R1) tmp[index++] = A[i++];
    while (j <= R2) tmp[index++] = A[j++];
    for (int k = L1; k <= R2; ++k) {
        A[k] = tmp[k - L1];
    }
}

void MergeSort(int *A, int n)
{
    for (int step = 2; step / 2 <= n; step *= 2) {
        for (int i = 1; i <= n; i += step) {
            int mid = i + step / 2 - 1;
            if (mid + 1 <= n)  // 右边要存在才行, 不要漏掉= 
                Merge(A, i, mid, mid + 1, min(n, i + step - 1)); // 不能大于n
        }
    }
}

int main()
{
    int n, A[maxn];
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> A[i];
    }
    MergeSort(A, n);

    cout << "排序结果:\n";
    for (int i = 1; i <= n; ++i) {
        cout << A[i] << ' ';
    }
    cout << endl;
}

/*
7
7 1 5 4 3 6 2

10 
49 38 65 97 76 13 27 49 55 04
*/ 

堆排序

堆排序(英语:Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

通常堆是通过一维数组来实现的。在数组起始位置为1的情形中:
父节点i的左子节点在位置为 2i
父节点i的右子节点在位置为 2i + 1
子节点i的父节点在位置 i / 2

利用这个性质可以很方便的进行父亲节点和子结点之间的跳转.

维基百科-堆排序
堆排序

堆排序可以简化为两个过程

  1. 将一个序列堆化
  2. 输出堆顶元素, 然后交换堆顶元素和序列尾部元素, 总数-1, 再进行堆化.

可见, 随着不断输出堆顶元素,序列就已经排好序了.

第一个过程中,要进行n/2次向下调整,向下调整在代码中是用downAdjust函数实现.
第二个过程中,只要把交换上去的最后一个元素进行向下调整即可.

堆排序的时间复杂度是O(nlogn)空间复杂度是O(n)

#include <iostream>
#include <algorithm>
using namespace std;

const int maxn = 1005;

void downAdjust(int *A, int low, int high)
{
    int i = low, j = 2 * low;
    while (j <= high) {
        if (j + 1 <= high && A[j] < A[j + 1]) {
            j += 1;
        }
        if (A[i] < A[j]) {
            swap(A[i], A[j]);
            i = j;
            j *= 2;
        } else break;
    }
}

void HeapSort(int *A, int n)
{
    for (int i = n / 2; i >= 1; --i) {
        downAdjust(A, i, n);
    }

    int m = n;
    for (int i = 1; i <= m; ++i) {
        cout << A[1] << ' ';
        swap(A[1], A[n--]);
        downAdjust(A, 1, n);
    }
    cout << endl;
}

int main()
{
    int n, A[maxn];
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> A[i];
    }
    HeapSort(A, n);
}
/*
7
4 3 6 7 2 5 1 

10 
49 38 65 97 76 13 27 49 55 04
*/

如果需要插入新的元素的话, 可以使用upAdjust函数, 即向上调整. 时间复杂度为O(logn)

void upAdjust(int low, int high)
{
    int i = high, j = i / 2;
    while (j >= low) {
        if (heap[j] < heap[i]) {
            swap(heap[j], heap[i]);
            i = j;
            j = i / 2;
        } else break;
    }
} 

快速排序

快速排序(英语:Quicksort),又称划分交换排序(partition-exchange sort),简称快排,一种排序算法,最早由东尼·霍尔提出。快速排序通常明显比其他算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地达成。

快速排序平均时间复杂度是O(nlogn), 最差是O(n²).

维基百科-快速排序
这里写图片描述
这里写图片描述
这里写图片描述

按照我的经历, 快速排序比较难写. 虽然代码简洁, 但是写出的代码非常非常容易出bug!里面有很多<, <= 等边界控制, 很容易出错的. 一般来说, 隔一段时间再写快排, 常常要参照以前的代码debug.

快排被研究得比较多, 有很多的优化, 感兴趣的可以做深入了解.

示例代码, 此代码是以中间数为基准点, 平均来说似乎比以区间第一个或最后一个为基准要快(根据做题的经验)

#include <iostream>
using namespace std;

const int maxn = 1005;
int n, A[maxn];

void QuickSort(int *A, int l, int r)
{
    if (l >= r) return;
    int mid = A[(l + r) >> 1];
    int i = l, j = r;
    while(i <= j) {
        while (A[i] < mid) i++;
        while (A[j] > mid) j--;
        if (i <= j) swap(A[i++], A[j--]);
    }
    if (j > l) QuickSort(A, l, j);
    if (i < r) QuickSort(A, i, r);
}

void show(int *A, int n)
{
    for (int i = 0; i < n; ++i)
        cout << A[i] << ' ';
    cout << endl;
}

int main()
{
    cin >> n;
    for (int i = 0; i < n; ++i) {
        cin >> A[i];
    }
    QuickSort(A, 0, n - 1);
    show(A, n);
}

/*
10
85 55 82 57 68 92 99 98 66 56
4
43 101 96 -1
5
3 2 5 1 4 
*/

以上就是四种基础的排序算法

希尔排序
归并排序
堆排序
快速排序

至于更基础的, 或者更复杂度, 都不在此博客讨论范围之内.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值