让我们先看一看常用排序算法的效率对比

c#实现常用排序算法-LMLPHP

接着请看代码和注释~

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace ConsoleApplicationAlgorithm
{
interface ISortAlgorithm
{
/// <summary>
/// 排序
/// </summary>
/// <param name="a">数组</param>
/// <param name="n">数组长度</param>
void Sort(int[] a, int n);
} #region 直接插入排序
public class StraightInsertionSort : ISortAlgorithm
{
//直接插入排序(straight insertion sort)的作法是:
//每次从无序表中取出第一个元素,把它插入到有序表的合适位置,使有序表仍然有序。
//第一趟比较前两个数,然后把第二个数按大小插入到有序表中; 第二趟把第三个数据与前两个数从前向后扫描,把第三个数按大小插入到有序表中;依次进行下去,进行了(n-1)趟扫描以后就完成了整个排序过程。 //直接插入排序是由两层嵌套循环组成的。外层循环标识并决定待比较的数值。内层循环为待比较数值确定其最终位置。直接插入排序是将待比较的数值与它的前一个数值进行比较,所以外层循环是从第二个数值开始的。当前一数值比待比较数值大的情况下继续循环比较,直到找到比待比较数值小的并将待比较数值置入其后一位置,结束该次循环。
//值得注意的是,我们必需用一个存储空间来保存当前待比较的数值,因为当一趟比较完成时,我们要将待比较数值置入比它小的数值的后一位 插入排序类似玩牌时整理手中纸牌的过程。插入排序的基本方法是:每步将一个待排序的记录按其关键字的大小插到前面已经排序的序列中的适当位置,直到全部记录插入完毕为止。 public void Sort(int[] a, int n)//直接插入排序属于稳定的排序,最好情况O(n),最坏时间复杂性为Θ(n^2),平均时间O(n^2),空间复杂度为O(1)。
{
int i;
int j;
int temp;
for (i = 1; i < n; i++)
{
Console.WriteLine("第{0}遍排序,即从第{1}个数开始", (i).ToString(), (i + 1).ToString());
temp = a[i];
//只要大于前一个则跳出循环,因为在此之前的序列肯定有序的
for (j = i; j > 0; j--)
{
if (temp > a[j - 1])
{
Console.WriteLine("由于{0}>{1}所以跳出排序", temp.ToString(), a[j - 1].ToString());
break;
}
a[j] = a[j - 1];
}
Console.WriteLine("在下标为{0}处元素插入{1},第{2}个元素前有序", (j).ToString(), temp.ToString(), (i + 1).ToString());
a[j] = temp;
Helper.Print(a, n);
}
}
}
#endregion #region 插入排序
//冒泡排序(BubbleSort)的基本概念是:依次比较相邻的两个数,将小数放在前面,大数放在后面。即在第一趟:首先比较第1个和第2个数,将小数放前,大数放后。然后比较第2个数和第3个数,将小数放前,大数放后,如此继续,直至比较最后两个数,将小数放前,大数放后。至此第一趟结束,将最大的数放到了最后。在第二趟:仍从第一对数开始比较(因为可能由于第2个数和第3个数的交换,使得第1个数不再小于第2个数),将小数放前,大数放后,一直比较到倒数第二个数(倒数第一的位置上已经是最大的),第二趟结束,在倒数第二的位置上得到一个新的最大数(其实在整个数列中是第二大的数)。如此下去,重复以上过程,直至最终完成排序。   由于在排序过程中总是小数往前放,大数往后放,相当于气泡往上升,所以称作冒泡排序。   用二重循环实现,外循环变量设为i,内循环变量设为j。假如有10个数需要进行排序,则外循环重复9次,内循环依次重复9,8,...,1次。每次进行比较的两个元素都是与内循环j有关的,它们可以分别用a[j]和a[j+1]标识,i的值依次为1,2,...,9,对于每一个i,j的值依次为1,2,...10-i。
public class Bubblesort : ISortAlgorithm
{
public void Sort(int[] a, int n)//冒泡排序属于稳定的排序,最好情况O(n),最坏时间复杂性为O(n^2),平均时间O(n^2),空间复杂度为O(1)。
{ int temp;
for (int i = 0; i < a.Length - 1; i++)
{
Console.WriteLine("第{0}遍排序,由于{0}个元素前有序,所以到第{0}个元素结束", (i + 1).ToString());
//从后往前比较
for (int j = a.Length - 1; j > i; j--)
{
//如果小于前面一个则交换,否则不动
if (a[j] < a[j - 1])
{
temp = a[j];
a[j] = a[j - 1];
a[j - 1] = temp;
Console.WriteLine("第{0}与第{1}个元素交换", (j + 1).ToString(), j.ToString());
Helper.Print(a, n);
}
else
{
Console.WriteLine("不用移动");
}
}
}
}
}
#endregion #region 选择排序
// n个记录的文件的直接选择排序可经过n-1趟直接选择排序得到有序结果:
//①初始状态:无序区为R[1..n],有序区为空。
//②第1趟排序
//在无序区R[1..n]中选出关键字最小的记录R[k],将它与无序区的第1个记录R[1]交换,使R[1..1]和R[2..n]分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。
//……
//③第i趟排序
//第i趟排序开始时,当前有序区和无序区分别为R[1..i-1]和R(i..n)。该趟排序从当前无序区中选出关键字最小的记录 R[k],将它与无序区的第1个记录R交换,使R[1..i]和R分别变为记录个数增加1个的新有序区和记录个数减少1个的新无序区。
//这样,n个记录的文件的直接选择排序可经过n-1趟直接选择排序得到有序结果。
//常见的选择排序细分为简单选择排序、树形选择排序(锦标赛排序)、堆排序。上述算法仅是简单选择排序的步骤。
public class SelectSort : ISortAlgorithm//选择排序属于不稳定的排序,最好情况O(n^2),最坏时间复杂性为O(n^2),平均时间O(n^2),空间复杂度为O(1)。
{
public void Sort(int[] a, int n)
{
int temp = 0;
int minIndex;
for (int i = 0; i < n - 1; i++)
{
Console.WriteLine("第{0}遍排序", (i + 1).ToString());
Console.WriteLine("从第{0}处开始比较", (i + 1).ToString());
minIndex = i;
for (int j = i + 1; j < n; j++)
{
if (a[j] < a[minIndex])
{
temp = a[j];
minIndex = j;
} }
if (a[minIndex] != a[i])
{
Console.WriteLine("选择第{0}最小的元素{1}与第{2}元素交换", (minIndex + 1).ToString(), a[minIndex].ToString(), (i + 1).ToString());
a[minIndex] = a[i];
a[i] = temp;
}
Helper.Print(a, a.Length);
}
}
}
#endregion #region 希尔排序
//希尔排序,也称递减增量排序算法,是插入排序的一种高速而稳定的改进版本。
//希尔排序是基于插入排序的以下两点性质而提出改进方法的:
// 插入排序在对几乎已经排好序的数据操作时, 效率高, 即可以达到线性排序的效率
// 但插入排序一般来说是低效的, 因为插入排序每次只能将数据移动一位
//先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成(n除以d1)个组。所有距离为d1的倍数的记录放在同一个组中。先在各组内进行直接插入排序;然后,取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。   该方法实质上是一种分组插入方法。   比较相隔较远距离(称为增量)的数,使得数移动时能跨过多个元素,则进行一次比较就可能消除 多个元素交换。D.L.shell于1959年在以他名字命名的排序算法中实现了这一思想。算法先将要排序的一组数按某个增量d分成若干组,每组中记录的下标相差d.对每组中全部元素进行排序,然后再用一个较小的增量对它进行,在每组中再进行排序。当增量减到1时,整个要排序的数被分成一组,排序完成。   一般的初次取序列的一半为增量,以后每次减半,直到增量为1。   给定实例的shell排序的排序过程   假设待排序文件有10个记录,其关键字分别是:   49,38,65,97,76,13,27,49,55,04。   增量序列的取值依次为:   5,3,1
// 已知的最好步长序列由Marcin Ciura设计(1,4,10,23,57,132,301,701,1750,…) 这项研究也表明“比较在希尔排序中是最主要的操作,而不是交换。”用这样步长序列的希尔排序比插入排序和堆排序都要快,甚至在小数组中比快速排序还快,但是在涉及大量数据时希尔排序还是比快速排序慢。
//另一个在大数组中表现优异的步长序列是(斐波那契数列除去0和1将剩余的数以黄金分割比的两倍的幂进行运算得到的数列):(1, 9, 34, 182, 836, 4025, 19001, 90358, 428481, 2034035, 9651787, 45806244, 217378076, 1031612713, …)[2]
public class ShellSort : ISortAlgorithm//希尔排序属于不稳定的排序平均时间O(n^1.25),空间复杂度为O(1)。
{
public void Sort(int[] a, int n)
{
for (int gap = n / 2; gap > 0; gap /= 2)
{
Console.WriteLine("增量为{0}进行直接插入排序", gap);
for (int i = gap; i < n; i++)
{
int key = a[i];
int j = 0;
//此处实现插入排序
for (j = i - gap; j >= 0; j -= gap)
{
if (a[j] > key)
{
Console.WriteLine("由于{0}>{1}交换{2}和{3}处的数据", a[j].ToString(), key.ToString(), j.ToString(), (j + gap).ToString());
a[j + gap] = a[j];
}
else
{
Console.WriteLine("由于{0}<{1},不发生交换", a[j].ToString(), key.ToString());
break;
}
}
a[j + gap] = key;
Helper.Print(a, n);
}
}
Helper.Print(a, n);
}
}
#endregion #region 并归排序
//归并操作的过程如下:
//申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
//设定两个指针,最初位置分别为两个已经排序序列的起始位置
//比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
//重复步骤3直到某一指针达到序列尾
//将另一序列剩下的所有元素直接复制到合并序列尾
public class MergeSort : ISortAlgorithm//并归排序属于稳定的排序,最好情况O(nlgn),最坏时间复杂性为O(nlgn),平均时间O(nlgn),空间复杂度为O(n)。
{
public void Sort(int[] a, int n)
{
int[] temp = new int[n];
mergeSort(a, temp, 0, n - 1);
Helper.Print(a, n);
}
void Merge(int[] r, int[] r1, int low, int mid, int high)
{
int i = low; int j = mid + 1; int k = low;
while (i <= mid && j <= high)
{ if (r[i] <= r[j])
{
Console.WriteLine("由于{0}<{1},临时空间{2}处赋值{3}", r[i].ToString(), r[j].ToString(), k.ToString(), r[i].ToString());
r1[k++] = r[i++];
}
else
{
Console.WriteLine("由于{0}>{1},临时空间{2}处赋值{3}", r[i].ToString(), r[j].ToString(), k.ToString(), r[j].ToString());
r1[k++] = r[j++];
}
}
if (i <= mid)
{
Console.WriteLine("由于左边还有数字没有赋值到临时空间,已经有序,直接赋值");
while (i <= mid)
{
Console.WriteLine("在{0}处赋值{1}", k.ToString(), r[i].ToString());
r1[k++] = r[i++];
}
}
else
{
Console.WriteLine("由于右边还有数字没有赋值到临时空间,已经有序,直接赋值");
while (j <= high)
{
Console.WriteLine("在{0}处赋值{1}", k.ToString(), r[j].ToString());
r1[k++] = r[j++];
}
}
Helper.Print(r1, r1.Length);
Console.WriteLine("将临时空间的值赋值给真实的空间");
for (int l = 0; l < high + 1; l++)
r[l] = r1[l];
}
void mergeSort(int[] r, int[] r1, int low, int high)
{
if (low != high)
{
int mid = (low + high) / 2;
Console.WriteLine("从{0}-{1}处分治", low.ToString(), mid.ToString());
mergeSort(r, r1, low, mid);
Helper.Print(r, r.Length);
Console.WriteLine("从{0}-{1}处分治", (mid + 1).ToString(), high.ToString());
mergeSort(r, r1, mid + 1, high);
Helper.Print(r, r.Length);
Console.WriteLine("从{0}-{1}与{2}-{3}处合并", low.ToString(), mid.ToString(), (mid + 1).ToString(), high.ToString());
Merge(r, r1, low, mid, high);
}
}
}
#endregion #region 堆排序
//堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆性质:即子结点的键值或索引总是小于(或者大于)它的父节点。 //通常堆是通过一维数组来实现的。在起始数组为 0 的情形中:
//父节点i的左子节点在位置 (2*i+1);
//父节点i的右子节点在位置 (2*i+2);
//子节点i的父节点在位置 floor((i-1)/2); //在堆的数据结构中,堆中的最大值总是位于根节点。堆中定义以下几种操作:
//最大堆调整(Max_Heapify):将堆的末端子结点作调整,使得子结点永远小于父结点
//创建最大堆(Build_Max_Heap):将堆所有数据重新排序
//堆排序(HeapSort):移除位在第一个数据的根结点,并做最大堆调整的递归运算
public class Heapsort : ISortAlgorithm//并归排序属于稳定的排序,最好情况O(nlgn),最坏时间复杂性为O(nlgn),平均时间O(nlgn),空间复杂度为O(n)。
{
public void Sort(int[] a, int n)
{
Helper.PrintTree(a, n, (int)Math.Log(n, 2), 0);
heap_sort(a, n); Helper.Print(a, n);
}
void sift(int[] d, int ind, int len)
{
//#置i为要筛选的节点#%
int i = ind;
Console.WriteLine("选择节点{0}", d[i].ToString());
//#c中保存i节点的左孩子#%
int c = i * 2 + 1; //#+1的目的就是为了解决节点从0开始而他的左孩子一直为0的问题#%
if (c > len)
{
Console.WriteLine("节点{0}没有子孩子,跳出", d[i].ToString());
}
while (c < len)//#未筛选到叶子节点#%
{
Console.WriteLine("选择左孩子{0}", d[c].ToString());
//#如果要筛选的节点既有左孩子又有右孩子并且左孩子值小于右孩子#%
//#从二者中选出较大的并记录#%
if (c + 1 < len && d[c] < d[c + 1])
{
c++;
}
Console.WriteLine("左孩子,右孩子中选出最大的孩子{0}", d[c].ToString());
//#如果要筛选的节点中的值大于左右孩子的较大者则退出#%
if (d[i] > d[c])
{
Console.WriteLine("选择节点{0}大于他的最大孩子{1},跳出", d[i].ToString(), d[c].ToString());
break;
}
else
{
Console.WriteLine("选择节点{0}小于他的最大孩子{1},交换", d[i].ToString(), d[c].ToString());
//#交换#%
int t = d[c];
d[c] = d[i];
d[i] = t; Helper.PrintTree(d, len, (int)Math.Log(len, 2), 0); i = c;
c = 2 * i + 1;
if (c < len)
{
Console.WriteLine("重置要筛选的节点为{0}和要筛选的左孩子为{1}", d[i].ToString(), d[c].ToString());
}
else
{
Console.WriteLine("重置要筛选的节点为{0},没有孩子", d[i].ToString());
}
}
} return;
} void heap_sort(int[] d, int n)
{
for (int i = n / 2 - 1; i >= 0; i--)
{
Console.WriteLine("初始化建堆, {0}从最后一个非叶子节点开始", i.ToString());
sift(d, i, n);
Helper.PrintTree(d, n, (int)Math.Log(n, 2), 0);
} Console.WriteLine("开始排序");
for (int i = 0; i < n; i++)
{
//#交换#%
int t = d[0];
d[0] = d[n - i - 1];
d[n - i - 1] = t;
Console.WriteLine("交换元素{0}和{1},将最大元素放在最后面,交换之后的元素后面有序", d[0].ToString(), t.ToString());
Helper.PrintTree(d, n, (int)Math.Log(n, 2), 0);
//#筛选编号为0 #%
sift(d, 0, n - i - 1);
Helper.PrintTree(d, n, (int)Math.Log(n, 2), 0);
} }
}
#endregion #region 快速排序
//快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(n log n) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。
// 快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。
//步骤为:
//从数列中挑出一个元素,称为 "基准"(pivot),
//重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
//递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
//递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
public class Quicksort : ISortAlgorithm//并归排序属于不稳定的排序,最好情况O(nlgn),最坏时间复杂性为O(nlgn),平均时间O(n^2),最好情况是O(n log n)和最坏情况是O(n2 log n)的空间需求。
{
public void Sort(int[] a, int n)
{
Console.WriteLine("自顶向下分治法先从{0},{1}", 0, (n - 1).ToString());
QuickSort(a, 0, n - 1);
Helper.Print(a, n);
}
private static void QuickSort(int[] numbers, int left, int right)
{
if (left < right)
{ int middle = numbers[(left + right) / 2];
Console.WriteLine("选择基准数{0}", middle.ToString());
int i = left - 1;
int j = right + 1;
while (true)
{
if (numbers[i + 1] >=middle)
{
Console.WriteLine("{0}大于=基准数,下标不变,跳出向后循环", numbers[i + 1].ToString());
}
while (numbers[++i] < middle)
{
Console.WriteLine("{0}小于基准数,下标后移", numbers[i].ToString());
}; while (numbers[--j] > middle)
{
Console.WriteLine("{0}大于基准数,下标前移", numbers[j].ToString());
};
if (numbers[j] <= middle)
{
Console.WriteLine("{0}小于=基准数,下标不变,跳出向后循环", numbers[j].ToString());
}
if (i >= j)
{
Console.WriteLine("{0}>{1},下标相遇,跳出", i.ToString(), j.ToString());
break;
}
Console.WriteLine("交换{0},{1}", numbers[i].ToString(), numbers[j].ToString());
Swap(numbers, i, j);
Helper.Print(numbers, numbers.Length);
}
Console.WriteLine("自顶向下分治法先从{0},{1}", left, (i - 1).ToString());
QuickSort(numbers, left, i - 1);
Console.WriteLine("自顶向下分治法先从{0},{1}", (j + 1).ToString(), right.ToString());
QuickSort(numbers, j + 1, right);
}
} private static void Swap(int[] numbers, int i, int j)
{
int number = numbers[i];
numbers[i] = numbers[j];
numbers[j] = number;
}
}
#endregion
static class Helper
{
public static void Print(int[] a, int n)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++)
{
sb.Append(a[i].ToString() + " ");
}
Console.Write(sb.ToString());
Console.WriteLine("\n");
sb.Clear();
}
public static void PrintTree(int[] a, int n, int height, int row)
{
StringBuilder sb = new StringBuilder(); //判断是否深度最大
if (row == (int)Math.Log(n, 2) + 1)
{
return;
}
//增加每一行前空格
for (int i = 0; i < Math.Pow(2, height) - 1; i++)
{
sb.Append(" ");
}
//添加每一行元素
for (int j = (int)Math.Pow(2, row) - 1; (j < Math.Pow(2, row + 1) - 1) && j < n; j++)
{
sb.Append(a[j].ToString());
//每个元素后的空格,等于上一行第一个元素前的空格数
for (int i = 0; i < Math.Pow(2, height + 1) - 1; i++)
{
sb.Append(" ");
}
}
sb.Append("\n");
Console.WriteLine(sb.ToString());
PrintTree(a, n, (height - 1), (row + 1));
}
}
}

Program类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace ConsoleApplicationAlgorithm
{
class Program
{
static void Main(string[] args)
{
ISortAlgorithm sort;
bool exist = false;
int[] a;
while (exist == false)
{
a = new int[] { 46, 58, 15, 10, 90, 18, 45, 62 };
Helper.Print(a, a.Length);
Console.WriteLine("请选择排序算法");
Console.WriteLine("1.直接插入排序");
Console.WriteLine("2.冒泡排序(从后往前冒泡)");
Console.WriteLine("3.选择排序");
Console.WriteLine("4.希尔排序");
Console.WriteLine("5.并归排序");
Console.WriteLine("6.堆排序");
Console.WriteLine("7.快速排序");
Console.WriteLine("\n");
ConsoleKeyInfo select = Console.ReadKey();
switch (select.KeyChar)
{
case '1':
{
Console.WriteLine("您选择了直接选择排序:\n");
sort = new StraightInsertionSort();
sort.Sort(a, a.Length);
break;
}
case '2':
{
Console.WriteLine("您选择了冒泡排序:\n");
sort = new Bubblesort();
sort.Sort(a, a.Length);
break;
}
case '3':
{
Console.WriteLine("您选择了选择排序:\n");
sort = new SelectSort();
sort.Sort(a, a.Length);
//exist = true;
break;
}
case '4':
{
Console.WriteLine("您选择了希尔排序:\n");
sort = new ShellSort();
sort.Sort(a, a.Length);
//exist = true;
break;
}
case '5':
{
Console.WriteLine("您选择了并归排序:\n");
sort = new MergeSort();
sort.Sort(a, a.Length);
//exist = true;
break;
}
case '6':
{
Console.WriteLine("您选择了堆排序:\n");
sort = new Heapsort();
sort.Sort(a, a.Length);
//exist = true;
break;
}
case '7':
{
Console.WriteLine("您选择了快速排序:\n");
sort = new Quicksort();
sort.Sort(a, a.Length);
//exist = true;
break;
}
}
Console.WriteLine("\n");
}
}
}
}
05-18 19:56