一:何为排序:
假设含 n 个记录的序列为{ R1, R2, …, Rn },其相应的关键字序列为 { K1, K2, …, Kn }
这些关键字相互之间可以进行比较
即在它们之间存在着这样一个关系 : Kp1≤Kp2≤…≤Kpn
按此固有关系将上式记录序列重新排列为 { Rp1, Rp2, …, Rpn } 的操作称作排序。
将一系列数据 从小到大 或 从大到小 这样有规律的排序
二:算法稳定性
设 Ki = Kj (1≤i≤n, 1≤j≤n, i≠j ),且在排序前的序列中 Ri 领先于 Rj(即 i < j )。
若在排序后的序列中 Ri 仍领先于 Rj,则称所用的排序方法是稳定的;
反之,则称所用的排序方法是不稳定的。
常见排序算法如下
😊简单排序
一:选择排序
基本思想:
首先通过 n –1 次关键字比较,从 n 个记录中找出关键字最小的记录,将它与第一个记录交换。
再通过 n –2 次比较,从剩余的 n –1 个记录中找出关键字次小的记录,将它与第二个记录交换。
重复上述操作,共进行 n –1 趟排序后,排序结束
最简单 且 最没用的排序算法( 时间复杂度为 O(N²) 额外空间复杂度为O(1) 不稳定)
public class SelectionSort {
public static void selectSort(int[] arrs) {
long startTime = System.nanoTime();//获取开始时间
//判断数组是否为空 或者只有一个元素
if(arrs.length <2 | arrs == null) {
return ;
}
//循环数组 比较大小 这里i< length-1 下面的j才不会发生数组越界 而且外层循环只需遍历length-1次即可
for(int i = 0; i <arrs.length-1; i ++) {
int minIndex = i;
for(int j = i+1; j < arrs.length; j++) {
minIndex = arrs[j] < arrs[minIndex] ? j : minIndex;
}
if(i != minIndex) {
swap(arrs,i,minIndex);
}
}
long endTime = System.nanoTime();//获取结束时间
System.out.println("程序共运行了 "+(endTime-startTime) +"纳秒");
}
public static void swap(int[] arrs,int i ,int minIndex) {
int temp = arrs[i];
arrs[i] = arrs[minIndex];
arrs[minIndex] = temp;
}
//测试demo
public static void main(String[] args) {
int[] arrs = {5,2,3,1};
selectSort(arrs);
for(int x:arrs) {
System.out.print(x+"\t");
}
}
}
二:冒泡排序
基本思想:
重复地走访过要排序的元素列,依次比较两个相邻的元素,如果顺序(如从大到小)错误就把他们交换过来
实质:把小(大)的元素往前(后)调
时间复杂度为O(N²) 额外空间复杂度为O(1) 稳定
public class BubbleSort {
public static void bubbleSort(int[] nums) {
//如果数组长度为0 或者小于2 则直接返回
if(nums.length <2 | nums == null) {
return;
}
//外层循环每次 得到一个最大值
for(int i = nums.length-1; i>0; i--) {
for(int y = 0; y<i; y++) {
if(nums[y] > nums[y+1]) {
//使用如下位运算的前提是 俩变量不是同一块内存
nums[y] = nums[y] ^ nums[y+1];
nums[y+1] = nums[y] ^ nums[y+1];
nums[y] = nums[y] ^ nums[y+1];
}
}
}
}
//测试冒泡排序
public static void main(String[] args) {
int[] arr = {2,4,1,6,7,5};
bubbleSort(arr);
for(int x : arr) {
System.out.print(x+"\t");
}
}
}
三:插入排序
基本思想:
先将序列中第 1 个记录看成是一个有序子序列,
然后从第 2 个记录开始,逐个进行插入,直至整个序列有序
对于基本有序的数组最好用,稳定。时间复杂度为O(N²) 额外空间复杂度为O(1)
public class InsertionSort {
public static int[] insertionSort(int[] arrays) {
//如果数组为空 或 数组长度小于2 则直接返回
if(arrays.length <2 | arrays == null) {
return arrays;
}
//思路 从第一个数开始 做到0~0有序、0~1有序、0~2有序...
for(int i = 1; i<arrays.length; i++) {//这一步是保证 0~i做到有序
//这步 判断是否需要交换 因为要判断i是否需要交换 而i-1又是肯定有序的
for(int y = i; y>0 ;y--) {
if(arrays[y]<arrays[y-1]) {
//两者交换
arrays[y] = arrays[y] ^ arrays[y-1];
arrays[y-1] = arrays[y] ^ arrays[y-1];
arrays[y] = arrays[y] ^ arrays[y-1];
}
}
}
return arrays;
}
public static void main(String[] args) {
int[] arrays = {1,5,4,2,6,4,2};
arrays = insertionSort(arrays);
for(int x: arrays) {
System.out.print(x+"\t");
}
}
}
😗简单排序算法总结
相同点:时间复杂度为O(N²) 额外空间复杂度为O(1)
冒泡排序:基本不用,太慢
选择排序:基本不用,不稳
插入排序:样本小且基本有序的时候效率比较高
😉高级排序
四:堆排序
基本思想:
堆排序(英语:HeapSort)是指利用堆这种数据结构所设计的一种排序算法。
- 堆是一个近似完全二叉树的结构,并同时满足堆积的性质:
- 即子结点的键值或索引总是小于(或者大于)它的父结点
- 时间复杂度:O(N*logN)
- 稳定性:不稳定
1、堆结构就是用数组实现的完全二叉树结构
2、完全二叉树中如果每棵子树的最大值都在顶部就是大根堆
3、完全二叉树中如果每棵子树的最小值都在顶部就是小根堆
4、堆结构的heapInsert 和 heapify 操作
5、堆结构的增大和减少
6、优先级队列结构,就是堆结构
堆排序
1、先让整个数组都变成大根堆结构,建立堆的过程:
- 从上到下的方法,时间复杂度为 O( N * logN )
- 从下到上的方法,时间复杂度为 O( N )
2、把堆的最大值和堆末尾的值交换,然后减少堆的大小之后(抛弃尾部,即抛弃最大值),再去调整堆,一直周而复始,时间复杂度为 O( N * logN )
3、堆的大小减小成0之后,排序完成
public class HeapSort {
// 堆排序方法实现
public static void heapSort(int[] arr) {
//如果数组为空 或 长度小于2 则直接return
if(arr == null || arr.length < 2) {
return;
}
// 第一步 将整个数组(原来就有值) 变成 大根堆(根据情况 对数组中内部的值进行交换)
// for(int i = 0; i < arr.length; i++) { // O(N)
// heapInsert(arr , i ); // O(logN)
// }
/*
* 或使用 heapify方法使数组变成大根堆
* 从树的右叶子结点 依次 heapify 从右往左、从下往上 依次成为大根堆
* 这种方式 稍快一些 但是 总的时间复杂度还是一样的
*/
for( int i = arr.length-1; i>=0;i--) {
heapify(arr,i,arr.length);
}
// 第二步
int heapSize = arr.length;
//将最大值 和 大根堆尾部进行交换 并 将尾部抛弃
swap(arr, 0 , --heapSize);
// 下面是让剩下的 树 依然成为大根堆的步骤
// 当 heapSize==0 时 则 数组已升序排好
while( heapSize > 0 ) {
/*
由于将 树的 头结点 与 尾部进行了交换 所以 该数可能已经不是大根堆
对该树进行调整 重新调整为大根堆
(while循环 -> 让index跟 它下面的最大值(左孩子或右孩子)进行交换 最后又是大根堆 )
*/
heapify(arr , 0 , heapSize); //O(logN)
//继续将 树的 头结点 和尾部进行交换
swap(arr, 0 , --heapSize); // O(1)
}
// 这一套流程下来 数组的次小值依次被抛弃 最大值在数组尾部 形成升序排列
}
/*
* 某个数现在处在 index位置,往上继续移动 形成大根堆
*/
public static void heapInsert(int[] arr,int index) {
/*
* 如果当前数大于父结点 则交换
* 判断条件:
* 1:index 不比 父结点的值大 结束循环
* 2:index已经跑到 整棵树的头结点 结束循环
*/
while(arr[index] > arr[(index-1)/2]) {
swap(arr,index,(index-1)/2);
//index往上跑
index = (index - 1)/2;
}
}
/* 重新调整树 为 大根堆的方法
*
* 某个数在index位置,能否往下移动
* 一般 index 为 0 重新调整树 为 大根堆
*/
public static void heapify(int[] arr , int index, int heapSize) {
int left = index*2 +1; //左孩子的下标
while(left < heapSize) { // 下方还有孩子 (如果没有左孩子 肯定没有右孩子)
//比较 两个孩子谁大 ,将下标给 largest
int largest = left +1 < heapSize && arr[left + 1] >arr[left] ? left +1 : left;
//将 父结点 和 刚刚比较出来的较大的孩子 进行比较
largest = arr[largest] > arr[index] ? largest : index;
//如果父结点的值依然大过子节点 直接break
if(largest == index) {
break;
}
//否则 将 父结点(值小) 与 子节点(值大) 进行交换
swap(arr,largest,index);
//将index的值往下
//index始终要 成为 是父结点的下标(以当前的旧子节点的下标,往下继续遍历)
index = largest;
//新的左孩子的下标 再重新判断是否存在这个左孩子
left = index *2 +1;
}
}
// 交换函数
public static void swap(int[] arr , int num1, int num2) {
if(num1 == num2) {
return;
}
arr[num1] = arr[num1] ^ arr[num2];
arr[num2] = arr[num1] ^ arr[num2];
arr[num1] = arr[num1] ^ arr[num2];
}
// 测试
public static void main(String[] args) {
int[] arr = {1,6,2,8,3,4,5,1,7,9};
heapSort(arr);
for(int x : arr) {
System.out.print(x+"\t");
}
}
}
五:希尔排序
基本思想:
希尔排序(Shell’s Sort)是插入排序的一种又称“缩小增量排序”(Diminishing Increment Sort),是直接插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。
希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止
改进的插入排序
特点:间隔大的时候 移动的次数比较少 间隔小的时候 移动的距离比较短
跳着排序 不稳定
时间复杂度为 O(n的1.3次方) 空间复杂度为 O(1)
public class ShellSort {
public static void shellSort(int[] arrs) {
// 如果数组长度小于2个 或者数组为空 则直接return
if(arrs.length <2 | arrs == null) {
return;
}
//间隔 使用Knuth序列求得
int h = 1;
while( h <= arrs.length/3) {
h = h*3+1;
}
for(int gap = h;gap >0 ;gap = (gap-1)/3) {
for(int i = gap; i < arrs.length; i++) {
for(int j = i; j>gap-1; j-=gap) {
//进行插入排序
if(arrs[j] < arrs[j-gap]) {
arrs[j] = arrs[j] ^ arrs[j-gap];
arrs[j-gap] = arrs[j] ^ arrs[j-gap];
arrs[j] = arrs[j] ^ arrs[j-gap];
}
}
}
}
}
public static void main(String[] args) {
int[] arrays = {1,5,4,2,6,4,2,3,8,7};
shellSort(arrays);
for(int x: arrays) {
System.out.print(x+"\t");
}
}
}
六:归并排序
基本思想:
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法
该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
将已有序的子序列合并,得到完全有序的序列
即 先使每个子序列有序,再使子序列段间有序。
若将两个有序表合并成一个有序表,称为二路归并
实质:将两个或两个以上的序列组合成一个新的有序表
就是一个简单的递归,左边排好序,右边排好序,让整体有序
时间复杂度 O(N*logN) ,额外空间复杂度 O(N)
public class MergeSort {
public static void sort(int[] arrs, int left, int right) {
//System.out.println("进入递归 left:"+left+" right:"+right);
if(left == right ) {
return;
}
int mid = left + ((right-left)>>1);//防止int太大溢出
sort(arrs,left,mid); //对左子分支进行递归
sort(arrs,mid + 1,right); //对右子分支进行递归
mergeSort(arrs,left,mid+1,right); //对刚刚递归完的左右子分支进行排序 不会进递归
}
public static void mergeSort(int[] arrs,int leftPtr , int rightPtr, int rightBound) {
//System.out.println("进入MergeSort left:"+leftPtr+" mid "+(rightPtr-1) +" right:"+rightBound);
//判断数组是否为空 或长度小于2
if(arrs == null || arrs.length < 2 ) {
return;
}
//把数组分成两部分进行排序
int mid = rightPtr-1; // 中间指针为右指针的前一个
int i = leftPtr; // 左指针
int j = rightPtr;// 右指针
int num = 0; //新数组的指针,从0开始
//新建一个长度一样的数组
int[] arrays = new int[rightBound-leftPtr+1];
while( i <= mid && j <= rightBound ) {
arrays[num++] = arrs[i]<=arrs[j] ? arrs[i++] : arrs[j++];
}
//考虑到可能有一部分数组全部拼接完成 但还剩下另一半数组(直接拼接)
while( i <= mid) {
arrays[num++] = arrs[i++];
}
while( j <= rightBound ) {
arrays[num++] = arrs[j++];
}
//排序完 直接重新修改旧数组的元素
for(int x = 0 ;x<arrays.length; x++) {
arrs[leftPtr + x] =arrays[x];
}
}
public static void main(String[] args) {
//数组只能奇数 且左右两边需有序 左4右3
// int[] arrs = {1,3,5,6,4,6,7};
int[] arrs = {5,2,4,6,8,1,5,6};
sort(arrs,0,arrs.length-1);
for(int x: arrs) {
System.out.print(x+"\t");
}
}
}
七:快速排序
基本思想:
任选一个记录,以它的关键字作为“基准”,凡关键字小于基准的记录均移至基准之前,凡关键字大于基准的记录均移至基准之后。
首先对无序的记录序列进行“一次划分”,之后分别对分割所得两个子序列“递归”进行一趟快速排序。
V1.0 时间复杂度O(N²)
在整个数组中 以最后一个数为基准 将前面的数按照 <=num | >num | num 排好
再将num与> num的最左边一个数做交换 变成 <=num | num | >num
然后让 <=num 和 >num 这两部分重复这个行为
V2.0 时间复杂度O(N²) 特点:适用于重复值对的情况
利用荷兰国旗问题,
在整个数组中 以最后一个数为基准 将前面的数按照 <num | ==num | >num | num 排好
再将 num与> num的最左边一个数做交换 变成 <num | ==num | >num
这样子每次就搞定了一批数据,比1.0版本稍快
V3.0 修改划分值(情况好的话 划分值应该在数组的数值中的中位数)
时间复杂度O(N*LogN)
解决办法 随机取 划分值
在整个数组中 随机取 划分值 交换到 数组的尾部
再将前面的数按照 <num | ==num | >num | num 排好
再将 num与 > num的最左边一个数做交换 变成 <num | ==num | >num
这样子每次就搞定了一批数据,每次num的值都是等概率事件
快速排序V3.0版本
//V3.0
public class QuickSort {
public static void quickSort(int[] arr) {
//如果数组长度小于2 或 为空 则直接return
if(arr == null || arr.length <2 ) {
return;
}
quickSort(arr,0,arr.length-1);
}
//arr[ L -> R 排好序]
public static void quickSort(int[] arr , int L ,int R) {
if(L < R) {
//随机等概率选一个位置和最后的位置上的数做交换
swap(arr,L+(int)(Math.random()*(R-L+1)),R);
int[] p = partition(arr,L,R); // 返回的数组长度一定为2
quickSort(arr,L,p[0]-1); // <区域
quickSort(arr,p[1]+1,R);// >区域
}
}
//交换方法
public static void swap(int[] arr,int num1 , int num2) {
int temp = 0;
temp = arr[num1];
arr[num1] = arr[num2];
arr[num2] = temp;
}
/*
* 这是一个处理arr[L....R] 的函数 分层函数
* 默认以最后一个数做划分 ,arr[R] 为P 划分成 <P ==P <P
* 返回等于区域( 左边界 、右边界),所以返回一个长度为2的数组res,res[0]和res[1]
*/
public static int[] partition(int[] arr, int L, int R) {
int less = L -1; // <区域的右边界
int more = R; // >区域的左边界
while(L < more) { // L表示当前数的位置 arr[R] 是划分值 说明L还没到大于区域还不能停止
if(arr[L] < arr[R]) { //当前数 < 划分值
swap(arr,++less,L++);
}else if(arr[L] > arr[R]) { //当前数 大于划分值
swap(arr,--more,L);
}else {
L++;
}
}
swap(arr,more,R);
return new int[] {less+1,more};
}
public static void main(String[] args) {
int[] arr = {1,6,2,4,8,1,5,3,4,8};
quickSort(arr);
for(int x: arr) {
System.out.print(x+"\t");
}
}
}
八:桶排序
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:
在额外空间充足的情况下,尽量增大桶的数量
使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中
同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要
桶排序的时间复杂度和空间复杂度都是O(n),并且桶排序是一种稳定的排序算法。
但是桶排序的性能并非是绝对稳定的,因为如果元素分布不均衡,比如说创建了5个桶,大多数元素都集中在了第2个桶,这样桶排序的时间复杂度就会退化为O(nlogn),而且还浪费了空间。
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
public class BucketSort {
public static double[] bucketSort(double[] array){
//得到数列的最大值和最小值,并计算出差值d
double max=array[0];
double min=array[0];
for (int i=1;i<array.length;i++){
if (array[i]>max){
max=array[i];
}
if (array[i]<min){
min=array[i];
}
}
double d=max-min;
//初始化桶
int bucketNum=array.length;
ArrayList<LinkedList<Double>> bucketList=new ArrayList<LinkedList<Double>>(bucketNum);
for (int i=0;i<bucketNum;i++){
bucketList.add(new LinkedList<Double>());
}
//遍历原始数组将每个元素放入桶中
for (int i=0;i<array.length;i++){
int num=(int)((array[i]-min)*(bucketNum-1)/d);
bucketList.get(num).add(array[i]);
}
//对每个桶内部进行排序
for(int i=0;i<bucketList.size();i++){
// 使用Collections.sort,其底层实现基于归并排序或归并排序的优化版本
Collections.sort(bucketList.get(i));
}
//输出全部元素
double[] sortedArray=new double[array.length];
int index=0;
for (LinkedList<Double> list:bucketList) {
for (double element:list){
sortedArray[index]=element;
index++;
}
}
return sortedArray;
}
public static void main(String[]args){
//排序的数组
double a[]={100,93,97,92,96,99,92,89,93,97,90,94,92,95};
double b[]=bucketSort(a);
for(double i:b){
System.out.print(i+" ");
}
}
}
九:计数排序
计数排序(Counting Sort)是一个非基于比较的排序算法,它的优势在于在对一定范围内的整数排序时,它的复杂度为Ο(n+k)(其中k是整数的范围),快于任何比较排序算法。当然这是一种牺牲空间换取时间的做法,而且当O(k)>O(nlog(n))的时候其效率反而不如基于比较的排序(基于比较的排序的时间复杂度在理论上的下限是O(nlog(n)), 如归并排序,堆排序),通过计数将时间复杂度降到了O(N)
基本思想:
对于给定的输入序列中的每一个元素x,确定该序列中值小于x的元素的个数(此处并非比较各元素的大小,而是通过对元素值的计数和计数值的累加来确定)。一旦有了这个信息,就可以将x直接存放到最终的输出序列的正确位置上。例如,如果输入序列中只有17个元素的值小于x的值,则x可以直接存放在输出序列的第18个位置上。当然,如果有多个元素具有相同的值时,我们不能将这些元素放在输出序列的同一个位置上,因此,上述方案还要作适当的修改
排序过程
第一步:找出数组中的最大值max、最小值min。
第二步:创建一个新数组count,其长度是max-min加1,其元素默认值都为0。
第三步:遍历原数组中的元素,以原数组中的元素作为count数组的索引,以原数组中的元素出现次数作为count数组的元素值
第四步:对count数组变形,新元素的值是前面元素累加之和的值,即count[i+1] = count[i+1] + count[i];
第五步:创建结果数组result,长度和原始数组一样。
第六步:遍历原始数组中的元素,当前元素A[j]减去最小值min,作为索引,在计数数组中找到对应的元素值count[A[j]-min],再将count[A[j]-min]的值减去1,就是A[j]在结果数组result中的位置,做完上述这些操作,count[A[j]-min]自减1。
public class CountSort{
public static int[] countSort(int[] A) {
// 找出数组A中的最大值、最小值
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
for (int num : A) {
max = Math.max(max, num);
min = Math.min(min, num);
}
// 初始化计数数组count
// 长度为最大值减最小值加1
//将数组长度定为max-min+1,即不仅要找出最大值,还要找出最小值,根据两者的差来确定计数数组的长度
int[] count = new int[max-min+1];
// 对计数数组各元素赋值
for (int num : A) {
// A中的元素要减去最小值,再作为新索引
count[num-min]++;
}
// 创建结果数组
int[] result = new int[A.length];
// 创建结果数组的起始索引
int index = 0;
// 遍历计数数组,将计数数组的索引填充到结果数组中
for (int i=0; i<count.length; i++) {
while (count[i]>0) {
// 再将减去的最小值补上
result[index++] = i+min;
count[i]--;
}
}
// 返回结果数组
return result;
}
public static void main(String[]args){
//排序的数组
int a[]={100,93,97,92,96,99,92,89,93,97,90,94,92,95};
int b[]=countSort(a);
for(int i:b){
System.out.print(i+" ");
}
}
}
计数排序结果
计数排序算法没有用到元素间的比较,它利用元素的实际值来确定它们在输出数组中的位置。因此,计数排序算法不是一个基于比较的排序算法,计数排序算法是一个稳定的排序算法。
虽然它可以将排序算法的时间复杂度降低到O(N),但是有两个前提需要满足:一是需要排序的元素必须是整数,二是排序元素的取值要在一定范围内,并且比较集中。只有这两个条件都满足,才能最大程度发挥计数排序的优势
十:基数排序
基数排序法是属于稳定性的排序
可参考教程
基本思想:
将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。
public class RadixSort {
public static void radixSort(int[] arr) {
if(arr == null || arr.length <2) {
return;
}
radixSort(arr,0,arr.length-1,maxbits(arr));
}
public static int maxbits(int[] arr) {
//最大值 为 系统最小
int max = Integer.MIN_VALUE;
// 遍历 求最大值
for(int i = 0; i< arr.length;i++) {
max = Math.max(max,arr[i]);
}
// 求 最大值有几个十进制位
int res = 0;
while(max != 0 ) {
res++;
max /= 10;
}
return res;
}
//arr[begin...end]排序 digit 表示这一批数据中 最大的值有几个十进制位
public static void radixSort(int[] arr,int L,int R,int digit) {
final int radix = 10;
int i = 0, j = 0;
//有多少个数 就准备多少个辅助空间
int[] bucket = new int[R-L+1];
for(int d = 1; d<= digit; d++) { //有多少位就进出几次
// 10 个空间
int[] count = new int[radix]; //count[0...9]
for(i = L;i<= R;i++) {
// 要哪个位上的数字
j = getDigit(arr[i],d);
count[j]++;
}
// 把count处理成前缀和
for(i = 1;i<radix;i++) {
count[i] = count[i]+count[i-1];
}
// 数组从右往左遍历 做完即出桶
for(i = R; i >=L;i --) {
j = getDigit(arr[i],d);
bucket[count[j]-1] = arr[i];
count[j]--;
}
// 将桶里面的数据导出到数组中 维持此次出桶的结果
for( i = L,j=0;i<=R;i++,j++) {
arr[i] = bucket[j];
}
}
}
public static int getDigit(int x ,int d) {
return ((x/((int)Math.pow(10, d-1)))%10);
}
public static void main(String[] args) {
int[] arr = {5,1,4,6,2,8,4,1,5,3,6,8,7,9};
radixSort(arr);
for( int x :arr) {
System.out.print(x+"\t");
}
}
}
基数排序 vs 计数排序 vs 桶排序
这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
基数排序:根据键值的每位数字来分配桶;
计数排序:每个桶只存储单一键值;
桶排序:每个桶存储一定范围的数值;
总结:
一般 用快速排序(3.0版本) ,如果有空间限制的话使用堆排序, 如果有要求稳定性的话一般使用归并排序
基于比较的排序
目前没有发现 有时间复杂度低于 O(N * logN) 以下的算法
说明 O(N * logN)这个指标已经是极限
没有发现 时间复杂度为 O(N * logN) 同时 空间复杂度为O(N) 以下 还能保持稳定性的 算法