zhangzeli / sorting_algorithm

算法

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

排序算法

常见的排序算法列表

选择排序

SelectionSort

不常用

算法描述

每一趟从待排序的记录中选出最小的元素,顺序放在已排好序的序列最后,直到全部记录排序完毕。
也就是:每一趟在n-i+1(i=1,2,…n-1)个记录中选取关键字最小的记录作为有序序列中第i个记录。
基于此**的算法主要有简单选择排序、树型选择排序和堆排序。(这里只介绍常用的简单选择排序)

效率分析

选择排序的时间复杂度:简单选择排序的比较次数与序列的初始排序无关。 假设待排序的序列有 N 个元素,
则比较次数永远都是N (N - 1) / 2。而移动次数与序列的初始排序有关。当序列正序时,移动次数最少,
为0。当序列反序时,移动次数最多,为3N (N - 1) /  2。所以,综上,简单排序的时间复杂度为 O(N2)

冒泡排序

BubbleSort

算法描述

👍 👍 👍

 比较两个相邻的元素,将值大的元素交换至右端。
冒泡排序的优点:每进行一趟排序,就会少比较一次,因为每进行一趟排序都会找出一个较大值。如上例:第一趟比较之后,排在最后的
一个数一定是最大的一个数,第二趟排序的时候,只需要比较除了最后一个数以外的其他的数,同样也能找出一个最大的数排在参与第二
趟比较的数后面,第三趟比较的时候,只需要比较除了最后两个数以外的其他的数,以此类推……也就是说,没进行一趟比较,每一趟少
比较一次,一定程度上减少了算法的量

效率分析

1.如果我们的数据正序,只需要走一趟即可完成排序。所需的比较次数C和记录移动次数M均达到最小值,即:Cmin=n-1;Mmin=0;所以,冒泡排序最好的时间复杂度为O(n)。

2.如果很不幸我们的数据是反序的,则需要进行n-1趟排序。每趟排序要进行n-i次比较(1≤i≤n-1),且每次比较都必须移动记录三次来达到交换记录位置。在这种情况下,比较和移动次数均达到最大值:

常见的排序算法列表

插入排序

InsertionSort

样本小且基本有序的时候效率比较高

算法描述

假定n是数组的长度,
首先假设第一个元素被放置在正确的位置上,这样仅需从1-n-1范围内对剩余元素进行排序。对于每次遍历,从0-i-1范围内的元素已经被排好序,
每次遍历的任务是:通过扫描前面已排序的子列表,将位置i处的元素定位到从0到i的子列表之内的正确的位置上。
将arr[i]复制为一个名为target的临时元素。
向下扫描列表,比较这个目标值target与arr[i-1]、arr[i-2]的大小,依次类推。
这个比较过程在小于或等于目标值的第一个元素(arr[j])处停止,或者在列表开始处停止(j=0)。
在arr[i]小于前面任何已排序元素时,后一个条件(j=0)为真,
因此,这个元素会占用新排序子列表的第一个位置。
在扫描期间,大于目标值target的每个元素都会向右滑动一个位置(arr[j]=arr[j-1])。
一旦确定了正确位置j,
目标值target(即原始的arr[i])就会被复制到这个位置。
与选择排序不同的是,插入排序将数据向右滑动,并且不会执行交换

效率分析

稳定 
空间复杂度O(1) 
时间复杂度O(n2) 
最差情况:反序,需要移动n*(n-1)/2个元素 
最好情况:正序,不需要移动元素
数组在已排序或者是“近似排序”时,插入排序效率的最好情况运行时间为O(n);
插入排序最坏情况运行时间和平均情况运行时间都为O(n2)。
通常,插入排序呈现出二次排序算法中的最佳性能。
对于具有较少元素(如n<=15)的列表来说,二次算法十分有效。

堆排序

希尔排序

ShellSort

算法描述(改进的插入排序)

直接插入排序在在本身数量比较少的时候情况下效率很高,如果待排数的数量很多,其效率不是很理想。

  回想一下直接插入排序过程,排序过程中,我们可以设置一条线,左边是排好序的,右边则是一个一个等待排序,

如果最小的那个值在最右边,那么排这个最小值的时候,需要将所有元素向右边移动一位。

是否能够减少这样的移位呢?

  我们不希望它是一步一步的移动,而是大步大步的移动。希尔排序就被发明出来了,它也是当时打破效率

O(n2)的算法之一。希尔排序算法通过设置一个间隔,对同样间隔的数的集合进行插入排序,此数集合中的元素

移位的长度是以间隔的长度为准,这样就实现了大步位移。但是最后需要对元素集合进行一次直接插入排序,所以

最后的间隔一定是1。

有人问,这个间隔怎么确定,这是个数学难题,至今没有解答。但是通过大量的实验,还是有个经验值。

    举例来说,含有1000个数据项的数组可能先以364为增量,然后以121为增量,以40为增量,以13为增量,以4
    
为增量,最后以1为增量进行希尔排序。用来形成间隔的数列被称为间隔序列。这里所表示的间隔序列由Knuth提出,

此序列是很常用的。数列以逆向形式从1开始,通过递归表达式

希尔排序比插入排序快很多,它是基于什么原因呢? 🔥

当h值大的时候,数据项每一趟排序需要移动元素的个数很少,但数据项移动的距离很长。这是非常有效率的。 当h减小时,每一趟排序需要移动的元素的个数增多,但是此时数据项已经接近于它们排序后最终的位置,这对于插入排序可以更有效率。 正是这两种情况的结合才使希尔排序效率那么高。

效率分析

空间复杂度 O(1)

😁 😁 😁

时间复杂度计算非常复杂 采用不同序列时间复杂度不一样,最好的情况可以达到0(n^1.3)

归并排序

快速排序

ShellSort

算法**

快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。

(1) 分治法的基本**
     分治法的基本**是:将原问题分解为若干个规模更小但结构与原问题相似的子问题。递归地解这些子问题,然后将这些子问题的解组合为原问题的解。

(2)快速排序的基本**
     设当前待排序的无序区为R[low..high],利用分治法可将快速排序的基本**描述为:
概括来说为 挖坑填数+分治法

①分解: 
     在R[low..high]中任选一个记录作为基准(Pivot),以此基准将当前无序区划分为左、右两个较小的子区间R[low..pivotpos-1)和R[pivotpos+1..high],并使左边子区间中所有记录的关键字均小于等于基准记录(不妨记为pivot)的关键字pivot.key,右边的子区间中所有记录的关键字均大于等于pivot.key,而基准记录pivot则位于正确的位置(pivotpos)上,它无须参加后续的排序。
  注意:
     划分的关键是要求出基准记录所在的位置pivotpos。划分的结果可以简单地表示为(注意pivot=R[pivotpos]):
     R[low..pivotpos-1].keys≤R[pivotpos].key≤R[pivotpos+1..high].keys
                  其中low≤pivotpos≤high。
②求解: 
     通过递归调用快速排序对左、右子区间R[low..pivotpos-1]和R[pivotpos+1..high]快速排序。

③组合: 
     因为当"求解"步骤中的两个递归调用结束时,其左、右两个子区间已有序。对快速排序而言,"组合"步骤无须做什么,可看作是空操作。

1、先从数列中取出一个数作为基准数
2、分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边
3、再对左右区间重复第二步,直到各区间只有一个数

快速排序的时间复杂度和稳定性

快速排序稳定性
快速排序是不稳定的算法,它不满足稳定算法的定义。
算法稳定性 -- 假设在数列中存在a[i]=a[j],若在排序之前,a[i]在a[j]前面;并且排序之后,a[i]仍然在a[j]前面。则这个排序算法是稳定的!

快速排序时间复杂度
快速排序的时间复杂度在最坏情况下是O(N2),平均的时间复杂度是O(N*lgN)。
这句话很好理解:假设被排序的数列中有N个数。遍历一次的时间复杂度是O(N),需要遍历多少次呢?至少lg(N+1)次,最多N次。
(01) 为什么最少是lg(N+1)次?快速排序是采用的分治法进行遍历的,我们将它看作一棵二叉树,它需要遍历的次数就是二叉树的深度,而根据完全二叉树的定义,它的深度至少是lg(N+1)。因此,快速排序的遍历次数最少是lg(N+1)次。
(02) 为什么最多是N次?这个应该非常简单,还是将快速排序看作一棵二叉树,它的深度最大是N。因此,快读排序的遍历次数最多是N次。

桶排序

计数排序

算法**

某大型企业数万员工年龄排序
如何快速得知高考名词

基数排序

About

算法


Languages

Language:Java 100.0%