快速排序变种实现:一次宏定义引发的熬夜事件

一、背景  

  睡前忽然想写两行代码练练手,想起快速排序的一种变种实现,于是写了快速排序,在理论上完全没问题的情况下测试结果却很诡异,debug半天发现是宏定义导致,作为经验教训记录下来。

二、快速排序变种实现

  原理:

    |_| |_| |_________________________________________|

    L    R                     unSorted

---------------------------------------------------------------------------------------------------------------------

   1、取哨兵下标为low,即首元素;初始状态L和R集合都为空,L集合用来存放小于pivot的值,R集合用于存放大于pivot的值。

pivot = vector<T>& vec[low] ;   mi = low;

↓mi

|——|

|_| |_| |_________________________________________|

    L    R                     unSorted

   2、遍历初始集合,如果发现当前下标元素值大于pivot则迭代器加1,不做任何操作,效果相当于移动元素到R中。

idx

|_| |__| |________________________________________|

    L    R                     unSorted

3、如果发现当前下标元素值小于pivot则需要将当前值放于集合L中,为了提升效率,使当前元素和R集合的首元素交换,然后迭代器加1;作用效果相当于L元素新增,R元素整体后移一位。

mi

↓      idx

|__| |__| |_______________________________________|

    L    R                     unSorted

  4、当遍历完当前序列后unSorted的将为空,L和R将包含所有小于/大于pivot元素的集合。

mi

                 ↓

|___________________| |_________________________|

                    L                                           R

  5、最终将首元素,即pivot和mi所指的元素交换,左右效果是mi这个选出来的哨兵节点已经就位,完成了一趟排序,之后将调用递归的方式对前半段和后半段进行排序(和大家所熟知的快排一致)。

三、具体实现,以下代码中swap函数用宏定义实现,为了逻辑紧凑,在swap的参数中用了“++”操作符。

  1、排序逻辑

 1 #ifndef __ALGO_SORT_QUICK_SORT_IMPROVED_H__
 2 #define __ALGO_SORT_QUICK_SORT_IMPROVED_H__
 3
 4 #include <vector>
 5 #include <iostream>
 6 using namespace std;
 7
 8 #define swap(a, b)    ({ 9     typeof(a) tmp = (a) ; 10     (a) = (b); 11     (b) = tmp; })
12
13 template<typename Type>
14 static int partion(std::vector<Type>& elems, int low, int high)
15 {
16     Type pivot = elems[low];
17     int mi = low;
18     for (int i = low + 1; i <= high; i++)
19     {
20         if (elems[i] < pivot)
21         {
22             //++mi;
23             swap(elems[++mi], elems[i]);
24         }
25     }
26     swap(elems[mi], elems[low]);
27     return mi;
28 }
29
30 template<typename Type>
31 void quick_sort_improved(std::vector<Type>& elems, int low, int high)
32 {
33     if (low < high)
34     {
35         int mi = partion(elems, low, high);
36         quick_sort_improved(elems, low, mi - 1);
37         quick_sort_improved(elems, mi + 1, high);
38     }
39 }
40
41 #endif

  2、测试程序

1     vector<double> nums{18.1,16.12,32.21,12.22,13.1,53.21,221.5,354,123,42,22.11,33};
2     quick_sort_improved<double>(nums, 0, nums.size() - 1);
3     for (int i = 0; i < nums.size(); i++ )
4     {
5         cout << nums[i] << " ";
6     }
7     cout << endl;

  3、测试输出

[email protected]:~/workspace/Algo/src/sort/insertSort$ ./a.out
6.52013e-319 6.52013e-319 6.52013e-319 12.22 12.22 16.12 33 0 0 0 42 42 

  4、分析:由上结果可知,测试结果并未按照预期输出,乱七八糟的。

四、修复版

  加了诸多debug信息,得知问题出在使用宏定义的过程中传入了++操作符,熟悉宏定义的人肯定遇到过类似问题;当前问题是:宏定义中表达式出现几次,++将会被调用几次,这当然不是期望的结果;知道原因后稍加修改。

template<typename Type>
static int partion(std::vector<Type>& elems, int low, int high)
{
    Type pivot = elems[low];
    int mi = low;
    for (int i = low + 1; i <= high; i++)
    {
        if (elems[i] < pivot)
        {
            ++mi;
            swap(elems[mi], elems[i]);
        }
    }
    swap(elems[mi], elems[low]);
    return mi;
}

  测试输出:正确,符合预期

[email protected]:~/workspace/Algo/src/sort/insertSort$ ./a.out
12.22 13.1 16.12 18.1 22.11 32.21 33 42 53.21 123 221.5 354

五、总结

  日常代码中宏定义有时候无法避免,就像上面用到的swap,即便实现上已经避免了很多低级错误;但宏定义依然有很多不尽人意之处,就像上面的使用,编译器甚至都不会给个警告,在运行期也可以正常运行,但结果通却是莫名其妙的错误;通过一番折腾找出原因所在,以后在使用宏定义的地方一定要避免这种操作。

原文地址:https://www.cnblogs.com/zhangyi-studio/p/12117269.html

时间: 2024-09-28 09:13:23

快速排序变种实现:一次宏定义引发的熬夜事件的相关文章

宏定义引发的困惑

最近使用一个开源库,代码中使用的名称为min和max的两个函数与WinDef.h里定义的相同名称的宏定义冲突, 提示各种离奇错误.最后使用了NOMINMAX的预处理器定义解决此问题.说明:宏定义虽方便,不能滥用. #ifndef NOMINMAX #ifndef max #define max(a,b)            (((a) > (b)) ? (a) : (b)) #endif #ifndef min #define min(a,b)            (((a) < (b))

一个宏定义引发的问题

问题1:对与buffer宏定义的理解 一些得到的基本结论:int型数据占有一个字的空间,char型数据占有一个字节的空间,并且char数据类型的定义是为ASCII字符表量身定制的 对与buffer的理解: #define buffer ((char*) * (int far*)0x200) 首先,复习对与基本宏定义的知识:对于# define pi (3.14)即pi = 3.14,在此我们首先应该认识到,最外面的括号仅仅是一个结构,来说明里面的内容是一个整体. 下面来研究buffer究竟是什么

宏定义的优缺点

接下来看看宏都有什么好处: 1. 提高了程序的可读性,同时也方便进行修改: 2. 提高程序的运行效率:使用带参的宏定义既可完成函数调用的功能,又能避免函数的出栈与入栈操作,减少系统开销,提高运行效率: 3.宏是由预处理器处理的,通过字符串操作可以完成很多编译器无法实现的功能.比如##连接符. 但是它也有自己的缺点: 1. 由于是直接嵌入的,所以代码可能相对多一点: 2. 嵌套定义过多可能会影响程序的可读性,而且很容易出错: 3. 对带参的宏而言,由于是直接替换,并不会检查参数是否合法,存在安全隐

C中的预编译宏定义

文章来自 http://www.uml.org.cn/c++/200902104.asp 在将一个C源程序转换为可执行程序的过程中, 编译预处理是最初的步骤. 这一步骤是由预处理器(preprocessor)来完成的. 在源流程序被编译器处理之前, 预处理器首先对源程序中的"宏(macro)"进行处理. C初学者可能对预处理器没什么概念, 这是情有可原的: 一般的C编译器都将预处理, 汇编, 编译, 连接过程集成到一起了. 编译预处理往往在后台运行. 在有的C编译器中, 这些过程统统由

C Program基础-宏定义

写好c语言,漂亮的宏定义是非常重要的,我们在阅读别人工程时,往往能看到大量的宏定义,宏定义可以增加代码的可读性,也能增加代码的可移植性,一个好的宏定义甚至是一件艺术品.今天我们就来看看宏定义的方方面面. (一) 宏 vs 函数 在软件开发中过程中,经常有很多重复使用的代码段或功能模块,这些功能既可以用函数实现,也可以用宏实现,那么究竟是用函数好还是宏定义好呢?这就要求我们对二者做合理的取舍. 我们来看下面的例子,求两个数的较大者: (1)宏定义: #define Max(a, b) ((a) >

宏定义的使用

宏定义是什么进入这里说明已经对宏定义的用途有所了解,顾名思义就是给某一个项东西重新定义一个名字.然后在我们在使用这项东西的时候可以用新定义的名字来替换.为什么使用宏定义我直接用原来的东西不就可以了?举个简单的例子.在一个数学计算的程序中,我们可能很多处用到一个圆周率,我们可以写成3.14.有一天程序因需求要把圆周率精确到小数点后四位也就是3.1416.如果有10处用到了圆周率,我们就需要改10处,那如果有100处?1000处呢?这时候就需要用到宏定义了,我们可以定义一个M_PI来代表圆周率,以后

宏定义中的#,##,...,do{}while(0),__VA_ARGS__

宏定义中的#,## 1.在一个预处理器宏中的参数前面使用一个#,预处理器会把这个参数转换为一个字符数组 #define syslog(a) fprintf(stderr,"Warning: " #a"\n"); 2.简单的说,"## "是一种分隔连接方式,它的作用是先分隔,然后进行强制连接 举列 -- 试比较下述几个宏定义的区别 #define A1(name, type)  type name_##type##_type 或 #define A

笔记3:预处理器-(2)宏定义

#define指令称为宏定义指令,通常用#define指令来定义一个宏用来代表其他东西的一个名字(如常量表达式等).通常来说预处理器会通过将宏的名字和它的定义存储在一起来响应#define指令.当这个宏在后面的程序中使用到时,预处理器会"扩展"宏,将宏替换为其定义值. 简单的宏 简单的宏的定义格式: #define 标识符 替换列表 如: #define DTE_LEN 80 #define TRUE 1 #define FALSE 0 #define PI 3.1415926 #de

宏定义中使用do{}while(0)的好处 (转载)

宏定义中使用do{}while(0)的好处   #define MACRO_NAME(para) do{macro content}while(0) 的格式,总结了以下几个原因: 1,空的宏定义避免warning: #define foo() do{}while(0) 2,存在一个独立的block,可以用来进行变量定义,进行比较复杂的实现. 3,如果出现在判断语句过后的宏,这样可以保证作为一个整体来是实现: #define foo(x) /action1(); /action2(); 在以下情况