zoukankan      html  css  js  c++  java
  • 系统程序员成长计划动态数组(三)(下)

    转载时请注明出处和作者联系方式
    文章出处:http://www.limodev.cn/blog
    作者联系方式:李先静 <xianjimli at hotmail dot com>

    排序

    对于前面提的两点额外要求:

    o 算法同时支持升序和降序。

    o 算法同时支持多种数据类型。

    只要认真阅读过前面章节的读者,马上会想到用回调函数。这是对的。软件设计的关键在于熟能生巧,我们反复练习这些基本技巧也意在于此。熟到凭本能就可以运用正确的方法时,那也离所谓的高手不远了。言归正传,我们已经定义过比较回调函数的原型了:

    typedef int (*DataCompareFunc)(void* ctx, void* data);

    我们先实现一个整数的比较函数,升序比较函数的实现如下:

    int int_cmp(void* a, void* b)
    {
    return (int)a - (int)b;
    }

    降序比较函数的实现如下:

    int int_cmp_invert(void* a, void* b)
    {
    return (int)b - (int)a;
    }

    比较函数也不依赖于具体的数据类型,这样我们就把算法的变化部分独立出来了,是升序还是降序完全由回调函数决定。下面我们看看算法的具体实现。

    冒泡排序

    冒泡排序的名字很形象的表达了算法的原理,对于降序排列时,排序可以认为是轻的物体不断往上浮的过程。不过对于升序排序,排序认为是重的物体不断向 下沉更为合适。升序排列更符合人类的思考方式,这里我们按升序排序来实现冒泡排序(通过使用不同的比较函数,同样支持降序排序)。

    Ret bubble_sort(void** array, size_t nr, DataCompareFunc cmp)
    {
    size_t i = 0;
    size_t max = 0;
    size_t right = 0;

    return_val_if_fail(array != NULL && cmp != NULL, RET_INVALID_PARAMS);

    if(nr < 2)
    {
    return RET_OK;
    }

    for(right = nr - 1; right > 0; right--)
    {
    for(i = 1, max = 0; i < right; i++)
    {
    if(cmp(array[i], array[max]) > 0)
    {
    max = i;
    }
    }

    if(cmp(array[max], array[right]) > 0)
    {
    void* data = array[right];
    array[right] = array[max];
    array[max] = data;
    }
    }

    return RET_OK;
    }

    冒泡排序是最简单直观的排序算法,教科书上通常作为第一个排序算法来讲。从性能上来看,与其它高级排序算法相比,它似乎没有存在的理由。它除了教学目的之外,是否有实际价值呢?答案是有的。原因有两点:

    o实现简单,简单的程序通常更可靠。虽然我很多年没有写过冒泡排序算法了,从写代码、编译到测试,都一次性通过了。写快速排序时却出了好几次错误, 而且最后参考了教科书才完成。如果非要自己动手写排序算法时,我会先写一个冒泡排序算法,直到一定需要更快的算法时才会考虑其它算法。

    o 在小量数据时,所有排序算法性能差别不大。有文章指出,高级排序算法在元素个数多于1000时,性能才出现显著提升。在90%的情况下,我们存储的元素个数只有几十到上百个而已,比如进程数、窗口个数和配置信息等等的数量都不会很大,冒泡排序其实是更好的选择。

    请记住:在完成同样任务的情况下,越简单越好。
    同时记住:学从难处学,用从易处用。

    快速排序

    快速排序当然是以其性能优异出名了,而且它不需要额外的空间。如果数据量大而且全部在内存中时,快速排序是首选的排序方法。排序过程是先将元素分成 两个区,所有小于某个元素的值在第一个区,其它元素在第二区。然后分别对这两个区进行快速排序,直到所分的区只剩下一个元素为止。

    void quick_sort_impl(void** array, size_t left, size_t right, DataCompareFunc cmp)
    {
    size_t save_left = left;
    size_t save_right = right;
    void* x = array[left];

    while(left < right)
    {
    while(cmp(array[right], x) >= 0 && left < right) right--;
    if(left != right)
    {
    array[left] = array[right];
    left++;
    }

    while(cmp(array[left], x) <= 0 && left < right) left++;
    if(left != right)
    {
    array[right] = array[left];
    right--;
    }
    }
    array[left] = x;

    if(save_left < left)
    {
    quick_sort_impl(array, save_left, left-1, cmp);
    }

    if(save_right > left)
    {
    quick_sort_impl(array, left+1, save_right, cmp);
    }

    return;
    }

    Ret quick_sort(void** array, size_t nr, DataCompareFunc cmp)
    {
    Ret ret = RET_OK;

    return_val_if_fail(array != NULL && cmp != NULL, RET_INVALID_PARAMS);

    if(nr > 1)
    {
    quick_sort_impl(array, 0, nr - 1, cmp);
    }

    return ret;
    }

    战胜软件复杂度是《系统程序员成长计划》的中心思想之一。战胜软件复杂度包括防止复杂度增长和降低复杂度两个方面。降低复杂度的方法主要有抽象和分而治之两种,快速排序则是分而治之的具体体现。

    归并排序

    与快速排序一样,归并排序也是分而治之的应用。不同的是,它先让左右两部分进行排序,然后把它们合并起来。在排序左右两部分时,同样使用归并排序。快速排序可以认为是自顶向下的方法,而归并排序可以认为是自底向上的方法。

    static Ret merge_sort_impl(void** storage, void** array, size_t low, size_t mid, size_t high, DataCompareFunc cmp)
    {
    size_t i = low;
    size_t j = low;
    size_t k = mid;

    if((low + 1) < mid)
    {
    size_t x = low + ((mid - low) >> 1);
    merge_sort_impl(storage, array, low, x, mid, cmp);
    }

    if((mid + 1) < high)
    {
    size_t x = mid + ((high - mid) >> 1);
    merge_sort_impl(storage, array, mid, x, high, cmp);
    }

    while(j < mid && k < high)
    {
    if(cmp(array[j], array[k]) <= 0)
    {
    storage[i++] = array[j++];
    }
    else
    {
    storage[i++] = array[k++];
    }
    }

    while(j < mid)
    {
    storage[i++] = array[j++];
    }

    while(k < high)
    {
    storage[i++] = array[k++];
    }

    for(i = low; i < high; i++)
    {
    array[i] = storage[i];
    }

    return RET_OK;
    }

    Ret merge_sort(void** array, size_t nr, DataCompareFunc cmp)
    {
    void** storage = NULL;
    Ret ret = RET_OK;

    return_val_if_fail(array != NULL && cmp != NULL, RET_INVALID_PARAMS);

    if(nr > 1)
    {
    storage = (void**)malloc(sizeof(void*) * nr);
    if(storage != NULL)
    {
    ret = merge_sort_impl(storage, array, 0, nr>>1, nr, cmp);

    free(storage);
    }
    }

    return ret;
    }

    归并排序需要额外的存储空间,其为被排序的数组一样大。大部分示例代码里,都在每次递归调用中分配空间,这些会带来性能上的下降。这里我们选择了事先分配一块空间,在排序过程中重复使用,算法更简单,性能也得到提高。

    根据把要排序的数组分成N个部分,可以把归并排序称为N路排序。上面实现的归并排序实际是归并算法一个特例:两路归并。看似归并排序与快速排序相比 几乎没有优势可言,但是归并排序更重要的能力在于处理大量数据的排序,它不要求被排序的数据全部在内存中,所以在数据大于内存的容纳能力时,归并排序就更 能大展身手了。归并排序最常用的地方是数据库管理系统(DBMS),因为数据库中存储的数据通常无法全部加载到内存中来的。有兴趣的读者可以阅读相关资 料。

    排序算法的测试

    排序算法的实现不同,但它们的目的都一样:让数据处于有序状态。所以在写自动测试时,没有必要为每一种算法写一个测试程序。通过将排序算法作为回调函数传入,我们可以共用一个测试程序:

    static void** create_int_array(int n)
    {
    int i = 0;
    int* array = (int*)malloc(sizeof(int) * n);

    for(i = 0; i < n; i++)
    {
    array[i] = rand();
    }

    return (void**)array;
    }

    static void sort_test_one_asc(SortFunc sort, int n)
    {
    int i = 0;
    void** array = create_int_array(n);

    sort(array, n, int_cmp);

    for(i = 1; i < n; i++)
    {
    assert(array[i] >= array[i-1]);
    }

    free(array);

    return;
    }

    void sort_test(SortFunc sort)
    {
    int i = 0;
    for(i = 0; i < 1000; i++)
    {
    sort_test_one_asc(sort, i);
    }

    return ;
    }

    集成排序算法到动态数组中

    把排序算法集中到动态数组并不合适,原因有:

    o 绑定动态数组与特定算法不如让用户根据需要去选择。

    o在动态数组中实现排序算法不利于算法的重用。

    所以我们给动态数组增加一个排序函数,但排序算法通过回调函数传入:

    Ret    darray_sort(DArray* thiz, SortFunc sort, DataCompareFunc cmp);

    本节示例代码请到这里下载。

  • 相关阅读:
    layaAir引擎制作游戏的图集动画、时间轴动画、和骨骼动画总结二
    layaAir引擎制作游戏的图集动画、时间轴动画、和骨骼动画总结一
    Flask 生成验证码 支持干扰线、噪点
    数组操作
    css_权威指南_选择符
    css权威指南_特指度
    *arg **kwargs
    一日一库—importlib
    一日一库—itertools
    FLask 流程图、上下文、上下文隔离原理
  • 原文地址:https://www.cnblogs.com/zhangyunlin/p/6167561.html
Copyright © 2011-2022 走看看