一、概述
1.内部排序:整个排序过程不需要访问外存便能完成
2.外部排序:若参加排序的记录数量很大, 整个序列的排序过程不可能在内存中 完成,则称此类排序问题为外部排序。
3.一种排序方法,如果排序后具有相同关键字的记录仍维持排序之前的相对次序,则称之为稳定的,否则称为不稳定的。
二、内部排序
本章算法描述所用的待排记录的静态存储结构
#define MAXSIZE 20
typedef int KeyType; //定义关键字类型为int
typedef struct
{
KeyType key; //关键字项
InfoType otherinfo; //其他信息
} RedType;
typedef struct
{
RedType r[MAXSIZE +1]; //r[0]作为哨兵
int length;
} SqList;
1.插入排序
(1)直接插入排序(顺序查找)
O(n^2) ,稳定的
void InsertSort(SqList &L)
{ // 对顺序表L作直接插入排序
for (i=2; i<=L.length; ++i)
if (L.r[i].key< L.r[i-1].key)
{ L.r[0] = L.r[i]; // 复制为哨兵
L.r[i] = L.r[i-1];
for (j=i-2; L.r[0].key< L.r[j].key; --j)
L.r[j+1] = L.r[j]; // 记录后移
L.r[j+1] = L.r[0]; // 插入到正确位置
}
} // InsertSort
(2)折半插入排序(折半查找)
O(n^2) ,稳定的
void BInsertSort( SqList &L)
{ for( i=2; i<=L.length; ++i){
L.r[0] = L.r[i]; low = 1; high = i-1;
while( low <= high ){
m = (low+high)/2;
if( L.r[0].key < L.r[m].key) high = m-1;
else low = m+1;
}
for( j=i-1; j>=high+1; --j) L.r[j+1] = L.r[j];
L.r[high+1] = L.r[0];
}
}
(3)希尔排序(逐趟缩小增量)
不稳定
void ShellInsert(SqList &L, int dk)
/* 本算法和一趟直接插入排序相比, 作了以下修改: (1)前后记录位置的增量是dk,而不是1; (2) r[0]只是暂存单元,不是哨兵。当j<=0时,插入位置已找到。 */
{ for (i=dk+1; i<=L.length; ++i)
if (LT(L.r[i].key, L.r[i-dk].key))
{ // 需将L.r[i]插入有序增量子表
L.r[0] = L.r[i]; // 暂存在L.r[0]
for (j=i-dk; j>0 && LT (L.r[0].key, L.r[j].key); j-=dk)
L.r[j+dk] = L.r[j]; // 记录后移,查找插入位置
L.r[j+dk] = L.r[0]; // 插入
}
} // ShellInsert
void ShellSort(SqList &L, int dlta[], int t)
{
for (int k=0; k<t; ++k)
ShellInsert(L, dlta[k]); // 一趟增量为dlta[k]的插入排序
} // ShellSort
//按增量序列dlta[0..t-1]对顺序表L作希尔排序
2.快速排序
(1)冒泡排序:两两比较相邻记录的关键字,如果反序则交换,知道没有反序记录为止。每个位置的要和剩余的都比较,确定该位置是哪个数之后再将下一个位置里的数字和剩余的都做比较,确定第二个位置是谁。
(2)一趟快速排序:找一个记录,以它的关键字作为“枢轴”,凡其关键字小于枢轴的记录均移动至该记录之前,反之,凡关键字大于枢轴的记录均移动至该记录之后。
(PPT37动态演示)
(3)快速排序:确定一个关键字(一般选第一个),设置high和low进行比较。
void QSort(SqList &L, int low, int high)
{
int pivotloc;
if (low < high) // 长度大于1
{
pivotloc = Partition(L, low, high);
QSort(L, low, pivotloc-1); // 对低子表递归排序
QSort(L, pivotloc+1, high); // 对高子表递归排序
}
} // QSort
(4)快速排序的时间分析
O(nlogn)
3.选择排序
(1)简单选择排序:先从n个里选最小的放在位置0,再从剩下的里面找到最小的放到位置1...
不稳定 O(n^2)
(2)树形选择排序
(3)堆排序
大顶堆:每个结点是的值都大于或等于其左右孩子结点的值
小顶堆:每个结点是的值都小于或等于其左右孩子结点的值
在大顶堆中,将堆顶和最后一个记录交换,即得第一趟的结果;再使剩余n-1个元素的序列重又建成一个堆,则得到n个元素的次大值。如此反复执行,便能得到一个有序序列,这个过程称为堆排序。
筛选:自堆顶到叶子结点的调整过程,
首先在堆顶元素与最后一个元素交换之后,树的新的根结点不满足堆的要求,此时用该根结点与其左右子树的根结点比较,选满足要求的结点交换; 其次被交换的子树不满足堆的要求,继续向下传递,直到叶子结点;
筛选算法:
Typedef SqList Heaptype;
void HeapAdjust(HeapType &H, int s, int m)
{ // 已知H.r[s..m]中记录的关键字除H.r[s].key之外均满足堆的定义,本函数调整H.r[s]的关键字,使H.r[s..m]成为一个大顶堆
RedType rc;
rc = H.r[s];
for (j=2*s; j<=m; j*=2)
{ // 沿key较大的孩子结点向下筛选
if (j<m && H.r[j].key<H.r[j+1].key) ++j; // j为key较大的下标
if (rc.key>= H.r[j].key) break;
H.r[s] = H.r[j]; s = j;
}
H.r[s] = rc; // rc应插入在位置s上
} // HeapAdjust
堆排序算法:
void HeapSort(HeapType &H)
{ RedType temp;
for (i=H.length/2; i>0; --i) // 把H.r[1..H.length]建成大顶堆
HeapAdjust ( H, i, H.length );
for (i=H.length; i>1; --i)
{ // 将堆顶记录和当前未经排序子序列Hr[1..i]中最
后一个记录相互交换
temp=H.r[i]; H.r[i]=H.r[1]; H.r[1]=temp;
HeapAdjust(H, 1, i-1); // 将H.r[1..i-1] 重新调整为大顶堆
}
} // HeapSort
最坏:O(nlog2n)
4.归并排序
每趟归并所花的时间是O(n) ; 二路归并排序的时间复杂性为O(nlog2n )。稳定的。
5.基数排序
最高为优先MSDF,最低位优先LSDF
6.各种排序方法的综合比较
O(nlogn) | 快速排序、堆排序、归并排序 |
O(n^2) | 直接插入排序、冒泡排序、简单选择排序 |
O(n) | 基数排序 |
(1)当待排记录序列按关键字顺序有序时快速排序的时间性能蜕化为O(n2) ;简单排序、堆排序和归并排序的时间性能不随记录序列中关键字的分布而改变。
(2)所有的简单排序方法(包括:直接插入、起泡和简单选择) 和堆排序的空间复杂度为O(1);
(3)快速排序为O(logn),为递归程序执行过程中栈所需的辅助空间;
(4)归并排序所需辅助空间最多,其空间复杂度为 O(n);
(5)链式基数排序需附设队列首尾指针,则空间复杂度为 O(r),r 为关键字的取值范围。
(6)快速排序、堆排序和希尔排序是不稳定的排序方法。
三、外部排序