排序算法 之 冒泡排序 插入排序 希尔排序 堆排序
简单排序
排序算法的模板函数
void x_Sort( ElementType A[], int N)
大多数情况下, 为简单起见,讨论都是从小到大的整数排序
N 是 正整数
只讨论基于比较的排序(> = < 有定义)
只讨论内部排序
稳定性: 任意两个相等的数据,排序前后的相对位置不发生变化
没有一种排序是任何情况下都表现最好的
冒泡排序
void Bubble_Sort ( ElementType A[], int N )
{
int flag;
for ( int P = N - 1; P >= 0; P-- ){
flag = 0;
for ( int i = 0; i < P; i++ ) { //一趟冒泡
if ( A[i] > A[i+1] ) {
Swap ( A[i], A[i+1] );
flag = 1;//标识发生了交换
}
}
if ( flag == 0 ) break; //全称无交换
}
}
最好情况: 顺序 T = O( N )
最坏情况: 逆序 T = O( N^2 )
冒泡排序算法是稳定的排序算法。
插入排序
/**********插入排序算法***********************/
void Insertion_Sort ( ElementType A[], int N )
{
ElementType Tmp;
for ( int P = 1; P < N; P++ ) {//默认起始时,只有一个元素时就是有序的
Tmp = A[ P ];//摸下一张牌, 即取出未排序序列中的第一个元素
for ( int i = P; i > 0 && A[ i - 1] > Tmp; i-- )
A[ i ] = A[ i - 1]; // 移出空位, 即一次与已经排序序列中元素比较并右移
A[ i ] = Tmp;//新牌落位, 即放进合适的位置
}
}
最好情况 : 顺序 T = O( N )
最坏情况 : 逆序 T = O ( N^2)
插入排序是稳定的排序算法
例: 给定初始序列 { 34, 8, 64, 51, 32, 21 },冒泡排序和插入排序
分别需要多少次元素交换才能完成?
时间复杂度下界
对于下标 i < j , 如果 A [ i ] > A [ j ], 则称 (i , j)是一对逆序对(inversion)
问题: 序列{ 34, 8, 64, 51, 32, 21}中有多少逆序对?
(34, 8) (34, 32) (34, 21) (64, 51) (64, 32) (64, 21) (51, 32) (51, 21) (32, 21)
交换2个相邻元素正好消去一个逆序对!
插入排序: T(N, I) = O( N + I)
如果序列基本有序, 则插入排序简单且高效
定理: 任意N个不同元素组成的序列平均具有 N (N - 1)/4个逆序对、
定理 : 任何仅交换相邻两个元素来排序的算法, 其平均时间复杂度为 Ω(N^2)
这意味着:要提高算法效率,我们必须
每次消去不止1个逆序对!
每次交换相隔较远的两个元素!
希尔排序 (by Donald Shell)
定义增量序列 D_m > D_(m-1) > .... > D_1 = 1
对每个D_k 进行"D_k - 间隔" 排序 (k = M, M -1, ..., 1)
注意:“D_k间隔”有序的序列, 在执行“D_(k - 1) - 间隔”排序后, 仍然是
"D_k - 间隔" 有序的
希尔增量序列
原始希尔排序 D_m = [N / 2]向下取整 , D_k = [ D_(k + 1) / 2] 向下取整
void Shell_Sort ( ElementType A[], int N )
{
for ( int D = N / 2; D > 0; D /= 2) { //希尔增量序列
for ( int P = D; P < N; P++) { // 插入排序
Tmp = A[P];
for ( int i = P; i >= D && A[i - D] > Tmp; i -= D )
A[i] = A[i - D];
A[i] = Tmp;
}
}
}
最坏情况: T = Θ(N^2)
更多的增量序列
Sedgewick 增量序列
void ShellSort ( ElementType A[], int N )
{//希尔排序 -- 用Sedgewick 增量序列
int Si, D, P, i;
ElementType Tmp;
//这里只列出了一小部分增量
int Sedgewick[] = {929, 505, 209, 109, 41, 19, 5, 1, 0};
for (Si = 0; Sedgewick[Si] >= N; Si++)
;//初始的增量Sedgewick[Si] 不能超过待排序序列长度
for ( D = Sedgewick[Si]; D > 0; D = Sedgewick[++Si] )
for ( P = D; P < N; P++ ) { //插入排序
Tmp = A[ P ];
for ( i = P; i >= D && A[i - D] > Tmp; i -=D )
A[i] = A[i - D];
A[ i ] = Tmp;
}
}
堆排序
选择排序
void Selection_Sort ( ElementType A[], int N)
{
for ( int i = 0; i < N; i++ ) {
MinPosition = ScanForMin ( A, i, N - 1 );
// 从 A[i] 到A[N - 1] 中找到最小元, 并将其位置赋给MinPosition
Swap ( A[i], A[MinPosition] );
// 将未排序的最小元换到有序部分的最后位置
}
}
如何快速找到最小元????
堆排序算法
算法1
void Heap_Sort ( ElementType A[], int N )
{
BuildHeap(A); // O(N)
for ( int i = 0; i < N; i++ )
TmpA[i] = DeleteMin(A); // O(logN)
for ( int i = 0; i < N ; i++ ) // O(N)
A[i] = TmpA[i];
}
T(N) = O( N logN )
需要额外O(N) 空间, 并复制元素需要时间。
算法2:
void Heap_Sort ( ElementType A[], int N )
{
for ( int i = N/2 - 1; i >= 0; i-- ) // BuildHeap
PerDown (A, i, N);
for ( int i = N - 1; i > 0 ; i-- ) {
Swap( &A[0], &A[i] ); //DeleteMax
PerDown ( A, 0, i );
}
}
定理: 堆排序处理 N 个不同元素的随机排列的平均比较次数是
2 N logN - O( N log logN) .
虽然堆排序给出最佳平均时间复杂度,但实际效果不如用Sedgewick增量序列的希尔排序。
-- code --
void Swap ( ElementType *a, ElementType *b)
{
ElementType t = *a;
*a = *b;
*b = t;
}
void PerDown( ElementType A[], int p, int N )
{
//将N个元素数组中以 A[p] 为根的子堆调整为最大堆
int Parent, Child;
ElementType X;
X = A[p];//取出根结点存放的值
for ( Parent = p; (Parent*2 + 1) < N; ParentType = Child) {
Child = Parent * 2 + 1;
if ( (Child ! = N-1) && (A[Child] < A[Child + 1]) )
Child++;//Child指向左右子节点的较大者
if ( X >= A[Child] ) break;//找到合适位置
else //下虑
A[Parent] = A[Child];
}
A[Parent] = X;
}
void Heap_Sort ( ElementType A[], int N )
{
for ( int i = N/2 - 1; i >= 0; i-- ) // BuildHeap
PerDown (A, i, N);
for ( int i = N - 1; i > 0 ; i-- ) {
Swap( &A[0], &A[i] ); //DeleteMax
PerDown ( A, 0, i );
}
}