七大经典排序

针对现实中的排序问题,算法有七把利剑可以助你马道成功。

 

首先排序分为四种: 

      交换排序: 包括冒泡排序,快速排序。

      选择排序: 包括直接选择排序,堆排序。

      插入排序: 包括直接插入排序,希尔排序。

      合并排序: 合并排序。

 

那么今天我们讲的就是交换排序,我们都知道,C#类库提供的排序是快排,为了让今天玩的有意思点,

我们设计算法来跟类库提供的快排较量较量。争取KO对手。

 

冒泡排序:

首先我们自己来设计一下“冒泡排序”,这种排序很现实的例子就是:

我抓一把沙仍进水里,那么沙子会立马沉入水底, 沙子上的灰尘会因为惯性暂时沉入水底,但是又会立马像气泡一样浮出水面,最后也就真相大白咯。

 

关于冒泡的思想,我不会说那么官方的理论,也不会贴那些文字上来,我的思想就是看图说话。

那么我们就上图.

           

要达到冒泡的效果,我们就要把一组数字竖起来看,大家想想,如何冒泡?如何来体会重的沉底,轻的上浮?

 

第一步:  我们拿40跟20比,发现40是老大,不用交换。

第二步:  然后向前推一步,就是拿20跟30比,发现30是老大,就要交换了。

第三步:拿交换后的20跟10比,发现自己是老大,不用交换。

第四步:拿10跟50交换,发现50是老大,进行交换。

 

最后,我们经过一次遍历,把数组中最小的数字送上去了,看看,我们向目标又迈进了一步。

 

现在大家思想都知道了,下面我们就强烈要求跟快排较量一下,不是你死就是我活。

复制代码
 1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Diagnostics;
6 using System.Threading;
7
8 namespace BubbleSort
9 {
10 public class Program
11 {
12 static void Main(string[] args)
13 {
14 //五次比较
15 for (int i = 1; i <= 5; i++)
16 {
17 List<int> list = new List<int>();
18 //插入2k个随机数到数组中
19 for (int j = 0; j < 2000; j++)
20 {
21 Thread.Sleep(1);
22 list.Add(new Random((int)DateTime.Now.Ticks).Next(0, 100000));
23 }
24 Console.WriteLine("\n第" + i + "次比较:");
25 Stopwatch watch = new Stopwatch();
26 watch.Start();
27 var result = list.OrderBy(single => single).ToList();
28 watch.Stop();
29 Console.WriteLine("\n快速排序耗费时间:" + watch.ElapsedMilliseconds);
30 Console.WriteLine("输出前是十个数:" + string.Join(",", result.Take(10).ToList()));
31 watch.Start();
32 result = BubbleSort(list);
33 watch.Stop();
34 Console.WriteLine("\n冒泡排序耗费时间:" + watch.ElapsedMilliseconds);
35 Console.WriteLine("输出前是十个数:" + string.Join(",", result.Take(10).ToList()));
36 }
37 }
38
39 //冒泡排序算法
40 static List<int> BubbleSort(List<int> list)
41 {
42 int temp;
43 //第一层循环: 表明要比较的次数,比如list.count个数,肯定要比较count-1次
44 for (int i = 0; i < list.Count - 1; i++)
45 {
46 //list.count-1:取数据最后一个数下标,
47 //j>i: 从后往前的的下标一定大于从前往后的下标,否则就超越了。
48 for (int j = list.Count - 1; j > i; j--)
49 {
50 //如果前面一个数大于后面一个数则交换
51 if (list[j - 1] > list[j])
52 {
53 temp = list[j - 1];
54 list[j - 1] = list[j];
55 list[j] = temp;
56 }
57 }
58 }
59 return list;
60 }
61 }
62 }
复制代码

 

 

呜呜,看着这两种排序体检报告,心都凉了,冒泡被快排KO了,真惨,难怪人家说冒泡效率低,原来真tmd低。

 

快速排序:

既然能把冒泡KO掉,马上就激起我们的兴趣,tnd快排咋这么快,一定要好好研究一下。

首先上图:    

 

从图中我们可以看到:

left指针,right指针,base参照数。

其实思想是蛮简单的,就是通过第一遍的遍历(让left和right指针重合)来找到数组的切割点。

第一步:首先我们从数组的left位置取出该数(20)作为基准(base)参照物。

第二步:从数组的right位置向前找,一直找到比(base)小的数,

            如果找到,将此数赋给left位置(也就是将10赋给20),

            此时数组为:10,40,50,10,60,

            left和right指针分别为前后的10。

第三步:从数组的left位置向后找,一直找到比(base)大的数,

             如果找到,将此数赋给right的位置(也就是40赋给10),

             此时数组为:10,40,50,40,60,

             left和right指针分别为前后的40。

第四步:重复“第二,第三“步骤,直到left和right指针重合,

             最后将(base)插入到40的位置,

             此时数组值为: 10,20,50,40,60,至此完成一次排序。

第五步:此时20已经潜入到数组的内部,20的左侧一组数都比20小,20的右侧作为一组数都比20大,

            以20为切入点对左右两边数按照"第一,第二,第三,第四"步骤进行,最终快排大功告成。

 

同样,我们把自己设计的快排跟类库提供的快拍比较一下。看谁牛X。

复制代码
  1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading;
6 using System.Diagnostics;
7
8 namespace QuickSort
9 {
10 public class Program
11 {
12 static void Main(string[] args)
13 {
14 //5次比较
15 for (int i = 1; i <= 5; i++)
16 {
17 List<int> list = new List<int>();
18
19 //插入200个随机数到数组中
20 for (int j = 0; j < 200; j++)
21 {
22 Thread.Sleep(1);
23 list.Add(new Random((int)DateTime.Now.Ticks).Next(0, 10000));
24 }
25
26 Console.WriteLine("\n第" + i + "次比较:");
27
28 Stopwatch watch = new Stopwatch();
29
30 watch.Start();
31 var result = list.OrderBy(single => single).ToList();
32 watch.Stop();
33
34 Console.WriteLine("\n系统定义的快速排序耗费时间:" + watch.ElapsedMilliseconds);
35 Console.WriteLine("输出前是十个数:" + string.Join(",", result.Take(10).ToList()));
36
37 watch.Start();
38 new QuickSortClass().QuickSort(list, 0, list.Count - 1);
39 watch.Stop();
40
41 Console.WriteLine("\n俺自己写的快速排序耗费时间:" + watch.ElapsedMilliseconds);
42 Console.WriteLine("输出前是十个数:" + string.Join(",", list.Take(10).ToList()));
43
44 }
45 }
46 }
47
48 public class QuickSortClass
49 {
50
51 ///<summary>
52 /// 分割函数
53 ///</summary>
54 ///<param name="list">待排序的数组</param>
55 ///<param name="left">数组的左下标</param>
56 ///<param name="right"></param>
57 ///<returns></returns>
58 public int Division(List<int> list, int left, int right)
59 {
60 //首先挑选一个基准元素
61 int baseNum = list[left];
62
63 while (left < right)
64 {
65 //从数组的右端开始向前找,一直找到比base小的数字为止(包括base同等数)
66 while (left < right && list[right] >= baseNum)
67 right = right - 1;
68
69 //最终找到了比baseNum小的元素,要做的事情就是此元素放到base的位置
70 list[left] = list[right];
71
72 //从数组的左端开始向后找,一直找到比base大的数字为止(包括base同等数)
73 while (left < right && list[left] <= baseNum)
74 left = left + 1;
75
76
77 //最终找到了比baseNum大的元素,要做的事情就是将此元素放到最后的位置
78 list[right] = list[left];
79 }
80 //最后就是把baseNum放到该left的位置
81 list[left] = baseNum;
82
83 //最终,我们发现left位置的左侧数值部分比left小,left位置右侧数值比left大
84 //至此,我们完成了第一篇排序
85 return left;
86 }
87
88 public void QuickSort(List<int> list, int left, int right)
89 {
90 //左下标一定小于右下标,否则就超越了
91 if (left < right)
92 {
93 //对数组进行分割,取出下次分割的基准标号
94 int i = Division(list, left, right);
95
96 //对“基准标号“左侧的一组数值进行递归的切割,以至于将这些数值完整的排序
97 QuickSort(list, left, i - 1);
98
99 //对“基准标号“右侧的一组数值进行递归的切割,以至于将这些数值完整的排序
100 QuickSort(list, i + 1, right);
101 }
102 }
103 }
104 }
复制代码

 

 

不错,快排就是快,难怪内库非要用他来作为排序的标准。

 

嗯,最后要分享下:

冒泡的时间复杂度为: 0(n) - 0(n^2)

快排的时间复杂度为: 

    平均复杂度: N(logN)

    最坏复杂度:  0(n^2)

选择排序

1.直接选择排序: 

先上图:

 

说实话,直接选择排序最类似于人的本能思想,比如把大小不一的玩具让三岁小毛孩对大小排个序,

那小孩首先会在这么多玩具中找到最小的放在第一位,然后找到次小的放在第二位,以此类推。。。。。。

,小孩子多聪明啊,这么小就知道了直接选择排序。羡慕中........

 

对的,小孩子给我们上了一课,

第一步: 我们拿80作为参照物(base),在80后面找到一个最小数20,然后将80跟20交换。

第二步:  第一位数已经是最小数字了,然后我们推进一步在30后面找一位最小数,发现自己最小,不用交换。

第三步:........

最后我们排序完毕。大功告成。

 

既然是来挑战的,那就5局3胜制。

复制代码
 1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading;
6 using System.Diagnostics;
7
8 namespace SelectionSort
9 {
10 public class Program
11 {
12 static void Main(string[] args)
13 {
14 //5次比较
15 for (int i = 1; i <= 5; i++)
16 {
17 List<int> list = new List<int>();
18
19 //插入2w个随机数到数组中
20 for (int j = 0; j < 20000; j++)
21 {
22 Thread.Sleep(1);
23 list.Add(new Random((int)DateTime.Now.Ticks).Next(1000, 1000000));
24 }
25
26 Console.WriteLine("\n第" + i + "次比较:");
27
28 Stopwatch watch = new Stopwatch();
29
30 watch.Start();
31 var result = list.OrderBy(single => single).ToList();
32 watch.Stop();
33
34 Console.WriteLine("\n快速排序耗费时间:" + watch.ElapsedMilliseconds);
35 Console.WriteLine("输出前十个数:" + string.Join(",", result.Take(10).ToList()));
36
37 watch.Start();
38 result = SelectionSort(list);
39 watch.Stop();
40
41 Console.WriteLine("\n直接选择排序耗费时间:" + watch.ElapsedMilliseconds);
42 Console.WriteLine("输出前十个数:" + string.Join(",", list.Take(10).ToList()));
43
44 }
45 }
46
47 //选择排序
48 static List<int> SelectionSort(List<int> list)
49 {
50 //要遍历的次数
51 for (int i = 0; i < list.Count - 1; i++)
52 {
53 //假设tempIndex的下标的值最小
54 int tempIndex = i;
55
56 for (int j = i + 1; j < list.Count; j++)
57 {
58 //如果tempIndex下标的值大于j下标的值,则记录较小值下标j
59 if (list[tempIndex] > list[j])
60 tempIndex = j;
61 }
62
63 //最后将假想最小值跟真的最小值进行交换
64 var tempData = list[tempIndex];
65 list[tempIndex] = list[i];
66 list[i] = tempData;
67 }
68 return list;
69 }
70 }
71 }
复制代码

比赛结果公布:

 

堆排序:

要知道堆排序,首先要了解一下二叉树的模型。

下图就是一颗二叉树,具体的情况我后续会分享的。

那么堆排序中有两种情况(看上图理解):

    大根堆:  就是说父节点要比左右孩子都要大。

    小根堆:  就是说父节点要比左右孩子都要小。

 

那么要实现堆排序,必须要做两件事情:

   第一:构建大根堆。

           首先上图:

           

首先这是一个无序的堆,那么我们怎样才能构建大根堆呢?

     第一步: 首先我们发现,这个堆中有2个父节点(2,,3);

     第二步: 比较2这个父节点的两个孩子(4,5),发现5大。

     第三步: 然后将较大的右孩子(5)跟父节点(2)进行交换,至此3的左孩子堆构建完毕,

                 如图:

                         

     第四步: 比较第二个父节点(3)下面的左右孩子(5,1),发现左孩子5大。

     第五步: 然后父节点(3)与左孩子(5)进行交换,注意,交换后,堆可能会遭到破坏,

                 必须按照以上的步骤一,步骤二,步骤三进行重新构造堆。

           

最后构造的堆如下:

                 

 

   第二:输出大根堆。

             至此,我们把大根堆构造出来了,那怎么输出呢?我们做大根堆的目的就是要找出最大值,

         那么我们将堆顶(5)与堆尾(2)进行交换,然后将(5)剔除根堆,由于堆顶现在是(2),

         所以破坏了根堆,必须重新构造,构造完之后又会出现最大值,再次交换和剔除,最后也就是俺们

         要的效果了,

 

 

发现自己兄弟被别人狂殴,,堆排序再也坐不住了,决定要和快排干一场。

同样,快排也不甘示弱,谁怕谁?

 

复制代码
  1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading;
6 using System.Diagnostics;
7
8 namespace HeapSort
9 {
10 public class Program
11 {
12 static void Main(string[] args)
13 {
14 //5次比较
15 for (int j = 1; j <= 5; j++)
16 {
17 List<int> list = new List<int>();
18
19 //插入2w个数字
20 for (int i = 0; i < 20000; i++)
21 {
22 Thread.Sleep(1);
23 list.Add(new Random((int)DateTime.Now.Ticks).Next(1000, 100000));
24 }
25
26 Console.WriteLine("\n第" + j + "次比较:");
27
28 Stopwatch watch = new Stopwatch();
29 watch.Start();
30 var result = list.OrderBy(single => single).ToList();
31 watch.Stop();
32 Console.WriteLine("\n快速排序耗费时间:" + watch.ElapsedMilliseconds);
33 Console.WriteLine("输出前十个数" + string.Join(",", result.Take(10).ToList()));
34
35 watch = new Stopwatch();
36 watch.Start();
37 HeapSort(list);
38 watch.Stop();
39 Console.WriteLine("\n堆排序耗费时间:" + watch.ElapsedMilliseconds);
40 Console.WriteLine("输出前十个数" + string.Join(",", list.Take(10).ToList()));
41 }
42
43 }
44
45 ///<summary>
46 /// 构建堆
47 ///</summary>
48 ///<param name="list">待排序的集合</param>
49 ///<param name="parent">父节点</param>
50 ///<param name="length">输出根堆时剔除最大值使用</param>
51 static void HeapAdjust(List<int> list, int parent, int length)
52 {
53 //temp保存当前父节点
54 int temp = list[parent];
55
56 //得到左孩子(这可是二叉树的定义,大家看图也可知道)
57 int child = 2 * parent + 1;
58
59 while (child < length)
60 {
61 //如果parent有右孩子,则要判断左孩子是否小于右孩子
62 if (child + 1 < length && list[child] < list[child + 1])
63 child++;
64
65 //父亲节点大于子节点,就不用做交换
66 if (temp >= list[child])
67 break;
68
69 //将较大子节点的值赋给父亲节点
70 list[parent] = list[child];
71
72 //然后将子节点做为父亲节点,已防止是否破坏根堆时重新构造
73 parent = child;
74
75 //找到该父亲节点较小的左孩子节点
76 child = 2 * parent + 1;
77 }
78 //最后将temp值赋给较大的子节点,以形成两值交换
79 list[parent] = temp;
80 }
81
82 ///<summary>
83 /// 堆排序
84 ///</summary>
85 ///<param name="list"></param>
86 public static void HeapSort(List<int> list)
87 {
88 //list.Count/2-1:就是堆中父节点的个数
89 for (int i = list.Count / 2 - 1; i >= 0; i--)
90 {
91 HeapAdjust(list, i, list.Count);
92 }
93
94 //最后输出堆元素
95 for (int i = list.Count - 1; i > 0; i--)
96 {
97 //堆顶与当前堆的第i个元素进行值对调
98 int temp = list[0];
99 list[0] = list[i];
100 list[i] = temp;
101
102 //因为两值交换,可能破坏根堆,所以必须重新构造
103 HeapAdjust(list, 0, i);
104 }
105 }
106 }
107 }
复制代码

结果公布:

 

堆排序此时心里很尴尬,双双被KO,心里想,一定要捞回面子,一定要赢,

 

于是堆排序提出了求“前K大问题”。(就是在海量数据中找出前几大的数据),

快排一口答应,小意思,没问题。

双方商定,在2w随机数中找出前10大的数:

复制代码
  1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading;
6 using System.Diagnostics;
7
8 namespace QuickSort
9 {
10 public class Program
11 {
12 static void Main(string[] args)
13 {
14 //5此比较
15 for (int j = 1; j <= 5; j++)
16 {
17 List<int> list = new List<int>();
18
19 for (int i = 0; i < 20000; i++)
20 {
21 Thread.Sleep(1);
22 list.Add(new Random((int)DateTime.Now.Ticks).Next(1000, 100000));
23 }
24
25 Console.WriteLine("\n第" + j + "次比较:");
26
27 Stopwatch watch = new Stopwatch();
28 watch.Start();
29 var result = list.OrderByDescending(single => single).Take(10).ToList();
30 watch.Stop();
31 Console.WriteLine("\n快速排序求前K大耗费时间:" + watch.ElapsedMilliseconds);
32 Console.WriteLine("输出前十个数:" + string.Join(",", result.Take(10).ToList()));
33
34 watch = new Stopwatch();
35 watch.Start();
36 result = HeapSort(list, 10);
37 watch.Stop();
38 Console.WriteLine("\n堆排序求前K大耗费时间:" + watch.ElapsedMilliseconds);
39 Console.WriteLine("输出前十个数:" + string.Join(",", list.Take(10).ToList()));
40 }
41
42 }
43
44 ///<summary>
45 /// 构建堆
46 ///</summary>
47 ///<param name="list">待排序的集合</param>
48 ///<param name="parent">父节点</param>
49 ///<param name="length">输出根堆时剔除最大值使用</param>
50 static void HeapAdjust(List<int> list, int parent, int length)
51 {
52 //temp保存当前父节点
53 int temp = list[parent];
54
55 //得到左孩子(这可是二叉树的定义哇)
56 int child = 2 * parent + 1;
57
58 while (child < length)
59 {
60 //如果parent有右孩子,则要判断左孩子是否小于右孩子
61 if (child + 1 < length && list[child] < list[child + 1])
62 child++;
63
64 //父节点大于子节点,不用做交换
65 if (temp >= list[child])
66 break;
67
68 //将较大子节点的值赋给父亲节点
69 list[parent] = list[child];
70
71 //然后将子节点做为父亲节点,已防止是否破坏根堆时重新构造
72 parent = child;
73
74 //找到该父节点左孩子节点
75 child = 2 * parent + 1;
76 }
77 //最后将temp值赋给较大的子节点,以形成两值交换
78 list[parent] = temp;
79 }
80
81 ///<summary>
82 /// 堆排序
83 ///</summary>
84 ///<param name="list">待排序的集合</param>
85 ///<param name="top">前K大</param>
86 ///<returns></returns>
87 public static List<int> HeapSort(List<int> list, int top)
88 {
89 List<int> topNode = new List<int>();
90
91 //list.Count/2-1:就是堆中非叶子节点的个数
92 for (int i = list.Count / 2 - 1; i >= 0; i--)
93 {
94 HeapAdjust(list, i, list.Count);
95 }
96
97 //最后输出堆元素(求前K大)
98 for (int i = list.Count - 1; i >= list.Count - top; i--)
99 {
100 //堆顶与当前堆的第i个元素进行值对调
101 int temp = list[0];
102 list[0] = list[i];
103 list[i] = temp;
104
105 //最大值加入集合
106 topNode.Add(temp);
107
108 //因为顺序被打乱,必须重新构造堆
109 HeapAdjust(list, 0, i);
110 }
111 return topNode;
112 }
113 }
114 }
复制代码

求前K大的输出结果:

 

 

最后堆排序赶紧拉着直接选择排序一路小跑了,因为求前K大问题已经不是他原本来的目的。

 

ps: 直接选择排序的时间复杂度为:O(n^2)

       堆排序的时间复杂度:O(NlogN)

 

最后三种排序: 直接插入排序,希尔排序和归并排序。

 

直接插入排序:

       这种排序其实蛮好理解的,很现实的例子就是俺们斗地主,当我们抓到一手乱牌时,我们就要按照大小梳理扑克,30秒后,

   扑克梳理完毕,4条3,5条s,哇塞......  回忆一下,俺们当时是怎么梳理的。

       最左一张牌是3,第二张牌是5,第三张牌又是3,赶紧插到第一张牌后面去,第四张牌又是3,大喜,赶紧插到第二张后面去,

   第五张牌又是3,狂喜,哈哈,一门炮就这样产生了。

 

     怎么样,生活中处处都是算法,早已经融入我们的生活和血液。

     

     下面就上图说明:

             

      看这张图不知道大家可否理解了,在插入排序中,数组会被划分为两种,“有序数组块”和“无序数组块”,

     

      对的,第一遍的时候从”无序数组块“中提取一个数20作为有序数组块。

              第二遍的时候从”无序数组块“中提取一个数60有序的放到”有序数组块中“,也就是20,60。

              第三遍的时候同理,不同的是发现10比有序数组的值都小,因此20,60位置后移,腾出一个位置让10插入。

                      然后按照这种规律就可以全部插入完毕。

  

复制代码
 1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace InsertSort
7 {
8 public class Program
9 {
10 static void Main(string[] args)
11 {
12 List<int> list = new List<int>() { 3, 1, 2, 9, 7, 8, 6 };
13
14 Console.WriteLine("排序前:" + string.Join(",", list));
15
16 InsertSort(list);
17
18 Console.WriteLine("排序后:" + string.Join(",", list));
19 }
20
21 static void InsertSort(List<int> list)
22 {
23 //无须序列
24 for (int i = 1; i < list.Count; i++)
25 {
26 var temp = list[i];
27
28 int j;
29
30 //有序序列
31 for (j = i - 1; j >= 0 && temp < list[j]; j--)
32 {
33 list[j + 1] = list[j];
34 }
35 list[j + 1] = temp;
36 }
37 }
38 }
39 }
复制代码

 

希尔排序:

        观察一下”插入排序“:其实不难发现她有个缺点:

              如果当数据是”5, 4, 3, 2, 1“的时候,此时我们将“无序块”中的记录插入到“有序块”时,估计俺们要崩盘,

       每次插入都要移动位置,此时插入排序的效率可想而知。

   

      shell根据这个弱点进行了算法改进,融入了一种叫做“缩小增量排序法”的思想,其实也蛮简单的,不过有点注意的就是:

  增量不是乱取,而是有规律可循的。

首先要明确一下增量的取法:

      第一次增量的取法为: d=count/2;

      第二次增量的取法为:  d=(count/2)/2;

      最后一直到: d=1;

看上图观测的现象为:

        d=3时:将40跟50比,因50大,不交换。

                   将20跟30比,因30大,不交换。

                   将80跟60比,因60小,交换。

        d=2时:将40跟60比,不交换,拿60跟30比交换,此时交换后的30又比前面的40小,又要将40和30交换,如上图。

                   将20跟50比,不交换,继续将50跟80比,不交换。

        d=1时:这时就是前面讲的插入排序了,不过此时的序列已经差不多有序了,所以给插入排序带来了很大的性能提高。

 

既然说“希尔排序”是“插入排序”的改进版,那么我们就要比一下,在1w个数字中,到底能快多少?

 

下面进行一下测试:

View Code
  1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading;
6 using System.Diagnostics;
7
8 namespace ShellSort
9 {
10 public class Program
11 {
12 static void Main(string[] args)
13 {
14 //5次比较
15 for (int i = 1; i <= 5; i++)
16 {
17 List<int> list = new List<int>();
18
19 //插入1w个随机数到数组中
20 for (int j = 0; j < 10000; j++)
21 {
22 Thread.Sleep(1);
23 list.Add(new Random((int)DateTime.Now.Ticks).Next(10000, 1000000));
24 }
25
26 List<int> list2 = new List<int>();
27 list2.AddRange(list);
28
29 Console.WriteLine("\n第" + i + "次比较:");
30
31 Stopwatch watch = new Stopwatch();
32
33 watch.Start();
34 InsertSort(list);
35 watch.Stop();
36
37 Console.WriteLine("\n插入排序耗费的时间:" + watch.ElapsedMilliseconds);
38 Console.WriteLine("输出前十个数:" + string.Join(",", list.Take(10).ToList()));
39
40 watch.Restart();
41 ShellSort(list2);
42 watch.Stop();
43
44 Console.WriteLine("\n希尔排序耗费的时间:" + watch.ElapsedMilliseconds);
45 Console.WriteLine("输出前十个数:" + string.Join(",", list2.Take(10).ToList()));
46
47 }
48 }
49
50 ///<summary>
51 /// 希尔排序
52 ///</summary>
53 ///<param name="list"></param>
54 static void ShellSort(List<int> list)
55 {
56 //取增量
57 int step = list.Count / 2;
58
59 while (step >= 1)
60 {
61 //无须序列
62 for (int i = step; i < list.Count; i++)
63 {
64 var temp = list[i];
65
66 int j;
67
68 //有序序列
69 for (j = i - step; j >= 0 && temp < list[j]; j = j - step)
70 {
71 list[j + step] = list[j];
72 }
73 list[j + step] = temp;
74 }
75 step = step / 2;
76 }
77 }
78
79 ///<summary>
80 /// 插入排序
81 ///</summary>
82 ///<param name="list"></param>
83 static void InsertSort(List<int> list)
84 {
85 //无须序列
86 for (int i = 1; i < list.Count; i++)
87 {
88 var temp = list[i];
89
90 int j;
91
92 //有序序列
93 for (j = i - 1; j >= 0 && temp < list[j]; j--)
94 {
95 list[j + 1] = list[j];
96 }
97 list[j + 1] = temp;
98 }
99 }
100 }
101 }


截图如下:

 

看的出来,希尔排序优化了不少,w级别的排序中,相差70几倍哇。

 

归并排序:

       个人感觉,我们能容易看的懂的排序基本上都是O (n^2),比较难看懂的基本上都是N(LogN),所以归并排序也是比较难理解的,尤其是在代码

 编写上,本人就是搞了一下午才搞出来,嘻嘻。

 

首先看图:

归并排序中中两件事情要做:

            第一: “分”,  就是将数组尽可能的分,一直分到原子级别。

            第二: “并”,将原子级别的数两两合并排序,最后产生结果。

代码:

复制代码
 1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace MergeSort
7 {
8 class Program
9 {
10 static void Main(string[] args)
11 {
12 int[] array = { 3, 2, 1, 8, 9, 0 };
13
14 MergeSort(array, new int[array.Length], 0, array.Length - 1);
15
16 Console.WriteLine(string.Join(",", array));
17 }
18
19 ///<summary>
20 /// 数组的划分
21 ///</summary>
22 ///<param name="array">待排序数组</param>
23 ///<param name="temparray">临时存放数组</param>
24 ///<param name="left">序列段的开始位置,</param>
25 ///<param name="right">序列段的结束位置</param>
26 static void MergeSort(int[] array, int[] temparray, int left, int right)
27 {
28 if (left < right)
29 {
30 //取分割位置
31 int middle = (left + right) / 2;
32
33 //递归划分数组左序列
34 MergeSort(array, temparray, left, middle);
35
36 //递归划分数组右序列
37 MergeSort(array, temparray, middle + 1, right);
38
39 //数组合并操作
40 Merge(array, temparray, left, middle + 1, right);
41 }
42 }
43
44 ///<summary>
45 /// 数组的两两合并操作
46 ///</summary>
47 ///<param name="array">待排序数组</param>
48 ///<param name="temparray">临时数组</param>
49 ///<param name="left">第一个区间段开始位置</param>
50 ///<param name="middle">第二个区间的开始位置</param>
51 ///<param name="right">第二个区间段结束位置</param>
52 static void Merge(int[] array, int[] temparray, int left, int middle, int right)
53 {
54 //左指针尾
55 int leftEnd = middle - 1;
56
57 //右指针头
58 int rightStart = middle;
59
60 //临时数组的下标
61 int tempIndex = left;
62
63 //数组合并后的length长度
64 int tempLength = right - left + 1;
65
66 //先循环两个区间段都没有结束的情况
67 while ((left <= leftEnd) && (rightStart <= right))
68 {
69 //如果发现有序列大,则将此数放入临时数组
70 if (array[left] < array[rightStart])
71 temparray[tempIndex++] = array[left++];
72 else
73 temparray[tempIndex++] = array[rightStart++];
74 }
75
76 //判断左序列是否结束
77 while (left <= leftEnd)
78 temparray[tempIndex++] = array[left++];
79
80 //判断右序列是否结束
81 while (rightStart <= right)
82 temparray[tempIndex++] = array[rightStart++];
83
84 //交换数据
85 for (int i = 0; i < tempLength; i++)
86 {
87 array[right] = temparray[right];
88 right--;
89 }
90 }
91 }
92 }
复制代码

结果图:

 

ps: 插入排序的时间复杂度为:O(N^2)

     希尔排序的时间复杂度为:平均为:O(N^3/2)

                                       最坏: O(N^2)

     归并排序时间复杂度为: O(NlogN)

                空间复杂度为:  O(N) 

 

 

 

posted @ 2012-12-19 17:49  梓涵VV  阅读(288)  评论(0编辑  收藏  举报