归并排序

归并排序也是采用“分而治之”的方式。刚发现分治法是一种算法范式,我还一直以为是一种需要意会的思想呢。

不好意思了,孤陋寡闻了,哈哈!

原理:将两个有序的数列,通过比较,合并为一个有序数列。 维基入口

为方便理解,此处实现用了List<int>的一些方法,随后有IList<int>版本。

实现如下:

public static List<int> MergeSortOnlyList(List<int> data, int low, int high)
{
if (low == high)
return new List<int> { data[low] };
List<int> mergeData = new List<int>();
int mid = (low + high) / ;
List<int> leftData = MergeSortOnlyList(data, low, mid);
List<int> rightData = MergeSortOnlyList(data, mid + , high);
int i = , j = ;
while (true)
{
if (leftData[i] < rightData[j])
{
mergeData.Add(leftData[i]);
if (++i == leftData.Count)
{
mergeData.AddRange(rightData.GetRange(j, rightData.Count - j));
break;
}
}
else
{
mergeData.Add(rightData[j]);
if (++j == rightData.Count)
{
mergeData.AddRange(leftData.GetRange(i, leftData.Count - i));
break;
}
}
}
return mergeData;
} public static List<int> MergeSortOnlyList(List<int> data)
{
data = MergeSortOnlyList(data, , data.Count - ); //不会改变外部引用 参照C#参数传递
return data;
}

过程解析:将数列分为两部分,分别得到两部分数列的有序版本,然后逐个比较,将比较出的小数逐个放进

新的空数列中。当一个数列放完后,将另一个数列剩余数全部放进去。

IList<int>版本

实现如下:

public static IList<int> MergeSort(IList<int> data)
{
data = MergeSort(data, , data.Count - );
return data;
} public static IList<int> MergeSort(IList<int> data, int low, int high)
{
int length = high - low + ;
IList<int> mergeData = NewInstance(data, length);
if (low == high)
{
mergeData[] = data[low];
return mergeData;
}
int mid = (low + high) / ;
IList<int> leftData = MergeSort(data, low, mid);
IList<int> rightData = MergeSort(data, mid + , high);
int i = , j = ;
while (true)
{
if (leftData[i] < rightData[j])
{
mergeData[i + j] = leftData[i++]; //不能使用Add,Array Length不可变
if (i == leftData.Count)
{
int rightLeft = rightData.Count - j;
for (int m = ; m < rightLeft; m++)
{
mergeData[i + j] = rightData[j++];
}
break;
}
}
else
{
mergeData[i + j] = rightData[j++];
if (j == rightData.Count)
{
int leftleft = leftData.Count - i;
for (int n = ; n < leftleft; n++)
{
mergeData[i + j] = leftData[i++];
}
break;
}
}
}
return mergeData; }

过程原理与上个一样,此处就不赘述了。

堆排序

堆排序是根据堆这种数据结构设计的一种算法。堆的特性:父节点的值总是小于(或大于)它的子节点。近似二叉树。

原理:将数列构建为最大堆数列(即父节点总是最大值),将最大值(即根节点)交换到数列末尾。这样要排序的数列数总和减少,

同时根节点不再是最大值,调整最大堆数列。如此重复,最后得到有序数列。 维基入口   有趣的演示

实现准备:如何将数列构造为堆——父节点i的左子节点为2i+1,右子节点为2i+2。节点i的父节点为floor((i-1)/2)。

实现如下(这个实现判断和临时变量使用太多,导致效率低,评论中@小城故事提出了更好的实现):

public static void HeapSort(IList<int> data)
{
BuildMaxHeapify(data);
int j = data.Count;
for (int i = ; i < j; )
{
Swap(data, i, --j);
if (j - < ) //只剩下1个数 j代表余下要排列的数的个数
break;
int k = ;
while (true)
{
if (k > (j - ) / ) break; //即:k > ((j-1)-1)/2 超出最后一个父节点的位置
else
{
int temp = k;
k = ReSortMaxBranch(data, k, * k + , * k + , j - );
if (temp == k) break;
}
}
}
} public static void BuildMaxHeapify(IList<int> data)
{
for (int i = data.Count / - ; i >= ; i--) //(data.Count-1)-1)/2为数列最大父节点索引
{
int temp = i;
temp = ReSortMaxBranch(data, i, * i + , * i + , data.Count - );
if (temp != i)
{
int k = i;
while (k != temp && temp <= data.Count / - )
{
k = temp;
temp = ReSortMaxBranch(data, temp, * temp + , * temp + , data.Count - );
}
}
}
} public static int ReSortMaxBranch(IList<int> data, int maxIndex, int left, int right, int lastIndex)
{
int temp;
if (right > lastIndex) //父节点只有一个子节点
temp = left;
else
{
if (data[left] > data[right])
temp = left;
else temp = right;
} if (data[maxIndex] < data[temp])
Swap(data, maxIndex, temp);
else temp = maxIndex;
return temp;
}

过程解析:BuildMaxHeapify为排序前构建的最大堆数列方法,主要内容为从最后一个父节点开始往前将每个三角组合

(即父节点与它的两个子节点)符合父节点值最大的规则。ReSortMaxBranch为将三角调整为父节点值最大,

并返回该值之前的索引,用来判断是否进行了交换,以及原来的父节点值交换到了什么位置。在HeapSort里首先

构建了最大堆数列,然后将根节点交换到末尾,根节点不是最大值了,在while语句中对最大堆数列进行调整。

插曲:自从看了Martin Fowler大师《重构》第三版,我发现我更不喜欢写注释了。每次都想着尽量让方法的名字更贴切,

即使会造成方法的名字很长很丑。这算不算曲解了大师的意思啊!?上面的代码注释都是写博客的时候现加的(源代码很干净的。汗!)。

希尔排序

希尔排序是插入排序的一种更高效的改进版本。

在前面介绍的插入排序,我们知道1.它对有序数列排序的效率是非常高的 2.要排序的数向前移动是一步步进行的导致插入排序效率低。

希尔排序正是利用第一点,改善第二点,达到更理想的效果。

原理:通过奇妙的步长,插入排序间隔步长的元素,随后逐渐缩短步长至1,实现数列的插入排序。 维基入口

疑问:可以想象到排序间隔步长的数,会逐渐让数列变得有序,提升最后步长为1时标准插入排序的效率。在维基上看到这么

一句话“可能希尔排序最重要的地方在于当用较小步长排序后,以前用的较大步长仍然是有序的”注意用词是‘可能’。我的疑问是

这是个正确的命题吗?如何证明呢?看维基上也是由果推因,说是如果不是这样,就不会排序那么快了。可这我感觉还是太牵强了,

哪位大哥发现相关资料,希望能分享出来,不胜感激。

实现如下:

public static void ShellSortCorrect(IList<int> data)
{
int temp;
for (int gap = data.Count / ; gap > ; gap /= )
{
for (int i = gap; i < data.Count; i++) // i+ = gap 改为了 i++
{
temp = data[i];
for (int j = i - gap; j >= ; j -= gap)
{
if (data[j] > temp)
{
data[j + gap] = data[j];
if (j == )
{
data[j] = temp;
break;
}
}
else
{
data[j + gap] = temp;
break;
}
}
}
}
}

基数排序

基数排序是一种非比较型整数排序。

“非比较型”是什么意思呢?因为它内部使用的是桶排序,而桶排序是非比较型排序。

这里就要说说桶排序了。一个非常有意思的排序。

桶排序

原理:取一定数量(数列中的最大值)的编好序号的桶,将数列每个数放进编号为它的桶里,然后将不是空的桶依次倒出来,

就组成有序数列了。  维基入口

好吧!聪明的人一眼就看出桶排序的破绽了。假设只有两个数1,10000,岂不是要一万个桶!?这确实是个问题啊!我也

没想出解决办法。我起初也以为桶排序就是一个通过牺牲空间来换取时间的排序算法,它不需要比较,所以是非比较型算法。

但看了有趣的演示桶排序后,发现世界之大,你没有解决,不代表别人没解决,睿智的人总是很多。

1,9999的桶排序实现:new Int[2];总共有两个数,得出最大数9999的位数4,取10的4次幂即10000作为分母,

要排序的数(1或9999)作为分子,并乘以数列总数2,即1*2/10000,9999*2/10000得到各自的位置0,1,完成排序。

如果是1,10000进行排序的话,上面的做法就需要稍微加一些处理——发现最大数是10的n次幂,就将它作为分母,并

放在数列末尾就好了。

如果是9999,10000进行排序的话,那就需要二维数组了,两个都在位置1,位置0没数。这个时候就需要在放

入每个位置时采用其它排序(比如插入排序)办法对这个位置的多个数排序了。

为基数排序做个过渡,我这里实现了一个个位数桶排序

涉及到了当重复的数出现的处理。

实现如下:

public static void BucketSortOnlyUnitDigit(IList<int> data)
{
int[] indexCounter = new int[];
for (int i = ; i < data.Count; i++)
{
indexCounter[data[i]]++;
}
int[] indexBegin = new int[];
for (int i = ; i < ; i++)
{
indexBegin[i] = indexBegin[i-]+ indexCounter[i-];
}
IList<int> tempList = NewInstance(data, data.Count);
for (int i = ; i < data.Count; i++)
{
int number = data[i];
tempList[indexBegin[number]++] = data[i];
}
data = tempList;
}

过程解析:indexCounter进行对每个数出现的频率的统计。indexBegin存储每个数的起始索引。

比如 1 1 2,indexCounter统计到0个0,2个1,1个2。indexBegin计算出0,1,2的起始索引分别为

0,0,2。当1个1已取出排序,那索引将+1,变为0,1,2。这样就通过提前给重复的数空出位置,解决了

重复的数出现的问题。当然,你也可以考虑用二维数组来解决重复。

下面继续基数排序。

基数排序原理:将整数按位数切割成不同的数字,然后按每个位数分别比较。

取得最大数的位数,从低位开始,每个位上进行桶排序。

实现如下:

public static IList<int> RadixSort(IList<int> data)
{
int max = data[];
for (int i = ; i < data.Count; i++)
{
if (data[i] > max)
max = data[i];
}
int digit = ;
while (max / != )
{
digit++;
max /= ;
}
for (int i = ; i < digit; i++)
{
int[] indexCounter = new int[];
IList<int> tempList = NewInstance(data, data.Count);
for (int j = ; j < data.Count; j++)
{
int number = (data[j] % Convert.ToInt32(Math.Pow(, i + ))) / Convert.ToInt32(Math.Pow(, i)); //得出i+1位上的数
indexCounter[number]++;
}
int[] indexBegin = new int[];
for (int k = ; k < ; k++)
{
indexBegin[k] = indexBegin[k - ] + indexCounter[k - ];
}
for (int k = ; k < data.Count; k++)
{
int number = (data[k] % Convert.ToInt32(Math.Pow(, i + ))) / Convert.ToInt32(Math.Pow(, i));
tempList[indexBegin[number]++] = data[k];
}
data = tempList;
}
return data;
}

过程解析:得出最大数的位数,从低位开始桶排序。我写的这个实现代码并不简洁,但逻辑更清晰。

后面测试的时候我们就会发现,按理来说这个实现也还行吧! 但并不如想象的那么快!

循环的次数太多?(统计频率n次+9次计算+n次放到新的数组)*位数。

创建的新实例太多?(new int[10]两次+NewInstance is反射判断创建实例+new int[n])*位数

测试比较

添加随机数组,数组有序校验,微软Linq排序

代码如下:

public static int[] RandomSet(int length, int max)
{
int[] result = new int[length];
Random rand = new Random();
for (int i = ; i < result.Length; i++)
{
result[i] = rand.Next(max);
}
return result;
} public static bool IsAscOrdered(IList<int> data)
{
bool flag = true;
for (int i = ; i < data.Count - ; i++)
{
if (data[i] > data[i + ])
flag = false;
}
return flag;
} public static void TestMicrosoft(IList<int> data)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
List<int> result = data.OrderBy(a => a).ToList();
stopwatch.Stop();
string methodName = "TestMicrosoft";
int length = methodName.Length;
for (int i = ; i < - length; i++)
{
methodName += " ";
}
Console.WriteLine(methodName +
" IsAscOrdered:" + IsAscOrdered(result) + " Time:" + stopwatch.Elapsed.TotalSeconds); }

测试主体如下:

static void Main(string[] args)
{
int[] aa = RandomSet(, );
//int[] aa = OrderedSet(5000);
Console.WriteLine("Array Length:" + aa.Length);
RunTheMethod((Action<IList<int>>)SelectSort, aa.Clone() as int[]);
RunTheMethod((Action<IList<int>>)BubbleSort, aa.Clone() as int[]);
RunTheMethod((Action<IList<int>>)BubbleSortImprovedWithFlag, aa.Clone() as int[]);
RunTheMethod((Action<IList<int>>)BubbleCocktailSort, aa.Clone() as int[]);
RunTheMethod((Action<IList<int>>)InsertSort, aa.Clone() as int[]);
RunTheMethod((Action<IList<int>>)InsertSortImprovedWithBinarySearch, aa.Clone() as int[]);
RunTheMethod((Action<IList<int>>)QuickSortStrict, aa.Clone() as int[]);
RunTheMethod((Action<IList<int>>)QuickSortRelax, aa.Clone() as int[]);
RunTheMethod((Action<IList<int>>)QuickSortRelaxImproved, aa.Clone() as int[]);
RunTheMethod((Func<IList<int>, IList<int>>)MergeSort, aa.Clone() as int[]);
RunTheMethod((Action<IList<int>>)ShellSort, aa.Clone() as int[]);
RunTheMethod((Func<IList<int>, IList<int>>)RadixSort, aa.Clone() as int[]);
RunTheMethod((Action<IList<int>>)HeapSort, aa.Clone() as int[]);
TestMicrosoft(aa.Clone() as int[]);
Console.Read();
} public static void RunTheMethod(Func<IList<int>, IList<int>> method, IList<int> data)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
IList<int> result = method(data);
stopwatch.Stop();
string methodName = method.Method.Name;
int length = methodName.Length;
for (int i = ; i < - length; i++)
{
methodName += " ";
}
Console.WriteLine(methodName +
" IsAscOrdered:" + IsAscOrdered(result) + " Time:" + stopwatch.Elapsed.TotalSeconds);
} public static void RunTheMethod(Action<IList<int>> method, IList<int> data)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
method(data);
stopwatch.Stop();
string methodName = method.Method.Name;
int length = methodName.Length;
for (int i = ; i < - length; i++)
{
methodName += " ";
}
Console.WriteLine(methodName +
" IsAscOrdered:" + IsAscOrdered(data) + " Time:" + stopwatch.Elapsed.TotalSeconds);
}

剩余代码折叠在此处

public static void Swap(IList<int> data, int a, int b)
{
int temp = data[a];
data[a] = data[b];
data[b] = temp;
} public static int[] OrderedSet(int length)
{
int[] result = new int[length];
for (int i = ; i < length; i++)
{
result[i] = i;
}
return result;
} public static IList<int> NewInstance(IList<int> data, int length)
{
IList<int> instance;
if (data is Array)
{
instance = new int[length];
}
else
{
instance = new List<int>(length);
for (int n = ; n < length; n++)
{
instance.Add(); // 初始添加
}
}
return instance;
}

以上动图由“图斗罗”提供

8种主要排序算法的C#实现 (二)的更多相关文章

  1. JavaScript版几种常见排序算法

    今天发现一篇文章讲“JavaScript版几种常见排序算法”,看着不错,推荐一下原文:http://www.w3cfuns.com/blog-5456021-5404137.html 算法描述: * ...

  2. php四种基础排序算法的运行时间比较

    /** * php四种基础排序算法的运行时间比较 * @authors Jesse (jesse152@163.com) * @date 2016-08-11 07:12:14 */ //冒泡排序法 ...

  3. 7种基本排序算法的Java实现

    7种基本排序算法的Java实现 转自我的Github 以下为7种基本排序算法的Java实现,以及复杂度和稳定性的相关信息. 以下为代码片段,完整的代码见Sort.java 插入排序 /** * 直接插 ...

  4. PHP四种基本排序算法

    PHP的四种基本排序算法为:冒泡排序.插入排序.选择排序和快速排序. 下面是我整理出来的算法代码: 1. 冒泡排序: 思路:对数组进行多轮冒泡,每一轮对数组中的元素两两比较,调整位置,冒出一个最大的数 ...

  5. 七种经典排序算法及Java实现

    排序算法稳定性表示两个值相同的元素在排序前后是否有位置变化.如果前后位置变化,则排序算法是不稳定的,否则是稳定的.稳定性的定义符合常理,两个值相同的元素无需再次交换位置,交换位置是做了一次无用功. 下 ...

  6. php四种基础排序算法的运行时间比较!

    /** * php四种基础排序算法的运行时间比较 * @authors Jesse (jesse152@163.com) * @date 2016-08-11 07:12:14 */ //冒泡排序法 ...

  7. 基于python的七种经典排序算法

    参考书目:<大话数据结构> 一.排序的基本概念和分类 所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作.排序算法,就是如何使得记录按照要求排列的方法. ...

  8. 几种经典排序算法的R语言描述

    1.数据准备 # 测试数组 vector = c(,,,,,,,,,,,,,,) vector ## [] 2.R语言内置排序函数 在R中和排序相关的函数主要有三个:sort(),rank(),ord ...

  9. 8种主要排序算法的C#实现

    作者:胖鸟低飞 出处:http://www.cnblogs.com/fatbird/ 简介 排序算法是我们编程中遇到的最多的算法.目前主流的算法有8种. 平均时间复杂度从高到低依次是: 冒泡排序(o( ...

  10. Java中几种常见排序算法

    日常操作中常见的排序方法有:冒泡排序.快速排序.选择排序.插入排序.希尔排序等. 冒泡排序是一种简单的排序算法.它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来.走访数 ...

随机推荐

  1. Linux中安装配置spark集群

    一. Spark简介 Spark是一个通用的并行计算框架,由UCBerkeley的AMP实验室开发.Spark基于map reduce 算法模式实现的分布式计算,拥有Hadoop MapReduce所 ...

  2. ORDER BY 语句用于对结果集进行排序。

    ORDER BY 语句 ORDER BY 语句用于根据指定的列对结果集进行排序. ORDER BY 语句默认按照升序对记录进行排序. 如果您希望按照降序对记录进行排序,可以使用 DESC 关键字.

  3. hive经常使用命令

    hive经常使用命令 show tables; 列出hive里面全部数据表名 desc userProfile; 显示数据表userProfile的基本表字段及字段type desc extended ...

  4. Linux kernel parameter command line设置

    现在CPU2核以上比较普遍了,平时用linux上上网可能用不着双核甚至4核,大部分发行版内核都启用了CPU_HOTPLUG,到/sys/devices/system/cpu下可以看到文件夹cpu0.c ...

  5. MyBitis(iBitis)系列随笔之一:MyBitis入门实例

        MyBits前身是iBitis,相对于Hibernate而言,它是半自动化ORM框架.本着分享和学习的目的,笔者将陆续把学习笔记与代码贴出,希望对想学习mybitis的同窗们有所帮助.     ...

  6. linux 远端执行shell脚本 批量结束各个远端节点进程

    #以下是一个本地的shell脚本,用于同时重启远端多台服务器tomcat服务 #!/bin/sh# ancyshi 重新启动节点tomcat服务function restartNodeTomcatSe ...

  7. VS编译错误:fatal error C1859:unexpected precompiled header error, simply rerunning the compiler might fix this problem

    fatal error C1859:unexpected precompiled header error, simply rerunning the compiler might fix this ...

  8. iOS开发之--从URL加载图片

    + (UIImage *) imageFromURLString: (NSString *) urlstring { // This call is synchronous and blocking ...

  9. 自定义控件_VIewPager显示多个Item

    一直以来想搞明白这个不完全的VIewPager是怎么做到的,有幸看到这片篇文章 有二种实现方法 1.设置的属性 1.clipChildren属性 2.setPageMargin 3.更新Item外界面 ...

  10. Objective-C代码学习大纲(3)

    Objective-C代码学习大纲(3) 2011-05-11 14:06 佚名 otierney 字号:T | T 本文为台湾出版的<Objective-C学习大纲>的翻译文档,系统介绍 ...