算法系列(四)排序算法下篇--如何超越排序算法下界

概述

算法系列(四)排序算法中篇--归并排序和快速排序一文中,我们介绍了归并排序和快速排序,最坏的情况下,最快的排序算法的时间复杂度是O(nlogn),是否有更好的算法呢?到目前为止,没有特殊的规则,O(nlogn)已经是最好的排序算法了,也就是说通用排序算法的时间复杂度下界就是O(nlogn)。如果限定一些规则,是可以打破这个下界的。下面说一下尽在O(n)时间内就能实现对数组排序的算法。

基于排序的规则

基于什么样的规则才能突破排序的下界呢?我们需要分析一下排序消耗的时间。排序需要遍历,比较,交换。能否省略其中的一些步骤呢?这就是要定义的规则,通过规则减少排序步骤。下面举一个最简单的例子。

一组待排序的元素仅有1和2,没有其它值,对这组数进行排序。

输入A0,A1,A2,A3......An-1,Ai为1或者2

排序步骤

1、令k=0

2、令i从0到n-1依次取值,如果A[i]=1,k自增1

3、令i从0到k-1依次取值,将A[i]赋值为1

4、令i从k到n-1依次取值,将A[i]赋值为2

这样我们完成了排序,花费的时间为O(n)

之前我们所说的算法都是通过比较元素对来确定顺序,那种排序叫做比较排序。凡是比较排序,通用下界为O(nlogn)

刚才所说的简单例子,是一个简单计数排序。下面详细说明一下

使用基数排序超越排序下界

简单例子每个元素仅有两种可能的取值,扩展一下,如果每个元素有m个不同取值,只要取值是m个连续整数之内的整数,算法是通用的。

首先,通过计算出有多少个元素的排序关键字等于某个值,随后就能就算出有多少个元素的排序关键字小于每个可能的排序。

基本思想

计数排序的基本思想是对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数(此处并非比较各元素的大小,而是通过对元素值的计数和计数值的累加来确定)。一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。

计数排序算法详细描述

该算法需要三个基本方法

COUNT-KEY-EQUAL(A,n,m)

输入 A 一个数组,

n 数组A中的元素个数

m数组A中元素的取值范围

输出一个数组equal[0......m],是equal[j]等于数组A中元素值为j的元素个数

1、创建一个新数组equal[0......m]

2、令equal数组每个元素都为0

3、i从0到n-1依次取值,每次将equal[A[i]]的值自增1

4、返回equal

COUNT-KEY-LESS(equal,m)

输入值 COUNT-KEY-EQUAL方法对应的值equal,m

输出一个数组less[0......m],less[j]=equal[0]+equal[1]+......+equal[j-1]

1、创建一个新数组less[0...m]

2、令less[0]=0

3、j从1取到m,less[j]=less[j-1]+equal[j-1](这是普通的迭代算法)

4、返回less

REARRANGE(A,less,n,m)

输入 COUNT-KEY-EQUAL COUNT-KEY-LESS方法对应的A,less,n,m

输出 数组B,B中包含A中所有元素,并且已经排好序

1、创建新数组B[0...n-1],next[0.....m]

2、j从0到m依次取值

令next[j]=less[j]+1

3、令i从0到n-1依次取值

key=A[i];index=next[key],B[index]=A[i],next[key]++

4、返回数组B

代码实现

进行了逻辑整合,基本思路相同

package com.algorithm.sort;

/**
 * 计数排序
 *
 * @author chao
 *
 */
public class CountSort {

	public static void main(String[] args) {
		int[] num = { 1, 1 };
		sort(num);
		for (int i = 0; i < num.length; i++)
			System.out.print(num[i] + " ");
	}

	/**
	 * 计数排序
	 *
	 * @param num
	 */
	public static void sort(int[] num) {
		int len = num.length;
		int[] orign = new int[len];
		int max = 0;// 我们只对正整数排序
		for (int i = 0; i < len; i++) {
			orign[i] = num[i];
			if (num[i] > max) {
				max = num[i];
			}
		}
		max = max + 1;
		int[] count = new int[max];
		for (int i = 0; i < max; i++) {
			count[i] = 0;
		}
		for (int i = 0; i < len; i++) {
			count[num[i]]++;
		}
		int t1, t2;
		t1 = count[1];
		count[0] = count[1] = 0;
		for (int i = 2; i < max; i++) {
			t2 = count[i];
			count[i] = t1 + count[i - 1];
			t1 = t2;
		}
		int key, index;
		for (int i = 0; i < len; i++) {
			key = orign[i];
			index = count[key];
			num[index] = orign[i];
			count[key]++;
		}
	}
}

复杂度分析

计数排序的复杂性为O(n),但是有空间代价,如果最大数很大的话,空间代价非常大。

还有一种排序叫做基数排序,是基于计数排序的,有约束条件,时间复杂度为O(n)

桶排序,基数排序跟计数排序类似,不再详细说明,堆排序(很常用,在树形算法分析中会再说明)

代码实现可以看github,地址https://github.com/robertjc/simplealgorithm

github代码也在不断完善中,有些地方可能有问题,还请多指教

欢迎扫描二维码,关注公众账号

时间: 2024-10-05 06:25:31

算法系列(四)排序算法下篇--如何超越排序算法下界的相关文章

数据结构与算法系列四(单链表)

1.引子 1.1.为什么要学习数据结构与算法? 有人说,数据结构与算法,计算机网络,与操作系统都一样,脱离日常开发,除了面试这辈子可能都用不到呀! 有人说,我是做业务开发的,只要熟练API,熟练框架,熟练各种中间件,写的代码不也能“飞”起来吗? 于是问题来了:为什么还要学习数据结构与算法呢? #理由一: 面试的时候,千万不要被数据结构与算法拖了后腿 #理由二: 你真的愿意做一辈子CRUD Boy吗 #理由三: 不想写出开源框架,中间件的工程师,不是好厨子 1.2.如何系统化学习数据结构与算法?

算法(第四版)之并查集(union-find算法)

开个新坑, 准备学习算法(第四版), 并把上面学到的东西写成博客, 毕竟以前也学过一点算法, 但效果甚微 并查集, 在这本书的第一章1.5中叫做union-find算法, 但在其他地方这个叫做并查集,就是说一系列点的连通问题,比如, 我们有十个点, 分别记作0~9: 加入我们要把2和4连接起来怎么表示呢? 首先我们会想到,给所有的点标上一个号, 来代表他们的连通关系, 我们初始化这个数就是他们id本身: 如果我们要连接2和4, 就使得4的id为2: 之后要连接间隔点任意两个点, 就把它们和它们相

Javascript数组系列四之数组的转换与排序Sort方法

今天我们继续来介绍 Javascirpt 数组中的方法,也是数组系列的第四篇文章,因为数组的方法众多,每篇文章我们都对数组的每个方法都有比较细致的描述,只要你能够从中成长一点点,那我们的目的就达到了,学习是一个持续的,渐进的过程.每天进步一点点,最终会有大成就. 直接进入主题 数组的转换 我们在项目的开发过程中,数据类型之间的转换有着非常重要的作用,而数组转换成其他数据类型是我们常见的一种. toString 该方法是对数组转换成字符串,数组的每一个元素都会调用 「toString」方法 ,返回

深度解析(一)数据结构与算法系列目录

数据结构与算法系列 目录 最近抽空整理了"数据结构和算法"的相关文章.在整理过程中,对于每种数据结构和算法分别给出"C"."C++"和"Java"这三种语言的实现:实现语言虽不同,但原理如出一辙.因此,读者在了解和学习的过程中,择其一即可! 下面是整理数据数据和算法的目录表,对于每一种按照C/C++/Java进行了划分,方便查阅.若文章有错误或纰漏,请不吝指正.谢谢! 数据结构和算法目录表   C C++ Java 线性结构

算法系列15天速成——第一天 七大经典排序【上】

原文:算法系列15天速成--第一天 七大经典排序[上] 今天是开篇,得要吹一下算法,算法就好比程序开发中的利剑,所到之处,刀起头落. 针对现实中的排序问题,算法有七把利剑可以助你马道成功. 首先排序分为四种: 交换排序: 包括冒泡排序,快速排序. 选择排序: 包括直接选择排序,堆排序. 插入排序: 包括直接插入排序,希尔排序. 合并排序: 合并排序. 那么今天我们讲的就是交换排序,我们都知道,C#类库提供的排序是快排,为了让今天玩的有意思点, 我们设计算法来跟类库提供的快排较量较量.争取KO对手

三白话经典算法系列 Shell排序实现

山是包插入的精髓排序排序.这种方法,也被称为窄增量排序,因为DL.Shell至1959提出命名. 该方法的基本思想是:先将整个待排元素序列切割成若干个子序列(由相隔某个"增量"的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序. 由于直接插入排序在元素基本有序的情况下(接近最好情况),效率是非常高的,因此希尔排序在时间效率上比前两种方法有较大提高. 以n=10的一个数组49, 38, 65, 97

白话经典算法系列之三 希尔排序的实现

分类: 白话经典算法系列 2011-08-08 11:41 47406人阅读 评论(46) 收藏 举报 算法shell优化c 希尔排序的实质就是分组插入排序,该方法又称缩小增量排序,因DL.Shell于1959年提出而得名. 该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的 元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序.因为 直接插入排序在元素基本有序的情况下(接近最好情

排序算法系列——八大排序算法对比分析

本系列最后一篇,综合分析下前面介绍的八种排序算法的效率,以及各自的适用情况. 下面先看看八种排序算法的时间复杂度表格: 图中八种排序被分成了两组,一组时间复杂度为O(n^2),另一组相对高效些. 下面先对第一组O(n^2)的四种排序算法进行对比,分别取数组长度为100,1000,10000,100000四个数量级,各个元素在0-10000000之间随机获取.下面看下结果的分析. 排序算法 长度=100 长度=1000 长度=10000 长度=100000 直接插入排序 535 2,198 135

算法系列15天速成——第三天 七大经典排序【下】

原文:算法系列15天速成--第三天 七大经典排序[下] 今天跟大家聊聊最后三种排序: 直接插入排序,希尔排序和归并排序. 直接插入排序: 这种排序其实蛮好理解的,很现实的例子就是俺们斗地主,当我们抓到一手乱牌时,我们就要按照大小梳理扑克,30秒后, 扑克梳理完毕,4条3,5条s,哇塞......  回忆一下,俺们当时是怎么梳理的. 最左一张牌是3,第二张牌是5,第三张牌又是3,赶紧插到第一张牌后面去,第四张牌又是3,大喜,赶紧插到第二张后面去, 第五张牌又是3,狂喜,哈哈,一门炮就这样产生了.