概念
排序
所谓排序,是整理表中的记录,使之按关键字递增(或递减)有序排列。
内、外排序
在排序过程中,若整个表都是放在内存中处理,排序时不涉及数据的内、外存交换,则称之为内排序;反之,若排序过程中要进行数据的内、外存交换,则称之为外排序。
待排序元素类型定义:
typedef int KeyType; //定义关键字类型
typedef struct //记录类型
{ KeyType key; //关键字项
InfoType data; //其他数据项,类型为InfoType
} RecType; //排序的记录类型定义
算法总括
插入排序
直接插入排序
就像体育课排队一样,按照个子的高低,一个一个插入队列中,最终达成有序。
void InsertSort(RecType R[],int n)
{ int i, j; RecType tmp;
for (i=1;i<n;i++)
{ if (R[i].key<R[i-1].key]) //反序时
{ tmp=R[i];
j=i-1;
do //找R[i]的插入位置
{ R[j+1]=R[j]; //将关键字大于R[i].key的记录后移
j--;
} while (j>=0 && R[j].key>tmp.key)
R[j+1]=tmp; //在j+1处插入R[i]
}
}
}
时间复杂度分析:
折半插入排序
区别于直接插入排序,将数据元素以折半查找的方式从无序区移向有序区
void BinInsertSort(RecType R[],int n)
{ int i, j, low, high, mid;
RecType tmp;
for (i=1;i<n;i++)
{ if (R[i].key<R[i-1].key]) //反序时
{ tmp=R[i]; //将R[i]保存到tmp中
low=0; high=i-1;
while (low<=high) //在R[low..high]中查找插入的位置
{ mid=(low+high)/2; //取中间位置
if (tmp.key<R[mid].key)
high=mid-1; //插入点在左半区
else
low=mid+1; //插入点在右半区
} //找位置high
for (j=i-1;j>=high+1;j--) //记录后移
R[j+1]=R[j];
R[high+1]=tmp; //插入tmp
}
}
希尔排序
思路:
- d=n/2
- 将排序序列分为d个组,在各组内进行直接插入排序
- 递减d=d/2,重复② ,直到d=1(此时第一层while循环里的循环体相当于直接插入排序算法,可以与上面对照,将d换为1)
分块有序+整合=希尔排序。
void ShellSort(RecType R[],int n)
{ int i, j, d;
RecType tmp;
d=n/2; //增量置初值
while (d>0)
{ for (i=d;i<n;i++)
{ //对相隔d位置的元素组直接插入排序
tmp=R[i];
j=i-d;
while (j>=0&&tmp.key<R[j].key)
{ R[j+d]=R[j];
j=j-d;
}
R[j+d]=tmp;
}
d=d/2; //减小增量,最后一轮d=1,结束时d=0
}
}
希尔排序的时间复杂度约为O(n1.3)
交换排序
总体思路
冒泡排序
有序区总是在序列的上部或下部,像冒泡一样将最大值或最小值依次从无序区安排到有序区的正确位置。
void BubbleSort(RecType R[],int n)
{ int i, j; bool exchange; RecType temp;
for (i=0;i<n-1;i++)
{
exchange=false;
for (j=n-1;j>i;j--) //比较,找出最小关键字的记录
if (R[j].key<R[j-1].key)
{ temp=R[j]; R[j]=R[j-1]; R[j-1]=temp;
exchange=true;
}
if (exchange==false) return; //中途结束算法
}
}
快速排序
基本思路
设定基准将大无序递归分解成区域——有分块的思想,建议和归并排序一起看。
最后的子序列小到1(或0)时就可以从递归出口返回。
void QuickSort(RecType R[],int s,int t)
//对R[s]至R[t]的元素进行快速排序
{ int i=s,j=t; RecType tmp;
if (s<t) //区间内至少存在2个元素的情况
{ tmp=R[s]; //用区间的第1个记录作为基准
while (i!=j) //两端交替向中间扫描,直至i=j为止
{ while (j>i && R[j].key>=tmp.key) j--;
R[i]=R[j];
while (i<j && R[i].key<=tmp.key) i++;
R[j]=R[i];
}
R[i]=tmp;
QuickSort(R,s,i-1); //对左区间递归排序
QuickSort(R,i+1,t); //对右区间递归排序
}
//递归出口:不需要任何操作
}
快速递归排序树最后只要把叶子结点按照逻辑位置输出即可得到指定顺序的数列。
算法分析:
最坏的情况:此时时间复杂度为O(n2),空间复杂度为O(n)
最好的情况:此时时间复杂度为O(nlog2n),空间复杂度为O(log2n)
平均情况:平均时间复杂度为O(nlog2n),平均空间复杂度为O(log2n)
选择排序
直接选择排序
基本思路:
从无序区寻找一个最小的元素,让它与有序区后面第一个元素(无序区与有序区交接处的第一个无序区元素)交换位置。
void SelectSort(RecType R[],int n)
{ int i,j,k; RecType tmp;
for (i=0;i<n-1;i++) //做第i趟排序
{ k=i;
for (j=i+1;j<n;j++)
if (R[j].key<R[k].key)
k=j;
if (k!=i) //R[i]R[k]
{ tmp=R[i]; R[i]=R[k]; R[k]=tmp; }
}
}
简单选择排序的最好、最坏和平均时间复杂度为O(n2)
堆排序
基本思路:与直接选择排序相对比,采用堆排序的方法从无序区选择最小的元素。
构造堆
void sift(RecType R[],int low,int high) //调整堆的算法
{ int i=low, j=2*i; //R[j]是R[i]的左孩子
RecType tmp=R[i];
while (j<=high)
{
if (j<high && R[j].key<R[j+1].key) j++;
if (tmp.key<R[j].key) //双亲小
{ R[i]=R[j]; //将R[j]调整到双亲结点位置上
i=j; //修改i和j值,以便继续向下筛选
j=2*i;
}
else break; //双亲大:不再调整
}
R[i]=tmp;
}//完成了结点i在其左右子树中的定位——需要根据代码部分中的循环结合理解
代码
void HeapSort(RecType R[],int n)
{ int i; RecType tmp;
for (i=n/2;i>=1;i--) //循环建立初始堆
sift(R,i,n);
for (i=n; i>=2; i--) //进行n-1次循环,完成推排序
{
temp=R[1]; //R[1] R[i]
R[1]=R[i]; R[i]=tmp;
sift(R,1,i-1); //筛选R[1]结点,得到i-1个结点的堆
}
}
堆排序的大致时间:4n+klog2n
归并排序
思路:归并排序是多次将相邻两个或两个以上的有序表合并成一个新的有序表。
二路归并
void Merge(RecType R[],int low,int mid,int high)
{ RecType *R1;
int i=low, j=mid+1, k=0;
//k是R1的下标,i、j分别为第1、2段的下标
R1=(RecType *)malloc((high-low+1)*sizeof(RecType));
while (i<=mid && j<=high)
if (R[i].key<=R[j].key) //将第1段中的记录放入R1中
{ R1[k]=R[i]; i++;k++; }
else //将第2段中的记录放入R1中
{ R1[k]=R[j]; j++;k++; }
while (i<=mid) //将第1段余下部分复制到R1
{ R1[k]=R[i]; i++;k++; }
while (j<=high) //将第2段余下部分复制到R1
{ R1[k]=R[j]; j++;k++; }
for (k=0,i=low;i<=high;k++,i++) //将R1复制回R中
R[i]=R1[k];
free(R1);
}
代码:
void MergePass(RecType R[],int length,int n)
{ int i;
for (i=0;i+2*length-1<n;i=i+2*length) //归并length长的两相邻子表
Merge(R,i,i+length-1,i+2*length-1);
if (i+length-1<n) //余下两个子表,后者长度小于length
Merge(R,i,i+length-1,n-1); //归并这两个子表
}
排序:
void MergeSort(RecType R[],int n)
{ int length;
for (length=1;length<n;length=2*length)
MergePass(R,length,n);
}
每一趟归并的时间复杂度为 O(n)
总共需进行 [log2n] 趟。
二路归并排序的时间复杂度为Ο(nlog2n)