经典排序算法 — C# 版(上)

提起排序,与我们的息息相关,平时开发的代码少不了排序。

经典的排序算法又非常多,我们怎么评价一个排序算法的好坏呢?

其实可以这样想,要细致的比较排序算法好坏,那我们就从多方面尽可能详细的对比

一、效率方面

1、排序算法的执行效率:最好、最坏、平均

2、 我们之前舍弃的时间复杂度的系数、常量、低阶,在这里需要拿回来

3、排序,免不了比较和移动

二、内存消耗方面

没错就是 算法的空间复杂度,不过对于排序的空间复杂度来说,又赋予了新的名词 — 原地排序。

顾名思义是 原地排序的肯定是消耗内存少,反之需要往外走几步那就需要临时申请内存了。

原地排序 = O(1)

三、算法稳定性

字面意义就是不论怎么摆弄,这个算法稳定,不会对顺序有影响。

上面这句话应该加上一个定语:对于拥有相同值的元素的前后顺序不会发生改变。

举个例子:有两个对象,其中的金额字段一样,按照金额排序,经过算法一顿折腾后,相同金额的对象先后顺序不能发生改变。

讲完评估排序算法的优劣的几个方面,那就直接看看我们平时常见的几个经典算法:

1、冒泡排序

图例演示

> C#

 1          //排序 — 冒泡排序
 2         private static void BubbleSort(int[] source)
 3         {
 4             if (source.Length <= 1)
 5                 return;
 6
 7             bool isChanged = false;
 8             for (int i = 0; i < source.Length; i++)
 9             {
10                 for (int j = 0; j < source.Length - i - 1; j++)
11                 {
12                     var left = source[j];
13                     var right = source[j + 1];
14                     Console.WriteLine("【比较】");
15                     if (left <= right)
16                         continue;
17
18                     source[j] = right;
19                     source[j + 1] = left;
20                     isChanged = true;
21                     Console.WriteLine("{交换}");
22                 }
23                 if (!isChanged)
24                     break;
25             }
26             Printf(source);
27         }

Q:冒泡排序的时间算法复杂度

A:最坏时间复杂度 — O(n^2):循环 n*n次

   最好时间复杂度 — O(n)    :循环 n次即可

   平均时间复杂度 — O(?)

这里我们使用概率来分析平均复杂度,情况比较复杂。

   我们使用一种新的概念来分析平均复杂度,这个就是 有序度。

有序度:看作是向量,左<= 右

      逆序度:正好相反,左 >= 右

   满有序度 = n*(n-1) / 2

   逆序度 = 满有序度 - 有序度

对于 n 个数据来说,最坏情况时间复杂度的有序度是0,要交换 n*(n-1)/2次才能正确输出。

对于最好情况复杂度的有序度是n*(n-1)/2,需要交换0次就能达到完全有序。

最坏 n*(n-1)/2次,最好0次,取个中间值来表示中间情况,也可以看作是平均情况 n*(n-1) /4

所以平均下来 要做 n*(n-1) / 4 次才能有序,因为冒泡排序的时间复杂度的上限是 O(n^2)

所以平均情况时间复杂度为 O(n^2)

虽然这样推论平均个情况并不严格,但是比起概率推论来说,这样简单且有效。

Q:冒泡排序是不是原地排序

A:是,临时变量为了交换数据,常量级别的临时空间申请,所以空间复杂度为O(1)

Q:冒泡排序是不是稳定排序

A:是,因为没有改变相同元素的先后顺序。

2、插入排序

假定,我们将排序串分为两个区:已排序区,未排序区

一个元素要找到正确的的位置进行插入,那么需要去已排序区域找到自己的位置后,

将这个位置的元素们向后移动,空出位置,然后新元素入坑。

从以上这个思路来看,插入排序也是涉及到了元素的比较和移动。

给我们一个无序数组,哪块是已排序区?哪里是未排序区?

比如:9, 0, 1, 5, 2, 3, 6

初始时,9 就是已排序区域;

0开始去已排序区域挨个比较,即 i=1,0<9,9向后挪动,空出位置,0入坑;

1开始去 [ 0,9 ] 已排序区域比较,1 < 9,9向后移动腾位置,1入坑,1 > 0 无需操作;

依次重复以上操作,即可达成有序。

图例演示

> C#

 1         //排序 — 插入排序
 2         private static void InsertionSort(int[] source)
 3         {
 4             if (source == null || source.Length <= 0)
 5                 return;
 6
 7             for (int i = 1; i < source.Length; i++)
 8             {// 未排序区
 9                 var sorting = source[i];
10                 int j = i - 1;
11
12                 for (; j >= 0; j--)
13                 {// 已排序区
14
15                     // 比较
16                     if (sorting >= source[j])
17                     {
18                         break;
19                     }
20
21                     // 后移
22                     source[j + 1] = source[j];
23                 }
24
25                 // 入坑
26                 source[j + 1] = sorting;
27             }
28             Printf(source);
29         }

Q:插入排序的时间算法复杂度

A:最坏时间复杂度 — O(n^2):完全倒序,循环n次,比较n次

   最好时间复杂度 — O(n):完全有序,循环n次跳出

   平均时间复杂度 — O(n^2):循环 n次数据,在一个数组中插入数据的平均情况时间复杂度为O(n),所以是 O(n^2)

Q:插入排序是不是原地排序

A:是,没有临时变量申请,所以空间复杂度为O(1)

Q:插入排序是不是稳定排序

A:是, if (sorting >= source[j]) 这个判断保证了相同元素的先后顺序不变,

         去掉等于号也可以发生改变。可以实现稳定排序所以说是稳定排序

开始我们也说了,这么多排序算法,我们要对比一下,择优选择。

排序 最好情况 最坏情况 平均情况 是否稳定 是否原地
冒泡 O(n) O(n^2) O(n^2)
插入 O(n) O(n^2) O(n^2)

那么问题来了平均都是 O(n^2),为什么倾向于使用插入排序呢?

这两种排序我们将常量都放进来会发现,冒泡使用的常量数比排序多,所以在数据量上来后  常量*n 会有很大的差距。

我们的编程语言中的排序算法很多都会倾向于插入排序算法。

3、选择排序

其实操作类似于插入排序,只不过是换了换操作方式。

所以也分为 已排序区和未排序区,操作方式是在未排序区间找到最小的,然后放到已排序区间最后。

图例:

> C#

        private static void SelectionSort(int[] source)
        {
            if (source.Length <= 1)
                return;

            for (int i = 0; i < source.Length - 1; i++)
            {// 已排序
                var minIndex = i;

                for (int j = i+1; j < source.Length; j++)
                {//未排序

                    if (source[minIndex] > source[j])
                    {
                        minIndex = j;
                    }
                }
                if (i != minIndex)
                {
                    int tmp = source[i];
                    source[i] = source[minIndex];
                    source[minIndex] = tmp;
                }
            }

            Printf(source);
        }

Q:选择排序的时间算法复杂度

A:最坏时间复杂度 — O(n^2)

   最好时间复杂度 — O(n^2)

   平均时间复杂度 — O(n^2)

Q:选择排序是不是原地排序

A:是,没有临时变量申请,所以空间复杂度为O(1)

Q:选择排序是不是稳定排序

A:不是

4、对比 随机生成1000个元素的 int 数组

分别执行时间如下:

原文地址:https://www.cnblogs.com/sunchong/p/10150288.html

时间: 2024-09-30 17:46:07

经典排序算法 — C# 版(上)的相关文章

经典排序算法(Java版)

经典排序算法(Java版)  转载 1.冒泡排序 Bubble Sort最简单的排序方法是冒泡排序方法.这种方法的基本思想是,将待排序的元素看作是竖着排列的“气泡”,较小的元素比较轻,从而要往上浮.在冒泡排序算法中我们要对这个“气泡”序列处理若干遍.所谓一遍处理,就是自底向上检查一遍这个序列,并时刻注意两个相邻的元素的顺序是否正确.如果发现两个相邻元素的顺序不对,即“轻”的元素在下面,就交换它们的位置.显然,处理一遍之后,“最轻”的元素就浮到了最高位置:处理二遍之后,“次轻”的元素就浮到了次高位

经典排序算法(动图演示)

算法概述 0.1 算法分类 十种常见排序算法可以分为两大类: 非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序. 线性时间非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此称为线性时间非比较类排序. 0.2 算法复杂度 0.3 相关概念 稳定:如果a原本在b前面,而a=b,排序之后a仍然在b的前面. 不稳定:如果a原本在b的前面,而a=b,排序之后 a 可能会出现在 b

【转】十大经典排序算法

[转]十大经典排序算法:https://www.cnblogs.com/onepixel/articles/7674659.html 0.算法概述 0.1 算法分类 十种常见排序算法可以分为两大类: 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序. 非比较类排序:不通过比较来决定元素间的相对次序,它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序. 0.2 算法复杂度 0.3 相关概念 稳定:如果

七种经典排序算法最全攻略

经典排序算法在面试中占有很大的比重,也是基础.包括冒泡排序,插入排序,选择排序,希尔排序,归并排序,快速排序,堆排序.希望能帮助到有需要的同学.全部程序采用JAVA实现. 本篇博客所有排序实现均默认从小到大. 一.冒泡排序 BubbleSort 介绍: 冒泡排序的原理非常简单,它重复地走访过要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来. 步骤: 比较相邻的元素.如果第一个比第二个大,就交换他们两个. 对第0个到第n-1个数据做同样的工作.这时,最大的数就"浮"到了

常见经典排序算法学习总结,附算法原理及实现代码(插入、shell、冒泡、选择、归并、快排等)

博主在学习过程中深感基础的重要,经典排序算法是数据结构与算法学习过程中重要的一环,这里对笔试面试最常涉及到的7种排序算法(包括插入排序.希尔排序.选择排序.冒泡排序.快速排序.堆排序.归并排序)进行了详解.每一种算法都有基本介绍.算法原理分析.算法代码. 转载请注明出处:http://blog.csdn.net/lsh_2013/article/details/47280135 插入排序 1)算法简介 插入排序(Insertion Sort)的算法描述是一种简单直观的排序算法.它的工作原理是通过

C/C++中的经典排序算法总结

C/C++中的经典排序算法总结 在C/C++中,有一些经典的排序算法,例如:冒泡排序.鸡尾酒排序或双向冒泡排序(改进的冒泡排序).选择排序.直接插入排序.归并排序.快速排序.希尔排序和堆排序等等.下面对这些排序算法进行一一解析并给出示例代码以共享之. 1.冒泡排序 冒泡排序是最基本的排序算法,之所以称之为冒泡排序是因为在冒泡排序的过程中总是大数往前放,小数往后放,相当于气泡上升. 冒泡排序的基本原理是:依次比较相邻的两个数,将大数放在前面,小数放在后面. 影响冒泡排序算法性能的主要部分就是循环和

经典排序算法 - 圈排序Cycle Sort

经典排序算法 - Cycle Sort Cycle sort的思想与计数排序太像了,理解了基数排序再看这个会有很大的帮助, 圈排序与计数排序的区别在于圈排序只给那些需要计数的数字计数,先看完文章吧,看完再回来理解这一句话 所谓的圈的定义,我只能想到用例子来说明,实在不好描述 待排数组[ 6 2 4 1 5 9 ] 排完序后[ 1 2 4 5 6 9 ] 数组索引[ 0 1 2 3 4 5 ] 第一部分 第一步,我们现在来观察待排数组和排完后的结果,以及待排数组的索引,可以发现 排完序后的6应该出

经典排序算法 - 图书馆排序(Library Sort)

经典排序算法 - 图书馆排序(Library Sort) 思路简介,大概意思是说,排列图书时,如果在每本书之间留一定的空隙,那么在进行插入时就有可能会少移动一些书,说白了就是在插入排序的基础上,给书与书之间留一定的空隙,这个空隙越大,需要移动的书就越少,这是它的思路,用空间换时间 看红线标的那句话知道,这个空隙留多大,你自己定 图书馆排序的关键是分配空间,分配完空间后直接使用插入排序即可 进行空间分配的过程 这个我实在是找不到相关的资料,没准就是平均分配嘞 进行插入排序的过程 举例待排数组[ 0

经典排序算法的零基础通俗讲解

一.几种经典排序算法的排序过程及时间复杂度 冒泡:时间复杂度O(n²) 第一个数和第二个数比较,大的放在后边,然后第二个数再和第三个数比较,大的放在后面.以此类推. 选择:时间复杂度O(n²): 0到n-1里找最小值放位置0上:1到n-1里找最小值放位置1上 以此类推 插入:时间复杂度O(n²) 一个数 和他前边的数比较 如果大于那个数就放在那个数后面 如果比那个数小就一直往前比较 直到比某个数大就放在那个数后面.如果都没有就放在首位置. 时间复杂度:O(N*logN) : 归并排序,快速排序,