交换排序:冒泡排序vs快速排序
在开发的过程中, 经常会遇到集合排序, 那么一般情况下, 我们都是使用list.OrderBy()的方式来排序, 也无需关注到里面算法的实现是个什么样子. 正好这几天准备回顾一下数据结构与算法.
首先来了解一下, 排序大致可以分为哪几种:
交换排序: 包括冒泡排序,快速排序。
选择排序: 包括直接选择排序,堆排序。
插入排序: 包括直接插入排序,希尔排序。
合并排序: 合并排序。
List.OrderBy()采取的就是快速排序方式. 冒泡排序既然和它是同一种排序方式, 那么我们将他们比较一下看看. 看看哪一种排序更好一点.
一、示例(先看结果, 再讲思想)
static void Test()
{
//五次比较
for (int i = ; i <= ; i++)
{
List<int> list = new List<int>();
//插入2k个随机数到数组中
for (int j = ; j < ; j++)
{
Thread.Sleep();
list.Add(new Random((int)DateTime.Now.Ticks).Next(, ));
} Console.WriteLine("\n第" + i + "次比较:{0}...", string.Join(",", list.Take())); Stopwatch watch = new Stopwatch();
watch.Start();
var result = list.OrderBy(single => single).ToList();
watch.Stop();
Console.WriteLine("\n快速排序耗费时间:" + watch.ElapsedMilliseconds);
Console.WriteLine("输出前是十个数:" + string.Join(",", result.Take().ToList()));
watch.Start();
result = BubbleSort(list);
watch.Stop();
Console.WriteLine("\n冒泡排序耗费时间:" + watch.ElapsedMilliseconds);
Console.WriteLine("输出前是十个数:" + string.Join(",", result.Take().ToList()));
}
} //冒泡排序算法
static List<int> BubbleSort(List<int> list)
{
int temp;
int count = list.Count;
//第一层循环: 表明要比较的次数,比如list.count个数,肯定要比较count-1次
for (int i = ; i < count - ; i++)
{
//list.count-1:取数据最后一个数下标,
//j>i: 从后往前的的下标一定大于从前往后的下标,否则就超越了。
for (int j = count - ; j > i; j--)
{
//如果前面一个数大于后面一个数则交换
if (list[j - ] > list[j])
{
temp = list[j - ];
list[j - ] = list[j];
list[j] = temp;
}
}
}
return list;
}
这段代码是从参考链接里面拿的, 个人比较懒啊, 而且冒泡排序算法还是很简单的, 就不自己写了
从上面的结果来看, 快速排序比冒泡排序快的不是一星半点啊.
看到了神奇之处, 接下来就是深入了解一下, 算法的思想和实现.
二、思想及实现
1. 冒泡排序
首先来解释一下冒泡吧, 在水里面, 呼出一口气, 形成一个泡泡, 这个泡泡会在上升的过程中, 逐渐变大(水压越来越小导致的). 最后露出说面破掉了.
联系着这种思想, 可以想到, 冒泡排序, 应该就是让大的数, 逐渐往上升, 一直升到比它大的数前面, 破掉了.
根据这种思想, 就大致有一个过程在脑海中形成了, 来看一下代码: (下面的图还蛮形象的, 就偷过来了)
//冒泡排序算法
static List<int> BubbleSort1(List<int> list)
{
int temp;
int count = list.Count;
for (int i = ; i < count - ; i++)
{
for (int j = ; j < count; j++)
{
//如果前面一个数大于后面一个数则交换
if (list[j - ] > list[j])
{
temp = list[j - ];
list[j - ] = list[j];
list[j] = temp;
}
}
}
return list;
}
首先, 每一次循环, 我都能确定一个数的位置, 那么需要循环多少次呢? 最后一个数应该不需要再循环比较了吧, 也没数跟他比较了. 所以循环n-1次就行了.
接着, 这里面的每一次循环, 其实就是一个冒泡的过程了. 把开始的数与后面一位数进行比较, 如果大于后面的数, 就向后移一位. (其实想想, 这个也挺麻烦的, 为啥比较一次就要移动一次呢? 我不能找到他的位置, 才互换么? 起码减少了换的操作了)
我这里的代码与上面示例的稍有不同, 稍微注意一下.
来看一下这里的时间复杂度, 虽然外面只循环了n-1次, 里面也只循环了n-3次, 看似复杂度为(n-1)*(n-3), 但是如果n够大的话, -1或者-3甚至-100, 对最后的结果影响都是很小的.
按照最坏的情况算的话, 这里的冒泡排序的时间复杂度, 极限情况是O(n2), 那么他有没有理想情况呢? 好像没有啊, 就算这个数组已经排好序了, 好像程序还是要这样从头走到尾啊, 一点都没有少什么. 所以这里的平均复杂度, 也是 O(n2). 这么看来, 冒泡排序并不是一种理想的排序方式.
如果不能提供更好的排序方式的话, 还是老老实实的使用List.OrderBy的方式去排序吧.
2. 快速排序
快速排序算法其实也叫分治法, 其步骤大致可以分为这么几步:
1. 先从数列中取出一个数作为基准数Num(取得好的话, 是可以减少步骤的)
2. 分区, 将大于Num的数放在它的右边, 小于或等于它的数放在它的左边
3. 再对左右区间重复前两操作, 直到各个区间只有一个数为止.
从上面的文字可能还是不太好理解这个过程, 那么我用一张图片来描绘一下这个过程
经过一轮比较之后, 总感觉这里要递归啊. 牵涉到递归, 那么他的空间复杂度可能会比冒泡排序高一点.
既然一轮的过程已经清楚了, 那么就先写出一轮的代码好了
static int Division(List<int> list, int left, int right)
{
int baseNum = list[left]; while (left < right)
{
while (left < right && list[right] > baseNum)
{
right -= ;
} list[left] = list[right];
while (left < right && list[left] <= baseNum)
{
left += ;
} list[right] = list[left];
}
list[left] = baseNum;
return left;
}
这里的Left+=1和Right-=1都是有前提条件的, 前提条件为:Left < Right
接下来就比较简单了, 一个递归调用, 这个递归的思想是很简单的:
static void QuickSort(List<int> list, int left, int right)
{
if (left < right)
{
int i = Division(list, left, right); QuickSort(list, left, i - ); QuickSort(list, i + , right);
}
}
快速排序的复杂度:
最糟糕的情况下, 复杂度为: O(n2)
最优的情况下, 复杂度为: O(nlog2n)
其复杂度的计算和证明, 额, 我是给不出来了, 但是参考里面是有的.
最差情况下, 快速排序和冒泡排序的时间复杂度是一样的, 但是最优情况下, 冒泡排序是 n * n, 而快速排序的是 n * log2n,
如果n=16,
则冒泡是 16 * 16
快速排序是 16 * 4
可见, 只要你不是背到家, 都是比冒泡来的快的.
参考:
交换排序:冒泡排序vs快速排序的更多相关文章
- 八大排序方法汇总(选择排序,插入排序-简单插入排序、shell排序,交换排序-冒泡排序、快速排序、堆排序,归并排序,计数排序)
2013-08-22 14:55:33 八大排序方法汇总(选择排序-简单选择排序.堆排序,插入排序-简单插入排序.shell排序,交换排序-冒泡排序.快速排序,归并排序,计数排序). 插入排序还可以和 ...
- 常见排序算法总结:插入排序,希尔排序,冒泡排序,快速排序,简单选择排序以及java实现
今天来总结一下常用的内部排序算法.内部排序算法们需要掌握的知识点大概有:算法的原理,算法的编码实现,算法的时空复杂度的计算和记忆,何时出现最差时间复杂度,以及是否稳定,何时不稳定. 首先来总结下常用内 ...
- 牛客网Java刷题知识点之插入排序(直接插入排序和希尔排序)、选择排序(直接选择排序和堆排序)、冒泡排序、快速排序、归并排序和基数排序(博主推荐)
不多说,直接上干货! 插入排序包括直接插入排序.希尔排序. 1.直接插入排序: 如何写成代码: 首先设定插入次数,即循环次数,for(int i=1;i<length;i++),1个数的那次不用 ...
- [C#][算法] 用菜鸟的思维学习算法 -- 马桶排序、冒泡排序和快速排序
用菜鸟的思维学习算法 -- 马桶排序.冒泡排序和快速排序 [博主]反骨仔 [来源]http://www.cnblogs.com/liqingwen/p/4994261.html 目录 马桶排序(令人 ...
- PHP描述冒泡排序和快速排序算法
使用PHP描述冒泡排序和快速排序算法,对象可以是一个数组.使用PHP描述顺序查找和二分查找(也叫做折半查找)算法,顺序查找必须考虑效率,对象可以是一个有序数组.写一个二维数组排序算法函数,能够具有通用 ...
- JavaScript实现冒泡排序、快速排序、插入排序
JavaScript实现冒泡排序.快速排序.插入排序 时间:2014-01-09 18:05:51 来源: 作者:胡晗 冒泡排序的基本思想:所谓冒泡就是泡泡一个一个往上冒,让体积最轻的泡泡浮在最上 ...
- python 实现冒泡排序与快速排序 遇到的错误与问题
今天看了兄弟连php里面的冒泡排序与快速排序,想了下应该可以用python实现. 冒泡排序函数: def mysort(x): len1 = len(x) for i in range(len1-1, ...
- js 冒泡排序与快速排序
刚好今晚看了js的冒泡排序跟快速排序,趁着还没忘记先记下来. 1. 冒泡排序:遍历数组,每个元素都与后一个元素比较,如果大于下一个元素,则两个元素位置调换.否则的话当前元素再与下下个元素比较,一直到 ...
- 【PHP面试题】通俗易懂的两个面试必问的排序算法讲解:冒泡排序和快速排序
又到了金三银四找工作的时间,相信很多开发者都在找工作或者准备着找工作了.一般应对面试,我们无可厚非的去刷下面试题.对于PHPer来说,除了要熟悉自己所做的项目,还有懂的基本的算法.下面来分享下PHP面 ...
随机推荐
- android-基础编程-TextView
TextView是简单而又复杂的控件,简单是使用上,复杂是源代码研究.基础编程这里只讲是如何使用. TextView主要分为两种使用方法,一种是xml中不带span的textview,另外一种是Spa ...
- kepware http接口 swift
读取某变量的值 import Foundation let headers = [ "Connection": "keep-alive", "Cach ...
- Greys--JVM异常诊断工具
https://github.com/oldmanpushcart/greys-anatomy/wiki/greys-pdf 一.简介 我们平时在线上或者开发中会遇到各种性能.功能等问题,在运行过程中 ...
- 怎样去写线程安全的代码(Java)
使用多线程就可能会存在线程安全的问题.很多 java 程序员对写多线程都很挣扎,或者仅仅理解那些是线程安全的代码,那些不是.这篇文章我并不是详述线程安全,详述同步机制的文章,相反我只是用一个简单的非线 ...
- CSS 基础 例子 定位及z-index
position 属性指定了元素的定位类型. position 属性的四个值: static 不设置默认为该值,即没有定位,元素出现在正常的流中.不能使用top,bottom,left,righ ...
- [mobile]监听手机mobile上面软键盘的回车[enter]事件
$(document).keypress(function(e) { if(e.which == 13) { if(!$(".qaSearchInput").val()) { Hn ...
- rsync+sersync+inotify实现服务器间文件同步之一
rsync+sersync+inotify实现服务器间文件同步之一:rsync安装配置 2013年12月14日 ⁄ Linux管理, 服务器集群技术 ⁄ 共 4925字 ⁄ rsync+sersync ...
- IntelliJ IDEA通过maven构建ssm项目找不到mapper
idea运行ssm项目的时候一直报错 org.apache.ibatis.binding.BindingException: Invalid bound statement (not found) 原 ...
- 合成的默认构造函数定义为delete的一种情况(针对C++11标准)
1. 默认初始化 如果定义变量时没有指定初值,则变量会被默认初始化,此时变量被赋予了"默认值". 对于类类型的变量来说,初始化都是依靠构造函数来完成的.因此,即使定义某个类的变量( ...
- mysql rand
在where语句中,rand有时候会出现多条记录