数据类型设置
数据存储方式为顺序存储结构,待排序的一组记录存放在地址连续的一组存储单元上
typedef int KeyType; //定义关键字类型为整数类型
typedef struct
{
KeyType key[MAXSIZE+1]; //key[0]闲置或用作哨兵
int length; //顺序表长度
}
插入排序
1.直接插入排序
主要思想:
将一个记录插入到已经排好序的有序表中,得到一个新的、长度加1的有序表
算法:
void InsertSort(SqList &l)
{
//插入排序
int i,j;
for(i=2;i<=l.length;i++)
{
if(l.key[i]<l.key[i-1]) //待插入关键字l.key[i]比已排好序部分l.key[1...i-1]小时进行插入
{
l.key[0]=l.key[i]; //复制哨兵
for(j=i-1;l.key[0]<l.key[j];j--) //记录后移
l.key[j+1]=l.key[j];
l.key[j+1]=l.key[0]; //插入到正确位置
}
}
}
直接插入排序容易实现,辅助空间只需一个。时间上,最好情况比较次数为n-1,不需要移动。最坏情况比较次数为(n+2)(n-1)/2,移动次数(n+4)(n-1)/2,时间复杂度为O(n²)
2.折半插入排序
主要思想:
插入排序的基本操作为在一个有序表中进行查找和插入,查找利用折半查找实现
算法:
void BInsertSort(SqList &l)
{
//折半插入排序
int i,j,low,high,mid;
for(i=2;i<=l.length;i++)
{
l.key[0]=l.key[i]; //l.keyp[i]暂存到l.key[0]
low=1;
high=i-1;
while(low<=high) //折半查找有序插入的位置
{
mid=(low+high)/2;
if(l.key[0]<l.key[mid])
high=mid-1;
else
low=mid+1;
}
for(j=i-1;j>=high+1;j--) //记录后移
l.key[j+1]=l.key[j];
l.key[high+1]=l.key[0]; //插入
}
}
折半插入排序辅助空间和直接插入排序形同。时间上,折半排序减少了比较次数,记录移动次数不变,时间复杂度为O(n²)
3.希尔排序
主要思想:
将整个待排序列分割成若干个子序列分别进行直接插入排序,等到整个序列整体有序时,再对全体记录进行一次直接插入排序
算法:
void ShellSort(SqList &l,int dlta[],int t)
{
//希尔排序
int i,j,k;
for(k=0;k<t;k++) //增量序列中的值没有除1之外的公因子
{ //最后一个增量值必须为1
int dk=dlta[k];
for(i=dk+1;i<=l.length;i++)
{
if(l.key[i]<l.key[i-dk]) //将l.key[i]插入有序增量子表,此过程为直接插入排序
{
l.key[0]=l.key[i]; //l.key[0]暂存数据
for(j=i-dk;j>0&&l.key[0]<l.key[j];j-=dk)
l.key[j+dk]=l.key[j];
l.key[j+dk]=l.key[0];
}
}
}
}
希尔排序时间效率较前两种排序方法有较大进步,时间跟所取的增量序列有关,增量序列中的的值没有除1之外的公因子,且最后一个增量值必须为1
交换排序
1.冒泡排序
主要思想:
第一趟,将关键字最大的记录安置在最后一个记录位置上;第二趟排序,将前n-1个记录中关键字最大的记录安置在第n-1个位置;以此类推
整个排序要进行k(1≤k≤n-1),当一趟排序中没有进行交换记录的操作时结束排序
算法:
void BubbleSort(SqList &l)
{
//冒泡排序
int i,j,temp,flag=1;
for(i=1;flag==1;i++) //当在一趟排序中没有进行交换记录的操作时结束排序
{
flag=0;
for(j=1;j<=l.length-i;j++)
{
if(l.key[j]>l.key[j+1])
{
temp=l.key[j+1];
l.key[j+1]=l.key[j];
l.key[j]=temp;
flag=1;
}
}
}
}
冒泡排序借助交换进行排序,辅助空间只需一个。时间上,最好情况进行n-1比较,不移动记录,最坏情况需要进行n-1趟排序,要进行n(n-1)/2次比较,时间复杂度为O(n²)
2.快速排序
主要思想:
取一个记录作为枢纽,通过一趟排序将待排记录分割成独立的两部分,一部分记录关键字记录小于枢纽记录,另一部分记录大于枢纽记录,继续对两部分记录进行排序
算法:
void QuickSort(SqList &l,int low,int high)
{
//快速排序
if(low<high)
{
int pivotloc=Partition(l,low,high); //找出枢纽,将l.key[low...high]一分为二
QuickSort(l,low,pivotloc-1); //对低子表递归排序
QuickSort(l,pivotloc+1,high); //对高子表递归排序
}
}
int Partition(SqList &l,int low,int high)
{
//将枢纽移至正确位置
int pivotkey=l.key[low]; //用第一个记录作为枢纽记录
l.key[0]=l.key[low]; //记录枢纽关键字
while(low<high) //从表的两端向中间扫描
{
while(low<high&&l.key[high]>=pivotkey)
high--;
l.key[low]=l.key[high]; //将比枢纽记录小的记录移到低端
while(low<high&&l.key[low]<=pivotkey)
low++;
l.key[high]=l.key[low]; //将比枢纽记录大的记录移到高端
}
l.key[low]=l.key[0];
return low; //返回枢纽位置
}
快速排序需要一个栈空间实现递归。时间上,快速排序平均时间复杂度为O(nlogn),平均性能最好,但不稳定当序列基本有序时,蜕化为冒泡排序,时间复杂度为O(n²)
选择排序
1.简单选择排序
主要思想:
通过n-i次关键字间的比较,从n-i+1个记录中选择出关键字最小的记录,并和第i个记录交换算法:
void SelectSort(SqList &l)
{
//简单选择排序
int i,j,temp,k;
for(i=1;i<=l.length;i++)
{
temp=l.key[i];
k=i;
for(j=i;j<=l.length;j++) //选择第i小的记录
{
if(temp>l.key[j])
{
temp=l.key[j];
k=j;
}
}
if(k!=i)
{
l.key[k]=l.key[i];
l.key[i]=temp;
}
}
}
简单选择排序辅助空间只需一个。时间上,比较次数为n(n-1)/2,移动次数为0-3(n-1),时间复杂度为O(n²)
2.堆排序
归并排序
主要思想:
将数组中前后相邻的两个有序序列归并为一个有序序列
算法:
void MergeSort(SqList l1,SqList &l2,int s,int t)
{
//归并排序
//将l1.key[s...t]归并排序为l2.key[s...t]
if(s==t)
l2.key[s]==l1.key[s];
else
{
int m=(s+t)/2;
MergeSort(l1,l1,s,m); //递归将l1.key[s..m]归并为有序
MergeSort(l1,l1,m+1,t); //递归将l1.key[m+1..t]归并有序
Merge(l1,l2,s,m,t); //将l1.key[s..m]和l1.key[m+1..t]归并到l2[s..t]
}
}
void Merge(SqList l1,SqList &l2,int i,int m,int n)
{
//将有序的l1.key[i...m]和l1.key[m+1...n]归并为有序的l2.key[i..n]
int j,k;
for(j=m+1,k=i;i<=m&&j<=n;k++)
{
if(l1.key[i]<l1.key[j])
l2.key[k]=l1.key[i++];
else
l2.key[k]=l1.key[j++];
}
if(i<=m)
while(i<=m)
l2.key[k++]=l1.key[i++];
if(j<=n)
while(j<=n)
l2.key[k++]=l1.key[j++];
}
归并排序是一种稳点的排序方法,辅助空间需要n个,时间复杂度为O(nlogn)
基数排序
1.简单选择排序
主要思想:
借助多关键字排序的思想对单逻辑关键字进行排序
算法:
typedef struct LNode
{
KeyType data;
struct LNode *next;
}LNode,*LinkList;
void RadixSort(SqList &l)
{
//基数排序
LinkList ll[10];
for(int i=0;i<10;i++) //构造静态链表
{
ll[i]=(LinkList)malloc(sizeof(LNode));
ll[i]->data=0;
ll[i]->next=NULL;
}
for(int i=1;i<=MAX_NUM_OF_KEY;i++) //按最低位优先依次对各关键字进行分配和收集
{ //MAX_NUM_OF_KEY为关键字的最大位数
Distribute(l,ll,i); //第i趟分配
Collect(l,ll); //第i趟收集
}
}
void Distribute(SqList l,LinkList *ll,int i)
{
int j,k;
int a[l.length+1];
for(j=1;j<=l.length;j++) //求各关键字的第i位上的值
{
ll[j-1]->data=0; //每趟分配中,初始化链表长度
a[j]=l.key[j];
for(k=1;k<=i;k++)
{
a[j]=a[j]/10;
}
a[j]=a[j]%10;
}
for(j=1;j<=l.length;j++) //映射到链表中
{
LinkList node;
node=(LinkList)malloc(sizeof(LNode));
node->data=l.key[j];
node->next=NULL;
int num=0;
LinkList p=ll[a[j]];
while(num++<(ll[a[j]]->data))
p=p->next;
p->next=node;
ll[a[j]]->data++; //链表头结点记录链表的长度
}
}
void Collect(SqList &l,LinkList *ll)
{
int i,num,k;
for(i=0,k=1;i<10;i++) //将各链表连接起来
{
LinkList p=ll[i];
num=0;
while(num<ll[i]->data)
{
p=p->next;
l.key[k++]=p->data;
num++;
}
}
}
基数排序的辅助空间需要rd个,r为基数值,d为关键字位数。时间上,每一趟分配时间复杂度为O(n),每一趟收集时间复杂度为O(rd),时间复杂为O(d(n+rd))
各种内部排序方法的比较
参考书目:数据结构(C语言版) 严蔚敏
图片来源于网络