【问题标题】:How to determine the ordering of elements in a vector? [closed]如何确定向量中元素的顺序? [关闭]
【发布时间】:2015-05-01 16:20:07
【问题描述】:

这看起来很奇怪,但我在以下问题上没有发现任何东西:给定一个长度为n 的向量x,如何构造一个长度为n 的向量oo 使得x[oo[i]] (对于i=1,..,n)已排序。要求:只允许使用标准库中的 C 函数,并且代码必须要快(注意:我不是 C 程序员,但有 R 经验。R 有 order() 完成此任务。

我找到了here 的帖子,但这直接讨论了排序。

【问题讨论】:

  • 答案在您发布的链接中。
  • @CroCo:不,答案在发布的链接中并不简单。为了能够使用qsort(),向量x 必须可用作全局变量,可以从传递给qsort() 的比较器访问,这限制了任何解决方案的灵活性。
  • 请注意,C 索引大小为 N 的数组从 0 到 N-1,而不是从 1 到 N。需要哪种索引方案 - 基于 0(正常)的 C 数组或基于 1(不寻常) 数组。
  • @JonathanLeffler:谢谢你的帮助,乔纳森。从 0 开始很好。
  • @JonathanLeffler qsort 没有任何全局变量要求。你将它传递给void 的指针,它可能指向一个包含执行排序所需的任何其他信息的结构。

标签: c sorting


【解决方案1】:

您链接到 (C library function to do sort) 的问题一般显示了如何使用称为 qsort() 的标准 C 库函数,但您的要求不是常见问题之一。为了能够对oo 数组进行排序,比较器函数必须能够访问x 数组以及从qsort() 本身传递给它的数据。

此代码以合理的经济性实现了这一目标:

#include <stdio.h>
#include <stdlib.h>

typedef double VecType;
#define PRIf_VecType "f"

static VecType *base;

static int compare(const void *p1, const void *p2)
{
    const int i1 = *(int *)p1;
    const int i2 = *(int *)p2;
    if (base[i1] < base[i2])
        return -1;
    else if (base[i1] > base[i2])
        return +1;
    else
        return 0;
}

static void print_arrays(const char *tag, size_t x_size, VecType *x, int *oo)
{
    printf("%s:\n", tag);
    for (size_t i = 0; i < x_size; i++)
        printf("%zu: oo[%zu] = %d, x[oo[%zu]] = %4.2" PRIf_VecType
               ", x[%zu] = %4.2" PRIf_VecType "\n",
               i, i, oo[i], i, x[oo[i]], i, x[i]);
}

int main(void)
{
    VecType x[] = { 3.45, 1.23, 9.14, 4.67, 2.19, 3.45, 5.92 };
    size_t x_size = sizeof(x) / sizeof(x[0]);
    int oo[x_size];

    for (size_t i = 0; i < x_size; i++)
        oo[i] = (int)i;

    print_arrays("Before", x_size, x, oo);
    base = x;
    qsort(oo, x_size, sizeof(oo[0]), compare);
    print_arrays("After", x_size, x, oo);

    return 0;
}

样本输出:

Before:
0: oo[0] = 0, x[oo[0]] = 3.45, x[0] = 3.45
1: oo[1] = 1, x[oo[1]] = 1.23, x[1] = 1.23
2: oo[2] = 2, x[oo[2]] = 9.14, x[2] = 9.14
3: oo[3] = 3, x[oo[3]] = 4.67, x[3] = 4.67
4: oo[4] = 4, x[oo[4]] = 2.19, x[4] = 2.19
5: oo[5] = 5, x[oo[5]] = 3.45, x[5] = 3.45
6: oo[6] = 6, x[oo[6]] = 5.92, x[6] = 5.92
After:
0: oo[0] = 1, x[oo[0]] = 1.23, x[0] = 3.45
1: oo[1] = 4, x[oo[1]] = 2.19, x[1] = 1.23
2: oo[2] = 5, x[oo[2]] = 3.45, x[2] = 9.14
3: oo[3] = 0, x[oo[3]] = 3.45, x[3] = 4.67
4: oo[4] = 3, x[oo[4]] = 4.67, x[4] = 2.19
5: oo[5] = 6, x[oo[5]] = 5.92, x[5] = 3.45
6: oo[6] = 2, x[oo[6]] = 9.14, x[6] = 5.92

'after' 的打印向我们保证数组 x 没有改变,但数组 oo 已更新,因此 x[oo[i]] 位于 ith 位置排序顺序。

BSD(因此也包括 Mac OS X)提供了 qsort() 的非标准替代方案,即 qsort_r()

void qsort_r(void *base, size_t nel, size_t width, void *thunk, int (*compar)(void *, const void *, const void *));

qsort_r() 函数的行为与qsort() 相同,不同之处在于它需要一个附加参数thunk,它作为第一个参数原样传递给指向compar 的函数。这允许比较函数在不使用全局变量的情况下访问其他数据,因此qsort_r() 适用于必须可重入的函数。

根据qsort_r() 编写代码是一组相当微不足道的更改:

#include <stdio.h>
#include <stdlib.h>

typedef double VecType;
#define PRIf_VecType "f"

static int compare(void *thunk, const void *p1, const void *p2)
{
    const VecType *base = (VecType *)thunk;
    const int i1 = *(int *)p1;
    const int i2 = *(int *)p2;
    if (base[i1] < base[i2])
        return -1;
    else if (base[i1] > base[i2])
        return +1;
    else
        return 0;
}

static void print_arrays(const char *tag, size_t x_size, VecType *x, int *oo)
{
    printf("%s:\n", tag);
    for (size_t i = 0; i < x_size; i++)
        printf("%zu: oo[%zu] = %d, x[oo[%zu]] = %4.2" PRIf_VecType
               ", x[%zu] = %4.2" PRIf_VecType "\n",
               i, i, oo[i], i, x[oo[i]], i, x[i]);
}

int main(void)
{
    VecType x[] = { 3.45, 1.23, 9.14, 4.67, 2.19, 3.45, 5.92 };
    size_t x_size = sizeof(x) / sizeof(x[0]);
    int oo[x_size];

    for (size_t i = 0; i < x_size; i++)
        oo[i] = (int)i;

    print_arrays("Before", x_size, x, oo);
    qsort_r(oo, x_size, sizeof(oo[0]), x, compare);
    print_arrays("After", x_size, x, oo);

    return 0;
}

这样的示例输出(与其他代码的输出相同):

Before:
0: oo[0] = 0, x[oo[0]] = 3.45, x[0] = 3.45
1: oo[1] = 1, x[oo[1]] = 1.23, x[1] = 1.23
2: oo[2] = 2, x[oo[2]] = 9.14, x[2] = 9.14
3: oo[3] = 3, x[oo[3]] = 4.67, x[3] = 4.67
4: oo[4] = 4, x[oo[4]] = 2.19, x[4] = 2.19
5: oo[5] = 5, x[oo[5]] = 3.45, x[5] = 3.45
6: oo[6] = 6, x[oo[6]] = 5.92, x[6] = 5.92
After:
0: oo[0] = 1, x[oo[0]] = 1.23, x[0] = 3.45
1: oo[1] = 4, x[oo[1]] = 2.19, x[1] = 1.23
2: oo[2] = 5, x[oo[2]] = 3.45, x[2] = 9.14
3: oo[3] = 0, x[oo[3]] = 3.45, x[3] = 4.67
4: oo[4] = 3, x[oo[4]] = 4.67, x[4] = 2.19
5: oo[5] = 6, x[oo[5]] = 5.92, x[5] = 3.45
6: oo[6] = 2, x[oo[6]] = 9.14, x[6] = 5.92

【讨论】:

    【解决方案2】:

    标准库的通用排序函数是qsort。它需要一个比较函数,该函数需要比较两个元素。无法将其他信息传递给函数。

    如果您有权访问在其比较功能中接受附加数据的排序功能,例如qsort_rg_qsort_with_data 你应该使用它。乔纳森·莱弗勒(Jonathan Leffler)向您展示了如何做。 (很遗憾,qsort_r 不是标准的一部分。额外的数据通常很有用,并且有助于解决您的问题。)

    如果您依赖qsort,一个简单的解决方案是将附加信息存储在全局变量中。这不是一个好的解决方案,因为它没有正确封装数据并且不是线程安全的。不过,对于一个小规模的项目,它可能已经足够了;请参阅 Rob 的回答。

    另一种方法,通常用于将两个数组并排排序,将它们组合成这些变量的结构,并根据其中一个字段进行排序。如果数据属于一起,这通常是一个好方法,但在您的情况下,这意味着创建一个辅助结构数组。

    最后,您可以创建一个指针数组并使用一级间接对它们进行排序。

    编辑:我首先提议使用这种方法来创建问题中要求的索引数组。该解决方案依赖于size_tvoid *which isn't necessarily true by the C standard 的大小相同,并且还作为指向数组和数组索引的指针访问相同的内存,从而打破了严格的别名规则。该解决方案在帖子末尾仍然可用。

    我现在得出的结论是指针解决方案是可行的,但是order 函数应该将一个指针数组填充到数组中。因此,现在不是通过索引x[oo[i]] 访问元素,而是通过指针*oref[i] 访问它。如果需要索引,可以通过指针运算得到:

    ix = oref[i] - x;
    

    这是一个不错的 C 解决方案。这是一个带有示例客户端代码的实现:

    #include <stdlib.h>
    #include <stdio.h>
    
    typedef int Type;
    
    int ptrcmp(const void *a, const void *b)
    {
        const Type *const *aa = a;
        const Type *const *bb = b;
    
        return (**aa > **bb) - (**aa < **bb);
    }
    
    void order_ref(Type **ptr, Type *arr, size_t n)
    {
        size_t i;
    
        for (i = 0; i < n; i++) ptr[i] = arr + i;    
        qsort(ptr, n, sizeof(*ptr), ptrcmp);    
    }
    
    #define countof(x) (sizeof(x) / sizeof(*x))
    
    int main()
    {
        Type arr[] = {8, 5, 4, 9, 1, 7, 6, 3, 2, 0};
        Type *ptr[countof(arr)];
        size_t n = countof(arr);
        size_t i;
    
        order_ref(ptr, arr, n);
    
        for (i = 0; i < n; i++) {
            int ix = ptr[i] - arr;
    
            printf("%4d%16d\n", ix, *ptr[i]);
        }
    
        return 0;
    }
    

    我最初提出的代码如下。我说:您可以利用size_t 与指针大小相同的事实,并使用指针算法将指针转换为size_t 类型的无符号整数。该条件不一定适用于所有平台,但至少由assert 强制执行。

    对指针和索引使用相同的数组是一种节省分配辅助数组的技巧。我不确定是否破坏了严格的别名,因为它一次对数组的元素进行类型双关,但它肯定不是一个干净的解决方案。

    它仍然可能有用,所以这里是代码,但更喜欢qsort_r 或指针解决方案。

    typedef int Type;    // Source data type 
    
    int ptrcmp(const void *a, const void *b)
    {
        const Type *const *aa = a;
        const Type *const *bb = b;
    
        return (**aa > **bb) - (**aa < **bb);
    }
    
    size_t *order(const Type *arr, size_t n)
    {
        const Type **ptr = malloc(n * sizeof(*ptr));
        size_t *res = (size_t *) ptr;
        size_t i;
    
        assert(sizeof(size_t) == sizeof(Type *));
    
        for (i = 0; i < n; i++) ptr[i] = arr + i;    
        qsort(ptr, n, sizeof(*ptr), ptrcmp);    
        for (i = 0; i < n; i++) res[i] = ptr[i] - arr;
    
        return res;
    }
    
    /*
     *      Example client code
     */
    int main()
    {
        Type arr[] = {8, 5, 4, 9, 1, 7, 6, 3, 2, 0};
        size_t n = sizeof(arr) / sizeof(*arr);
    
        size_t *ind = order(arr, n);
        size_t i;
    
        for (i = 0; i < n; i++) {
            printf("%4d%16d\n", ind[i], arr[ind[i]]);
        }
    
        free(ind);
    
        return 0;
    }
    

    【讨论】:

    • 可以修改代码,以便main() 函数分配一个数组size_t oo[n]; 而不是order() 分配空间,该数组被传递给order() 以供使用/滥用。我怀疑它违反了“严格别名”规则,使用与在单个函数中包含 Type *size_t 类型的元素相同的数组。我不确定断言assert(sizeof(size_t) == sizeof(Type *)) 是否必须是安全的,但它会在大多数系统上保持不变。
    • @JonathanLeffler:感谢您的评论和编辑。我同意一种更像 C 的方法是传递要填充的数组。毕竟调用的时候元素个数是已知的,用户可以根据情况选择是在栈上分配还是在堆上分配。我认为size_tvoid * 应该具有相同的尺寸,但是I was mistaken
    • 我知道严格的混叠问题,确实应该指出这一点。一种更简洁和 C 风格的方法是让 order 填充一个排序的指针数组。我会修改答案,但我必须先处理我周日的洗衣家务。
    【解决方案3】:

    我不会评论它是否很快,但这以一种相当经济的方式使用代码。请注意,由于使用静态在两个函数之间传递信息,代码不是可重入的,也不是线程安全的。

    此代码假定数组xoo 的长度均为size

    #include <stdlib.h>
    const WhateverType  *array;
    
    int our_comparison_thing(const void *a, const void *b)
    {
         WhateverType *aval = array + *(size_t *)a;
         WhateverType *bval = array + *(size_t *)b;
         return (*aval == *bval) ? 0 : ((*aval < *bval) ? -1 : 1);
    }
    
    void DoOurThing(const WhateverType *x, size_t *oo, size_t size)
    {
          size_t i;
          array = x;
          for (i = 0; i < size; ++i)
              oo[i] = i;
          qsort((void *)oo, size, sizeof(*oo), our_comparison_thing);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多