插入排序
插入排序基本思想:
每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的文件中的适当位置,直到全部记录插入完成为止。
直接插入排序
核心思想
假设待排序的记录存放在数组R中,排序过程中的某一中间时刻,R被划分为2个区间【R[1],R[i-1]】和【R[i],R[n]】,前一个区间是已经完成排序的有序区,后一个区间则是为排序的无序区。直接插入排序就是将无序区的第一个记录R[i]插入到有序区中适当位置,R[1]到R[i]就成为新的有序区,递归下去,无序区为空时(即R[n]插入有序区),排序完成。
动画演示
如下图,蓝色就是无序区,橙色就是有序区,绿色表示正在比较,红色是无序区取出的第一个记录。
时间复杂度
文件初始态为正序时,算法时间复杂度为O(n)
最坏情况:文件初始态为逆序,算法时间复杂度为O()
平均时间复杂度:O()
空间复杂度
空间复杂度:O(1)
稳定性
直接插入是稳定的排序方法
算法优化
监视哨
优化后的代码
void InsertSort(int R[],int n){
int i,j;
for(i=2;i<=n;i++)
{
R[0]=R[i];
j=i-1;
while(R[0]<R[j])
{
R[j+1]=R[j];
j--;
}
R[j+1]=R[0];
}
}
#注意:此处的n是待排序数数量+1,因为定义数组时要增加把R[0]赋值为0作为监视哨,数组空间从R[1]开始存放数据
例题:
待排序的记录有8个,关键字分别为 49,38,65,97,76,13,27,18,用直接插入排序算法或者直接选择排序算法进行排序。程序实现对该排序的排序码动态变化过程进行模拟显示,要求按递增顺序排序。
答案:
#include<stdio.h>
void InsertSort(int num[]){
int i,j;
for(i=2;i<=9;i++)
{
num[0]=num[i];
j=i-1;
while(num[0]<num[j])
{
num[j+1]=num[j];
j--;
}
num[j+1]=num[0];
}
}
int main(){
int num[9]={0,49,38,65,97,76,13,27,18};
InsertSort(num);
int i=0;
for(i=1;i<9;i++) #输出时从R[1]开始,因为R[0]是监视哨
{
printf("%d ",num[i]);
}
}
希尔排序
核心思想
取定一个小于n的整数d1作为第一个增量,把文件全部记录分成若干个组,所有距离为d1的倍数的记录放在同一个组内,再在各个组内使用直接插入排序进行排序。完成后缩小整数d1的取值作为下一个增量,再次分组,继续组内直接插入排序。递归,当d1取值为1时,则为最后一次排序,为0时排序结束。
动画演示
抱歉,没时间学动画,先网图凑合
时间复杂度
最好情况:时间复杂度为O(n)
最坏情况:文件初始态为逆序,算法时间复杂度为O()
平均时间复杂度:O()
空间复杂度
空间复杂度:O(1)
稳定性
不稳定
希尔排序为何优于直接插入排序
直接插入排序在文件初始状态为正序时所需时间最少,实际上,当文件初始状态基本有序时直接排序所需的比较和移动次数均较少。在希尔排序刚开始时增量较大,分组较多,每组的记录数目少,故各组直接插入较快,当增量逐渐减小时,分组数直接减少,而组内记录数目增加,由于已经按前一次的增量拍过序,使文件较接近于有序状态,所以新的一趟排序较快,所以在效率上,希尔排序较直接插入排序有较大改进。
代码(未添加监视哨)
void shellsort(int *k,int n)
{
int i, j, temp, gap = n;
while(gap > 1)
{
gap = gap/2; /*增量缩小,每次减半*/
for(i=0;i<n-gap;i++) //n-gap 是控制上限不让越界
{
j = i + gap; //相邻间隔的前后值进行比较
if(k[i]>k[j])
{
temp = k[i];
k[i] = k[j];
k[j] = temp;
}
}
}
}
交换排序
基本思想
两两比较待排序记录的关键字,发现两个记录的次序相反时即进行交换,直到没有反序的记录为止。
冒泡排序
核心思想
设想被排序的记录数组R[0]到R[n-1]垂直竖立,将每个记录R[i]看成质量为R[i]的气泡,轻气泡会在重气泡的上方,从上往下扫描数组R,凡扫描到违反这一原则的气泡,使其往上飘(冒),反复执行,直到轻的气泡都在重的上方为止。
动画演示
蓝色未排序,橙色已排序,绿色正在比较
时间复杂度
最坏:O()
最好: O ( )
平均:O()
空间复杂度
O(1)
稳定性
稳定
代码(优化)
void sort(int a[],int len)
{
int i=0;
int j;
int t;
int flag; #循环提前结束变量
for(i=0;i<len-1;i++) [1]
{
flag=0;
for(j=0;j<len-i-1;j++)
{
if(a[j]>a[j+1])
{
t=a[j];
a[j]=a[j+1];
a[j+1]=t;
flag=1;
}
}
if(flag==0)
break;
}
}
快速排序
核心思想
在当前无序区R[low]和R[high]中任取一个记录作为比较的“基准”,用此基准将当前无序区划分为左右两个较小的无序子区:R[low]到R[i-1]和R[i+1]到R[high]。且左边的无序子区中记录均小于或等于基准,右边的无序子区中记录的关键字均大于或等于基准,基准则位于最终排序的位置上。左右两个无序子区均非空时,重复上述步骤,直到所有无序区中记录均已排好序为止。
动画演示
时间复杂度
最坏 O()
最好 O()
平均 O()
稳定性
不稳定
代码
// 划分算法
int Partition(RcdType rcd[], int low, int high){
// 对子序列rcd[low..high]进行一次划分,并返回枢轴应当所处的位置
// 使得在枢轴之前的关键字均不大于它的关键字,枢轴之后的关键字均不小于它的关键字
rcd[0] = rcd[low]; // 将枢轴移至数组的闲置单元
while (low < high){ // low和high从待排序列的两端交替地向中间移动
while (low < high && rcd[high].key >= rcd[0].key) --high;
rcd[low] = rcd[high]; // 将比枢轴关键字小得关键字移至低端
while (low < high && rcd[low].key <= rcd[0].key) ++low;
rcd[high] = rcd[low]; // 将比枢轴关键字打的关键字移至高端
}
rcd[low] = rcd[0]; // 枢轴关键字移到正确位置
return low; // 返回枢轴的位置
}
// 快速排序
void QSort(RcdType rcd[], int s, int t){
// 对记录序列rcd[s..t]进行快速排序
int privotloc;
if (s < t){ // 长度大于1
privotloc = Partition(rcd, s, t); // 对rcd[s..t]进行划分并返回枢轴位置
QSort(rcd, s, privotloc - 1); // 对枢轴之前的子序列递归快排
QSort(rcd, privotloc + 1, t); // 对枢轴之后的子序列递归快排
}
}
// 对记录的顺序表L进行快速排序
void QuickSort(RcdSqList &L){
QSort(L.rcd, 1, L.length);
}
选择排序
核心思想
每一趟从待排序的记录中选出关键字最小的记录,顺序放在排好序的子文件的最后,直到全部记录排好为止。
直接选择排序
思想
第一趟排序在无序区中R[0]到R[n-1]中选出最小记录,与R[0]交换;第二趟则是在无序区R[1]到R[n-1]中选出最小记录,与R[1]交换,没趟都使有序区中记录加一。如此循环下去。进行n-1趟后,整个文件就是有序的。
动画演示
时间复杂度
O()
空间复杂度
O(1)
稳定性
不稳定
代码
void select_sort(int a[],int n)//n为数组a的元素个数
{
//进行N-1轮选择
for(int i=0; i<n-1; i++)
{
int min_index = i;
//找出第i小的数所在的位置
for(int j=i+1; j<n; j++)
{
if(a[j] < a[min_index])
{
min_index = j;
}
}
//将第i小的数,放在第i个位置;如果刚好,就不用交换
if( i != min_index)
{
int temp = a[i];
a[i] = a[min_index];
a[min_index] = temp;
}
}
}