白话算法(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 }。
我们圈里人都知道,计算机里面其实并没有一个小妖精手忙脚乱地替我们工作。如果说计算机知道怎么工作,那是因为我们程序员预先判断出了所有可能的输入情况,并且告诉计算机在每种情况下应该怎么处理。但是,我们看到,输入的元素个数是不确定的,并且是无序的,这两个特点将导致输入的状态是无限多的,所以,我们不可能像杨修那样写程序:
更快的排序算法?
插入排序的最坏情况(譬如输入是{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的方法呢?
接下来的几篇,我们将介绍一些更快的排序算法和理解这些算法的思路。
注:开始的几篇我们会先将重点放到理解算法的思路上面,而不会介绍如何证明算法的正确性和分析算法性能。但是事实上它们是同样基础、重要和有用的知识。想想看,如果万一有一天我们发明了一种新的排序算法,如果没有这些知识,我们如何证明新算法在理论上是正确的,又如何评估新算法是世界上最快的还是第二快的算法呢?而且学习这些内容也有助于理解为什么某些算法性能更好。所以以后有机会一定会和大家一起认真地学习一下这部分内容。
不过我怎么都不觉得这是小聪明。要我说,这简直就是奇迹。即使是曹操问一题、杨修答一题的模式,能够全部回答出来就已经算得上是很聪明、很有才了。但是,杨修所做的事情却是,他不但可以预先判断出问题的内容,就连问题的数量和顺序也能丝毫不差地预先判断出来,这可就有些神了。如果杨老师给高考押题的话,那就不叫押题,那看上去根本就是泄题,估计也是难逃一死。
算法设计的一个常用思路
但是身为程序员,却必须时不时地扮演一下杨修。不信?准备接招:实现一个函数“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的方法呢?
接下来的几篇,我们将介绍一些更快的排序算法和理解这些算法的思路。
注:开始的几篇我们会先将重点放到理解算法的思路上面,而不会介绍如何证明算法的正确性和分析算法性能。但是事实上它们是同样基础、重要和有用的知识。想想看,如果万一有一天我们发明了一种新的排序算法,如果没有这些知识,我们如何证明新算法在理论上是正确的,又如何评估新算法是世界上最快的还是第二快的算法呢?而且学习这些内容也有助于理解为什么某些算法性能更好。所以以后有机会一定会和大家一起认真地学习一下这部分内容。
相关文章推荐
- 算法竞赛入门经典 2.1 for 循环
- DP(六)——多重背包的三重循环算法(效率不是很高)
- 第十周-1 循环算法(for的用法 )
- 算法竞赛入门经典 2.1 for 循环
- Effective JavaScript Item 49 对于数组遍历,优先使用for循环,而不是for..in循环
- 算法竞赛入门经典 2.1 for 循环
- for循环时间复杂度算法理解
- 当你输入信用卡号码的时候,有没有担心输错了而造成损失呢?其实可以不必这么担心,因为并不是一个随便的信用卡号码都是合法的,它必须通过Luhn算法来验证通过。 该校验的过程:1、从卡号最后一位数字开始,逆
- 数组迭代要优先使用for循环而不是for...in循环
- 简化以下程序,将两个 for 循环都替换为标准的 C++ 算法
- 安卓删除List里面某些一样的数据为何使用的是迭代器而不是for循环
- Effective JavaScript Item 49 对于数组遍历,优先使用for循环,而不是for..in循环
- 在用v-for循环渲染列表后,实现滑动一个div时就只有它对应的滑块滑出,而不是所有的滑块都滑出
- [Effective JavaScript 笔记]第49条:数组迭代要优先使用for循环而不是for...in循环
- 简化以下程序,将函数对象 divide_by 转换为一个函数,并将 for 循环替换为用一个标准的 C++ 算法来输出数据
- 循环算法求和(for语句,while语句,do-while语句汇总!)
- 非变异算法之循环(for_each)
- php for 跳到下一次循环 不是跳出循环
- js算法一则:不用if。。while。。for等循环和判断语句实现1+2+3+...+n的和
- 当你输入信用卡号码的时候,有没有担心输错了而造成损失呢?其实可以不必这么担心, 因为并不是一个随便的信用卡号码都是合法的,它必须通过Luhn算法来验证通过。