1 如何评价、分析一个排序算法?算法
不少语言、数据库都已经封装了关于排序算法的实现代码。因此咱们学习排序算法目的更多的不是为了去实现这些代码,而是灵活的应用这些算法和解决更为复杂的问题,因此更重要的是学会如何评价、分析一个排序算法并在合适的场景下正确使用。数据库
分析一个排序算法,主要从如下3个方面入手:学习
1.1 排序算法的执行效率测试
1)最好状况、最坏状况和平均状况时间复杂度spa
待排序数据的有序度对排序算法的执行效率有很大影响,因此分析时要区分这三种时间复杂度。除了时间复杂度分析,还要知道最好、最坏状况复杂度对应的要排序的原始数据是什么样的。code
2)时间复杂度的系数、常数和低阶blog
时间复杂度反映的是算法执行时间随数据规模变大的一个增加趋势,平时分析时每每忽略系数、常数和低阶。但若是咱们排序的数据规模很小,在对同一阶时间复杂度的排序算法比较时,就要把它们考虑进来。排序
3)比较次数和交换(移动)次数内存
内排序算法中,主要进行比较和交换(移动)两项操做,因此高效的内排序算法应该具备尽量少的比较次数和交换次数。博客
1.2 排序算法的内存消耗
也就是分析算法的空间复杂度。这里还有一个概念—原地排序,指的是空间复杂度为O(1)的排序算法。
1.3 稳定性
若是待排序的序列中存在值相等的元素,通过排序以后,相等元素之间原有的前后顺序不变,那么这种排序算法叫作稳定的排序算法;若是先后顺序发生变化,那么对应的排序算法就是不稳定的排序算法。
在实际的排序应用中,每每不是对单一关键值进行排序,而是要求排序结果对全部的关键值都有序。因此,稳定的排序算法每每适用场景更广。
2 三种时间复杂度为O(n2)的排序算法
2.1 冒泡排序
2.1.1 原理
两两比较相邻元素是否有序,若是逆序则交换两个元素,直到没有逆序的数据元素为止。每次冒泡都会至少让一个元素移动到它应该在的位置。
2.1.2 实现
void BubbleSort(int *pData, int n) //冒泡排序 { int temp = 0; bool orderlyFlag = false; //序列是否有序标志 for (int i = 0; i < n && !orderlyFlag; ++i) //执行n次冒泡 { orderlyFlag = true; for (int j = 0; j < n - 1 - i; ++j) //注意循环终止条件 { if (pData[j] > pData[j + 1]) //逆序 { orderlyFlag = false; temp = pData[j]; pData[j] = pData[j + 1]; pData[j + 1] = temp; } } } }
测试结果
2.1.3 算法分析
1)时间复杂度
最好状况时间复杂度:当待排序列已有序时,只需一次冒泡便可。时间复杂度为O(n);
最坏状况时间复杂度:当待排序列彻底逆序时,须要n次冒泡。时间复杂度为O(n2);
平均状况时间复杂度:当待排序列彻底逆序时,逆序度为n * (n - 1) / 2。只有当交换逆序对时才会才会使得有序,取中间逆序度n * (n - 1) / 4,那么就要进行n * (n - 1) /4次交换,而比较的次数大于交换次数,因此平均状况时间复杂度为O(n2)。
2)空间复杂度
只借助了一个临时变量temp,因此空间复杂度为O(1)。
3)稳定性
该算法中只有交换操做会改变数据元素的顺序,只要咱们在数据元素值相等时不交换数据元素,那么算法就是稳定的。
4)比较和交换的次数
交换操做的执行次数与逆序度相等,比较操做的执行次数大于等于逆序度小于等于n * (n - 1) / 2。
2.2 插入排序
2.2.1 原理
将待排序序列分为已排序区间和未排序区间,开始时已排序区间只有一个数据元素也就是序列的第一个元素,将未排序区间中的数据元素插入已排序区间中同时保持已排序区间的有序,直到未排序区间没有数据元素。
2.2.2 实现
void InsertSort(int *pData, int n) //插入排序 { int temp = 0, i, j; for (i = 1; i < n; ++i) //未排序区间 { if (pData[i] < pData[i - 1]) //逆序 { temp = pData[i]; for (j = i - 1; pData[j] > temp; --j) //搬移数据元素 pData[j + 1] = pData[j]; pData[j + 1] = temp; //插入数据 } } }
测试结果:
2.2.3 算法分析
1)时间复杂度
最好状况时间复杂度:当待排序列已有序时,只需遍历一次便可完成排序。时间复杂度为O(n);
最坏状况时间复杂度:当待排序列彻底逆序时,须要进行n-1次数据搬移和插入操做。时间复杂度为O(n2);
平均状况时间复杂度:与冒泡法的分析过程同样,平均状况时间复杂度为O(n2)。
2)空间复杂度
排序过程当中只须要一个临时变量存储待插入数据,空间复杂度为O(1)。
3)稳定性
插入排序过程当中只有插入操做会改变数据元素的相对位置,只要元素大小比较时相等状况下不进行插入操做,插入排序算法就是稳定的。
4)比较操做和数据搬移操做执行次数
数据搬移操做执行次数和逆序度相同。比较操做次数大于等于逆序度,小于等于n * (n - 1) / 2。
2.3 选择排序
2.3.1 原理
选择排序的原理相似于插入排序都分为已排序区间和未排序区间,选择排序的已排序区间初始大小为零,每次从未排序区间取关键值最大(或最小)的数据元素放在已排序区间的后一个位置,直到未排序区间没有数据元素则完成排序。
2.3.2 实现
void SelectSort(int *pData, int n) //选择排序 { int i, j, min, temp; for (i = 0; i < n; ++i) //未排序区间 { min = i; //最小值下标 for (j = i + 1; j < n; ++j) { if (pData[min] > pData[j]) //逆序 min = j; //保存当前较小值下标 } if (i != min) //若是不是最小值,交换元素 { temp = pData[i]; pData[i] = pData[min]; pData[min] = temp; } } }
测试结果:
2.3.3 算法分析
1)时间复杂度
无论是已有序序列仍是彻底逆序序列,都要进行n次遍历无序区间操做,时间复杂度为O(n2)。
2)空间复杂度
排序过程当中只须要保存每次遍历无序区间最小值的下标和第i个元素的数值,因此空间复杂度为O(1)。
3)稳定性
选择排序算法中改变数据元素相对位置的操做为交换操做,当第i次中第i个数据元素不为当前无序区间最小值时则和最小值交换数据元素。当有重复元素时,就有可能发生相对位置改变。例如5,3,4,5,1第一次选择操做后为1,3,4,5,5,此时两个5的相对位置已经改变。因此选择排序算法不是稳定的。
4)比较操做和交换操做的执行次数
比较操做执行次数为n * (n - 1) / 2,交换操做执行次数小于等于n-1。
2.4 三种算法之间的比较
1)通常待排序列长度n较小时,咱们选择这三种排序算法;
2)当排序要求稳定时,通常选择插入排序,由于相同的状况下,移动数据比交换数据执行速度快;
3)当数据元素信息量较大时,能够考虑用选择排序,由于它交换操做执行次数最少。
该篇博客是本身的学习博客,水平有限,若是有哪里理解不对的地方,但愿你们能够指正!