注:
(1)以下所有排序算法均按照从小到大的顺序排列
(2)以下算法中用到的交换函数都一样,如下:
void Swap(int *a,int i,int j)
{
int temp;
temp = a[i];
a[i] = a[j];
a[j] = temp;
}
因此不在每个排序算法中进行详解
1.冒泡排序
1.最简单的冒泡排序
思想:
该排序算法在排序的过程中总共进行n-1趟排序,每一趟排序都将当前的关键字和其后面的每一个关键字进行比较,若当前关键字大于其后面的关键字则进行交换。
代码实现如下:
void BubbleSort(int *a) //a表示待排序的记录集合,这里以数组存储为例,以下排序算法相同,即都是对数组里的记录元素进行排序
{
int i,j;
for(i = 0;i < MAXSIZE-1;i++)
{
for(j = (i + 1);j < MAXSIZE;j++) //注意j从前往后循环与当前记录比较
{
if(a[i] > a[j]) //若当前记录大于其后的记录
{
Swap(a,i,j); //两者交换在数组中的位置
}
}
}
}
分析:
该算法从严格意义上讲并不是标准的冒泡排序,因为不满则“两两比较相邻记录”的冒泡排序思想,它更应该是最最简单的交换排序。
该算法代码简单易懂,却是有缺陷的。每一趟的比较和交换对下一轮没有任何影响,将导致进行很多重复的比较,算法效率是非常低的
2.冒泡排序
思想:
跟上面的排序算法的思想一样,都是当前趟的记录跟其之后的记录相比,只是该算法不同的是:每一趟都是两个相邻记录的比较
代码实现如下(蓝色加粗的即为和上面算法不同的部分):
void BubbleSort(int *a)
{
int i,j;
for(i = 0;i < MAXSIZE-1;i++)
{
for(j = MAXSIZE-2;j >= i;j--) //注意j是从后往前遍历和其前面相邻的记录进行比较
{
if(a[j] > a[j+1]) //若前者大于后者(注意两者相邻)
{
Swap(a,j,j+1);
}
}
}
}
3.冒泡排序算法的优化
思想:该算法在上诉冒泡排序算法的基础上进行优化,由上面可知,若进过第一趟排序后若结果已经有序,则后面还要进行循环判断工作,因此该算法实现的是当在某一趟排序后若该序列已经有序,则不再进行循环比较了。
代码实现如下:
void BubbleSort(int *a)
{
int i,j;
int flag = 1; //新增flag标志,默认为1
for(i = 0;i < MAXSIZE-1 && flag;i++) //若i<n-1趟,且上一趟没有进行任何记录交换时说明该记录集合已经有序了,退出循环
{
flag = 0;
for(j = MAXSIZE-2;j >= i;j--)
{
if(a[j] > a[j+1]) //若前者都小于后者(即不需要交换时),说明该记录集合已经有序,则在当前循环结束后退出总循环
{
flag = 1; //若只要有一次记录交换,则说明该集合还没有完全有序,继续循环
Swap(a,j,j+1);
}
}
}
}
4.冒泡复杂度分析
时间复杂度:
最好的情况:也就是带排序的记录集合本身是有序的,那么需要进行n-1(n为待排序的记录个数,代码中为MAXSIZE)次比较,没有数据交换,时间复杂度为O(n)
最坏的情况:也就是待排序的记录集合本市是逆序的,那么时间复杂度为O(n*n)。
因此平均复杂度为O(n * n)。
空间复杂度:
由于该算法没有借助辅助空间,所以空间复杂度为常数,即为O(1)。
稳定性:
稳定
2.简单选择排序
思想:对待排序的n个记录进行n-1趟循环,每一趟进行两两相邻比较找到该趟最小的记录和当前记录进行比较,若最小记录的位置不是当前记录所在的位置,则两者进行交换。
代码实现如下:
void SelectSort(int *a)
{
int i,j,min;
for(i = 0;i < MAXSIZE - 1;i++)
{
min = i; //初始化当前记录为最小的记录
for(j = i + 1;j < MAXSIZE;j++) //将最小记录依次与其后面的记录比较
{
if(a[min] > a[j]) //若其后的记录小于该机记录
{
min = j; //把其后的记录的下标赋给min
}
}
if(i != min) //若min不等于当前记录的下标
{
Swap(a,min,i); //进行交换
}
}
}
简单选择排序复杂度分析:
时间复杂度:
最好情况:即待排序记录集合本身是有序的,需要比较的次数是1+2+...+(n-1) = n * (n - 1) / 2,不需要交换数据。由于最终的时间复杂度是比较与交换次数之和,因此总的时间复杂度为O(n * n)。
最坏情况:即待排序的记录集合本身是逆序的,需要比较的次数同最好情况相同都为n * (n - 1) / 2,交换的次数是n-1次,因此最终的时间复杂度也是O(n * n)。
因此平均复杂度为O(n * n)。
空间复杂度:
由于该算法不需要辅助空间,因此空间复杂度为常数,即为O(1)。
稳定性:
不稳定
3.直接插入排序
思想:该算法的存储结构数组a[0]不存放记录(不同于上面算法),用作哨兵,每一趟都假设其前面的记录集合是有序的,然后比较当前记录和其前面相邻的记录,若小于其前面相邻记录,则把该值放在a[0]的位置上,然后从其前面相邻记录向前开始遍历,只要记录小于a[0]的记录,则把当前记录后移,直到大于等于a[0]元素则退出循环,然后把a[0]的记录插入到适当位置,则结束当前趟的操作。
代码实现如下:
void InsertSort(int *a)
{
int i,j;
for(i = 2;i < MAXSIZE;i++) //i从2开始循环,默认第一个记录是有序的
{
if(a[i] < a[i - 1]) //若当前记录大于前面的记录
{
a[0] = a[i]; //设置哨兵,把当前记录赋给哨兵
for(j = i - 1;a[0] < a[j];j--)
{
a[j+1] = a[j]; //记录后移
}
a[j + 1] = a[0]; //插入到正确位置
}
}
}
直接插入排序算法复杂度分析:
时间复杂度:
最好情况:即带排序的集合本身就是有序的,则需要比较n-1次,不需要移动,因此时间复杂度为O(n)。
最坏情况:即待排序的集合本身是逆序的,则需要比较2+3+...+n = (n + 2) * (n - 1) / 2次,需要移动3+4+...+(n+1) = (n + 4)*(n - 1) /2次,因此时间复杂度为O(n * n)。
因此平均复杂度为O(n * n)。
空间复杂度:
由于该算法需要一个辅助空间(a[0]),因此空间复杂度为常数,即为O(1)。
稳定性:
稳定