您的位置:首页 > 理论基础 > 数据结构算法

1. C#数据结构与算法 -- 排序(插入,冒泡,希尔,快速,选择)

2015-06-03 11:51 651 查看
1. 插入排序
=====================================================
算法思想简单描述:
在要排序的一组数中,假设前面(n-1) [n>=2] 个数已经是排
好顺序的,现在要把第n个数插到前面的有序数中,使得这n个数
也是排好顺序的。如此反复循环,直到全部排好顺序。
直接插入排序是稳定的。算法时间复杂度O(n2)--[n的平方]

一般来说,插入排序都采用in-place在数组上实现。具体算法描述如下:
1.从第一个元素开始,该元素可以认为已经被排序
2.取出下一个元素,在已经排序的元素序列中从后向前扫描
3.如果该元素(已排序)大于新元素,将该元素移到下一位置
4.重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
5.将新元素插入到该位置后
6.重复步骤2~5
如果比较操作的代价比交换操作大的话,可以采用二分查找法来减少比较操作的数目。该算法可以认为是插入排序的一个变种,称为二分查找排序。

使用插入排序为一列数字进行排序的过程



最差时间复杂度


最优时间复杂度


平均时间复杂度


=====================================================
实例1:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace InsertionSorter
{
public class InsertionSorter
{
public void Sort(int[] list)
{
//从第二个元素开始遍历
for (int i = 1; i < list.Length;++i )
{
//将第i个元素赋值给一个临时变量
int t = list[i];
//记录第i个元素的下标
int j = i;
//比较若list[j-1]>list[j],小标j -1; 并执行交换。
while ((j > 0) && (list[j - 1] > t))
{
list[j] = list[j - 1];
--j;
}
//不管list[j-1]>list[j],或list[j-1]<=list[j],都执行下面语句;
list[j] = t;
}
}
}
class Program
{
static void Main(string[] args)
{
int[] iArray = new int[] { 1,5,3,6,10,55};
InsertionSorter ii = new InsertionSorter();
ii.Sort(iArray);
for (int m = 0; m <= iArray.Length-1; m++)
Console.WriteLine("{0}",iArray[m]);
Console.ReadLine();
}
}
}
[b]2. [b]希尔排序[/b][/b]
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法。

====================================================
算法思想简单描述:


在直接插入排序算法中,每次插入一个数,使有序序列只增加1个节点,
并且对插入下一个数没有提供任何帮助。如果比较相隔较远距离(称为
增量)的数,使得数移动时能跨过多个元素,则进行一次比较就可能消除
多个元素交换。D.L.shell于1959年在以他名字命名的排序算法中实现
了这一思想。算法先将要排序的一组数按某个增量d分成若干组,每组中
记录的下标相差d.对每组中全部元素进行排序,然后再用一个较小的增量
对它进行,在每组中再进行排序。当增量减到1时,整个要排序的数被分成
一组,排序完成。
下面的函数是一个希尔排序算法的一个实现,初次取序列的一半为增量,
以后每次减半,直到增量为1。
希尔排序是不稳定的。

原始的算法实现在最坏的情况下需要进行O(n2)的比较和交换。V. Pratt的书[1] 对算法进行了少量修改,可以使得性能提升至O(n log2 n)。这比最好的比较算法的O(n log n)要差一些。
希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能。这样可以让一个元素可以一次性地朝最终位置前进一大步。然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需排序的数据几乎是已排好的了(此时插入排序较快)。
假设有一个很小的数据在一个已按升序排好序的数组的末端。如果用复杂度为O(n2)的排序(冒泡排序或插入排序),可能会进行n次的比较和交换才能将该数据移至正确位置。而希尔排序会用较大的步长移动数据,所以小数据只需进行少数比较和交换即可到正确位置。
一个更好理解的希尔排序实现:将数组列在一个表中并对列排序(用插入排序)。重复这过程,不过每次用更长的列来进行。最后整个表就只有一列了。将数组转换至表是为了更好地理解这算法,算法本身仅仅对原数组进行排序(通过增加索引的步长,例如是用i += step_size而不是i++)。

排序过程





最差时间复杂度 根据步长串行的不同而不同。


最优时间复杂度 O(n)
平均时间复杂度 根据步长串行的不同而不同。

====================================================
实例2:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ShellSorter
{
/// <summary>
/// 希尔排序
/// </summary>
public class ShellSorter
{
public void Sort(int[] list)
{
int inc;
for (inc = 1; inc <= list.Length / 9; inc = 3 * inc + 1) ;
for (; inc > 0; inc /= 3)
{
for (int i = inc + 1; i <= list.Length; i += inc)
{
int t = list[i - 1];
int j = i;
while ((j > inc) && (list[j - inc - 1] > t))
{
list[j - 1] = list[j - inc - 1];
j -= inc;
}
list[j - 1] = t;
}
}
}
}
class Program
{
static void Main(string[] args)
{
int[] iArray = new int[] { 1, 5, 3, 6, 10, 55 };
ShellSorter ii = new ShellSorter();
ii.Sort(iArray);
for (int m = 0; m <= iArray.Length - 1; m++)
Console.WriteLine("{0}", iArray[m]);
Console.ReadLine();
}
}
}

3. 选择排序
====================================================
算法思想简单描述:
在要排序的一组数中,选出最小的一个数与第一个位置的数交换;
然后在剩下的数当中再找最小的与第二个位置的数交换,如此循环
到倒数第二个数和最后一个数比较为止。
选择排序是不稳定的。算法复杂度O(n2)--[n的平方]

选择排序(Selection sort)是一种简单直观的排序算法。
它的工作原理如下。
首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,
然后,再从剩余未排序元素中继续寻找最小(大)元素,
然后放到已排序序列的末尾。
以此类推,直到所有元素均排序完毕。

选择排序的主要优点与数据移动有关。如果某个元素位于正确的最终位置上,则它不会被移动。选择排序每次交换一对元素,它们当中至少有一个将被移到其最终位置上,因此对n个元素的表进行排序总共进行至多n-1次交换。在所有的完全依靠交换去移动元素的排序方法中,选择排序属于非常好的一种。

实现过程




最差时间复杂度 О(n)
最优时间复杂度 О(n)
平均时间复杂度 О(n)

=====================================================
实例3:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SelectionSorter
{
public class SelectionSorter
{
private int min;
public void Sort(int[] list)
{
//对数组list从第0个元素进行遍历
for (int i = 0; i < list.Length - 1; ++i)
{
//记录第i个小标
min = i;
//j=i+1;比较list[i]和list[i+1],如果list[i+1]<list[i],记录新的min=j;
for (int j = i + 1; j < list.Length; ++j)
{
if (list[j] < list[min])
min = j;
}
//两个大小数交换
int t = list [min];
list[min] = list[i];
list[i] = t;
}
}
}
class Program
{
static void Main(string[] args)
{
int[] iArray = new int[] { 1, 5, 3, 6, 10, 55 };
SelectionSorter ii = new SelectionSorter();
ii.Sort(iArray);
for (int m = 0; m <= iArray.Length - 1; m++)
Console.WriteLine("{0}", iArray[m]);
Console.ReadLine();
}
}
}
4. 冒泡排序
====================================================
算法思想简单描述:
依次比较相邻的两个数,把大的放前面,小的放后面.即首先比较第1个数和第2个数,大数放前,小数放后.然后比较第2个数和第3个数......直到比较最后两个数.第一趟结束,最小的
一定沉到最后.重复上过程,仍从第1个数开始,到最后第2个数.然后......
由于在排序过程中总是大数往前,小数往后,相当气泡上升,所以叫冒泡排序.

冒泡排序(Bubble Sort,台湾译为:泡沫排序或气泡排序)是一种简单的排序算法。
它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。

冒泡排序对n个项目需要O(n^{2})的比较次数,且可以原地排序。尽管这个算法是最简单了解和实作的排序算法之一,但它对于少数元素之外的数列排序是很没有效率的。

冒泡排序是与插入排序拥有相等的执行时间,但是两种法在需要的交换次数却很大地不同。在最坏的情况,冒泡排序需要O(n^{2})次交换,而插入排序只要最多O(n)交换。

冒泡排序的实现(类似下面)通常会对已经排序好的数列拙劣地执行(O(n^{2})),而插入排序在这个例子只需要O(n)个运算。因此很多现代的算法教科书避免使用冒泡排序,而用插入排序取代之。冒泡排序如果能在内部循环第一次执行时,使用一个旗标来表示有无需要交换的可能,也有可能把最好的复杂度降低到O(n)。在这个情况,在已经排序好的数列就无交换的需要。若在每次走访数列时,把走访顺序和比较大小反过来,也可以稍微地改进效率。有时候称为往返排序,因为算法会从数列的一端到另一端之间穿梭往返。

2.算法实现
1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
3.针对所有的元素重复以上的步骤,除了最后一个。
4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
3.实现过程




最差时间复杂度


最优时间复杂度


平均时间复杂度


====================================================

实例4:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Bubblesort
{
/// <summary>
/// 冒泡排序
/// </summary>
public class bubblesort
{
public void BubbleSort(int[] R)
{
int i, j, temp; //交换标志
bool exchange;
for (i = 0; i < R.Length; i++) //最多做R.Length-1趟排序
{
exchange = false; //本趟排序开始前,交换标志应为假
for (j = R.Length - 2; j >= i; j--)
{
if (R[j + 1] < R[j]) //交换条件
{
temp = R[j + 1];
R[j + 1] = R[j];
R[j] = temp;
exchange = true; //发生了交换,故将交换标志置为真
}
}
if (!exchange) //本趟排序未发生交换,提前终止算法
{
break;
}
}
}
}
class Program
{
static void Main(string[] args)
{
int[] iArray = new int[] { 1, 5, 3, 6, 10, 55 };
bubblesort ii = new bubblesort();
ii.BubbleSort(iArray);
for (int m = 0; m <= iArray.Length - 1; m++)
Console.WriteLine("{0}", iArray[m]);
Console.ReadLine();
}
}
}
5. 快速排序
====================================================
算法思想简单描述:

快速排序(Quicksort)是对冒泡排序的一种改进。
快速排序的基本概念是
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,
然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
下面通过一个例子来了解快速排序的具体含义:
{ 23, 45, 60, 10, 17, 101,12}
第一遍排序:



由此思想,我们可以实现快速排序的代码:
注意基准数据永远不变,永远是和基准数据进行比较,无论在什么位置,最后的目的就是把基准数据放在中间,小的放前面大的放后面。

====================================================
实例5:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace QuickSorter
{
class Program
{
static void Main(string[] args)
{
int[] n = { 23, 45, 60, 10, 17, 101,12};
QuickSort qs = new QuickSort();
qs.QuickSortFunc(n, 0, n.Length - 1);
for (int i = 0; i < n.Length; i++)
{
Console.WriteLine(n[i]);
}
Console.ReadLine();
}
}
class QuickSort
{
//方法一:
private int Func(int[] n, int left, int right)
{
int baseNum = n[left]; // 基准数据
int i = left;
int j = right;
while (true)
{
if (n[i] < baseNum && i < j)
{
i++;
}
else if (n[i] > baseNum && i < j)
{
int number = n[i];
n[i] = n[j];
n[j] = number;
j--;
}
else if (n[j] < baseNum && i < j)
{
int number = n[i];
n[i] = n[j];
n[j] = number;
i++;
}
else if (n[j] > baseNum && i < j)
{
j--;
}
else if (i == j)
{
n[i] = baseNum;
break;
}
}
return i;
}
public void QuickSortFunc(int[] n, int left, int right)
{
//左下标一定小于右下标,否则就超越了
if (left < right)
{
//对数组进行分割,取出下次分割的基准标号
int i = Func(n, left, right);
//对“基准标号“左侧的一组数值进行递归的切割,以至于将这些数值完整的排序
QuickSortFunc(n, left, i - 1);
//对“基准标号“右侧的一组数值进行递归的切割,以至于将这些数值完整的排序
QuickSortFunc(n, i + 1, right);
}
}
//end 方法一
//方法二:其实我们还可以取中间的数作为基准,具体示例如下:
private void Sort(int[] numbers, int left, int right)
{
//左边索引小于右边,则还未排序完成   
if (left < right)
{
//取中间的元素作为比较基准,小于他的往左边移,大于他的往右边移   
int middle = numbers[(left + right) / 2];
int i = left - 1;
int j = right + 1;
while (true)
{
while (numbers[++i] < middle && i < right) ;
while (numbers[--j] > middle && j > 0) ;
if (i >= j)
break;
Swap(numbers, i, j);
}
Sort(numbers, left, i - 1);
Sort(numbers, j + 1, right);
for (int k = 0; k < numbers.Length; k++)
{
Console.WriteLine(numbers[k]);
}
Console.ReadLine();
}
}
private void Swap(int[] numbers, int i, int j)
{
int number = numbers[i];
numbers[i] = numbers[j];
numbers[j] = number;
}
//end 方法二
}
}


参考:
http://www.360doc.com/content/13/0419/14/11338798_279466782.shtml
http://www.cnblogs.com/WangJinYang/p/3553792.html
http://www.cnblogs.com/jiajiayuan/archive/2012/08/31/2665302.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C#