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

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

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

一、效率方面

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

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

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

二、内存消耗方面

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

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

原地排序 = O(1)

三、算法稳定性

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

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

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

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

1、冒泡排序

图例演示

> C#

          //排序 — 冒泡排序
private static void BubbleSort(int[] source)
{
if (source.Length <= )
return; bool isChanged = false;
for (int i = ; i < source.Length; i++)
{
for (int j = ; j < source.Length - i - ; j++)
{
var left = source[j];
var right = source[j + ];
Console.WriteLine("【比较】");
if (left <= right)
continue; source[j] = right;
source[j + ] = left;
isChanged = true;
Console.WriteLine("{交换}");
}
if (!isChanged)
break;
}
Printf(source);
}

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#

         //排序 — 插入排序
private static void InsertionSort(int[] source)
{
if (source == null || source.Length <= )
return; for (int i = ; i < source.Length; i++)
{// 未排序区
var sorting = source[i];
int j = i - ; for (; j >= ; j--)
{// 已排序区 // 比较
if (sorting >= source[j])
{
break;
} // 后移
source[j + 1] = source[j];
} // 入坑
source[j + 1] = sorting;
}
Printf(source);
}

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 <= )
return; for (int i = ; i < source.Length - ; i++)
{// 已排序
var minIndex = i; for (int j = i+; 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 数组

分别执行时间如下:

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

  1. Python实现十大经典排序算法(史上最简单)。

    十大排序算法(Python实现)一. 算法介绍及相关概念解读 算法分类十种常见排序算法可以分为两大类: 非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn), ...

  2. Python实现十大经典排序算法(史上最简单)

    十大排序算法(Python实现)一. 算法介绍及相关概念解读 算法分类十种常见排序算法可以分为两大类: 非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn), ...

  3. 十大经典排序算法+sort排序

    本文转自:十大经典排序算法,其中有动图+代码详解,本文简单介绍+个人理解. 排序算法 经典的算法问题,也是面试过程中经常被问到的问题.排序算法简单分类如下: 这些排序算法的时间复杂度等参数如下: 其中 ...

  4. 十大经典排序算法(Javascript实现)

    前言 总括: 本文结合动图详细讲述了十大经典排序算法用Javascript实现的过程. 原文博客地址:十大经典排序算法 公众号:「菜鸟学前端」,回复「666」,获取一揽子前端技术书籍 人生有情泪沾衣, ...

  5. 七种常见经典排序算法总结(C++实现)

    排序算法是非常常见也非常基础的算法,以至于大部分情况下它们都被集成到了语言的辅助库中.排序算法虽然已经可以很方便的使用,但是理解排序算法可以帮助我们找到解题的方向. 1. 冒泡排序 (Bubble S ...

  6. 十大经典排序算法(java实现、配图解,附源码)

    前言: 本文章主要是讲解我个人在学习Java开发环境的排序算法时做的一些准备,以及个人的心得体会,汇集成本篇文章,作为自己对排序算法理解的总结与笔记. 内容主要是关于十大经典排序算法的简介.原理.动静 ...

  7. 经典排序算法总结与实现 ---python

    原文:http://wuchong.me/blog/2014/02/09/algorithm-sort-summary/ 经典排序算法在面试中占有很大的比重,也是基础,为了未雨绸缪,在寒假里整理并用P ...

  8. 经典排序算法及python实现

    今天我们来谈谈几种经典排序算法,然后用python来实现,最后通过数据来比较几个算法时间 选择排序 选择排序(Selection sort)是一种简单直观的排序算法.它的工作原理是每一次从待排序的数据 ...

  9. C# 经典排序算法大全

    C# 经典排序算法大全 选择排序 using System; using System.Collections.Generic; using System.Linq; using System.Tex ...

随机推荐

  1. windows下使用 Secure Shell Client工具操作linux常用命令

    如果项目部署在linux系统上,而我们使用的是windows系统,那我们可以使用Secure Shell软件进行操作,那怎么使用它来操作tomcat呢? 1.  cd /usr/share/apach ...

  2. 第四天 Java语言基础

    一.函数的概念 1)什么函数 函数就是定义在类中的具有特定功能的一段独立小程序,并能被多次使用. 2)问题引入 在昨天讲述使用循环嵌套画出矩形.但有问题,每次要画矩形都要写很多重复性的代码,能不能将这 ...

  3. in_flight_pqueue.go

    // right child         }         if (*pq)[j].pri >= (*pq)[i].pri {             break         }    ...

  4. nsq源码阅读笔记之nsqd(四)——Channel

    与Channel相关的代码主要位于nsqd/channel.go, nsqd/nsqd.go中. Channel与Topic的关系 Channel是消费者订阅特定Topic的一种抽象.对于发往Topi ...

  5. synchronized和volatile简介

    简介 volatile是一个变量修饰符,而synchronized是一个方法或块的修饰符.所以我们使用这两种关键字来指定三种简单的存取变量的方式. 2345678 int i1;int geti1() ...

  6. LCA 各种神奇的LCA优化方法

    LCA(Least Common Ancestors) 树上问题的一种. 朴素lca很简单啦,我就不多说了,时间复杂度n^2 1.倍增LCA 时间复杂度 nlongn+klogn 其实是一种基于朴素l ...

  7. GraphQL 入门介绍

    写在前面 GraphQL是一种新的API标准,它提供了一种更高效.强大和灵活的数据提供方式.它是由Facebook开发和开源,目前由来自世界各地的大公司和个人维护.GraphQL本质上是一种基于api ...

  8. ReentrantLock之非公平锁源码分析

    本文分析的ReentrantLock所对应的Java版本为JDK8. 在阅读本文前,读者应该知道什么是CAS.自旋. 由于ReentrantLock的公平锁和非公平锁中有许多共同代码,本文只会对这两种 ...

  9. 带着新人看java虚拟机07(多线程篇)

    这一篇说一下比较枯燥的东西,为什么说枯燥呢,因为我写这都感觉很无聊,无非就是几个阻塞线程的方法和唤醒线程的方法... 1.线程中断 首先我们说一说怎么使得一个正在运行中的线程进入阻塞状态,这也叫做线程 ...

  10. 系统的讲解 - PHP WEB 安全防御

    目录 常见漏洞 SQL注入攻击 XSS攻击 SSRF攻击 CSRF攻击 文件上传漏洞 信息泄露 越权 设计缺陷 小结 常见漏洞 看到上图的漏洞是不是特别熟悉,如果不进行及时防御,就会产生蝴蝶效应. 往 ...