排序
存储结构
#define MAXSIZE 20
typedef int KeyType
typedef struct
{
KeyType key;
} RcdType;
typedef struct
{
RcdType r[MAXSIZE+1];
int length;
} SqList;
一、插入排序
1.直接插入排序
思路:将无序块首元素与左侧元素由后向前逐个比较,边比较边后移;
最后定位到一个大于等于待插入元素的记录左侧,填入即可
void InsertionSort(SqList &L)
{
for(int i=2; i<=L.length; i++)
{
int j=i-1,k;
if(L.r[j]>L.r[i])
{
L.r[0]=L.r[i];
L.r[i]=L.r[j];
}
for(k=i-2; L.r[k]>L.r[0]; k--)
{
L.r[k+1]=L.r[k];
}
L.r[k+1]=L.r[0];
}
}
时间复杂度:最好O(n),最坏O(n的平方)
稳定性:稳定
2.折半插入排序
思路:折半法定位第一个大于待插入元素的记录位置,在其左侧插入
void BinInsertionSort(SqList &L)
{
for(int i=2; i<=L.length; i++)
{
int low=1,high=i-1;
while(low<=high)
{
int mid=(low+high)/2;
if(L.r[mid].key>L.r[i].key)
high=mid-1;
else
low=mid+1;
}
L.r[0]=L.r[i];
for(int j=i-1; j>=high+1; j--)
{
L.r[j+1]=L.r[j];
}
L.r[high+1]=L.r[0];
}
}
时间复杂度:O(n的平方)3.希尔排序
思路:设一个递增的增量序列,如5-3-1
每一趟都将记录序列分成若干子序列,各子序列分别进行直接插入排序
最后一趟增量为1,对全部记录进行直接插入排序
void ShellInsert(SqList &L,int dk)
{
for(int i=dk+1; i<=L.length; i++)
{
if(L.r[i].key<L.r[i-dk].key)
{
L.r[0]=L.r[i];
for(int j=i-dk; j>0&&(L.r[j]>L.r[0]); j-=dk)
{
L.r[j+dk]=L.r[j];
}
L.r[j+dk]=L.r[0];
}
}
}
void ShellSort(SqList &L,int dlta[],int length)
{
for(int k=0; k<length; k++)
{
ShellInsert(L,dlta[k]);
}
}
时间性能:O(n的1.3次方)
空间性能:O(1)
稳定性:不稳定
二、快速排序
思路:冒泡排序的优化,选择一个元素做枢轴,将原序列一分为二,左侧小于枢轴,右侧大于枢轴
左右两侧子序列重复上述过程,递归完成各自的排序
递归边界:序列长为0或1则不必排序
递归关系:序列长度大于1,则选择某元素做枢轴(如最左侧元素),根据枢轴元素将原序列一分为二
左右两侧子序列通过重复上述过程,递归完成各自的排序
int Partition(SqList L,int low,int high)
{
L.r[0]=L.r[low];
while(low<high)
{
while(L.r[high]>L.r[0])
{
high--;
}
L.r[low]=L.r[high];
while(L.r[low]<L.r[0])
{
low++;
}
L.r[high]=L.r[low];
}
L.r[low]=L.r[0];
return low;
}
void QSort(SqList L,int low,int high)
{
if(!(low<high))
return;
else
{
int pivotLoc=Partition(L,low,high);
QSort(L,low,pivotLoc-1);
QSort(L,pivotLoc+1,high);
}
}
时间复杂度:平均O(N*log以2为底N),最坏O(N的平方)
空间复杂度:平均O(log以2为底N),最坏O(N)
不稳定:3 2 2* -> 2* 2 3
三、选择排序
1.简单选择排序
思路:每一趟选择出当前最小记录,将其交换到无序块的最前面。如此无序块逐渐变小,N-1趟完成。
void selectSort(SqList &L)
{
int i,j,min,tmp;
for(int i=1;i<=L.length-1;i++)
{
min=i;
for(int j=i+1;j<=L.length;j++)
{
if(L.r[j]<L.r[min])
{
min=j;
}
}
tmp=L.r[i];
L.r[i]=L.r[min];
L.r[min]=tmp;
}
}
2.堆排序
一个序列,设它对应某完全二叉树的层序序列,若该二叉树任意一个节点均比其左右孩子节点大(小),
则称该序列为大(小)顶堆
筛选 互换
void HeapAdjust(SqList &H,int head,int tail)
{
H.r[0]=H.r[head];
int lchild=head*2,rchild=2*head+1;
while(lchild<=tail)
{
int bigchild;
if(rchild<tail&&H.r[lchild].key>H.r[rchild].key)
bigchild=lchild;
else
bigchild=rchild;
if(H.r[0].key<H.r[bigchild].key)
{
H.r[head]=H.r[bigchild];
head=bigchild;
lchild=head*2;
rchild=2*head+1;
}
else
break;
}
H.r[head]=H.r[0];
}
void HeapSort(SqList &H)
{
for(int i=H.length/2; i>=0; i--) //初始大顶堆的建立
{
HeapAdjust(H,i,H.length);
}
for(int i=1; i<H.length; i++)
{
H.r[0]=H.r[1];
H.r[1]=H.r[H.length-i+1];
H.r[H.length-i+1]=H.r[0]; //交换堆顶堆尾后只有根不符合要求
HeapAdjust(H,1,H.length-i);
}
}
时间复杂度:O(n*log以2为底的n)
空间复杂度:O(1)
稳定性:不稳定
四、归并排序
void Merge(SqList L,int low,int mid,int high,SqList T)
{
int left=low,right=mid+1,result=low;
while(left<=mid&&right<=high)
{
if(L.r[left]<L.r[right])
{
T.r[result]=L.r[left];
result++;
left++;
}
else
{
T.r[result]=L.r[right];
result++;
right++;
}
}
while(left<=mid)
{
T.r[result]=L.r[left];
result++;
left++;
}
while(right<=high)
{
T.r[result]=L.r[right];
result++;
right++;
}
for(int i=low;i<=high;i++)
{
L.r[i]=T.r[i];
}
}
void MSort(SqList L,int low,int high)
{
if(low==high)
return;
else
{
int mid=(low+high)/2;
MSort(L,low,mid);
MSort(L,mid+1,high);
Merge(L,low,mid,high,T);
}
}
时间复杂度:O(n*logn)
空间复杂度:O(n)
稳定
五、基数排序
1.多关键字的排序
分为分配和收集两个过程
void Distribute(SqList L,Queue bucketArray[RADIX],int k)
{
for(int i=1;i<=L.length;i++)
{
int radix=GetRadixNumber(L.r[i].key,k);
EnQueue(bucketArray[radix],L.r[i]);
}
}
void Collect(SqList L,Queue bucketArray[RADIX])
{
int j=1;
for(int i=0;i<RADIX;i++)
{
while(!QueueEmpty(bucketArray[i]))
{
DeQueue(bucketArray[i],e);
L.r[j++]=e;
}
}
}
void RadixSort(SqList L)
{
for(i=int i=0;i<RADIX;i++)
{
InitQueue(bucketArray[i]);
}
int len=GetRoundCount(L.r);
for(int k=1;k<=len;k++)
{
Distribute(L,bucketArray,k);
Collect(L,bucketArray);
}
}
分配时间复杂度:O(n)
收集时间复杂度:O(n)
时间复杂度:O(length*n)
空间复杂度:O(n+RADIX)
2.链式基数排序
void Distribute(SLList &L,int i,ArrType &f,ArrType &e) //L为静态链表,i为趟数,f和e分别为头指针、尾指针数组
{
for(int j=0;j<Radix;j++)
{
f[j]=0;
}
for(int p=L.r[0].next;p!=0;p=L.r[p].next)
{
int k=Ord(L.r[p],i);
if(f[k]==0)
{
f[k]=p;
}
else
{
L.r[e[k]].next=p;
}
e[k]=p;
}
}
void Collect(SLList &L,int i,ArrType &f,ArrType &e)
{
int j=0;
while(f[j]==0)
{
j++;
}
L.r[0].next=f[j];
int tail=e[j];
while(j<=Radix-2)
{
j++;
while(f[j]==0)
{
j++;
}
if(j<=Radix-1)
{
L.r[tail].next=f[j];
tail=e[j];
}
}
L.r[tail].next=0;
}
时间复杂度:O(length*(n+radix))
空间复杂度:O(radix)
稳定
直接插入排序正序时最好时间复杂度O(n),逆序最坏O(n2),平均O(n2),空间复杂度O(1);稳定;原始序列基本有序时该方法好
折半插入排序逆序最坏O(n2),正序时O(NLogN) , S(n)=O(1);稳定 希尔排序(缩小增量排序):平均时间复杂度O(n1.4),S(n)=O(1);不稳定
冒泡排序(改进的)正序时间复杂度最好O(n),逆序最坏O(n2),平均O(n2),S(n)=O(1); 稳定.注意改进算法
快速排序平均时间复杂度O(nlogn),平均性能最优,正序或逆序最坏O(n2), 有辅助栈,空间复杂度最坏O(n),平均O(logn);不稳定. 枢轴改进
选择排序复杂度T(n)=O(n2),原本有序无序均如此,S(n)=O(1);稳定 堆排序T(n)=O(nlogn),S(n)=O(1);不稳定(因为间隔着比和移动)
归并排序最好最坏复杂度为O(nlogn),空间复杂度O(n),稳定
链式基数排序最好最坏时间复杂度为O(d*(n+ rd )),空间O(rd),稳定
内部排序方法分类:复杂度O(n2)的简单排序方法,O(nlogn)的高效排序方法(比较法的理论下界),O(d*(n+rd))的基数排序方法.
各排序方法各有优缺点,具体选择时考虑稳定性、记录多少、原始数据是否基本有序、关键字大小等因素。
直接插入排序适合基本有序、n值较小时 基数排序适合关键字较短、n较大、要求稳定时;
快速排序适合n大、不要求稳定、分布随机时;
堆排序适合n大、关键字分布可能有序、不要求稳定;
归并排序适合n大、关键字分布可能有序、要求稳定且内存空间充裕时;
如果只选择最小的前几个元素,则简单选择排序优先 理论上可证明基于比较的排序方法可能达到的最快的时间复杂度为O(nlogn);基数排序不是基于“比较”
为避免顺序存储时大量移动记录的时间开销,可考虑用链表作为存储结构 :直接插入排序、归并排序(非递归)、基数排序
不宜采用链表作为存储结构的 :折半插入排序、希尔排序、快速排序、堆排序