使用 MACRO 进行摇动排序

Posted

技术标签:

【中文标题】使用 MACRO 进行摇动排序【英文标题】:Shake sort using MACRO 【发布时间】:2017-09-02 06:30:36 【问题描述】:

Shake 类向量: 程序有效,但是:

我尝试使用相同的函数进行冒泡排序和向下冒泡进行摇动排序(向上冒泡以获得右侧的最大值,向下冒泡以获得左侧的最小值)。为了做到这一点,我试图使用以下无法编译的 MACRO:

符号为“+”,操作符为“>”代表气泡

符号为“-”,操作符为“

为了冒泡 -

start 是迭代器 i(迭代向量索引)

结束是 n-1-i;

对于泡沫下降 - 交换开始和结束值

#define bubble_up_down(var_t, pVector, _Is_swp, start, end, sign, oper)\
\
    var_t current_index;\
    var_t current_val;\
    var_t next_val;\              
    for (current_index = *(start) ; current_index (oper) *(end) ; (sign)(sign)current_index)\
    \
        VectorGet((pVector), current_index, &current_val);\
        VectorGet((pVector), current_index(sign)1, &next_val);\
        if(current_val (oper) next_val)\
        \
            VectorSet((pVector), current_index, next_val);\
            VectorSet((pVector), current_index(sign)1, current_val);\
            *(_Is_swp) = 1;\
        \
    \

需要您的建议来修复此宏。

【问题讨论】:

错误是什么? 开发更长的代码作为宏是邪恶的。顺便说一句,您的思维方式在 C++ 模板中具有很好的视角 这里使用宏有什么意义?在实现向上/向下冒泡排序和摇动排序时,您是否只想替换冗余代码?如果Get/SetValue 显然不是类型独立的,那么类型是什么?以及如何调用宏? 这是我想要复制的函数,所以我可以用它来冒泡。获取/设置值是整数 - for (current_index = *start ; current_index > *end ; --current_index) VectorGet(pVector, current_index, &current_val); VectorGet(pVector, current_index - 1, &next_val); if(current_val 【参考方案1】:

目前还不清楚为什么要在此处使用宏。你想避免重复代码吗?或者您想让您的排序例程类型独立?

不管怎样,你的宏有几个错误:

您可能已经读过应该用括号保护宏参数。这通常是个好建议,因为宏是文本替换;例如臭名昭​​著的SQ(x + 1) 将解析为x + 1*x + 1。在你的情况下,这个建议是错误的。您将在代码中得到语法错误的“运算符”,例如 (-)(<)。只需使用signoper。 即便如此,sign sign 将解析为 - -+ +,这不是您想要的。您可以将i++ 重写为同样有效的i = i + 1,或者您可以使用令牌粘贴运算符sign##sign,这将产生--++。 宏不是函数。您可能会在函数中调用宏。调用宏时在范围内的所有局部变量也在宏的范围内。这意味着可能不需要定义所有这些指针。 为什么要传递数组元素类型var_t?我认为 SetVectorGetVector 不是宏,因此类型独立性下降。 如果var_t 是数组元素的类型,则索引不一定是相同类型;它应该是整数类型。 (您的元素必须与 < 运算符相当,因此它是算术类型之一,但想象一下,如果您有一个超过 256 个元素的 char 数组会发生什么?) 如果您的元素属于算术类型,则可能不需要GetValueSetValue 调用。您可以使用 = 运算符分配值。

这一切让我觉得你真的不知道自己在做什么。再加上宏的已知缺陷和缺点是不在这里使用任何宏的一个很好的理由。


附录   在 cmets 中,PO 已经说过宏应该实现两件事:它应该避免重复代码,它应该使排序独立于数组元素的类型。这是两个不同的东西。

编写简短的局部宏以避免重复代码可能是一种有用的技术,尤其是在代码需要在多个位置保持变量同步的情况下。它对您的情况有用吗?

所以你得到了向上冒泡的代码:

int done = 0;

while (!done) 
    done = 1;

    for (int i = 1; i < n; i++) 
        if (a[i - 1] > a[i]) 
            swap(a, i - 1, i);
            done = 0;
        
    

(这使用 swap 函数来交换两个数组元素。它比您的版本更简单,因为它不使用 get/set 访问器函数。)现在您编写向下冒泡的对应物:

while (!done) 
    done = 1;

    for (int i = n - 1; i > 0; i--) 
        if (a[i - 1] > a[i]) 
            swap(a, i - 1, i);
            done = 0;
        
    

这两个 sn-ps 仅在循环控制上有所不同。两者都访问从 1 到 n - 1 的所有索引。所以你的宏需要传递开始和结束值。但它还需要知道比较的方向——小于或大于——以及是增加还是减少索引。这是一个简单循环的四段数据。

您可以尝试摆脱比较并将!= 用于两个方向。但是,如果数组为空,您的循环将失败。

当您使用无符号整数作为索引时,上述向后循环在空数组上已经失败。前向和后向 lops 在 C 中是不对称的,因为下限和上限也是不对称的:下限总是包含的,上限总是互斥的。这个前向循环:

for (unsigned int i = 0; i < n; i++) ...

具有以下向后等效项:

for (unsigned int i = n; i-- > 0; ) ...

这里,减量发生在条件中,更新部分为空。优点是它使用完全相同的边界,0n,逐字逐句,但是通过在进入循环体之前递减,访问相同的有效数字范围,0n - 1。它适用于无符号整数,这是循环变量的自然选择。

长话短说:前向和后向循环在 C 语言中是不对称的,因此为它们编写宏并不容易。 C 的语法比for i = 1 to n 更冗长,但就是这样。接受它并通过选择适当的索引名称来减轻打字的痛苦:它是i,而不是current_index

如果没有宏,你能减少代码的冗余吗?当然:你可以写两个函数来一次冒泡:

static int bubble_up(int a[], int n)

    int done = 1;

    for (int i = 1; i < n; i++) 
        if (a[i - 1] > a[i]) 
            swap(a, i - 1, i);
            done = 0;
        
    

    return done;


static int bubble_down(int a[], int n)

    int done = 1;

    for (int i = n; i-- > 1; ) 
        if (a[i - 1] > a[i]) 
            swap(a, i - 1, i);
            done = 0;
        
    

    return done;

(这些函数是static,即当前编译单元的私有函数。)现在您的实际排序函数如下所示:

void sort_bubble_up(int a[], int n)

    int done = 0;

    while (!done) 
        done = bubble_down(a, n);
    


void sort_bubble_down(int a[], int n)

    int done = 0;

    while (!done) 
        done = bubble_down(a, n);
    


void sort_shaker(int a[], int n)

    int done = 0;

    while (!done) 
        done = bubble_up(a, n) || bubble_down(a, n);
    

如果你不怕空循环体,你甚至可以把它们归结为:

void sort_bubble_up(int a[], int n)

    while (bubble_down(a, n))  


void sort_bubble_down(int a[], int n)

    while (bubble_down(a, n))  


void sort_shaker(int a[], int n)

    while (bubble_up(a, n) || bubble_down(a, n))  

不过,所有这些代码仅适用于 int 数组。标准库接近类型独立的方法是通过void * 指针和用户定义的比较函数在字节级别上工作。例如,排序函数qsort 就是这样做的。

C++ 和其他语言都有模板,您可以在其中为多种类型编写算法。当你“实例化”一个模板时,编译器会为这个类型创建一个函数,然后调用它。

你可以用宏来模拟这个。如果你只想在函数体中调用你的宏,你可以定义:

#define BUBBLE_SORT(ARRAY, N, TYPE) do          \
        int done = 0;                            \
        int i;                                   \
                                                 \
        while (!done)                           \
            done = 1;                            \
                                                 \
            for (i = 1; i < N; i++)             \
                if (ARRAY[i - 1] > ARRAY[i])    \
                    TYPE sawp = ARRAY[i];        \
                                                 \
                    ARRAY[i] = ARRAY[i - 1];     \
                    ARRAY[i - 1] = swap;         \
                    done = 0;                    \
                                                \
                                                \
                                                \
     while (0)

然后像这样使用宏:

char c[] = "Mississippi";

BUBBLE_SORT(c, strlen(c), char);

do ... while (0) 宏周围的东西使宏的行为类似于函数调用。循环体的新范围允许局部变量。)

这里的问题是这样的多行宏很难调试。当正文中出现错误时,您只需在错误消息中获取调用宏的行号。 (但您可以在大多数编译器中使用 -E 来查看预处理器如何解析该宏。)

结论:

宏可能很有用,但您必须知道自己在做什么。一般来说,尽量避免使用它们,因为它们很难调试,而且通常很难被其他人理解。 (这个另一个人可能是半年后的你。) 如果您必须使用宏,请尝试使 then 看起来尽可能自然。传递 &gt;+ 之类的运算符应该让您保持警惕。 对通用代码使用函数,而不是宏。 采用 C 处理不同类型的方式。学习qsort 的工作原理比为冒泡排序实现摆弄宏更有用(如果不那么有趣的话)。 如果您确实需要编写大量与类型无关的代码,您可能不应该使用 C。

【讨论】:

感谢您的详细回答。关于您的第一条评论 - 是的,我想避免重复代码,这就是我选择使用 MACRO 的原因。您是否有任何其他建议如何使用相同的函数将 MAX 值气泡到向量的右侧和 MIN 值到向量的左侧(对于摇动类型的向量)?你认为使用函数参数作为指向另一个函数的指针可以解决它吗? 此外 - 插入 var_t 是为了将此函数用于其他向量 - 其他类型的变量(不仅仅是整数)。在这种情况下,我也可以使用指向 void 的指针。

以上是关于使用 MACRO 进行摇动排序的主要内容,如果未能解决你的问题,请参考以下文章

如何使用分布排序(基数排序等)对字符串进行排序?

使用选择排序对对象数组进行排序c ++

如何使用现有的整数排序对整数元组进行排序?

使用合并排序对(名称)进行排序

使用插入排序对链表进行并行排序

excel快速排序技巧,如何使用SORTBY函数对数据进行排序