各大排序算法
各种排序算法的简要复习
冒泡排序
思想:重复地走访要排序的数列,比较相邻的两个数,若顺序错误就交换,每走遍一次要排序数列,就会有一个未排数列里的最值到已排数列成序。
稳定性:相邻元素相等时不交换,则冒泡排序稳定
适用场景:代码少思路简单,适合少数据的排序。但是算法时间复杂度相对较高,不适用于大体量数据的排序。
优化:设置一个flag,如果是在最好情况下走一遍就可以了。
最好情况时间复杂度:O(n)
其他情况时间复杂度:O(n^2)
代码:
#include <iostream>
#include <stdio.h>
using namespace std;
void bubbleSort(int arr[],int len){
int flag,temp;
for(int i=len-1;i>0;i--){
flag=0;
for(int j=0;j<i;j++){
if(arr[j]>arr[j+1]){
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
flag=1;
}
}
if(flag==0) break;
}
}
复习:
求int array[]的长度
len=(int)sizeof(array)/sizeof(*array)
len=(int)sizeof(array)/sizeof(int)
len=(int)sizeof(array)/sizeof(array[0])
应用思考:可以用于求某个数列的几个最值,因为跑一趟就有一个最值浮动到一边。
选择排序
思想:也是一种交换排序,和冒泡排序有点类似,从未排序的数列中找最值,放到已排数列起始端,直至排序完成。
稳定性:一般用数组实现,不稳定。
适用场景:数据少的排序
时间复杂度:O(n^2)
#include <iostream>
#include <stdio.h>
using namespace std;
//把小的放前面
void selectSort(int arr[],int len){
int temp,mark,i,j;
for(i=0;i<len;i++){
mark=i;
for(j=i+1;j<len;j++){
if(arr[mark]>arr[j]) mark=j;
}
if(mark!=i){
temp=arr[i];
arr[i]=arr[mark];
arr[mark]=temp;
}
}
}
//大的放后面
void selectSort(int arr[],int len){
int temp,mark,i,j;
for(i=len-1;i>=0;i--){
mark=i;
for(j=i-1;j>=0;j--){
if(arr[mark]<arr[j]) mark=j;
}
if(mark!=i){
temp=arr[i];
arr[i]=arr[mark];
arr[mark]=temp;
}
}
}
注意:理解本质后,可以很多变化!不要太粗心哦!
插入排序
思想:对未排数列,遍历已排数列找到合适位置插入未排元素
描述:开始的时候,分号已排和未排,一般默认头一个为已排元素。从第二个元素起,在已排的数列中找到合适位置插入。直至排序完成。
稳定性:找到不大于当前数就不用换,从前往后去看的,因此插入排序是最稳定的排序算法。
时间复杂度:O(n^2)
适用场景:不适合大体量数据
#include <iostream>
#include <stdio.h>
using namespace std;
//默认首端已排
void insertSort(int arr[],int len){
int position,value;
for(int i=1;i<len;i++){
//默认第一个元素已排,从第二个元素开始寻机插入
position=i;
value=arr[position];
//记录下当前要插入元素的信息
while(position>0&&arr[position-1]>value) //从小到大排
{
arr[position]=arr[position-1];//大的往后移
position--;
}
//最后就确定了要插入的位置
arr[position]=value;
}
}
//默认尾端已排
void insertSort(int arr[],int len){
int position,value;
for(int i=len-2;i>0;i--){
//默认最后一个元素已排,从倒数第二个元素开始寻机插入
position=i;
value=arr[position];
//记录下当前要插入元素的信息
while(position<len-1&&arr[position+1]>value) //从小到大排
{
arr[position]=arr[position+1];//大的往后移
position++;
}
//最后就确定了要插入的位置
arr[position]=value;
}
}
注意:理解后注意已排和未排的遍历方向基本ok!
归并排序
思想:利用归并操作,先把子序列排好序,子序列间再排序,归并排好的子序列组成有序的序列。
两个有序子列的排序归并。
若由两个有序表归并为一,称为2-路归并。
时间复杂度:O(nlogn)
空间复杂度:O(n)
稳定性:稳定
适用场景:因为时间复杂度较低,数据量大的时候可以采用,但是太大就不行了,因为会产生较多的额外空间。
两种实现:
1.递归法:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
void Merge(int arr[],int tempA[],int L,int R,int rightEnd){
int leftEnd,num,tempStart;
tempStart=L;
leftEnd=R-1;
num=rightEnd-L+1;
while(L<=leftEnd&&R<=rightEnd){
if(arr[L]<=arr[R]) tempA[tempStart++]=arr[L++];//相等时把左边的先放,确保稳定性
else tempA[tempStart++]=arr[R++];
}
while(L<=leftEnd){
tempA[tempStart++]=arr[L++]; //把左边剩的放过去
}
while(R<=rightEnd){
tempA[tempStart++]=arr[R++];//右边剩的放过去
}
for(int i=0;i<num;i++,rightEnd--){
arr[rightEnd]=tempA[rightEnd];
}
}
void Msort(int arr[],int tempA[],int L,int rightEnd){
int center;
if(L<rightEnd){
center=(rightEnd+L)/2;
Msort(arr,tempA,L,center);
Msort(arr,tempA,center+1,rightEnd);
Merge(arr,tempA,L,center+1,rightEnd);
}
}
void mergeSort(int arr[],int len){//统一的函数接口
int *tempA;
tempA=(int*)malloc(len*sizeof(int));//省去很多次的中间操作
if(tempA!=NULL){
Msort(arr,tempA,0,len-1);
free(tempA);
}
else cout<<"空间不足";
}
参考了mooc里浙江大学的数据结构精品课。
老师讲得非常好。
注意:中间产生空间和操作的简化技巧!
2.非递归实现:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
void Merge(int arr[],int tempA[],int L,int R,int rightEnd){
int leftEnd,num,tempStart;
tempStart=L;
leftEnd=R-1;
num=rightEnd-L+1;
while(L<=leftEnd&&R<=rightEnd){
if(arr[L]<=arr[R]) tempA[tempStart++]=arr[L++];//相等时把左边的先放,确保稳定性
else tempA[tempStart++]=arr[R++];
}
while(L<=leftEnd){
tempA[tempStart++]=arr[L++]; //把左边剩的放过去
}
while(R<=rightEnd){
tempA[tempStart++]=arr[R++];//右边剩的放过去
}
for(int i=0;i<num;i++,rightEnd--){
arr[rightEnd]=tempA[rightEnd];
}
}
void Merge_pass(int arr[],int tempA[],int len,int length){
int i,j;
for(i=0;i<=len-2*length;i+=2*length)
Merge(arr,tempA,i,i+length,i+2*length-1);
if(i+length<len) Merge(arr,tempA,i,i+length,len-1);
else
for(j=i;j<len;j++) tempA[j]=arr[j];
}
void Merge_Sort(int arr[],int len){
int length=1;
int* tempA;
tempA=(int*)malloc(len*sizeof(int));
if(tempA!=NULL){
while(length<len){
Merge_pass(arr,tempA,len,length);
length*=2;
Merge_pass(tempA,arr,len,length);
length*=2;
}
free(tempA);
}
else cout<<"空间不足"<<endl;
}
两种实现方式的核心都是归并操作。
区别在于一种利用归并操作的方式是递归的而另一种是迭代的。
快速排序
思想:采用分而治之的策略,在待排数列中找出一个中枢元素,大于中枢元素放右边,小于中枢元素放左边。在分别用快排去排左边和右边。
选取中枢元素很重要,会影响到算法的复杂度。
如果直接选取第一个元素作为主元,当数列有序时,时间复杂度会是O(n^2)
一般采用几个数里选中位数的办法,如头,中,尾取中位数的办法:
子集划分
停下来继续交换,虽然会做很多次无用的交换但是每一次主元都可以放到一个相对中间的位置,复杂度O(nlogn),而如果继续不做交换,主元会总是在一端,就出现前面O(n^2)的情况。
因为快速排序采用的是递归的办法,递归中间会有很多额外的操作比如进栈出栈等等
代码:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
void Swap( int *a, int *b )
{
int t = *a;
*a = *b;
*b = t;
}
void insertSort(int arr[],int len){
int position,value;
for(int i=1;i<len;i++){
//默认第一个元素已排,从第二个元素开始寻机插入
position=i;
value=arr[position];
//记录下当前要插入元素的信息
while(position>0&&arr[position-1]>value) //从小到大排
{
arr[position]=arr[position-1];//大的往后移
position--;
}
//最后就确定了要插入的位置
arr[position]=value;
}
}
int Median3( int A[], int Left, int Right )
{
int Center = (Left+Right) / 2;
if ( A[Left] > A[Center] )
Swap( &A[Left], &A[Center] );
if ( A[Left] > A[Right] )
Swap( &A[Left], &A[Right] );
if ( A[Center] > A[Right] )
Swap( &A[Center], &A[Right] );
/* 此时A[Left] <= A[Center] <= A[Right] */
Swap( &A[Center], &A[Right-1] ); /* 将基准Pivot藏到右边*/
/* 只需要考虑A[Left+1] … A[Right-2] */
return A[Right-1]; /* 返回基准Pivot */
}
void Qsort( int A[], int Left, int Right )
{ /* 核心递归函数 */
int Pivot, Cutoff=100, Low, High;
if ( Cutoff <= Right-Left ) { /* 如果序列元素充分多,进入快排 */
Pivot = Median3( A, Left, Right ); /* 选基准 */
Low = Left; High = Right-1;
while (1) { /*将序列中比基准小的移到基准左边,大的移到右边*/
while ( A[++Low] < Pivot ) ;
while ( A[--High] > Pivot ) ;
if ( Low < High ) Swap( &A[Low], &A[High] );
else break;
}
Swap( &A[Low], &A[Right-1] ); /* 将基准换到正确的位置 */
Qsort( A, Left, Low-1 ); /* 递归解决左边 */
Qsort( A, Low+1, Right ); /* 递归解决右边 */
}
else insertSort( A+Left, Right-Left+1 ); /* 元素太少,用简单排序 */
}
void QuickSort( int A[], int N )
{ /* 统一接口 */
Qsort( A, 0, N-1 );
}
直接调用快速排序函数的办法
/* 快速排序 - 直接调用库函数 */
#include <stdlib.h>
/*---------------简单整数排序--------------------*/
int compare(const void *a, const void *b)
{ /* 比较两整数。非降序排列 */
return (*(int*)a - *(int*)b);
}
/* 调用接口 */
qsort(A, N, sizeof(int), compare);
/*---------------简单整数排序--------------------*/
/*--------------- 一般情况下,对结构体Node中的某键值key排序 ---------------*/
struct Node {
int key1, key2;
} A[MAXN];
int compare2keys(const void *a, const void *b)
{ /* 比较两种键值:按key1非升序排列;如果key1相等,则按key2非降序排列 */
int k;
if ( ((const struct Node*)a)->key1 < ((const struct Node*)b)->key1 )
k = 1;
else if ( ((const struct Node*)a)->key1 > ((const struct Node*)b)->key1 )
k = -1;
else { /* 如果key1相等 */
if ( ((const struct Node*)a)->key2 < ((const struct Node*)b)->key2 )
k = -1;
else
k = 1;
}
return k;
}
/* 调用接口 */
qsort(A, N, sizeof(struct Node), compare2keys);
/*--------------- 一般情况下,对结构体Node中的某键值key排序 ---------------*/
另一种写法,以首个为中枢,但是算法效率不如前面的写法:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
int pivotP(int arr[],int low,int high){
int pivot=arr[low];
while(low<high){
while(low<high&&arr[high]>=pivot) high--;
arr[low]=arr[high];
while(low<high&&arr[low]<=pivot) low++;
arr[high]=arr[low];
}
arr[low]=pivot;
return low;
}
void qsort(int arr[],int low,int high){
if(low>=high) return;
int pivot=pivotP(arr,low,high);
qsort(arr,low,pivot-1);
qsort(arr,pivot+1,high);
}
void quickSort(int arr[],int len){
qsort(arr,0,len-1);
}
稳定性:不稳定的,和pivot的选取有很大关系
适用场合:应用于大体量数据
时间复杂度:O(nlogn)
堆排序
思想:构造一个堆,根节点为最值,每个父节点都是子树里的最值,根节点的最值找出来后,和堆最后一个元素交换位置,再重新调整堆确保根节点是最值,再和没排序的最后一个元素交换位置,重复到数列排序完成。
描述:
(以最大堆为例描述)
1.先构造一个堆顶值为最值的无序堆
方法:从最后一个父节点开始,确定调整每个小堆的父节点都是最大值,往前移至根节点。
2.交换堆顶的最值和已排好的前一个元素,调整未排元素构造堆顶值为最值得无序堆,再交换堆顶和已排好的前一个元素,重复前面步骤,直至堆有序。
时间复杂度:O(nlogn)
适用场景:
堆排序在建立堆和调整堆的过程中会产生较大的开销,在元素少的时候用不划算。但是,在元素比较多的情况下,是个很不错的选择。尤其是在解决诸如“前n大的数”一类问题时,几乎是首选算法。
代码:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
void max_heapify(int arr[],int start,int tail){
int dad=start;
int son=2*dad+1;
while(son<=tail){
if(son+1<=tail&&arr[son+1]>arr[son]) son++;
if(arr[dad]>arr[son]) return;
else{
swap(arr[son],arr[dad]);
dad=son;
son=2*dad+1;
}
}
}
void heap_sort(int arr[],int len){
//初始化,构造原始最堆
for(int i=len/2-1;i>=0;i--){
max_heapify(arr,i,len-1);
}
//堆顶元素和没排好的最后一个元素交换位置
for(int i=len-1;i>=0;i--){
swap(arr[0],arr[i]);
max_heapify(arr,0,i-1);
}
}
堆排序算法效率比较高
希尔排序
思想:利用了插入排序的简单,克服插入排序每次只交换相邻两个元素的缺点。
时间复杂度:
最坏可达:O(n^2)
描述:
1.先定义一个增量序列,Dm>Dm-1>…>D1=1
2.对每个Dk进行一个Dk间隔的排序
注意:
a.小间隔的排序没有破坏大间隔排序,执行Dk-1间隔排序后,Dk排序仍是有序的
b.增量元素不互质,小增量可能根本不起作用
代码:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
void shellsort(int arr[],int len){
int sedgewick[]={929, 505, 209, 109, 41, 19, 5, 1};
int si,d,p,i,temp;
for(si=0;sedgewick[si]>=len;si++);//初始增量序列不能超出待排数列长度
for(d=sedgewick[si];d>0;d=sedgewick[++si]){
for(p=d;p<len;p++){
temp=arr[p];
for(i=p;i>=d&&arr[i-d]>temp;i-=d)
arr[i]=arr[i-d];
arr[i]=temp;
}
}
}
适用场景:希尔排序算法的复杂度相对于其他nlogn的算法还是要高好多,适用于中小型数据规模场景。
计数排序
思想:
计数排序不是一个比较排序算法,该算法于1954年由 Harold H. Seward提出,通过计数将时间复杂度降到了O(N)
虽然它可以将排序算法的时间复杂度降低到O(N),但是有两个前提需要满足:一是需要排序的元素必须是整数,二是排序元素的取值要在一定范围内,并且比较集中。只有这两个条件都满足,才能最大程度发挥计数排序的优势。
描述:
1.找出待排序的数组中最大和最小的元素;
2.统计数组中每个值为min_num+i的元素出现的次数,存入数组C的第i项;
3.反向导回数组
void countsort(long arr[],int len){
if(len==0||len==1) return;
int min_num=arr[0], max_num=arr[0];
for(int i=1;i<len;i++){
if(arr[i]>max_num) max_num=arr[i];
if(arr[i]<min_num) min_num=arr[i];
}
long countn[max_num-min_num+1]={};
for(int i=0,temp;i<len;i++)
{
temp=arr[i];
countn[temp-min_num]++;
}
int index=0;
for(int i=0;i<max_num-min_num+1;i++){
while(countn[i]>0){
arr[index++]=i+min_num;
countn[i]--;
}
}
}
慎用!用pta在第二个测试点出现段错误,现在还不明白什么原因,但是其他测试答案正确。已经创建提问,现在还是原因不明。
稳定性:稳定
适用场景:整数,范围确定,数值比较集中的数据
桶排序
思想:设定一定数量的桶,每个桶有代表不同数值范围(min-max),遍历一遍原数组,符合哪个数值范围就放进哪个桶里。一般用链表,方便数据放桶的时候排序时插入。
如果设置的桶的数量远远小于n,那么该算法可达O(n)的算法复杂度。
稳定性:目前是稳定的,因为用的是插入排序的思想。
适用场景:数据范围比较小,数值分布比较均匀。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include<algorithm>
using namespace std;
struct Node
{
int data;
Node *next;
};
Node **LinkList;
void bucketsort(int *arr, int len,int build_num,int minimum,int scope)
{
LinkList = (Node **)malloc(sizeof(Node *) * build_num);//创建数组指针
float gap = float(scope)/ build_num;
for (int i = 0; i < build_num;i++)
{
LinkList[i] = (Node *)malloc(sizeof(Node));
LinkList[i]->data = 0; //第一个节点保存存储的数据数量
LinkList[i]->next = NULL;
}
for (int i = 0; i < len;i++)
{
int index = (*(arr + i) - minimum - 1) / gap;//确定数据所在的桶
if(index<0) index=0;
Node *p=(Node *)malloc(sizeof(Node));
p->next = NULL;
p->data = *(arr + i);
Node *pre = LinkList[index];
pre->data++;
bool flag = false;
if(pre->next!=NULL)//跳过第一个计数节点
{
flag = true;//非空链表标记置标记位为真
pre = pre->next;//初始pre在计数Node之后
}
while(pre->next != NULL&&p->data>pre->data)
pre = pre->next;
if(pre->next==NULL&&pre->data<p->data)
{
pre->next = p;
}
else
{
//前后两个链表元素的data域互置,因为没有前驱指针,而while循环后pre->data的值大于p->data
if(flag){
int smaller = p->data;
int larger = pre->data;
p->next = pre->next;
pre->next = p;
p->data = larger;
pre->data = smaller;
}
else
pre->next = p;
}
}
int num=0;
for(int i = 0; i < build_num;i++)
{
Node *pre=LinkList[i];
if(pre->next!=NULL){
pre = pre->next;
while(pre!=NULL)
{
if(num!=len-1) cout << pre->data << " ";
else cout << pre->data;
pre = pre->next;
num++;
}
}
else continue;
}
}
int main()
{
int len;
cin>>len;
int *arr=(int *)malloc(len*sizeof(int));
for(int i=0;i<len;i++)cin>>*(arr+i);
int maximum = *max_element(arr, arr + len);
int minimum = *min_element(arr, arr + len);
int scope = maximum - minimum;
bucketsort(arr,len,10,minimum,scope);
return 0;
}
学到了之:stl max_element min_element
还有这里和计数排序一样,提交到pta,第二个测试点出现段错误,其他均正确。
基数排序
思想:将整数按不同位数切割,按每一位上的数字来分别比较。采用次位优先的方法,位数较少的高位填0。
描述:
1.取最大数有多少位
2.从低位起,建立每个位的数字的radix数组
3.把那些数分别放到对应radix数组的位置里
4.串起来
时间复杂度:O(P(N+B))
稳定性:稳定
适用场景:整数,数据范围10w-100w之间效率最好,还可应用于其他关键字场景。如日期,纸牌。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
/* 假设元素最多有MaxDigit个关键字,基数全是同样的Radix */
#define MaxDigit 6
#define Radix 10
/* 桶元素结点 */
typedef struct Node *Pnode;
struct Node{
int key;
Pnode next;
};
/* 桶头结点 */
struct Headnode {
Pnode head, tail;
};
typedef struct Headnode Bucket[Radix];
int GetDigit ( int X, int D )
{ /* 默认次位D=1, 主位D<=MaxDigit */
int d, i;
for (i=1; i<=D; i++) {
d = X % Radix;
X /= Radix;
}
return d;
}
void LSDRadixSort( int A[], int N )
{ /* 基数排序 - 次位优先 */
int D, Di, i;
Bucket B;
Pnode tmp, p, List = NULL;
for (i=0; i<Radix; i++) /* 初始化每个桶为空链表 */
B[i].head = B[i].tail = NULL;
for (i=0; i<N; i++) { /* 将原始序列逆序存入初始链表List */
tmp = (Pnode)malloc(sizeof(struct Node));
tmp->key = A[i];
tmp->next = List;
List = tmp;
}
/* 下面开始排序 */
for (D=1; D<=MaxDigit; D++) { /* 对数据的每一位循环处理 */
/* 下面是分配的过程 */
p = List;
while (p) {
Di = GetDigit(p->key, D); /* 获得当前元素的当前位数字 */
/* 从List中摘除 */
tmp = p; p = p->next;
/* 插入B[Di]号桶尾 */
tmp->next = NULL;
if (B[Di].head == NULL)
B[Di].head = B[Di].tail = tmp;
else {
B[Di].tail->next = tmp;
B[Di].tail = tmp;
}
}
/* 下面是收集的过程 */
List = NULL;
for (Di=Radix-1; Di>=0; Di--) { /* 将每个桶的元素顺序收集入List */
if (B[Di].head) { /* 如果桶不为空 */
/* 整桶插入List表头 */
B[Di].tail->next = List;
List = B[Di].head;
B[Di].head = B[Di].tail = NULL; /* 清空桶 */
}
}
}
/* 将List倒入A[]并释放空间 */
for (i=0; i<N; i++) {
tmp = List;
List = List->next;
A[i] = tmp->key;
free(tmp);
}
}
int main()
{
int len;
cin>>len;
int arr[len];
for(int i=0;i<len;i++) cin>>arr[i];
LSDRadixSort(arr,len);
for(int i=0;i<len;i++){
if(i!=len-1) cout<<arr[i]<<" ";
else cout<<arr[i];
}
return 0;
}
这个提交到pta还是第二个测试点有段错误,其他都正确,还是不知道为什么。
总结
快速复盘思想
1.选择排序:遍历每趟找到一个最值放到后面,一般写法不稳定,因为前面先找出来的那个放后面去了。
平均时间复杂度:O(n^2)
最坏时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:一般不稳定
2.冒泡排序:过N趟未排数列,每次都比较相邻的两个数,不序就交换,把最值浮动到已排数列前面。
平均时间复杂度:O(n^2)
最坏时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
3.直接插入排序:一般把头一个视作已排,从第二个开始找合适位置插入,直到排序完成。
平均时间复杂度:O(n^2)
最坏时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:稳定
4.希尔排序:先定义一个增量序列D,每个间隔Di的数构成一个待排序列,用插入排序把每个待排序列排好,直到Di=1所有元素排序完成。
ps:d和增量序列的选择有关
一般选sedgewick[]={929, 505, 209, 109, 41, 19, 5, 1};
平均时间复杂度:O(n^d)
最坏时间复杂度:O(n^2)
空间复杂度:O(1)
稳定性:不稳定
在前面大增量排的时候可能已经破坏了稳定性。
5.快速排序:选一个中枢,大于中枢放右边,小于中枢放左边,再分而治之,分开的两边又分别展开快速排序,直到所有序列完成。
平均时间复杂度:O(nlogn)
最坏时间复杂度:O(n^2)
每次选的中枢都是待排里本应在前的。
空间复杂度:O(logn)
因为用递归会有中间消耗
稳定性:不稳定
它的稳定性和中枢的选择还有很大关系
6.堆排序:构造最大堆或者最小堆,确定根值是待排序列的最值后,把它放到已排数列里。再在未排数列构造最大最小堆,重复直到排序完成。
(利用树的结构)
平均时间复杂度:O(nlogn)
最坏时间复杂度:O(nlogn)
空间复杂度:O(1)
稳定性:不稳定
7.归并排序:
非递归:先把每个元素看成一个待排序列,先相邻两个按序归并又构成待排序列,在归并组合,直到排序完成。
递归:先排两个序列,再递归去归并排子序列。
平均时间复杂度:O(nlogn)
最坏时间复杂度:O(nlogn)
空间复杂度:O(N)
稳定性:稳定
8.基数排序:
找出待排最大数的位数,p分为p个数组,长度为k表示关键字类型数,次级优先就先从低开始,把位置上的数字符合的放进数组里,一直到x次完成,再串起来。(排整数情况,还可以排其他)
平均时间复杂度:O(p(n+k))
最坏时间复杂度:O(p(n+k))
空间复杂度:O(n+k)
稳定性:稳定
9.计数排序:
根据max和min范围建立一个数组,该数组的第i个元素可以表示min+i这个数据出现的次数。最后根据数据复原。
k=max-min
平均时间复杂度:O(n+k)
最坏时间复杂度:O(n+k)
空间复杂度:O(n+k)
稳定性:稳定
这个整数数列,数据分布范围较小时适用
10.桶排序
设定一定数量的桶,桶有顺序,每个桶放不同数值范围的数,桶里排序,串起来。用链表插入比较方便。也可用二维数组用插入排序。
整数,分布均匀适用
平均时间复杂度:O(n+k)
最坏时间复杂度:O(n+k)
空间复杂度:O(n+k)
稳定性:稳定