您的位置:首页 > 其它

白话算法(1) for循环不是随便写的

2010-03-02 08:29 323 查看
  在杨修的那些颇具传奇色彩的小故事里面,有一个是最能体现他是如何的聪明,却又没有用到正地方的,说的是杨修身为曹操主簿,却又不肯老老实实坐在办公室里,老想溜出去玩,可是又怕曹操有问题要问,于是每当外出时,都要事先揣度曹操的心思,写出答案,按次序写好,并吩咐侍从,如果丞相有令传出,就按这个次序一一作答,结果每次都严丝合缝,没出过一点差错。可是后来有一次吹来一阵风,把纸张的顺序弄乱了。侍从按乱了的次序作答,自然文不对题,结果露了馅。
  不过我怎么都不觉得这是小聪明。要我说,这简直就是奇迹。即使是曹操问一题、杨修答一题的模式,能够全部回答出来就已经算得上是很聪明、很有才了。但是,杨修所做的事情却是,他不但可以预先判断出问题的内容,就连问题的数量和顺序也能丝毫不差地预先判断出来,这可就有些神了。如果杨老师给高考押题的话,那就不叫押题,那看上去根本就是泄题,估计也是难逃一死。

算法设计的一个常用思路

  但是身为程序员,却必须时不时地扮演一下杨修。不信?准备接招:实现一个函数“int[] Sort(int[] s)”;输入:一个长度不确定、元素不确定、顺序不确定的数组;输出:按从小到大的顺序排列的数组。
  例如:输入:{ 3, 1 }; 输出:{ 1, 3 }。
输入:{ 5, 2, 1, 4, 6, 7, 3 }; 输出:{ 1, 2, 3, 4, 5, 6, 7 }。
  我们圈里人都知道,计算机里面其实并没有一个小妖精手忙脚乱地替我们工作。如果说计算机知道怎么工作,那是因为我们程序员预先判断出了所有可能的输入情况,并且告诉计算机在每种情况下应该怎么处理。但是,我们看到,输入的元素个数是不确定的,并且是无序的,这两个特点将导致输入的状态是无限多的,所以,我们不可能像杨修那样写程序:

 Sort(int[] s)
{
if(IsArrayEquals(s, new int[] { 2, 1, 3 }))
return new int[] { 1, 2, 3 }
else if(IsArrayEquals(s, new int[] { 3, 1, 2 }))
return new int[] { 1, 2, 3 }
else if(IsArrayEquals(s, new int[] { 7, 6, 5, 9 }))
return new int[] { 5, 6, 7, 9 }
...
}

  那么,我们如何把具有无限多个状态的无序的集合(输入)转化为有序的集合(输出)呢?(注:这里的&ldquo]插入排序

代码

[code]class Program
{
static void Main(string[] args)
{
Console.WriteLine(InsertionSort(new int[] { }).Montage(p => p.ToString(), ","));
Console.WriteLine(InsertionSort(new int[] { 1 }).Montage(p => p.ToString(), ","));
Console.WriteLine(InsertionSort(new int[] { 1, 2 }).Montage(p => p.ToString(), ","));
Console.WriteLine(InsertionSort(new int[] { 2, 1 }).Montage(p => p.ToString(), ","));
Console.WriteLine(InsertionSort(new int[] { 4, 2, 5, 10, 7 }).Montage(p => p.ToString(), ","));
}

static int[] InsertionSort(int[] s)
{
for (int i = 0; i < s.Length-1; i++) // s[0..i] 是每次迭代前的 L
{
int b = s[i + 1]; // s[0..i+1] 是迭代后的 L
int j = i;
while (j >= 0 && s[j] > b)
{
s[j + 1] = s[j];
j--;
}
s[j + 1] = b;
}
return s;
}
}

public static class EnumerableExtension
{
/// <summary>
/// 将列表元素拼接成由splitter分隔的字符串
/// </summary>
/// <example>
///     拼接字符串:
///         <c>new List<string> { "aa", "bb", "cc" }.Montage(p => p, ","); // 返回:"aa,bb,cc"</c>
///     拼接对象属性:
///         <c>new List<string> { "aa", "bbb", "c" }.Montage(p => p.Length.ToString(), ","); // 返回:"2,3,1"</c>
///     拼接枚举值:
///         <c>new List<DomainType> { DomainType.GuanHao, DomainType.YaoJiKe }.Montage(p => ((int)p).ToString(), ","); // 返回:"1,2"</c>
///     拼接枚举名:
///         <c>new List<DomainType> { DomainType.GuanHao, DomainType.YaoJiKe }.Montage(p => p.ToString(), ","); // 返回:"GuanHao,YaoJiKe"</c>
/// </example>
/// <typeparam name="T"></typeparam>
/// <param name="source"></param>
/// <param name="toString">将列表元素转换为字符串的委托</param>
/// <param name="splitter">分隔符(可为空)</param>
/// <returns></returns>
public static string Montage<T>(this IEnumerable<T> source, Func<T, string> toString, string splitter)
{
StringBuilder result = new StringBuilder();
splitter = splitter ?? string.Empty;
foreach (T item in source)
{
result.Append(toString(item));
result.Append(splitter);
}
string resultStr = result.ToString();
if (resultStr.EndsWith(splitter))
resultStr = resultStr.Remove(resultStr.Length - splitter.Length, splitter.Length);
return resultStr;
}
}


更快的排序算法?

  插入排序的最坏情况(譬如输入是{5,4,3,2,1})时间代价为Θ(n2)。是否还有更快的排序算法呢?从直觉上,我们可以发现插入排序有2个地方有些浪费性能。
  1)每次迭代时,我们需要根据b把L分成2个子集:a和c。这里a和c的定义分别是“所有元素小于等于b”和“所有元素大于b”,而并不要求a和c本身一定是有序的,所以我们一开始的时候就把a和c处理成有序的是不是有些浪费呢?
  2)每次迭代时,我们需要根据b把L分成2个子集:a和c。插入排序在划分a和c时使用的方法是逐一比较L里面每个元素和b的大小,是否有更快的划分a和c的方法呢?
  接下来的几篇,我们将介绍一些更快的排序算法和理解这些算法的思路。

注:开始的几篇我们会先将重点放到理解算法的思路上面,而不会介绍如何证明算法的正确性和分析算法性能。但是事实上它们是同样基础、重要和有用的知识。想想看,如果万一有一天我们发明了一种新的排序算法,如果没有这些知识,我们如何证明新算法在理论上是正确的,又如何评估新算法是世界上最快的还是第二快的算法呢?而且学习这些内容也有助于理解为什么某些算法性能更好。所以以后有机会一定会和大家一起认真地学习一下这部分内容。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐