scala纯函数式编程-排序算法实现

记得《Function Thinking》这本书中提到,现在的编程范式有两类,一类是“命令式编程”,另一类是“函数式编程”,现在我们最常使用的许多语言像c、c++、java都是命令式的,但其中c++和java也都有一些函数式的类库,可见函数式特性还是受一些程序员的青睐的。还有一些纯函数式的语言如 clojure、haskell则完全是纯函数式的。像python、scala则是混合型的,包含两种范式,给程序员提供了巨大的灵活性,使解决问题的方式更多,可谓是程序员的一大利器。
现在就以scala语言的"pattern matching"来实现一些经典的排序算法,来展示一下函数式编程思维方式上带给我们的惊喜和享受。

1. 冒泡排序

原理:

通过相邻元素比较交换的方式,将最大的元素依次移动到列表中未排好序部分的尾部,重复操作,直到列表中未排好序的部分为空,从而使整个列表有序

scala实现思路:

通过相邻元素比较交换的方式,将最大的元素依次移动到列表中未排好序部分的尾部,重复操作,直到列表中未排好序的部分为空,从而使整个列表有序

  1. 新建一冒泡函数bubble(unSorteds: List[A]): A,实现一趟冒泡功能,即从输入列表中冒泡出一个最大元素A
  2. 给bubble函数添加两个参数remains: List[A], accOrdereds: List[A],添加后函数如下:bubble(unSorteds: List[A],remains: List[A], accOrdereds: List[A]): A
    其中remains用于记录每次冒泡后去掉冒出去的元素后剩余元素列表,
    accOrdereds用于累积每趟冒泡冒出来的元素,然后将返回值A改为List[A],即返回累积排好序的列表。
    函数bubble使用“模式匹配”匹配为排序的列表,分两种情况
  3. 列表中至少有两种元素的情况
  4. 列表中只剩余一个元素
    这种情况下,当剩余元素列表remains为空时,说明整个排序完成。否则继续递归bubble

具体scala代码如下

object BubbleSort {

  /**
    * @param list 待排序列表
    * @tparam A 列表元素类型
    * @return
    */
  def bubbleSort[A <% Ordered[A]](list: List[A]): List[A] = {
    /**
      * @param unSorteds 每一趟冒泡时待排序列表
      * @param remains  已遍历且未冒出的元素列表
      * @param accOrdereds 已冒出的元素组成的有序列表(是累积的)
      * @return 每一趟冒泡后排好序的列表
      */
    @tailrec def bubble(unSorteds: List[A], remains: List[A], accOrdereds: List[A]): List[A] = unSorteds match {
      case h1 :: h2 :: t =>
        if (h1 > h2) bubble(h1 :: t, h2 :: remains, accOrdereds)
        else bubble(h2 :: t, h1 :: remains, accOrdereds)
      case h1 :: Nil =>
        if (remains.isEmpty)
          return h1 :: accOrdereds
        else bubble(remains, Nil,h1 :: accOrdereds)
    }
    bubble(list, Nil, Nil)
  }

  def main(args: Array[String]): Unit = {
    val list = List(1,13,7,5,8,9,20,43,11,8)
    println(bubbleSort(list))
  }
}

2. 快速排序

原理:

使用分治思想,将数列用选好的基准点划分为两个子序列(也就是将比基准点小的元素放左边,比基准点大的元素放右边),递归对子序列使用此方法进行此操作,递归到最底部时,数列的大小是零或一,也就是已排好序。

使用scala的实现思路:

利用scala的模式匹配对序列进行匹配,分两种情况:

  1. 序列为空
    为空时返回一个空的List()
  2. 序列由head和tail组成,head不为空,这时候以head为基准点将序列划分为left和right两个子序列,然后然后对left和right进行同样操作并将结果quickSort(left)和quickSort(right)与基准元素head连接起来,如此递归操作,直到所有子序列都为空,便已排好序。

scala代码实现:

object QuickSort extends App {
  /**
    * 快速排序
    *
    * @param list 待排序列表
    * @tparam A 列表元素类型
    * @return
    */
  def quickSort[A <% Ordered[A]](list: List[A]): List[A] = list match {
      case Nil => List()
      case head :: tail =>
        val (left, right) = tail.partition(_ < head)
        quickSort(left) ::: head :: quickSort(right)
    }

  val list = List(1, 13, 7, 5, 8, 9, 20, 43, 11, 8)
  println(quickSort(list))
}

3. 插入排序

原理:

通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入,直到将所有未排序数据都插入到已排序序列中,排序便完成

scala实现思路:

  1. 新建一个insert函数实现将一个元素插入到已排序序列的功能,函数签名如下 def insert(a: A, accOrdereds: List[A]): List[A],其中accOrdereds为已经排序序列,且是累积的,即每次insert时传入的都是当前最新的已排序序列。此函数实现思路也是使用模式匹配来实现。
    这种情况下
  2. 新建一sort函数,函数签名如下:
    def sort(unSorteds: List[A], accOrdereds: List[A]): List[A]
    其中unSorteds是以模式匹配的方式匹配头和尾,将头元素使用insert函数插入到累积的已排序的序列。然后再使用sort进行下一轮插入。如此递归执行,直到为排序序列unSorteds为空,返回累积已经排好序的序列

注意
scala中List的头(head)是List中第一个元素,List的尾(tail)是去掉头元素(head)后的List

scala代码实现:

object InsertionSort extends App {
  /**
    * @param list 待排列表
    * @tparam A 列表元素类型
    * @return
    */
  def insertionSort[A <% Ordered[A]](list: List[A]): List[A] = {
    /**
      * @param unSorteds 待排列表
      * @param accOrdereds 累积有序列表
      * @return 有序列表
      */
    @tailrec def sort(unSorteds: List[A], accOrdereds: List[A]): List[A] = unSorteds match {
      case ha :: ta => sort(ta, insert(ha, accOrdereds))
      case Nil => accOrdereds
    }

    /**
      * @param a 待插入元素
      * @param accOrdereds 累积有序列表
      * @return
      */
    def insert(a: A, accOrdereds: List[A]): List[A] = accOrdereds match {
        case h :: t if (a > h) => h :: insert(a, t)
        case _ => a :: accOrdereds
      }


    sort(list, Nil)
  }

  val list = List(1,13,7,5,8,9,20,43,11,8)
  println(insertionSort(list).mkString(","))

}

4. 归并排序

原理

使用分治思想,将序列划分为若干个只有一个元素的子序列,重复进行merge排序操作,将子序列两两合并,直到最后只剩下一个子序列,这个子序列就是已排好的序列

scala实现思路:

  1. 创建一个merge函数用于合并两个排好序的子序列
    def merge(as: List[A], bs: List[A]): List[A]
    实现方式通过内建一个loop函数,实现对两个序列的遍历和排序,loop函数签名如下:
    def loop(cs: List[A], ds: List[A], accSorteds: List[A]): List[A]
    cs和ds是两个已排序序列,accSorteds是累积排序序列,cs和ds合并过程中产生的新的有序列序列
  2. 新建一个划分序列并将划分序列合并排序的函数:
    splitIn2AndSort(as: List[A]): List[A]

scala代码实现

object MergeSort extends App {

  def mergeSort[A <% Ordered[A]](list: List[A]): List[A] = {
    /**
      * @param p 待排序的包含两个列表的元组
      * @return
      */
    def sort(p: (List[A], List[A])): List[A] = {
      p match {
        case (Nil, Nil) => Nil
        case (a :: Nil, Nil) => a :: Nil
        case (Nil, a :: Nil) => a :: Nil
        case (as, bs) => merge(splitIn2AndSort(as), splitIn2AndSort(bs))
      }
    }

    /**
      * 将给定列表划分为两个列表,并归并排序返回一个有序列表
      * @param as 待划分列表
      * @return
      */
    def splitIn2AndSort(as: List[A]): List[A] = sort(splitIn2(as))

    /**
      * 合并两个有序列表
      * @param as 有序列表
      * @param bs 有序列表
      * @return 合并后的有序列表
      */
    def merge(as: List[A], bs: List[A]): List[A] = {
      def loop(cs: List[A], ds: List[A], accSorteds: List[A]): List[A] = (cs, ds) match {
        case (Nil, Nil) => accSorteds
        case (hc :: tc, hd :: td) =>
          if (hc < hd)
            loop(tc, ds, hc :: accSorteds)
          else
            loop(td, cs, hd :: accSorteds)
        case (hc :: tc, Nil) => loop(tc, Nil, hc :: accSorteds)
        case (Nil, hd :: td) => loop(Nil, td, hd :: accSorteds)
      }

      loop(as, bs, Nil).reverse
    }

    def splitIn2(as: List[A]): (List[A], List[A]) = {
      val mid = as.length / 2
      (as.slice(0, mid), as.slice(mid, as.length))
    }

    splitIn2AndSort(list)
  }

  val list = List(1, 13, 7, 5, 8, 9, 20, 43, 11, 8)
  println(mergeSort(list).mkString(","))
}

5. 选择排序

原理

从原序列中依次移出符合条件(最大或最小)的元素,放入到有序序列中,直到原序列吴待排序元素

scala实现思路:

  1. 新建一函数:
    def select(remains: List[A], sorteds: List[A], accSorteds: List[A]): List[A]
    实现从剩余的未排序序列remains中选出符合条件的元素,将它追加到已排序序列sorteds和累积已排序序列accSorteds中
  2. 新建函数:
    def sort(remains: List[A], accSorteds: List[A]): List[A]
    用来执行一趟选择排序过程,将排序结果累积在accSorteds中,当remains为空时,排序结束,返回accSorteds

scala代码实现:

object SelectionSort extends App {

  def selectionSort[A <% Ordered[A]](list: List[A]): List[A] = {
    /**
      * @param unSorteds 未排序列表
      * @param accSorteds 累积最终的有序列表
      * @return
      */
    def sort(unSorteds: List[A], accSorteds: List[A]): List[A] =
      unSorteds match {
        case h :: t => select(unSorteds, Nil, accSorteds)
        case Nil => accSorteds
      }

    /**
      *
      * @param unSorteds  未排序列表
      * @param sorteds    选择出的元素组成的有序列表
      * @param accSorteds 累积最终的有序列表
      * @return
      */
    @tailrec def select(unSorteds: List[A], sorteds: List[A], accSorteds: List[A]): List[A] =
      unSorteds match {
        case h1 :: h2 :: t =>
          if (h1 < h2)
            select(h2 :: t, h1 :: sorteds, accSorteds)
          else
            select(h1 :: t, h2 :: sorteds, accSorteds)
        case h :: Nil => sort(sorteds, h :: accSorteds)
        case Nil => sort(sorteds, accSorteds)
      }
    sort(list, Nil)
  }

  val list = List(1, 13, 7, 5, 8, 9, 20, 43, 11, 8)
  println(selectionSort(list))
}

以上五种排序算均采用scala函数式方式实现,实现过程多采用递归思维和模式匹配,这也是函数式编程通常使用的方式。个人水平有限,如发现纰漏,请指正!

获取更多scala学习代码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容