前提
void X_Sort(ElementType A[],int N)
- 大多数情况下,为简单起见,讨论从小到大整数排序
- N是正整数
- 只讨论基于比较的排序(<=>有定义)
- 只讨论内部排序
- 稳定性:任意两个相等的数据排序前后的相对位置不发生改变
- 没有一种排序是任何情况下都表现最好的
冒泡排序
顾名思义,比较相邻的泡泡大小,当上面的泡泡大时,互换位置。先比较1位和2位的大小,则互换位置。
然后比较2位和3位的大小,2比3小,则不动。依次类推,3位比4大,则互换
然后4位比5位大,互换位置。
这时完成了一趟排序,最大的泡泡位于最下面。依次第二趟只需要对1到n-1的泡泡进行排序。
代码实现:
void Bubble_Sort(ElementType A[],int N)
{ for(p=N-1;p>=0;p--){
flag=0;
for(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(N2)
优点:简单,对数组和链表均适用,稳定
插入排序
就像抓牌一样,首先抓一张J
第二张是K,比J大,直接放在后面
第三张A比K大,放在K后面
第四张是Q,需要和A比较,比A小,因此在A前面,所以把A向后一位,然后和K比较,依然比K小,将K也向后一位,再与J比较,比J大,因此放在J后面。
实现代码
void Insertion_Sort(ElementType A[],int N)
{ for(p=1;p<N;p++) {
Tmp=A[p];//摸下一张牌
for(i=p;i>0&&A[i-1]>Tmp;i--)
A[i]=A[i-1];//移出空位
A[i]=Tmp;//新牌落位
}
}
最好情况:顺序T=O(N)
最坏情况:逆序T=O(N2)
时间复杂度下界
- 对于下标i<j,如果A[i]>A[j],则称(i,j)是一对逆序对(inversion)
- 交换2个相邻元素正好消去1个逆序对
- 插入排序:T(N,I)=O(N+I) ——如果序列基本有序,则插入排序简单且高效
定理:任意N个不同元素组成的序列平均具有N(N-1)/4个逆序对。
定理:任何仅以交换相邻两元素来排序的算法,其平均时间复杂度为Ω(N2)
这意味着:要提高算法效率,我们必须
- 每次交换不止消去1个逆序对!
- 每次交换相隔较远的2个元素!
希尔排序(by Donald Shell)
基本思路:利用插入排序的简单,同时克服了插入排序每次只交换相邻两个元素的缺点。
例子:
81 | 94 | 11 | 96 | 12 | 35 | 17 | 95 | 28 | 58 | 41 | 75 | 15 |
先做5间隔的排序:即比较处于1,6,11位的3个数,并从小到大放到相应的位置。
接下来比较下一位间隔5的序列:
依次类推:
最后将12和58排序放入相应位置
接下来在5间隔排序的基础上进行3间隔的排序,接下来进行3趟排序:
此时3间隔的排序结果已经有了很大的"改观",最后还需要做1间隔的排序,发现大多数逆序对已经在前面被消除了。
:
- 定义增量序列DM>DM-1>...>D1=1
- 对每个Dk进行“DK-间隔”排序(k=M,M-1,...1)
- 注意:“Dk-间隔”有序的序列,在执行“Dk-1-间隔”排序后,仍然是“Dk-间隔”有序的
希尔增量序列
原始希尔排序
DM=N/2,DK=DK+1/2
代码实现:
void Shell_Sort(ElementType A[],int N)
{ for(D=N/2;D>0;D/=2){//希尔增量序列
for(p=D;p<N;p++){//插入序列
Tmp=A[p];
for(i=p;i>=0&&A[i-D]>Tmp;i-=D)
A[i]=A[i-D];
A[i]=Tmp;
}
}
}
最坏情况:逆序T=Θ(N2)