F#奇妙游(18):F#与百万浮点数MergeSort
1.F#奇妙游(32):缺失数据集合的处理2.F#奇妙游(29):PPTX注释变音频插入页面3.F#奇妙游(31):数据缺失的处理4.F#奇妙游(30):计算表达式与ADT5.F#奇妙游(28):ADT中简单值的F#实现6.F#奇妙游(27):大大的小小let7.F#奇妙游(26):计算表达式浅尝8.F#奇妙游(25):ADT和领域设计9.F#奇妙游(24):函数式编程与代数数据类型ADT10.F#奇妙游(23):领域驱动设计DDD与代数数据类型ADT11.F#奇妙游(22):Monte Carlo方法的F#实现12.F#奇妙游(21):为什么老是你,模式匹配!13.F#奇妙游(20):主动模式14.F#奇妙游(19):F#中的模式匹配(match with)
15.F#奇妙游(18):F#与百万浮点数MergeSort
16.F#奇妙游(17):F#与真空一维平面大地抛石飞行力学17.F#奇妙游(16):基于对象和面向对象18.F#奇妙游(15):优雅的WPF程序19.F#奇妙游(14):F#实现WPF的绑定20.F#奇妙游(13):代码风格21.F#奇妙游(12):并行编程与π22.F#奇妙游(11):函数+值=程序23.F#奇妙游(10):可区分联合24.F#奇妙游(9):来一点点画图25.F#奇妙游(8):计量单位26.F#奇妙游(7):也来整点测评27.F#奇妙游(6):如何不用for循环以及循环28.F#奇妙游(5):计算π的值29.F#奇妙游(4):F#的函数30.F#奇妙游(3):函数与编程31.F#奇妙游(2):dotnet命令行工具32.F#奇妙游(1):F#浅尝33.F#奇妙游(33):动态执行F#代码Mergesort
mergesort是一个典型的分而治之、迭代的算法。它将一个数组分成两个子数组,分别排序,然后将有序的子数组归并为一个有序的数组。归并操作是将两个有序数组归并成一个有序数组的过程。
mergesort的时间复杂度为O(nlogn),最佳和最坏情况下的时间复杂度都是O(nlogn)。它的空间复杂度为O(n)。
算法的基本思路是:将数组分成两半,分别对两半进行排序,然后将结果归并起来。归并操作的实现是:创建一个临时数组,将两个有序的子数组中较小的元素放入临时数组中,然后将临时数组中的元素复制回原数组。
C#代码实现
MergeSort算法的C#代码实现如下:
public static void MergeSort(int[] array)
{
int[] aux = new int[array.Length];
MergeSort(array, aux, 0, array.Length - 1);
}
private static void MergeSort(int[] array, int[] aux, int lo, int hi)
{
if (lo >= hi)
{
return;
}
int mid = lo + (hi - lo) / 2;
MergeSort(array, aux, lo, mid);
MergeSort(array, aux, mid + 1, hi);
Merge(array, aux, lo, mid, hi);
}
private static void Merge(int[] array, int[] aux, int lo, int mid, int hi)
{
for (int k = lo; k <= hi; k++)
{
aux[k] = array[k];
}
int i = lo;
int j = mid + 1;
for (int k = lo; k <= hi; k++)
{
if (i > mid)
{
array[k] = aux[j++];
}
else if (j > hi)
{
array[k] = aux[i++];
}
else if (aux[i] < aux[j])
{
array[k] = aux[i++];
}
else
{
array[k] = aux[j++];
}
}
}
这个算法实现的是in-place的,但是需要额外的空间来保存归并时的临时数组。所以,它的空间复杂度为O(n)。
F#代码实现
按照这个思路,可以直接翻译出F#的实现:
let split (lo: int) (hi: int) =
let mid = lo + (hi - lo) / 2
(lo, mid, hi)
let merge<'T when 'T: comparison> (a: 'T array) (aux: 'T array) (lo: int) (mid: int) (hi: int) =
for k in lo..hi do
aux[k] <- a[k]
let mutable i = lo
let mutable j = mid + 1
for k in lo..hi do
if i > mid then
a[k] <- aux[j]
j <- j + 1
elif j > hi then
a[k] <- aux[i]
i <- i + 1
elif aux[i] < aux[j] then
a[k] <- aux[i]
i <- i + 1
else
a[k] <- aux[j]
j <- j + 1
let mergesort<'T when 'T: comparison> (a: 'T array) =
let aux = Array.create a.Length a[0]
let rec sort (a: 'T array) (aux: 'T array) lo hi =
if hi <= lo then
()
else
let (lo, mid, hi) = split lo hi
sort a aux lo mid
sort a aux (mid + 1) hi
merge a aux lo mid hi
sort a aux 0 (a.Length - 1)
调用这个算法的代码如下:
let a = [| 3; 2; 1; 4; 5; 6; 9; 8; 7 |]
mergesort a
printfn "%A" a
或者,使用百万级别的数据来测试一下:
open System
open System.Diagnostics
let n = 100000000 // 100 million
let a = List.init n (fun _ -> Random().NextDouble())
let k = 5
printfn "%A" a[0 .. k - 1]
// print last 10 elements
printfn "%A" a[n - k .. n - 1]
let sw = Stopwatch.StartNew()
let b = mergesort a
sw.Stop()
printfn "%A" b[0 .. k - 1]
// print last 10 elements
printfn "%A" b[n - k .. n - 1]
printfn "%d elements sorted in %A seconds" n (float sw.ElapsedMilliseconds / 1000.0)
输出的结果:
[|0.4535403336; 0.4689574615; 0.8036827634; 0.01010983729; 0.548441069|]
[|0.3559696886; 0.1329930819; 0.1535221465; 0.7671093143; 0.5670336269|]
[|7.55451135e-09; 9.204160878e-09; 1.561528273e-08; 1.919768666e-08;
3.959270578e-08|]
[|0.9999999739; 0.999999974; 0.9999999792; 0.999999981; 0.9999999876|]
100000000 elements sorted in 131.298 seconds
唯一的问题是,这个代码很不函数式,基本上式命令式的,而且涉及到对数组的修改。如果要更加函数一点呢?
函数式的mergesort
函数式的mergesort的思路是:将数组分成两半,分别对两半进行排序,然后将结果归并起来。归并操作的函数式描述可以很好的以函数式的方式实现。
let rec split (a: 'T list) =
match a with
| [] -> ([], [])
| [x] -> ([x], [])
| x::y::xs ->
let (l1, l2) = split xs
(x::l1, y::l2)
let rec merge (a: 'T list) (b: 'T list) =
match (a, b) with
| ([], _) -> b
| (_, []) -> a
| (x::xs, y::ys) ->
if x < y then
x::(merge xs b)
else
y::(merge a ys)
let rec mergesort (a: 'T list) =
match a with
| [] -> []
| [x] -> [x]
| _ ->
let (l1, l2) = split a
merge (mergesort l1) (mergesort l2)
当算法这么写的时候,理解起来就更加容易了。特别是关于merge的实现,把两个有序的list合并成一个有序的list,就是把两个list中较小的元素放在前面,然后再把剩下的元素合并。这个思路是非常清晰的。因为这个部分是mergesort的核心。
但是这个递归算法每次递归都会创建新的list,所以空间复杂度是O(nlogn)。而且,因为递归的关系,对于非常大的列表,会导致栈溢出。所以,这个算法并不是很好用。
总结
- Mergesort是一个典型的分而治之、迭代的算法。它将一个数组分成两个子数组,分别排序,然后将有序的子数组归并为一个有序的数组。归并操作是将两个有序数组归并成一个有序数组的过程。
- Mergesort的时间复杂度为O(nlogn),最佳和最坏情况下的时间复杂度都是O(nlogn)。它的空间复杂度为O(n)。
- 采用函数式的方式可以以非常直观和很少的代码量实现Mergesort算法。但是这个算法很难(到底是不是不可能我还不能确定)实现尾递归优化,所以对于大的数据集,会导致栈溢出。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~