插入类
直接插入排序
将第i个记录的关键字Ki顺次与其前面记录的关键字Ki-1,Ki-2,…, K1进行比较,将所有关键字大于Ki的记录依次向后移动一个位置,直到遇见一个关键字小于或者等于Ki的记录Kj ,此时Kj后面必为空位置,将第i个记录插入空位置即可。
完整的直接插入排序是从i=2开始的,也就是说,将第1个记录视为已排好序的单元素子集合,然后将第2个记录插入到单元素子集合中。i从2循环到n,即可实现完整的直接插入排序。
算法实现
假设待排序记录存放在r[1…n]之中,为了提高效率,我们附设一个监视哨r[0],使得r[0]始终存放待插入的记录。
监视哨的作用有两个:
备份待插入的记录,以便前面关键字较大的记录后移;
防止越界
void InsSort(RecordType r[], int length)
{ for ( i=2 ; i< length ; i++ )
{
r[0]=r[i]; j=i-1;
while (r[0].key< r[j].key )
{ r[j+1]= r[j]; j=j-1; }
r[j+1]=r[0];
}
}
希尔排序
希尔排序又称缩小增量排序法,是一种基于插入思想的排序方法,它利用了直接插入排序的最佳性质,将待排序的关键字序列分成若干个较小的子序列,对子序列进行直接插入排序,使整个待排序序列排好序
具体实现:首先选定两个记录间的距离d1,在整个待排序记录序列中将所有间隔为d1的记录分成一组,进行组内直接插入排序,然后再取两个记录间的距离d2<d1,在整个待排序记录序列中,将所有间隔为d2的记录分成一组,进行组内直接插入排序,直至选定两个记录间的距离dt=1为止,此时只有一个子序列,即整个待排序记录序列。
void ShellInsert(RecordType r[ ], int length,int delta)
{ for(i=1+delta;i<= length;i++)
if(r[i].key < r[i-delta].key)
{ r[0]= r[i];
for(j=i-delta;j>0 &&r[0].key < r[j].key ;j-=delta)
r[j+delta]= r[j];
r[j+delta]= r[0];
}
}
void ShellSort(RecordType r[ ], int length)
{ for(i=0 ; i<=n-1; ++i)
ShellInsert(r,delta[i]);
}
交换类排序
冒泡排序
(相邻比序法)
冒泡排序是一种简单的交换类排序方法,它是通过相邻的数据元素的交换,逐步将待排序序列变成有序序列的过程。
冒泡排序的基本思想是:从头扫描待排序记录序列, 在扫描的过程中顺次比较相邻的两个元素的大小,逆序则交换位置。
void BubbleSort(RecordType r[ ], int length )
{ n=length; change=TRUE;
for ( i=1 ; i<= n-1 && change ;++i )
{ change=FALSE;
for ( j=1 ; j<= n-i ; ++j)
if (r[j].key> r[j+1].key )
{ x= r[j]; r[j]= r[j+1]; r[j+1]= z; change=TRUE;}
}
}
性能分析
最坏情况下,待排序记录按关键字的逆序进行排列,此时,每一趟冒泡排序需进行i次比较,3i次移动。
经过n-1趟冒泡排序后,总的比较次数为 , 总的移动次数为3n(n-1)/2次
因此该算法的时间复杂度为O(n2),空间复杂度为O(1)。
冒泡排序法是一种稳定的排序方法
快速排序
假设待划分序列为r[left],r[left+1],…,r[right],具体实现上述划分过程时,可以设两个指针i和j,它们的初值分别为left和right。
首先将基准记录r[left]移至变量x中,使r[left],即r[i]相当于空单元,然后反复进行如下两个扫描过程,直到i和j相遇:
j从右向左扫描,直到r[j].key < x.key时, 将r[j]移至空单元r[i],此时 r[j]相当于空单元。
i从左向右扫描, 直到r[i].key > x.key时, 将r[i]移至空单元r[j],此时r[i]相当于空单元。
当i和j相遇时,r[i](或r[j])相当于空单元,且r[i]左边所有记录的关键字均不大于基准记录的关键字,而r[i]右边所有记录的关键字均不小于基准记录的关键字。
最后将基准记录移至r[i]中,就完成了一次划分过程
int QKPass(RecordType r[], int left, int right)
{ x= r[left];low=left ; high=right;
while ( low<high )
{ while (low< high && r[high].key>=x.key ) high--;
if ( low <high ) { r[low]= r[high]; low++;}
while (low<high && r[low].key<x.key ) low++;
if ( low<high ) { r[high]= r[low]; high--;}
}
r[low]=x;return low;
}
void QKSort(RecordType r[ ], int low, int high )
{ if(1ow<high)
{ pos=QKPass(r, low, high);
QKSort(r, low, pos-1);
QKSort(r, pos+1, high);
}
}
分析快速排序的时间耗费, 共需进行多少趟排序,取决于递归调用深度。
快速排序的最好情况是每趟将序列一分两半,正好在表中间,将表分成两个大小相等的子表。同折半查找[log2n],总的比较次数 C(n)≤n+2C(n/2)。
快速排序的最坏情况是已经排好序, 其比较次数为:n^2 / 2 。
执行次数为:
T(n)≤Cn+2T(n/2)≤2n+4T(n/4)≤3n+4T(n/8)≤nlog2n+nT(1)
≈O(n log2n)
其中Cn是常数,表示n个元素排序一趟所需时间
选择类排序法
简单选择排序
基本思想:
第i趟简单选择排序是指通过n-i次关键字的比较,从n-i+1个记录中选出关键字最小的k号记录,并与第i个记录进行交换。
共需进行i-1趟比较,直到所有记录排序完成为止。
void SelectSort(RecordType r[], int length)
{
n=length;
for ( i=1 ; i<= n-1; ++i)
{
k=i;
for ( j=i+1 ; j<= n ; ++j)
if (r[j].key < r[k].key ) k=j;
if ( k!=i)
{ x= r[i]; r[i]= r[k]; r[k]=x;}
}
}
在简单选择排序过程中,所需移动记录的次数比较少。
最好情况下,即待排序记录初始状态就已经是正序排列了,则不需要移动记录。
最坏情况下,即待排序记录初始状态是按逆序排列的,则需要移动记录的次数最多为3(n-1)。
简单选择排序过程中需要进行的比较次数与初始状态下待排序的记录序列的排列情况无关。
当i=1时,需进行n-1次比较;
当i=2时,需进行n-2次比较;
依此类推, 共需要进行的比较次数是:
(n-1)+(n-2)+…+2+1=n(n-1)/2
即进行比较操作的时间复杂度为O(n2)。