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


    一、背景  

      睡前忽然想写两行代码练练手,想起快速排序的一种变种实现,于是写了快速排序,在理论上完全没问题的情况下测试结果却很诡异,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、测试输出

    yg@yg-PC:~/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; 
    }

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

    yg@yg-PC:~/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,即便实现上已经避免了很多低级错误;但宏定义依然有很多不尽人意之处,就像上面的使用,编译器甚至都不会给个警告,在运行期也可以正常运行,但结果通却是莫名其妙的错误;通过一番折腾找出原因所在,以后在使用宏定义的地方一定要避免这种操作。

  • 相关阅读:
    Meta标签详解
    Python: 什么是*args和**kwargs
    如何进行 WebSocket 协议的压测
    在Mac上使用Microsoft Remote Desktop
    报表测试方法与注意事项
    Linux服务部署Yapi项目(安装Node Mongdb Git Nginx等) Linux服务部署Yapi
    mac安装brew(亲测)
    Linux下查看系统配置
    iTerm--比Terminal(终端)更好用的命令行工具
    协方差矩阵
  • 原文地址:https://www.cnblogs.com/zhangyi-studio/p/12117269.html
Copyright © 2020-2023  润新知