排序算法
本文总结内部排序(数据在内存中而不是存储在文件中)的一些经典算法,对《数据结构》中相关知识给予总结和复习,同时方便查阅。
约定:为了方便下文把“内部排序”简称为“排序”。
按照排序过程中依据的不同原则对排序方法分类,大致可分为:插入排序、交换排序、选择排序、归并排序和基数排序等。
约定:为了简洁这里只研究如何对“关键字”排序,将“关键字”和“记录”视为一体,相信任何有编程知识的人都知道怎么推广到具体程序中对记录排序。同样为了简便,假设所有的排序算法都将无序序列排列为“非递减”顺序。
一、 插入排序
1.1 直接插入排序( Straight Insertion Sort )
它的基本操作是将一个记录插入到已经排好序的有 N 个记录的有序序列中,从而形成一个新的、记录数为 N+1 的有序序列。(此处的“序列”可以实作为数组或链表等,本文讨论的都是基于数组的实作,链表对于某些操作会更加方便)
对于一个有 N 个记录的待排序的序列,整个排序过程为进行 N - 1 趟插入:先将表中的第一个记录看成一个有序的子序列,然后从第二个记录起逐个查找合适的位置进行插入,直至整个序列变成有序序列为止。
算法代码:
template <typenameT>
void InsertSort( T *L, intlen )
{
T temp;
int j;
for (inti = 1; i < len; i++)
{
// 如果当前的数据比的已序序列中最大的数据小,
// 则需要插入到前方某个位置上
if (L[i] < L[i-1])
{
temp = L[i];
// 后移数据
L[i] = L[i-1]; // 减少一次没必要的比较
for (j = i-2; (j >= 0) && (temp < L[j]); j--)
L[j+1] = L[j];
// 插入到正确的位置
L[j+1] = temp;
}
}
}
小改进:
(1) 折半插入排序 :把“直接插入排序”中“查找合适的位置”的查找算法从“逐个比较”替换为“折半查找”。
(2) 2- 路插入排序 :这是在( 1 )的改进基础上,针对插入新记录而导致大块记录需要后移而进行的改进,具体地说:另设一个与原序列 L 同类型大小相等的序列 D ,并设置 D[0] = L[0] ;从 L 中的二记录开始依次插入到 D[0] 之前或之后的有序子序列。在实现算法时, D 被看成了一个循环序列,并设置两个指针 first 和 final 分别指示排序过程中得到的有序序列的第一个记录和最后一个记录在 D中的位置。
(3) 表插入排序 :用静态链表作为存储记录的数据结构,这样只需修改 next 指示量而无需移动记录(很怀疑为什么不直接用链表,搞得这么麻烦,这个算法就不多说了)。
上述几种算法的时间复杂度均为 O ( n2 ) 。
1.2 希尔排序( Shell’s Sort )
此算法又称为“缩小增量排序”( Diminshing Increment Sort ),基本思想是:先将整个待排记录序列分割成若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”(即减少“当前记录比已序序列中最大的记录要大”的情形)时,再对全体记录进行一次直接插入排序。子序列的构成不是简单地“逐段分割”,而是将向各某个“增量”的记录组成一个子序列,且增量本身在逐渐的缩小(例如:依次以增量 5、 3 、 1 对序列排序)。“增量”序列的选择可以有各种取法,且各种取法的时间复杂度不一,但必须满足:增量序列中的值没有除了 1 之外的公因子,并且最后一个增量值必须为 1 。
算法代码:
//------------------------------------------------------------------------
// 功能: 一趟希尔排序
// 参数: L ---- 待排序序列; len ---- 序列长度;dk ---- 子序列分割增量
//------------------------------------------------------------------------
template <typenameT>
void ShellInsert( T *L, intlen, intdk )
{
T temp;
int j;
for (inti = dk; i < len; i++)
{
// 如果当前的数据比的已序序列中最大的数据小,
// 则需要插入到前方某个位置上
if (L[i] < L[i - dk])
{
temp = L[i];
// 后移数据
L[i] = L[i - dk]; // 减少一次没必要的比较
for (j = i - 2 * dk; (j >= 0) && (temp < L[j]); j -= dk)
L[j + dk] = L[j];
// 插入到正确的位置
L[j + dk] = temp;
}
}
}
//----------------------------------------------------------------------------
// 功能: 希尔排序
// 参数: L ---- 待排序序列; tlen ---- 序列长度;
// dlta ---- 子序列分割增量数组; dlen ---- 子序列分割增量数组的长度
//----------------------------------------------------------------------------
template <typenameT>
void ShellSort(T *L, inttlen, intdlta[], intdlen)
{
for (intk = 0; k < dlen; k++)
ShellInsert(L, tlen, dlta[k]);
}
二、 交换排序
2 . 1 起(冒)泡排序( Bubble Sort )
n 个记录的序列排序过程:第 i ( i =1 , 2...n-1 )趟排序过程:从第 0 个记录开始,依次比较相邻两个记录,如果逆序则交换之,直到比较第 n – i – 1 和 n – i 个记录;重复上述排序过程直到“某趟排序没有发生交换”或排序到第 n-1 趟。
算法伪代码:
//-----------------------------------------------------------------------------
// 功能: 冒泡排序
// 参数: L ---- 待排序序列 len ---- 序列长度
//-----------------------------------------------------------------------------
template <typenameT>
void BubbleSort(T *L, intlen)
{
for (inti = 1; i < len; i++)
{
bSwap = false;
for (intj = 0; j < len - i; j++)
{
if (L[j] > L[j + 1])
{
// 交换记录
L [j] ←→ L[j + 1];
bSwap = true;
}
}
// 如果此趟没有发生交换,说明已序
if (!bSwap)
return;
}
}
复杂度为O(n2)
2 . 2 快速排序( Quick Sort )
基本思想 :通过一趟排序将待排记录分割成对立的两部分,其中一部分记录均比另一部分记录小,再分别对这两部分排序,已达到这个序列有序。
一趟排序具体步骤为:任选一个记录(一般选第一个记录)为枢轴 pivot ,设两个指针 low 和 high 指向当前序列的第一和最后一个记录,先从 high 所指位置向前搜索第一个小于 pivot 的记录并互换,然后从 low所指位置向后搜索第一个大于 pivot 的记录并互换,重复这两步直到 low = high 。
算法代码:
//-------------------------------------------------------------------
// 功能: 一趟快速排序,将序列分成两部分,一部分均大于另一部分
// 参数: L ---- 待排序序列;low ---- 待排序列的开始记录的索引
// high ---- 待排序列的最后一个记录的索引
//-------------------------------------------------------------------
template <typenameT>
int Partition(T *L, intlow, inthigh)
{
T pivot(L[low]);
while (low < high)
{
while ((low < high) && (L[high] >= pivot)) high--;
L[low] = L[high];
while ((low < high) && (L[low] <= pivot)) low++;
L[high] = L[low];
}
L[low] = pivot;
return low;
}
//-------------------------------------------------------------------
// 功能: 快速排序
// 参数: L ---- 待排序序列;low ---- 待排序列的开始记录的索引
// high ---- 待排序列的最后一个记录的索引
//-------------------------------------------------------------------
template <typenameT>
void QSort(T *L, intlow, inthigh)
{
if (low < high)
{
int pviotloc = Partition(L, low, high);
QSort(L, low, pviotloc - 1);
QSort(L, pviotloc + 1, high);
}
}
//-------------------------------------------------------------------
// 功能: 快速排序的参数简化写法
// 参数: L ---- 待排序序列;
// len ---- 待排序列的记录数目
//-------------------------------------------------------------------
template <typenameT>
void QuickSort(T* L, intlen)
{
QSort(L, 0, len - 1);
}
复杂度O( n log n ),在同数量级算法中,其平均性能好。