标签:
这篇主要写关于顺序排序的十二种算法,也是我有关算法的第一帖。主要是写,对每种算法的理解与测试。
速度测试,主要根据一千、一万、五万、百万这 四种。速度纪录还是用Stopwatch 这个类。使用随机数Random生成随机的集合。
其中数量五万左右的小数量排序,使用快速排序,速度最快。大数量百万左右使用鸽巢排序,速度最快。废话不多说,接下来上代码。
第一种:冒泡排序
冒泡排序我相信是每个程序员,都会学到的一种比较排序算法。非常简单,通过多次重复比较每对相邻元素,并按规定的顺序交换他们,最终把数列进行排序。
public static IList<int> BubbleSort(IList<int> list) { try { //获取集合数量,比较排序,所以取倒数第二个 int n = list.Count - 1; //大方向从前往后,一直到倒数第二个 for (int i = 0; i < n; i++) { //小方向从后往前,一直到大方向的索引 for (int j = n; j > i; j--) { //强转比较类型,从最后往前比较一位 if (((IComparable)list[j - 1]).CompareTo(list[j]) > 0) { //利用优先级 list[j - 1] = list[j] + (list[j] = list[j - 1]) * 0; } } } return list; } catch (Exception ex) { throw ex; } }
可以看到,是两个循环,大方向是从0到最后,小方向是从后往前。从后往前分别比较前后的数字,数字小的往前。
这种方法,是最慢的方法。因为只是一个一个比较,尾部小数问题严重影响速度。下面,上测试结果
一万就5秒了,十万和百万,就不测试了。总而言之,非常慢。
第二种:双向冒泡
双向冒泡是在冒泡排序的基础上由两个方向同时进行。只是解决了尾部小数问题,还是比较排序算法。效率也不高。
public static IList<int> BiDerectionalBubleSort(IList<int> list) { try { //获取集合数量 int limite = list.Count; int st = -1; bool swapped = false; do { swapped = false; st++; limite--; //从左开始往右循环 for (int j = st; j < limite; j++) { //强转排序类型比较,如果左边比右边大 if (((IComparable)list[j]).CompareTo(list[j + 1]) > 0) { list[j] = list[j + 1] + (list[j + 1] = list[j]) * 0; swapped = true; } } //从右开始往左循环 for (int j = limite - 1; j >= st; j--) { //强转排序类型比较,如果左边比右边大 if (((IComparable)list[j]).CompareTo(list[j + 1]) > 0) { list[j] = list[j + 1] + (list[j + 1] = list[j]) * 0; swapped = true; } } } while (st < limite && swapped); return list; } catch (Exception ex) { throw ex; } }
首先是定义集合总数与-1。分别代表两个循环的方向,然后使用do while,进行初步循环。
在do while里面,分别对定义的变量,进行增减操作。一直到相交为止。定义两个方向的循环,然后就行前后比较,交换位置。
因为终究也是比较性排序,所以效率也不是很高。我们看一下
第三种:桶排序
桶排序顾名思义,就是把数列划分成若干个桶的一种算法。属于分布排序算法。在每个桶内各自进行排序,每个桶内各自排序方式不限。
public static IList<int> BucketSort(IList<int> list) { int max = list[0]; int min = list[0]; //找集合中,最小值与最大值 for (int i = 0; i < list.Count; i++) { if (((IComparable)list[i]).CompareTo(max) > 0) { max = list[i]; } if (((IComparable)list[i]).CompareTo(min) < 0) { min = list[i]; } } //定义一个足够大的容器。因为是最大值-最小值。所以肯定是足够装下所有集合。 //注意事项:数组数量溢出 ArrayList[] holder = new ArrayList[max - min + 1]; //让数组变成二维数组 for (int i = 0; i < holder.Length; i++) { holder[i] = new ArrayList(); } //把集合的数据,付给二维数组 for (int i = 0; i < list.Count; i++) { holder[list[i] - min].Add(list[i]); } int k = 0; //循环容器 for (int i = 0; i < holder.Length; i++) { //判断是否有值 if (holder[i].Count > 0) { //重新给list进行赋值操作 for (int j = 0; j < holder[i].Count; j++) { list[k] = (int)holder[i][j]; k++; } } } return list; }
首先第一步就是创建一个桶,也就是一个交叉数组(数组的数组)。那么我们找集合中,最大与最小,来创建一个能够完全保存进去的集合。
然后循环进行交叉数组初始化操作。
接着遍历一遍集合,holder[list[i] - min].Add(list[i]); 这句话是关键,把集合的值,当作数组的索引,进行Add添加。因为是二维数组,所以相同的数据,再多也没事。
最后一步就简单了,循环遍历然后给list,进行复制操作。因为已经把list的值,放到桶里面了,所以操作数据,不会受到影响。
其实这也算是,插入排序算法。只不过声明这种非常大的容器是很消耗内存的。并不是很推荐这种方法。我们看一下性能
第四种:梳排序
梳排序中,是保持间距并不断减少的过程。开始的时候间距设定为列表长度,然后每一次都会除以损耗因子(一般为1.3)。间距可以四舍五入,不断重复,直到间距变为1。最后在进行一次冒泡排序。
public static IList<int> CombSort(IList<int> list) { //获取集合数量 int gap = list.Count; int swaps = 0; do { //计算递减率,必须大于1 gap = (int)(gap / 1.3); if (gap < 1) { gap = 1; } int i = 0; swaps = 0; do { //每次循环1与另一个数进行调换,直到循环尾部为止 if (((IComparable)list[i]).CompareTo(list[i + gap]) > 0) { list[i] = list[i + gap] + (list[i + gap] = list[i]) * 0; swaps = 1; } i++; } while (!(i + gap >= list.Count)); } while (!(gap == 1 && swaps == 0)); return list; }
首先计算递减率,集合总数除以损耗因子,递减率必须大于1。
从0开始 与 间隔值继续比较,调换位置。一直到间隔位置大于集合总数。
并且每次进行间隔递减,每次间隔都除以1.3。
其实重点也是比较排序,只不过是进行间隔排序基础上。性能也是比较好的。
第五种:圈排序
圈排序是一种不稳定的排序算法,是一种理论上最优的比较算法。他的思想是要把数列分解为圈,可以分别旋转得到排序结果。
与其他排序不同的是,元素不会被放入数组的任何位置,如果这个值在正确位置,则不动。否则只会写一次即可。
public static IList<int> CycleSort(IList<int> list) { //循环每一个数组 for (int cycleStart = 0; cycleStart < list.Count; cycleStart++) { int item = list[cycleStart]; int pos = cycleStart; do { int to = 0; //循环整个数组,找到其相应的位置 for (int i = 0; i < list.Count; i++) { if (i != cycleStart && ((IComparable)list[i]).CompareTo(item) < 0) { to++; } } if (pos != to) { while (pos != to && ((IComparable)item).CompareTo(list[to]) == 0) { to++; } int temp = list[to]; list[to] = item; item = temp; pos = to; } } while (cycleStart != pos); } return list; }
首先进行从前往后的循环。获取不同位置的数据,当获取到数据以后,会循环整个数组找到其相应的位置。然后进行位置插入。
看一下,具体的性能。对于非常杂乱无章的序列来讲,真的好慢。
第六种:堆排序
堆排序是从数据集构建一个数据堆,然后提取最大元素,放到有序数列末尾。然后重新构造新的数据堆,一直到没有数据为止。属于插入排序。
public static IList<int> HeapSort(IList<int> list) { //循环因为每次都能取出最大和最小,所以循环次数折中 for (int i = (list.Count - 1) / 2; i >= 0; i--) { Adjust(list, i, list.Count - 1); } for (int i = list.Count - 1; i >= 1; i--) { list[i] = list[0] + (list[0] = list[i]) * 0; Adjust(list, 0, i - 1); } return list; } public static void Adjust(IList<int> list, int i, int m) { int temp = list[i];//获取该标识值 int j = i * 2 + 1;//获取对应尾部标识 while (j <= m) //循环直到标识 <= 总数 { if (j < m) //尾部标识 小于 总数 { //如果左边小于右边,右边标识加一位 if (((IComparable)list[j]).CompareTo(list[j + 1]) < 0) { j = j + 1; } } if (((IComparable)temp).CompareTo(list[j]) < 0) { //交换位置 list[i] = list[j]; i = j; j = 2 * i + 1; } else { //结束循环 j = m + 1; } } list[i] = temp; }
看一下性能
第七种:插入排序
插入排序的原理是构造一个有序数列,对未排序的数据,从后向前扫描,找到相应的位置并插入。需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
public static IList<int> InsertionSort(IList<int> list) { for (int i = 1; i < list.Count; i++) { int val = list[i]; int j = i - 1; bool done = false; do { if (((IComparable)list[j]).CompareTo(val) > 0) { list[j + 1] = list[j]; j--; if (j < 0) { done = true; } } else { done = true; } } while (!done); list[j + 1] = val; } return list; }
首先是从前往后进行循环,将数据与前一个比较并交换位置。
看一下性能:
第八种:奇偶排序
通过比较相邻的奇偶数进行排序,对存在错误的顺序进行交换。并一直重复这个过程,直到列表有序。
public static IList<int> OddEventSort(IList<int> list) { bool sorted = false; while (!sorted) { sorted = true; for (int i = 1; i < list.Count - 1; i += 2) { if (((IComparable)list[i]).CompareTo(list[i + 1]) > 0) { list[i] = list[i + 1] + (list[i + 1] = list[i]) * 0; sorted = false; } } for (int i = 0; i < list.Count - 1; i += 2) { if (((IComparable)list[i]).CompareTo(list[i + 1]) > 0) { list[i] = list[i + 1] + (list[i + 1] = list[i]) * 0; sorted = false; } } } return list; }
看一下性能
第九种:鸽巢排序(大数据量中最快的排序方法)
鸽巢排序假设有个待排序的数组,给它建立一个空的辅助数组(俗称鸽巢)。把原始数组的每个值作为格子(鸽巢的索引),遍历原始数据,根据每个值放入辅助数组对应的格子中。
顺序遍历鸽巢数组,把非空的鸽巢中的元素放回原始数组。这种排序方式适合在差值很小的范围内使用。
public static IList<int> PigeonHoleSort(IList<int> list) { int min = list[0], max = list[0]; foreach (int x in list) { if (((IComparable)min).CompareTo(x) > 0) { min = x; } if (((IComparable)max).CompareTo(x) < 0) { max = x; } } int size = max - min + 1; int[] holes = new int[size]; foreach (int x in list) { holes[x - min]++; } int i = 0; for (int count = 0; count < size; count++) { while (holes[count]-- > 0) { list[i] = count + (int)min; i++; } } return list; }
看一下性能
第十种:快速排序(小数据量中最快方法)
快速排序会把集合分为两个集合,并选择一个元素作为基准。把小于基准的数据排到基准前面,大于放到后面。
public static IList<int> QuickSort(IList<int> list, int left, int right) { right = right == 0 ? list.Count - 1 : right; int i = left, j = right; double privotValue = (left + right) / 2; int x = list[(int)privotValue]; while (i <= j) { while (((IComparable)list[i]).CompareTo(x) < 0) { i++; } while (((IComparable)x).CompareTo(list[j]) < 0) { j--; } if (i <= j) { list[i] = list[j] + (list[j] = list[i]) * 0; i++; j--; } } if (left < j) { QuickSort(list, left, j); } if (i < right) { QuickSort(list, i, right); } return list; }
看一下性能
第十一种:选择排序
在未排序的列表中找到最小或最大的元素,存放到排序序列的起始位置,然后,再从剩余的排序元素中继续找寻最小(大)元素,放到末尾。
public static IList<int> SelectionSort(IList<int> list) { int min; for (int i = 0; i < list.Count; i++) { min = i; for (int j = i + 1; j < list.Count; j++) { if (((IComparable)list[j]).CompareTo(list[min]) < 0) { min = j; } } list[i] = list[min] + (list[min] = list[i]) * 0; } return list; }
看一下性能
第十二种:希尔排序
通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素一次性地朝最终位置前进一大步。然后步伐越来越小,最后就是普通的插入排序。
public static IList<int> ShellSort(IList<int> list) { int i, j, increment; int temp; increment = list.Count / 2; while (increment > 0) { for (i = 0; i < list.Count; i++) { j = i; temp = list[i]; while ((j >= increment) && ((IComparable)list[j - increment]).CompareTo(temp) > 0) { list[j] = list[j - increment]; j = j - increment; } list[i] = temp; } if (increment == 2) { increment = 1; } else { increment = increment * 5 / 11; } } return list; }
看一下性能
标签:
原文地址:http://www.cnblogs.com/chenxygx/p/5191488.html