社区编辑申请
注册/登录
C#快速排序的趣味实现:从Haskell说起
开发 后端
C#中缺少一些基础的数据结构,因此无法直接实现向Haskell里面那样的函数式快速排序。本文介绍一个C#快速排序的实现方法。

C#快速排序不好实现?

前一段时间有朋友问我,以下这段Haskell快速排序的代码,是否可以转化成C#中等价的Lambda表达式实现:

  1. qsort [] = []  
  2. qsort (x:xs) = qsort (filter (< x) xs) ++ [x] ++ qsort (filter (>= x) xs) 

我当时回答,C#中缺少一些基础的数据结构,因此不行。经过补充之后,就没有任何问题了。后来,我觉得这个问题挺有意思,难度适中,也挺考察“基础编程”能力的,于是就自己写了一个。如果您感兴趣的话,也不妨一试。

这段代码是经典的,常用的体现“函数式编程”省时省力的例子,用短短两行代码实现了一个快速排序的确了不起。您可能不了解Haskell,那么我在这里先解释一下。

首先,这里用到了函数式编程语言中最常用的一种数据结构:不可变的链表。这个数据结构事实上是一个单向链表,并且是“不可变”的。这种数据结构在F#中也有存在,它的结构用大致是这样的:

不可变的链表 

可见,这是一种递归的数据结构。如果我们把这种数据结构叫做是ImmutableList的话,那么每个ImmutableList对象就会包含一个元素的“值”,以及另一个ImmutableList对象(在上图中,每个框就是一个ImmutableList对象)。对于每个ImmutableList对象来说,这个“值”便是它的“头(Head)”,而内部的ImmutableList对象则是它的“尾(Tail)”。如果用C#来表示的话,ImmutableList在C#中的定义可能就是这样的:

  1. public class ImmutableList<T> : IEnumerable<T>  
  2. {  
  3.     public readonly static ImmutableList<T> Empty = new ImmutableList<T>(default(T));  
  4.  
  5.     private ImmutableList(T head, ImmutableList<T> tail)  
  6.     {  
  7.         this.Head = head;  
  8.         this.Tail = tail;  
  9.     }  
  10.  
  11.     public T Head { getprivate set; }  
  12.  
  13.     public ImmutableList<T> Tail { getprivate set; }  
  14.  
  15.     ...  
  16. }  

您一定意识到了,ImmutableList是一个不可变的链表数据结构,一旦构造之后就再也没有办法修改它的Head与Tail。此外,这里使用Empty来表示一个空链表1。因此,我们使用一个ImmutableList对象便可以代表整个链表,并可以通过Tail来遍历链表上所有的元素:

  1. public class ImmutableList<T> : IEnumerable<T>  
  2. {  
  3.     ...  
  4.  
  5.     #region IEnumerable<T> Members  
  6.  
  7.     public IEnumerator<T> GetEnumerator()  
  8.     {  
  9.         var current = this;  
  10.         while (current != Empty)  
  11.         {  
  12.             yield return current.Head;  
  13.             current = current.Tail;  
  14.         }  
  15.     }  
  16.  
  17.     #endregion  
  18.  
  19.     #region IEnumerable Members  
  20.  
  21.     IEnumerator IEnumerable.GetEnumerator()  
  22.     {  
  23.         return this.GetEnumerator();  
  24.     }  
  25.  
  26.     #endregion  
  27. }  

我们再来观察Haskell代码,这段代码分为两行:

  1. qsort [] = []  
  2. qsort (x:xs) = ... 

这两行都定义了qsort函数,不过参数不同。这种做法在Haskell里被称为“模式匹配”,qsort后面的参数即是“模式”。***行代码的参数“指明”是一个空链表,因此只有为qsort传入一个空的链表才会执行等号后的逻辑。如果为qsort函数传入的链表不为空,那么它即可被匹配为head和tail两部分,这在Haskell中表示为(head:tail)。因此,在调用第二行的qsort函数时,x会自动绑定为head元素,而xs会自动绑定为tail——结合之前的解释,我们可以知道x是“元素”类型,而xs是另一个链表(可能为空)。

C#快速排序的实现

由于C#没有Haskell的模式匹配方式,因此只能在方法内部使用if(或三元运算符?:)进行逻辑控制:

  1. public static class ImmutableListExtensions  
  2. {  
  3.     public static ImmutableList<T> QuickSort<T>(this ImmutableList<T> list, Func<T, T, int> compare)  
  4.     {  
  5.         if (list == nullthrow new ArgumentNullException("list");  
  6.         if (compare == nullthrow new ArgumentNullException("compare");  
  7.  
  8.         if (list == ImmutableList<T>.Empty)  
  9.         {  
  10.             ...  
  11.         }  
  12.         else 
  13.         {   
  14.             ...  
  15.         }  
  16.     }  
  17. }  

我们将QuickSort定义为ImmutableList的扩展方法,接受一个比较函数,最终则返回一个排序后的新的链表——因为ImmutableList是不可变的。

然后,我们再回到Haskell的代码,我们可以看出,***行qsort函数由于接受了一个空链表,因此排序后的结果自然也是一个空链表。而第二行qsort则是一个较为标准的快速排序实现(快速排序的原理大致是:取一个元素作为pivot,先把那些比pivot小的元素放到pivot之前,再把比pivot大的放到pivot之后,然后对pivot的前后两部分分别采取快速排序。递归至***,则整个链表排序完成):

  1. qsort (x:xs) = qsort (filter (< x) xs) ++ [x] ++ qsort (filter (>= x) xs) 

在上面这行代码中,filter高阶函数的作用是对一个链表进行过滤,并返回一个新的链表。它的***个参数为过滤条件(如(< x)或(>= x),它们都是匿名函数),而第二个参数则是被过滤的目标。(这里即为xs,它是qsort参数的tail)。“++”运算符在Haskell中的含义是连接两个链表,并返回连接的结果。

因此,这行代码的含义为:把小于x的元素使用qsort函数排序,连接上x元素,再连接上大于等于x的元素排序后的结果。于是,***的结果便是一个排好序的链表。

值得注意的是,由于链表是种不可变的数据结构,因此无论是qsort函数,filter函数,还是++运算符,它们都会返回一个新的链表,而不会对原有链表作任何修改。这点是和我们传统所做的“数组排序”相比有所不同的地方。

现在,您就来尝试实现那个QuickSort方法吧。您可以为ImmutableList补充所需的成员,只要保持ImmutableList的各种特性不变,并实现快速排序便可以了。

以上就实现了C#快速排序。本文来自老赵点滴:《趣味编程:函数式链表的快速排序》

【编辑推荐】

  1. 理解C# String类型:特殊的引用类型
  2. 关于interface继承来源的讨论
  3. C#显式实现接口原理浅析
  4. C# interface学习经验浅谈
  5. C# interface使用实例分析
责任编辑:yangsai 来源: 老赵点滴
相关推荐

2022-04-11 11:38:44

Python代码游戏

2022-05-09 15:08:56

存储厂商NFV领域华为

2022-05-20 14:54:33

数据安全数字化转型企业

2022-05-11 14:48:33

腾讯云寿险民生保险

2022-05-16 10:36:08

GitHub开源项目

2022-03-10 08:24:17

Docker容器SaaS

2022-05-23 10:55:19

华为数字化转型架构蓝图

2022-04-13 18:40:59

Python开发

2022-05-24 21:29:30

2022-04-19 14:41:29

Oracle数据库SQL

2022-02-12 12:26:45

2022-05-24 08:21:16

数据安全API

2022-05-12 14:22:39

NFC标签鸿蒙

2022-05-24 12:42:24

物联网

2022-05-18 20:28:23

数字化转型云计算

2022-05-20 08:17:43

Java日志

2022-05-19 19:26:33

区块链大数据数据分析

2022-05-20 14:08:13

Web3元宇宙区块链

2022-04-28 09:46:20

Nginx文件Linux

2022-04-26 08:00:00

存储UFSeMMC

同话题下的热门内容

简单的六种防止数据重复提交的方法!用Python进行人脸识别「包括源代码」超实用!教你用 Python 获取并下载美股数据在 Go 中实现一个支持并发的 TCP 服务端Python或R:哪种编程语言更适合数据科学?用 XML 和 Java 构建树莓派打印机的用户界面C语言:如何给全局变量起一个别名?什么!Sentinel流控规则可以这样玩?

编辑推荐

使用Kotlin做开发一个月后的感想面试官问你什么是消息队列?把这篇甩给他!五大自动化测试的Python框架图文详解两种算法:深度优先遍历(DFS)和广度优先遍历(BFS)2018年最流行的十大编程语言,其中包括你用的语言吗?
我收藏的内容
点赞
收藏

51CTO技术栈公众号