一.交换排序
所谓交换,是指根据序列中两个元素关键字的比较结果来对换这两个记录在序列中的位置。基于交换的排序算法很多,本文主要介绍冒泡排序和快速排序。
上一篇的博客学习了插入排序,今天这里是交换排序,它和插入排序都属于内部排序
二.冒泡排序
1.基本思想
冒泡排序的基本思想是:从后往前(或从前往后)两两比较相邻元素的值,若为逆序(即A[i-1]>A[i]),则交换它们,直到序列比较完。我们称它为第一趟冒泡, 结果是将最小的元素交换到待排序列的第一个位置(或将最大的元素交换到待排序列的最后一个位置),关键字最小的元素如气泡一般逐渐往 上“漂浮”直至“水面”(或关键字最大的元素如石头一般下沉至水底)。下一趟冒泡时,前一趟确定的最小元素不再参与比较,每趟冒泡的结果是把序列中的最小元素(或最大元素)放到了序列的最终位置....这样最多做n-1趟冒泡就能把所有元素排好序。
2.举例
下面是一个冒泡排序的过程,第一趟冒泡时: 27<49, 不交换; 13<27, 不交换; 76> 13,交换; 97>13, 交换; 65>13, 交换; 38>13, 交换; 49>13,交换。通过第一趟冒泡后,最小元素已交换到第一个位置, 也是它的最终位置。第二趟冒泡时对剩余子序列采用同样方法进行排序,以此类推,到第五趟结束后没有发生交换,说明表已有序,冒泡排序结束。
3.性能分析
冒泡排序的性能分析如下:
空间效率:仅使用了常数个辅助单元,因而空间复杂度为0(1)。
时间效率:当初始序列有序时,显然第一趟冒泡后 flag依然为false(本趟没有元素交换),从而直接跳出循环,比较次数为n-1,移动次数为0,从而最好情况下的时间复杂度为O(n);当初始序列为逆序时,需要进行n-1趟排序,第i趟排序要进行n-i次关键字的比较,而且每次比较后都必须移动元素3次来交换元素位置。这种情况下,
比较次数=$\sum_1^t (n-1)$=$\frac{n(n-1)}{2}$ ,移动次数=$\sum_1^t 3(n-i)$ =$\frac{3n(n-1)}{2}$
从而,最坏情况下的时间复杂度为O(n^2^ ),平均时间复杂度为0(n^2^ )。
稳定性:由于i>j且A[i]=A[j]时,不会发生交换,因此冒泡排序是一种稳定的排序方法。
三.快速排序
1.基本思想
快速排序的基本思想是基于分治法的:在待排序表L1..n]中任取-一个元素pivot 作为枢轴(或称基准,通常取首元素),通过一趟排序将待排序表划分为独立的两部分L[...k-1]和L[k+1...n],使得L[...k-1]中的所有元素小于pivot, L[k1...n]中的所有元素大于或等于pivot,则pivot放在了其最终位置L(k)上,这个过程称为一次划分。然后分别递归地对两个子表重复上述过程,直至每部分内只有一个元素或空为止,即所有元素放在了其最终位置上。
2.举例
一趟快速排序的过程是一个交替搜索和交换的过程,下面通过实例来介绍,附设两个指针i
和j,初值分别为low和high,取第- 一个元素49为枢轴赋值到变量pivot。
指针j从high往前搜索找到第一个小于枢轴的元素27,将27交换到i所指位置。
指针i从low往后搜索找到第一个大于枢轴的元素65,将65交换到j所指位置。
指针j继续往前搜索找到小于枢轴的元素13,将13交换到i所指位置。
- 按照同样的方法对各子序列进行快速排序,若待排序列中只有一个元素,显然已有序。
3.性能分析
快速排序算法的性能分析如下:
- 空间效率:由于快速排序是递归的,需要借助一个递归工作栈来保存每层递归调用的必要信息,其容量与递归调用的最大深度一致。 最好情况下为O(log~2~ n); 最坏情况下,因为要进行n-1次递归调用,所以栈的深度为O(n);平均情况下,栈的深度为O(log~2~ n)。
- 时间效率:快速排序的运行时间与划分是否对称有关,快速排序的最坏情况发生在两个区域分别包含n-1个元素和0个元素时,这种最大限度的不对称性若发生在每层递归上,即对应于初始排序表基本有序或基本逆序时,就得到最坏情况下的时间复杂度为0(n^2^ )。
- 快速排序是一种不稳定的算法。
四.核心算法实现
1.冒泡排序
//冒泡排序
void BubbleSore(SqList &L){
Elemtype temp;
for(int i=0;i<L.length;i++){
//每一趟确定第一个数据的位置
bool flag=false; //每一趟是否发生交换的标志
for(int j=L.length-1;j>i;j--){
//从最后开始冒泡
if(L.data[j-1].grade>L.data[j].grade){
temp=L.data[j];
L.data[j]=L.data[j-1];
L.data[j-1]=temp;
flag=true; //表示发生交换
}
}
if(flag==false)
return;
}
}
2.快速排序
//划分函数
int Partition(SqList &L,int low,int high){
Elemtype pivot=L.data[low];
while(low<high){
while(low<high && L.data[high].grade>=pivot.grade) high--;
L.data[low]=L.data[high];
while(low<high && L.data[low].grade<=pivot.grade) low++;
L.data[high]=L.data[low];
}
//出这个while循环的条件就是low=high
L.data[low]=pivot;
return low;
}
//快速排序
void QuickSort(SqList &L,int low,int high){
if(low<high){
int pivotpos=Partition(L,low,high); //开始划分
QuickSort(L,low,pivotpos-1); //依次对两个子表进行划分
QuickSort(L,pivotpos+1,high);
}
}
五.C语言展示
/*我们今天的主角插入排序是基于查找算法来的,所以我们还是利用线性表来进行模拟*/
/*为了便于我们后面演示希尔排序,所以我们采用顺序存储结构*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define MaxSize 50 //这里只是演示,我们假设这里最多存五十个学生信息
//定义学生结构
typedef struct {
char name[200]; //姓名
int grade; //分数,这个是排序关键字
} Elemtype;
//声明使用顺序表
typedef struct {
/*这里给数据分配内存,可以有静态和动态两种方式,这里采用动态分配*/
Elemtype *data; //存放线性表中的元素是Elemtype所指代的学生结构体
int length; //存放线性表的长度
} SqList; //给这个顺序表起个名字,接下来给这个结构体定义方法
//初始化线性表
void InitList(SqList &L){
/*动态分配内存的初始化*/
L.data = (Elemtype*)malloc(MaxSize * sizeof(Elemtype)); //为顺序表分配空间
L.length = 0; //初始化长度为0
}
//求表长函数
int Length(SqList &L){
return L.length;
}
//求某个数据元素值
bool GetElem(SqList &L, int i, Elemtype &e) {
if (i < 1 || i > L.length)
return false; //参数i错误时,返回false
e = L.data[i - 1]; //取元素值
return true;
}
//输出线性表
void DispList(SqList &L) {
if (L.length == 0)
printf("线性表为空");
//扫描顺序表,输出各元素
for (int i = 0; i < L.length; i++) {
printf("%s %d", L.data[i].name, L.data[i].grade);
printf("\n");
}
printf("\n");
}
//插入数据元素
bool ListInsert(SqList &L, int i, Elemtype e) {
/*在顺序表L的第i个位置上插入新元素e*/
int j;
//参数i不正确时,返回false
if (i < 1 || i > L.length + 1 || L.length == MaxSize)
return false;
i--; //将顺序表逻辑序号转化为物理序号
//参数i正确时,将data[i]及后面的元素后移一个位置
for (j = L.length; j > i; j--) {
L.data[j] = L.data[j - 1];
}
L.data[i] = e; //插入元素e
L.length++; //顺序表长度加1
return true;
/*平均时间复杂度为O(n)*/
}
//冒泡排序
void BubbleSore(SqList &L){
Elemtype temp;
for(int i=0;i<L.length;i++){
//每一趟确定第一个数据的位置
bool flag=false; //每一趟是否发生交换的标志
for(int j=L.length-1;j>i;j--){
//从最后开始冒泡
if(L.data[j-1].grade>L.data[j].grade){
temp=L.data[j];
L.data[j]=L.data[j-1];
L.data[j-1]=temp;
flag=true; //表示发生交换
}
}
if(flag==false)
return;
}
}
//划分函数
int Partition(SqList &L,int low,int high){
Elemtype pivot=L.data[low];
while(low<high){
while(low<high && L.data[high].grade>=pivot.grade) high--;
L.data[low]=L.data[high];
while(low<high && L.data[low].grade<=pivot.grade) low++;
L.data[high]=L.data[low];
}
//出这个while循环的条件就是low=high
L.data[low]=pivot;
return low;
}
//快速排序
void QuickSort(SqList &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(){
SqList L;
Elemtype stuents[10]={
{
"张三",649},{
"李四",638},{
"王五",665},{
"赵六",697},{
"冯七",676},
{
"读者",713},{
"阿强",627},{
"杨曦",649},{
"老六",655},{
"阿黄",604}};
//这一部分忘了的请回顾我的相关博客
printf("初始化顺序表并插入开始元素:\n");
InitList(L); //这时是一个空表,接下来通过插入元素函数完成初始化
for (int i = 0; i < 10; i++)
ListInsert(L, i + 1, stuents[i]);
DispList(L);
/*printf("根据分数进行冒泡排序的结果为:\n");
BubbleSore(L);
DispList(L);*/
printf("根据分数进行快速排序的结果为:\n");
int low=0;
int high=L.length-1;
QuickSort(L,low,high);
DispList(L);
}