内部排序
1.假设Ri=Rj,若排序前的序列中Ri领先于Rj,排序后Ri仍领先于Rj,则称所用的排序方法是稳定的,否则称所用的排序方法是不稳定的。
由于待排序的记录数量不同,使得排序过程中涉及的存储器不同,将排序方法分为两大类:
1.内部排序:指待排序记录存放在计算机随机存储器中进行的排序过程。
2.外部排序:待排序记录的数量很大,以致内存一次不能容纳全部记录,在排序过程中尚需对外存进行访问的排序过程。
2.插入排序
1.直接插入排序:将一个记录插入到已排好序的有序表中,从而得到一个新的,记录数增1的有序表。时间复杂度为O(n^2)
1 void InsertSort(SqList &L){ 2 //对顺序表L作直接插入排序 3 for(i=2;i <=L.length;++i) 4 if(LT(L.r[i].key,L.r[i-1].key )){ 5 L.r[0]=L.r[i]; 6 L.r[i]=L.r[i-1]; 7 for(j=i-2 ; LT(L.r[0].key ,L.r[j].key);--j) 8 L.r[j+1]=L.r[j]; 9 L.r[j+1]=L.r[0]; 10 } 11 }
2.其他插入排序
1.折半插入排序,时间复杂度为O(n^2)
void BInsertSort(SqList &L){ for(i=2;i<=L.length;++i){ L.r[0]=L.r[i]; low=1;high=i-1; while(low <= high){ m=(low+high)/2; if(LT(L.r[0].key,L.r[m].key)) high=m-1; else low=m+1; } for(j=i-1;j>=high+1;--j) L.r[j+1]=L.r[j]; L.r[high+1]=L.r[0]; } }
2. 2-路插入排序,时间复杂度仍为O(n^2)
2-路插入排序是在折半插入排序的基础上再改进的,目的是减少排序过程中移动记录的次数,但需要n个记录的辅助空间。
3.表插入排序,时间复杂度为O(n^2)
希望在排序过程中不移动记录,只有改变存储结构,进行表插入排序。
#define SIZE 100 typedef struct{ RcdType rc; //记录项 int next; //指针项 }SLNode; typedef struct{ SLNode r[SIZE]; int length; }SLinkListType;
void Arrange(SLinkListType &SL){
p=SL.r[0].next;
for(i=1;i<SL.length;++i){
while(p<i)p=SL.r[p].next;
q=SL.r[p].next;
if(p!=i){
swap(SL.r[p],SL.r[i]);
SL.r[i].next=p;
}
p=q;
}
}
3.希尔排序
希尔排序又称"缩小增量排序",它也是一种属插入排序类的方法,但在时间效率上较前几种排序方法有较大改进。
其基本思想是:先将整个待排记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录"基本有序"时,再对全体记录进行一次直接插入排序。
void ShellInsert(SqList &L,int dk){
//r[0]只是暂存单元,不是哨兵 当j<=0时,插入位置已经找到
for(i=dk+1;i<=L.length;++i)
if(LT(L.r[i].key,L.r[i-dk].key)){
L.r[0]=L.r[i];
for(j=i-dk;j>0 && LT(L.r[0].key,L.r[j].key);j -=dk)
L.r[j+dk]=L.r[j];
L.r[j+dk]=L.r[0];
}
}
void ShellSort(SqList &L,int dlta[],int t){
for(k=0;k<t;++k)
ShellInsert(L,dlta[k]);
}
4.快速排序
使用"交换"进行排序,最简单的交换排序就是起泡排序,其时间复杂度为O(n^2)。
快速排序是对起泡排序的一种改进,基本思想是,通过一趟排序将待排记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,
则可分别对这两部分记录继续进行排序,以达到整个序列有序。
int Partition(SqList &L,int low,int high){
pivotkey=L.row[low].key;
while(low < high){
while(low < high && L.r[high].key >= pivotkey) --high;
swap(L.r[low],L.r[high]);
while(low < high && L.r[low].key <= pivotkey) ++low;
swap(L.r[low],L.r[high]);
}
return low;
}
//在以上的算法中,排序过程中对枢轴记录的赋值是多余的,以下为改进算法
int Partition(SqList &L,int low,int high){
L.r[0]=L.r[low];
pivotkey=L.r[low].key;
while(low < high){
while(low < high && L.r[high].key >= pivotkey) --high;
L.r[low]=L.r[high];
while(low < high && L.r[low].key <= pivotkey) ++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){
pivotkey=Partition(L,low,high);
QSort(L,low,pivotkey-1);
QSort(L,pivotkey+1,high);
}
}
void QuickSort(SqList &L){
QSort(L,1,L.length);
}
快速排序的时间复杂度为O(nlogn),快速排序是目前被认为最好的一种内部排序方法。
5.选择排序
选择排序的基本思想是:每一趟在n-i+1个记录中选取关键字最小的记录作为有序序列中第i个记录。
1.简单选择排序
一趟简单选择排序的操作为:通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i个记录交换之,总的时间复杂度为O(n^2)。
void SelectSort(SqList &L){ for(i=1;i < L.length;++i){
j=SelectMinKey(L,i);
if(i!=j) swap(L.r[i],L.r[j]);
} }
2.树形选择排序
又称锦标赛排序,首先对n个记录的关键字进行两两比较,然后在[n/2]个较小者之间再进行两两比较,如此重复直至选出最小关键字的记录为止,整个过程可用
一棵有n个叶子结点的完全二叉树表示。
3.堆排序
堆排序只需要一个记录大小的辅助空间,每个待排序的记录仅占有一个存储空间。
堆的定义:n个元素三苏的序列{k1,k2,k3,....,kn}当且仅当满足以下关系时,称之为堆:
k(i) <= k(2i) && k <= k(2i+1)
https://www.cnblogs.com/chengxiao/p/6129630.html
typedef SqList HeapType
void HeapAdjust(HeapType &H,int s,int m){
//已知H.r[s..m]中记录的关键字除H.r[s].key之外均满足堆的定义,本函数调整H.r[s]的关键字,使H.r[s..m]成为一个大顶堆(对其中记录的关键字而言)
rc=H.r[s];
for(j=2*s;j<=m;j *=2){
if(j < m && LT(H.r[j].key,H.r[j+1].key)) ++j;
if(!LT(rc.key,H.r[j].key)) break;
H.r[s]=H.r[j];s=j;
}
H.r[s]=rc;
}
void HeapSort(HeapType &H){
for(i=H.length/2;i>0;--i)
HeapAdjust(H,i,H.length);
for(i=H.length;i>1;--i){
swap(H.r[1],H.r[i]);
HeapAdjust(H,1,i-1);
}
}
堆排序在最坏的情况下,其时间复杂度也为O(nlogn),并且堆排序仅需提供一个记录大小供交换用的辅助存储空间。
6.归并排序
归并的含义:将两个或两个以上的有序表组合成一个新的有序表。
2路归并排序:
void Merge(RcdType SR[],RcdType &TR[],int i,int m,int n){
//将有序的SR[i..m]和SR[m+1..n]归并为有序的TR[i..n]
for(j=m+1,k=i;i <= m && j<=n;++k){
if(LQ(SR[i].key,SR[j].key)) TR[k]=SR[i++];
else TR[k]=SR[j++];
}
if(i <= m) TR[k..n]=SR[i..m];
if(j <= n) TR[k..n]=SR[j..n];
}
7.基数排序
基数排序是一种借助多关键字排序的思想对单逻辑关键字进行排序的方法。
多关键字的排序:最高位优先法(MSD法)和最低位优先法(LSD法)。
链式基数排序:基数排序是借助"分配"和"收集"两种操作对单逻辑关键字进行排序的一种内部排序方法。
#define MAX_NUM_OF_KEY 8
#define RADIX 10
#define MAX_SPACE 10000
typedef struct{
KeysType keys[MAX_NUM_OF_KEY];
InfoType otheritems;
int next;
}SLCell; //静态链表的结点类型
typedef struct{
SLCell r[MAX_SPACE]; //静态链表可利用的空间
int keynum; //记录的当前关键字个数
int recnum; //静态链表的当前长度
}SLList;
typedef int ArrType[RADIX];
void Distribute(SLCell &r,int i,ArrType &f,ArrType &e){
for(j=0;j<Radix;++j) f[j]=0;
for(p=r[0].next;p;p=r[p].next){
j=ord(r[p].keys[i]);//ord将记录中第i个关键字映射到[0..RADIX-1]
if(!f[j]) f[j]=p;
else r[e[j]].next=p;
e[j]=p;
}
}
void Collect(SLCell &r,int i,ArrType f,ArrType e){
for(j=0;!f[j];j=succ(j));//找到第一个非空子表,succ为求后继函数
r[0].next=f[j];t=e[j];
while(j < RADIX){
for(j=succ(j);j < RADIX-1 && !f[j];j=succ(j));
if(f[j]){
r[t].next=f[j];
t=e[j];
}
}
r[t].next=0;
}
void RadixSort(SLList &L){
//L是采用静态链表表示的顺序表。
//对L作基数排序,使得L成为按关键字自小到大的有序静态链表
for(i=0;i<L.recnum;++i) L.r[i].next=i+1;
L.r[L.recnum].next=0;
for(i=0;i<L.keynum;++i){
Distribute(L.r,i,f,e);
Collect(L.r,i,f,e);
}
}
8.各种内部排序方法的比较讨论
排序方法 平均时间 最坏情况 辅助存储
简单排序 O(n^2) O(n^2) O(1)
快速排序 O(nlogn) O(n^2) O(logn)
堆排序 O(nlogn) O(nlog) O(1)
归并排序 O(nlogn) O(nlogn) O(n)
基数排序 O(d(n+rd)) O(d(n+rd)) O(rd)
简单排序包括除希尔排序之外的所有插入排序,起泡排序和简单选择排序。
归并插入排序。