首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

6.3 泛型代码开发:快速排序

排序是一种对许多应用都很重要的算法。排序需要作用于多种不同的类型,也需要紧凑的编码和很高的效率。因此,排序函数是泛型编码的首选。下面的代码实现了快速排序算法。快速排序算法是由C. Anthony R. Hoare创建的一个高效的、著名的排序算法,最初发表于1962年的论文《Quicksort》(Computer Journal,vol.5,no.1)。在各种排序算法中,快速排序可能是应用最广泛的内排序(排序时所有数据全部都存储在主存中)算法。

首先为特定类型编写快速排序算法程序,然后我们会看到,将它转换为泛型代码是非常容易的一件事情。下面的代码实现了快速排序算法。

文件 quicksort.cpp

// Quicksort

inline void swap(int& x, int& y)

{

   int t;

   t = x;

   x = y;

   y = t;

}

inline void order(int& x, int& y)

{

   if (x > y) swap(x, y);

}

bool find_pivot(int *left, int *right,int *pivot_ptr);

int* partition(int *left, int *right, int pivot);

void quicksort(int *left, int *right)

{

   int *p, pivot;

   if (find_pivot(left, right, &pivot)) {

      p = partition(left, right, pivot);

      quicksort(left, p - 1);

      quicksort(p, right);

   }

}

通常使用递归实现快速排序,采用的基本思想是分而治之(divide and conquer)。假设在main()函数中将a声明为一个长度为N的数组。在为数组赋值后,可以调用下面的函数进行排序:

quicksort(a, a + N - 1);

函数的第一个参数是指向数组首元素的指针,第二个参数是指向数组最后一个元素的指针。在quicksort()的函数定义中,很容易就能明白这些指针分别代表数组的左边界和右边界。如果可能的话,函数find_pivot()选择数组中的一个元素作为中心元素(pivot element),函数partition()重新排列数组中的各个元素,使得第一部分的所有元素的值都小于中心元素的值,剩余部分的所有元素的值都大于或者等于中心元素的值。另外,函数partition()返回一个指向数组元素的指针,该指针左边的元素值都小于中心元素的值,指针右边的元素的值以及指针指向的元素的值,都大于或等于中心元素的值。一旦数组以中心元素为基准进行了重新排列,就可以将函数quicksort()作用于每个子数组。

bool find_pivot(int *left, int *right, int *pivot_ptr)

{

   int a, b, c, *p;

   a = *left;                       // left value

   b = *(left + (right - left) / 2);        // middle value

   c = *right;                            // right value

   order(a, b);

   order(a, c);

   order(b, c);                           // order these 3 values

   if (a < b) {                           // pivot is higher of 2 values

      *pivot_ptr = b;

      return true;

   }

   if (b < c) {

      *pivot_ptr = c;

      return true;

   }

   for (p = left + 1; p <= right; ++p)

      if (*p != *left) {

         *pivot_ptr = (*p < *left) ? *left : *p;

         return true;

      }

   return false;                   // all elements have same value

}

理想状态下,中心元素的选择应该使得每一步都能将数组分割成两部分,每一部分都有相同(或者近似相同)的元素数目。这将使函数quicksort()处理任务的总量达到最小值。因为预先不知道这个中心值是多少,所以要试图从数组的首元素,中间元素和最末元素中选出能够作为中心元素的中间值。为了进一步进行划分,要求至少有一个元素小于中心元素值。如果所有元素的值相同,则中心元素不存在,函数返回false。

int *partition(int *left, int *right, int pivot)

{

   while (left <= right) {

      while (*left < pivot)

       ++left;

      while (*right >= pivot)

       --right;

      if (left < right) {

       swap(*left, *right);

       ++left;

       --right;

      }

   }

   return left;

}

函数partition()完成了主要工作。这里详细解释一下该函数是如何工作的。假设有一个包含12个元素的数组a[]:

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

调用函数find_pivot()时,首先比较数组的首元素、中间元素和最末元素。中间元素5比这三个值中最小的值大,因此将这个值选为中心元素值。下面的仿真过程显示了函数partition()中的外部while循环每一次运行后的数组元素值。每次循环中被交换的元素由下划线标识。

未排序的数据       7     4     3     5     2     5     8     2     1     9     -6    -3

第一次循环后       -3    4     3     5     2     5     8     2     1     9     -6    7

第二次循环后       -3    4     3     -6    2     5     8     2     1     9     5     7

第三次循环后       -3    4     3     -6    2     1     8     2     5     9     5     7

第四次循环后      -3    4     3     -6    2     1     2     8     5     9     5     7

注意,在第四次循环后,下标为0~6的元素的值都已经小于中心元素的值,而剩下的元素的值都大于或者等于中心元素的值。因此在第四次循环结束后,partition()返回元素a[7]的地址,并结束运行。

将quicksort()转换为泛型函数

现在将快速排序算法转换成能够作用于泛型数据的通用算法,这里的关键是要识别出需要泛化的所有类型。最初的算法作用于int型数据,下面将一步步将其改变为作用于类型T。

文件 genericQuicksort.cpp

template <class T>

inline void swap(T& x, T& y)

{

   T t;

   t = x;

   x = y;

   y = t;

}

template <class T>

inline void order(T& x, T& y)

{

   if (x > y) swap(x, y);

}

swap()函数与order()函数解析

■ template <class T>

 inline void order(T& x, T& y)

编写模板的关键是要在模板代码开始处使用template,以及适当数量的类型标识符,通常使用标识符T1、T2等。正常情况下,在只有一种参数化类型时通常使用class T。函数通常使用T作为它的签名的一部分。稍后,在使用给定类型调用order()时(例如在order(x,y)中给定类型为double类型),编译器产生专门处理这个类型的代码。这里的一个不利方面是,对每一个不同类型使用order()函数时都会产生一个函数体。泛型编码能够使用void*避免产生多个函数体,也即避免代码膨胀(code bloat)。

■ {

     if (x > y) swap(x, y);

 }

这段代码与int类型的代码没有什么区别。如果特殊类型T没有定义运算符>,编译器会给出语法错误。

■ template <class T>

 inline void swap(T& x, T& y)

 {

     T t;

这里,函数签名和块内变量都使用了类型T。

■     t = x;

     x = y;

     y = t;

 }

这段代码应该能够正常工作,因为在所有类型中都定义了operator=。但是,用户需要检查operator=的语义是不是深复制语义,也就是说,要复制数据,而不是只复制指向数据的指针。

一旦理解了算法和它的泛型编码模式,很容易就能够找到特定的类型。这个例子中的特定类型是int类型,因此应该对它进行参数化并将其修改为一个参数化类型T。下面我们不再做任何说明,只是简单地给出剩余的代码。

// Forward declarations of auxiliary functions

template <class T>

bool find_pivot(T *left, T *right, T *pivot_ptr);

template <class T>

T* partition(T *left, T *right, T pivot);

template <class T>

void quicksort(T *left, T *right)

{

   T   *p, pivot;

   if (find_pivot(left, right, &pivot)) {

      p = partition(left, right, pivot);

      quicksort(left, p - 1);

      quicksort(p, right);

   }

}

template <class T>

T *partition(T *left, T *right, T pivot)

{

   while (left <= right) {

      while (*left < pivot)

         ++left;

      while (*right >= pivot)

         --right;

      if (left < right) {

         swap(*left, *right);

         ++left;

         --right;

      }

   }

   return left;

}

template <class T>

bool find_pivot(T *left, T *right, T *pivot_ptr)

{

   T a, b, c, *p;

   a = *left;                       // left value

   b = *(left + (right - left) / 2);        // middle value

   c = *right;                            // right value

   order(a, b);

   order(a, c);

   order(b, c);                           // order these 3 values

   if (a < b) {                           // pivot is higher of 2 values

      *pivot_ptr = b;

      return true;

   }

   if (b < c) {

      *pivot_ptr = c;

      return true;

   }

   for (p = left + 1; p <= right; ++p)

      if (*p != *left) {

         *pivot_ptr = (*p < *left) ? *left : *p;

         return true;

      }

   return false;                   // all elements have same value

}

下面使用两种不同类型的数组测试这段代码。

文件 genericQuicksort.cpp

int main()

{

   cout << "quicksort\n";

   int a[12]={7, 4, 3, 5, 2, 5, 8, 2, 1, 9, -6, -3 };

   for (int i = 0; i < 12; i++)

      cout << a[i] << " , ";

   quicksort(a, a + 11);

   cout << "\n\nquicksorted\n";

   for (int i = 0; i < 12; i++)

      cout << a[i] << " , ";

   cout << endl;

   // Now use doubles

   double b[6] = { 7.8, 4.9, 3.8, 5.0, 2.8, 5.3 };

   for (int i = 0; i < 6; i++)

      cout << b[i] << " , ";

   quicksort(b, b + 5);

   cout << "\n\nquicksorted\n";

   for (int i = 0; i < 6; i++)

      cout << b[i] << " , ";

   cout << endl;

}

标准C语言库提供了qsort()函数,它是一个使用void*的快速排序泛型例程。在Al Kelley和Ira Pohl著的C by Dissection(第4版)一书中,对这种技术进行了展开讨论。

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论