所谓交换,是根据序列中两个元素关键字的比较结果来对换这两个记录在序列中的位置。
所谓交换排序就是指,两两比较待排序的元素,交换不满足顺序要求的那些元素对,直到所有的元素次序都满足顺序要求为止。
基于交换的排序算法有很多,这里主要介绍冒泡排序和快速排序。
冒泡排序又称起泡排序,其基本思想是:
假设待排序表长为 n n n ,从后往前(或从前往后)两两比较相邻元素的值,若为逆序( d a t a [ i − 1 ] > d a t a [ i ] data[i-1]>data[i] data[i−1]>data[i]),则交换它们,直到序列比完,我们称它为一趟起泡。结果是将最小的元素交换到待排序列的第一个位置(关键字最小的元素像气泡一样被交换到了 d a t a [ 0 ] data[0] data[0])。
下一趟冒泡时,前一趟确定的最小元素将不再参与比较,待排序列少一个元素,每趟冒泡的结果是把序列中的最小元素放到了序列的最终位置……
这样最多做 n − 1 n-1 n−1 趟冒泡就能把所有元素排好序。
冒泡排序
初始序列为: 8 , 9 , 10 , 4 , 5 , 6 , 20 , 1 , 2 8,9,10,4,5,6,20,1,2 8,9,10,4,5,6,20,1,2
第一趟: 1 , 8 , 9 , 10 , 4 , 5 , 6 , 20 , 2 1,8,9,10,4,5,6,20,2 1,8,9,10,4,5,6,20,2
第二趟: 1 , 2 , 8 , 9 , 10 , 4 , 5 , 6 , 20 1,2,8,9,10,4,5,6,20 1,2,8,9,10,4,5,6,20
第三趟: 1 , 2 , 4 , 8 , 9 , 10 , 5 , 6 , 20 1,2,4,8,9,10,5,6,20 1,2,4,8,9,10,5,6,20
第四趟: 1 , 2 , 4 , 5 , 8 , 9 , 10 , 6 , 20 1,2,4,5,8,9,10,6,20 1,2,4,5,8,9,10,6,20
第五趟: 1 , 2 , 4 , 5 , 6 , 8 , 9 , 10 , 20 1,2,4,5,6,8,9,10,20 1,2,4,5,6,8,9,10,20
//冒泡排序
void BubbleSort(SeqList &L){
int n = L.n; //待排序元素个数
for(int i=0; i<n-1; i++){
int flag = false; //本趟冒泡是否发生交换的标志
for(int j=n-1; j>i; j--){
//一趟冒泡过程
if(L.data[j-1] > L.data[j]){
//若为逆序
swap(L.data[j-1], L.data[j]); //交换
flag = true;
}
}
if(flag==false) return; //若本趟遍历没有发生交换,说明表已经有序
}
}
考虑到文章的可读性,完整代码在文章结尾。
注意
其实,不加 f l a g flag flag (删去所有关于 f l a g flag flag的语句)也可以实现冒泡排序,就是每次固定进行 n − 1 n-1 n−1 趟冒泡过程,而加上 f l a g flag flag 则可能会减少循环次数,比如一个基本有序的序列可能只需要若干次(不用进行 n − 1 n-1 n−1 次)的冒泡就能够有序,后面的循环做的就是无用功。
其实这就是冒泡排序的优化算法。
空间复杂度
冒泡算法的空间复杂度为 O ( 1 ) O(1) O(1),只需一个用于数据交换的工作单位和一个控制排序过程结束的标志变量。
时间复杂度
冒泡排序算法的关键码的比较次数和元素移动次数均受到待排序元素的初始状态的影响。
故冒泡排序的时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
由于冒泡排序每遇到两个元素逆序就要进行交换,与其他排序算法相比,需要移动更多的元素,所以它是比较慢的一种排序方法。
稳定性
由于 i > j i>j i>j且 L . d a t a [ i ] = L . d a t a [ j ] L.data[i]=L.data[j] L.data[i]=L.data[j]时不会交换两个元素,所以冒泡排序是一种稳定的排序方法。
另外,冒泡排序是中所产生的有序子序列一定是全局有序的(不同于直接插入排序),也就是说,有序子序列中的所有元素的关键字一定小于(或大于)无序子序列中所有元素的关键字,这样每趟排序都会将一个元素放置到最终位置上。
快速排序是对冒泡排序的一种改进,其基本思想是基于分治法的:
在待排序表 L [ 1... n ] L[1...n] L[1...n]中任取一个元素 p i v o t pivot pivot作为基准,通过一趟排序将排序表划分为独立的两部分 L [ 1... k − 1 ] L[1...k-1] L[1...k−1]和 L [ k + 1... n ] L[k+1...n] L[k+1...n],使得 L [ 1... k − 1 ] L[1...k-1] L[1...k−1]中的所有元素小于 p i v o t pivot pivot, L [ k + 1... n ] L[k+1...n] L[k+1...n]中的所有元素大于等于 p i v o t pivot pivot,则 p i v o t pivot pivot放在了其最终位置上,这个过程就称为一趟快速排序。
而后分别递归地对两个子表重复上述过程,直至每部分内只有一个元素或空为止,即所有元素放在了其最终位置上。
经过这一趟排序,基准元素23被放在了最终位置上,其前面的所有元素均小于等于23,而后面的所有元素均大于等于23。
//划分算法
int Partition(SeqList &L, int low, int high){
int pivot = L.data[low]; //将表中第一个元素设为基准元素,对表进行划分
while(low<high){
//循环跳出条件
while(low<high && L.data[high]>=pivot) --high;
L.data[low] = L.data[high]; //比基准元素小的元素移动到左端
while(low<high && L.data[low]<=pivot) ++low;
L.data[high] = L.data[low]; //比基准元素大的元素移动到右端
}
L.data[low] = pivot; //基准元素存放到最终位置
return low; //返回存放基准元素的最终位置
}
//快速排序
void QuickSort(SeqList &L, int low, int high){
if(low<high){
int pivotpos = Partition(L,low,high);
QuickSort(L, low, pivotpos-1);
QuickSort(L, pivotpos+1, high);
}
}
空间复杂度
由于快速排序是递归的,需要借助一个递归工作栈来保存每层递归调用的必要信息,其容量应与递归调用的最大深度一致。
时间复杂度
快速排序的运行时间与划分是否对称有关,而后者又与具体使用的划分算法有关。
故快速排序的时间复杂度为 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)。
稳定性
若右端区间有两个关键码相同,且均小于基准值的记录,则在交换到左端区间后,它们的相对位置就会发生变化。故快速排序是一种不稳定的排序方法。
适用性
快速排序适用于待排记录个数很多,且原始记录随机排列的情况。
实验结果表明,就平均计算时间而言,快速排序是我们所讨论的所有内排序方法中最好的一个。但是注意,对于 n n n较大的平均情况而言,快速排序是快速的,但是当 n n n很小时,快速排序往往会比其他简单排序算法还要慢。
在快速排序算法中,并不产生有序子序列,但每趟排序后会将一个元素(基准元素)放到其最终位置上。
#include
using namespace std;
//设待排序序列存储在静态分配的顺序表中
#define maxSize 20
typedef struct{
int data[maxSize];
int n;
}SeqList;
//输入待排序列并存入顺序表中
void CreateList(SeqList &L, int n){
L.n = n;
for(int i=0; i<n; i++){
int x;
cin>>x;
L.data[i] = x;
}
}
//输出序列
void PrintList(SeqList L){
for(int i=0; i<L.n; i++){
cout<<L.data[i]<<" ";
}
cout<<endl<<endl;
}
//冒泡排序
void BubbleSort(SeqList &L){
int n = L.n; //待排序元素个数
for(int i=0; i<n-1; i++){
int flag = false; //本趟冒泡是否发生交换的标志
for(int j=n-1; j>i; j--){
//一趟冒泡过程
if(L.data[j-1] > L.data[j]){
//若为逆序
swap(L.data[j-1], L.data[j]); //交换
flag = true;
}
}
if(flag==false) return; //若本趟遍历没有发生交换,说明表已经有序
}
}
//划分算法
int Partition(SeqList &L, int low, int high){
int pivot = L.data[low]; //将表中第一个元素设为基准元素,对表进行划分
while(low<high){
//循环跳出条件
while(low<high && L.data[high]>=pivot) --high;
L.data[low] = L.data[high]; //比基准元素小的元素移动到左端
while(low<high && L.data[low]<=pivot) ++low;
L.data[high] = L.data[low]; //比基准元素大的元素移动到右端
}
L.data[low] = pivot; //基准元素存放到最终位置
return low; //返回存放基准元素的最终位置
}
//快速排序
void QuickSort(SeqList &L, int low, int high){
if(low<high){
int pivotpos = Partition(L,low,high);
QuickSort(L, low, pivotpos-1);
QuickSort(L, pivotpos+1, high);
}
}
int main(){
SeqList L;
int n;
cin>>n; //元素个数
CreateList(L, n);
cout<<endl<<"L:";
PrintList(L);
//冒泡排序
BubbleSort(L);
PrintList(L);
//快速排序
QuickSort(L, 0, L.n-1);
PrintList(L);
return 0;
}