(最近一直在看PHP,加上OJ 404了,所以这两天都没有写C,但是还是有在写代码的。)
本人小白,而且脑子笨,理解算法比较困难,现在我把我对排序算法的理解贴出来以便以后复习理解。代码是其他博客里偷的,后面标识出处。
1. 冒泡排序
冒泡排序简单来说就是把当前数字与右边的数字进行比较,如果大,那么就交换位置,否则就把我们盯住的当前值换成右边大的那个数,再和右边的数字比较,于是完成第一轮,实现的结果就是把最大的数放到了最后(这里假设排序规则是是左小右大)。
冒泡排序的特征就是每一轮都找到最大值(或者最小值)。
思想理解:
要实现的功能就是将元素像冒泡一样移动。
打个通俗的比方,就假设一个寻宝藏的人,途径一片沙漠,沙漠里埋藏着金银珠宝,他只有两只手只能拿一个箱子的珠宝,那么就要找到装有最多财富的箱子,不断地把找到的箱子和自己拥有的箱子进行比较,如果自己的多,那就不交换。
算法实现:
#include <stdio.h>
// 分类 -------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- O(n^2)
// 最优时间复杂度 ---- 如果能在内部循环第一次运行时,使用一个旗标来表示有无需要交换的可能,可以把最优时间复杂度降低到O(n)
// 平均时间复杂度 ---- O(n^2)
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 稳定
void Swap(int A[], int i, int j)
{
int temp = A[i];
A[i] = A[j];
A[j] = temp;
}
void BubbleSort(int A[], int n)
{
for (int j = 0; j < n-1 ; j++) // 每次最大元素就像气泡一样"浮"到数组的最后
{
for (int i = 0; i < n - 1 - j; i++) // 依次比较相邻的两个元素,使较大的那个向后移
{
if (A[i] > A[i + 1]) // 如果条件改成A[i] >= A[i + 1],则变为不稳定的排序算法
{
Swap(A, i, i + 1);
}
}
}
}
int main()
{
int A[] = { 6, 5, 3, 1, 8, 7, 2, 4 }; // 从小到大冒泡排序
int n = sizeof(A) / sizeof(int);//n表示的是数组元素的个数
BubbleSort(A, n);
printf("冒泡排序结果:");
for (int i = 0; i < n; i++)
{
printf("%d ", A[i]);
}
printf("\n");
return 0;
}
分析程序实现:
首先BubbleSort里有两个参数,一个是存放一串随机数的数组,还有一个n就是数组元素个数。
然后BubbleSort里有两个for,可能是我对循环了解的还不是特别深入,一开始我并没有理解这段代码,但是把代码还原成最初的双层循环来看,就很清晰了:
#include <stdio.h>
//这是最简单的两层for循环,我们假设上面的n = 10;
int main(int argc, char* argv[])
{
for(int j = 0;j < 9;j++)
{
for(int i = 0;i < 9 - j;i++)
{
printf("%d,%d\t",j,i);
}
printf("\n");
}
}
运行结果:
可以看到,里面for循环的变量i从0一直增长到8,第二轮就只增长到7,逐渐减少到0;而外面第一层for循环变量j就是当i跑了一轮之后再增加1,最后增加到了8。
在冒泡排序算法里,变量i代表的就是每个元素在数组里对应的数组下标,数组下标的增长就实现了元素的向右移动。而每过一轮,i增长的终点就减一,意思就是不用再和最后一个数比较(因为最后一个数已经排好序了);
当里面的for循环走了一轮之后开始执行外面的for,也就是变量i完成了第一轮,变量j增一,变量i开始要执行第二轮,总共有9个数(n=10),那么就要执行9轮比较,因此就对应了j从0增长到8。
算法复杂度分析:
1.最好的情况:
当数串已经排好序,即正序。
T(n) = O(n)
2.最坏的情况:
当数串是反序。
T(n) = O(n^2)
3.平均时间复杂度:
T(n) = O(n2)
4.稳定性
所谓稳定性就是说之后的排序会不会打乱之前的顺序。
对于冒泡排序,原本是稳定的排序算法,如果将记录交换的条件改成A[i] >= A[i + 1],则两个相等的记录就会交换位置,从而变成不稳定的排序算法。
改进算法:鸡尾酒排序
所谓鸡尾酒排序就是先从左到右把最大的放在最右,回来的时候把最小的带到最左边。(这个还是比较容易理解)
算法实现:
#include <stdio.h>
// 分类 -------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- O(n^2)
// 最优时间复杂度 ---- 如果序列在一开始已经大部分排序过的话,会接近O(n)
// 平均时间复杂度 ---- O(n^2)
// 所需辅助空间 ------ O(1)
// 稳定性 ------------ 稳定
void Swap(int A[], int i, int j)
{
int temp = A[i];
A[i] = A[j];
A[j] = temp;
}
void CocktailSort(int A[], int n)
{
int left = 0; // 初始化边界
int right = n - 1;
while (left < right)
{
for (int i = left; i < right; i++) // 前半轮,将最大元素放到后面
{
if (A[i] > A[i + 1])
{
Swap(A, i, i + 1);
}
}
right--;
for (int i = right; i > left; i--) // 后半轮,将最小元素放到前面
{
if (A[i - 1] > A[i])
{
Swap(A, i - 1, i);
}
}
left++;
}
}
int main()
{
int A[] = { 6, 5, 3, 1, 8, 7, 2, 4 }; // 从小到大定向冒泡排序
int n = sizeof(A) / sizeof(int);
CocktailSort(A, n);
printf("鸡尾酒排序结果:");
for (int i = 0; i < n; i++)
{
printf("%d ", A[i]);
}
printf("\n");
return 0;
}
最后小感想:
两层循环是一个经典的二维实现方法,可以解决许多受两个变量控制的问题。经典的有输出金字塔,九九乘法表,二维数组,五子棋棋盘等。