【问题标题】:Sorting an array of integers in alternate fashion using qsort function.使用 qsort 函数以替代方式对整数数组进行排序。
【发布时间】:2018-05-16 09:25:45
【问题描述】:

/*我最近学习了qsort函数。此 c 代码给出不正确的输出。需要帮助。 问题 - 以交替方式对整数数组进行排序。 (偶数索引处的元素和奇数索引处的元素分别排序) 输出 - 0 4 1 2 5 8 7 5 9 3 10 5 */

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

// This function is used in qsort to decide the relative order
// of elements at addresses p and q.
int comparator(const void *p, const void *q)
{
    // Get the values at given addresses
    int l = *(const int *)p;
    int r = *(const int *)q;

    return (l-r);
}

// A utility function to print an array
void printArr(int arr[], int n)
{
    int i;
    for (i = 0; i < n; i = i+1)
        printf("%d ", arr[i]);
}

// Driver program to test above function
int main()
{
    int arr[] = {1,4,7,2,9,3,0,8,6,5};

    int size0 = sizeof(arr) / sizeof(arr[0]);
    int size1 = (int) ((float)sizeof(arr) / sizeof(arr[0]) / 2 + 0.5);
    int size2 = size0 - size1;

    qsort((void *)arr+1, size2, 2*sizeof(arr[0]), comparator);   
    //sort odd positions

    qsort((void *)arr, size1, 2*sizeof(arr[0]), comparator);
    //sort even positions

    printf("Output array is\n");
    printArr(arr, size0);
    printf("\n%d %d", size0, size1);

    return 0;
}

【问题讨论】:

  • 这个问题目前没有解决方案。 qsort() 隐含地假设它正在排序的东西在一个连续的数组中,元素之间没有间隙。这是它如何比较和交换元素的基础。因此,编写比较器不足以让qsort() 独立地对奇偶元素进行排序——因为比较器不能影响qsort() 进行交换的方式。
  • 这看起来像是一个班级作业,您打算为此编写自己的代码来对数据进行排序,而不是使用qsort
  • @@EricPostpischil - 我在网上阅读了一个问题,告诉我使用 qsort 专门回答这个问题。
  • @Peter - 明白了。谢谢。

标签: c algorithm sorting


【解决方案1】:

qsort:

void qsort( void *ptr, size_t count, size_t size, int (*comp)(const void *, const void *) );

按升序对 ptr 指向的给定数组进行排序。该数组包含 size 个字节的 count 个元素。 comp指向的函数用于对象比较。

ptr - 指向要排序的数组的指针

count - 数组中元素的个数

size - 数组中每个元素的大小,以字节为单位

comp - 返回负整数值的比较函数,如果 第一个参数小于第二个,

在您的程序中,您将 每个元素的大小 传递为 2*sizeof(arr[0]),这会导致 8 个字节错误地输入到 qsort()。因此,您得到的输出不正确。

【讨论】:

  • 我没有考虑交换(我的错)。在这种情况下,以下可能会起作用(需要对代码进行相当小的更改):qsort() 奇数元素的数组(交换偶数),奇数元素的输出结果,qsort() 偶数元素的数组,输出结果甚至元素。最关键的事情可能会成为越界访问。因此,适当数量的虚拟元素与例如INT_MAX 值应该在之前添加并在之后从输出中排除。
【解决方案2】:

可以使用qsort() 分别对偶数/奇数元素进行排序。 但是,必须稍微更改设置才能完成此操作。

正如彼得正确提到的(我必须承认,我之前没有考虑过)对偶数元素进行排序将“破坏”奇数元素的结果,因为交换考虑了元素大小,它表示为偶数对 奇数元素。

记住这一点,如果在第二次排序完成之前保存第一次排序的结果,则可以解决所有问题。

在我的示例中,我在第一次排序后复制了相关元素,并在第二次排序后将它们合并。

这是我的示例testQSortEvenOdd.c

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

int compEven(const int *p1, const int *p2)
{
  return (p1[0] > p2[0]) - (p1[0] < p2[0]);
}

int compOdd(const int *p1, const int *p2)
{
  return (p1[1] > p2[1]) - (p1[1] < p2[1]);
}

void printArray(size_t n, int *arr, int step)
{
  for (; n--; arr += step) printf(" %d", *arr);
  putchar('\n');
}

int main()
{
  int arr[] = { 1, 4, 7, 2, 9, 3, 0, 8, 6, 5 };
  enum { size = sizeof arr / sizeof *arr };
  assert(!(size & 1));
  /* sort odd positions */
  qsort(arr, (size + 1) / 2, 2 * sizeof *arr,
    (int(*)(const void*, const void*))&compOdd);
  /* output of sorted array for odd positions */
  puts("Odd elements sorted:");
  printArray(size / 2, arr + 1, 2);
  int arrRes[(size + 1) / 2];
  for (size_t i = 1; i < size; i += 2) arrRes[i / 2] = arr[i];
  /* sort even positions */
  qsort(arr, (size + 1) / 2, 2 * sizeof *arr,
    (int(*)(const void*, const void*))&compEven);
  /* output of sorted array for even positions */
  puts("Even elements sorted:");
  printArray((size + 1) / 2, arr, 2);
  /* merge array with copy */
  for (size_t i = 1; i < size; i += 2) arr[i] = arrRes[i / 2];
  puts("Merged elements:");
  printArray(size, arr, 1);
  /* done */
  return 0;
}

在 Windows 10(64 位)上的 Cygwin 中测试:

$ gcc --version
gcc (GCC) 6.4.0

$ gcc -std=c11 -o testQSortEvenOdd testQSortEvenOdd.c 

$ ./testQSortEvenOdd
Odd elements sorted:
 2 3 4 5 8
Even elements sorted:
 0 1 6 7 9
Merged elements:
 0 2 1 3 6 4 7 5 9 8

$

一些补充说明:

  1. 我(和提问者)使用qsort() 的方式,它同时处理两个连续的int 值。因此,必须允许数组具有适当数量的元素。 (否则,qsort() 要么进行越界访问,要么不能考虑最后一个元素。)考虑到这一事实,我插入了

    assert(!(size &amp; 1));

    可以读作“确保数组有偶数个元素。”

  2. 我决定制作单独的函数 compEven()compOdd(),因为恕我直言,它简化了事情。我将两者的签名都更改为我的需要,并从 gcc 收到有关错误函数签名的抱怨(警告)。因此,我将函数指针转换为预期的类型(使 gcc 静默)。

  3. Jonathon 给出了一个很好的提示,以使比较函数对下溢问题具有鲁棒性。当差值大于INT_MAX 或小于INT_MIN 时,return p1[0] - p2[0]; 可能会导致错误结果。相反,他建议使用:

    return (p1[0] &gt; p2[0]) - (p1[0] &lt; p2[0]);

    永远不会有任何上溢/下溢问题。

它是如何工作的:

如果a &lt; b(a &gt; b) - (a &lt; b)0 - 1-1

如果a == b(a &gt; b) - (a &lt; b)0 - 00

如果a &gt; b(a &gt; b) - (a &lt; b)1 - 01

非常聪明的乔纳森·莱弗勒——我印象深刻。

【讨论】:

  • 在样本数据上,您的比较器是安全的,但如果数字可以任意大且为负数(一般情况),那么您可能会从减法中溢出。避免这种情况的一种方法是return (p1[0] &gt; p2[0]) - (p1[0] &lt; p2[0]); 等。
  • @JonathanLeffler 好点 - 好修复。我将其编辑到我的示例中。 (我写了一条评论,后来我删除了我实际描述的那个问题。)
  • 这是一个很好的紧凑符号,但我在 SO 上学到了它(几年前,现在)。它的缺点是需要您给出的解释。此外,在更一般的情况下,您正在比较数组的结构或多个元素并且您需要“移动到相等的下一个字段”,那么长手版本最终更具可扩展性:if (p1[0] &lt; p2[0]) return -1; else if (p1[0] &gt; p2[0]) return +1; else if (p1[1] &lt; p2[1]) return -1; else if (p1[1] &gt; p2[1]) return +1; … else return 0;。这些都不会影响您的回答。
【解决方案3】:

qsort 需要一个连续的内存块才能正常运行。

如果您需要对奇数和偶数索引元素分别进行排序,您可以从分离元素开始,对它们进行单独排序,然后合并这两部分。

即使不分配任何额外内存,您也可以这样做:

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

int less_int(const void *lhs, const void *rhs)
{
    return *(const int *)lhs < *(const int *)rhs ? -1
        : *(const int *)lhs > *(const int *)rhs ? 1 : 0;
}

int greater_int(const void *lhs, const void *rhs)
{
    return *(const int *)lhs > *(const int *)rhs ? -1
        : *(const int *)lhs < *(const int *)rhs ? 1 : 0;
}

void sort_ascending(int* arr, size_t n)
{
    qsort(arr, n, sizeof *arr, less_int);
}

void sort_descending(int* arr, size_t n)
{
    qsort(arr, n, sizeof *arr, greater_int);
}

inline void swap_int(int* a, int* b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}

size_t partition_odd_even(int* arr, size_t n )
{
    size_t n_odds = n - n / 2;
    for (size_t i = 1, j = n_odds + n_odds % 2; i < n_odds; i += 2, j += 2)
    {
        swap_int(arr + i, arr + j);
    }
    return n_odds;
}

void interleave_odd_even(int* arr, size_t n )
{
    size_t n_odds = n - n / 2;
    for (size_t i = 1; i < n_odds; ++i )
    {
        for (size_t j = n_odds - i; j < n_odds + i; j += 2)
        {
            swap_int(arr + j, arr + j + 1);
        }
    }
}

void print_arr(int* arr, size_t n);

int main(void)
{
    int arr[] = {1, 4, 7, 2, 9, 3, 0, 8, 6, 5};
    size_t arr_size = sizeof arr / sizeof *arr;
    print_arr(arr, arr_size);

    size_t n_odds = partition_odd_even(arr, arr_size);
    size_t n_evens = arr_size - n_odds;
//  print_arr(arr, arr_size);

    sort_ascending(arr, n_odds);
//  print_arr(arr, n_odds);

    sort_descending(arr + n_odds, n_evens);
//  print_arr(arr + n_odds, n_evens);

    interleave_odd_even(arr, arr_size);
    print_arr(arr, arr_size);

    return 0;
}

void print_arr(int* arr, size_t n)
{
    for(size_t i = 0; i < n; ++i)
    {
        printf(" %d", arr[i]);  
    }
    puts("");
}

这给出了:

1 4 7 2 9 3 0 8 6 5 0 8 1 5 6 4 7 3 9 2

编辑

正如greybeard 在下面的 cmets 中所指出的,上面的代码并不是真正的时间效率,因为合并部分是 O(N²)。使用仅包含要以特定方式排序的元素的临时数组,以下程序只需要 O(N) 额外时间和 O(N/K) 空间,其中 K 是所需的不同排序顺序的数量(2 in OP的问题)。

Jonathan Leffler 还指出,它可以变得更通用,允许算法“处理 3、4、……N 个均匀交错的子数组,每个子数组可能具有不同的排序顺序”。我在下面的 sn-p 中通过将一个指针数组传递给排序函数来实现它,以比较函数。

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

// compare functions
typedef int (*PCMPFN)(const void*, const void*);

int ascending_cmp_int(const void *lhs, const void *rhs)
{
    return *(const int *)lhs < *(const int *)rhs ? -1
        : *(const int *)lhs > *(const int *)rhs ? 1 : 0;
}

int descending_cmp_int(const void *lhs, const void *rhs)
{
    return *(const int *)lhs > *(const int *)rhs ? -1
        : *(const int *)lhs < *(const int *)rhs ? 1 : 0;
}

// This function is never called. Whithout knowing the actual implementation
// of 'qsort' we can't make any assumption
int untouched_cmp_int(const void *lhs, const void *rhs)
{
    (void)lhs;  // Those parameters are unused here, this is to avoid a warning
    (void)rhs; 
    return 0;
}

// Copy the elements of the source array starting from index 'start' with stride 'step'
size_t strided_split(int* dest, const int *src, size_t n, size_t start, size_t step)
{
    size_t j = 0;
    for (size_t i = start; i < n; i += step, ++j)
    {
        dest[j] = src[i];
    }
    return j;
}

// Inverse of the previous
void strided_merge(int* dest, const int *src, size_t n, size_t start, size_t step)
{
    for (size_t i = start, j = 0; j < n; i += step, ++j)
    {
        dest[i] = src[j];
    }
}

// Apply different sort orders to different elements
void alternate_sort(int* arr, const size_t n, PCMPFN comps[], const size_t k)
{
    int tmp[n/k + 1];         // Please note that VLA are optional in C11

    for ( size_t i = 0; i < k; ++i )
    {
        if ( comps[i] == untouched_cmp_int )
            continue;
        // First select the elements 
        size_t n_copied = strided_split(tmp, arr, n, i, k);
        // then sort only them as needed
        qsort(tmp, n_copied, sizeof tmp[0], comps[i]);
        // Once sorted, copy back the elements in the source array
        strided_merge(arr, tmp, n_copied, i, k);
    }
}

void print_arr(const int* arr, const size_t n);

int main(void)
{
    int arr[] = {1, 4, 7, 2, 9, 3, 0, 8, 6, 5};
    const size_t N = sizeof arr / sizeof *arr;
    print_arr(arr, N);

    PCMPFN compares[] = {
        descending_cmp_int, untouched_cmp_int, ascending_cmp_int
    };
    const size_t K = sizeof compares / sizeof *compares;

    alternate_sort(arr, N, compares, K);
    print_arr(arr, N);

    return 0;
}

void print_arr(const int* arr, const size_t n)
{
    for(size_t i = 0; i < n; ++i)
    {
        printf(" %d", arr[i]);  
    }
    puts("");
}

【讨论】:

  • (看起来我不太热衷于解决的一种方法:愿意评论interleave_odd_even()的时间复杂度吗?)
  • @greybeard 半 O(n^2),我假设。这只是为了表明可以在现场进行,但是拆分和合并两个单独的数组当然会更容易。
  • 我想知道,如果您要编写快速排序,最好创建一个版本,该版本采用“跨度”参数来表示条目之间的距离,与“分开” size' 参数(在通用排序情况下)?然后您就可以执行以下操作:sort_ascending_strided(arr, arr_size, 2); sort_descending_strided(arr+1, arr_size-1, 2);。这将允许您处理 3、4、... N 个均匀交错的子数组,每个子数组可能具有不同的排序顺序。
  • @JonathanLeffler 我编辑了答案以使代码更通用。你的意思是这样的吗?
  • 我在想,如果排序只查看奇数索引条目或偶数索引条目,并且确保每次都跳过 2,那么您可以不使用正式的拆分和合并阶段 —在步幅为 2 的情况下,如问题所示。在更一般的情况下,排序将在数组中的每个第 2、第 3、第 4 ……条目上进行。也许我应该删除我的建议,或者我应该实施它,因为我可以清楚地设想它。我担心 O(N²) 合并阶段。它破坏了 O(NlogN) 排序阶段的好处。但它确实需要你(重新)实现排序。
猜你喜欢
  • 2021-06-23
  • 1970-01-01
  • 2019-05-23
  • 2021-05-08
  • 2015-09-16
  • 1970-01-01
  • 1970-01-01
  • 2016-09-02
相关资源
最近更新 更多