常用算法
如果分析一个排序算法
1.执行效率
执行效率分析一般从以下几个情况分析
(1)最好、最坏、平均情况时间复杂度
对于排序数据的有序度不同,排序算法在的性能表现会有影响,所以需要分析不同情况下的时间复杂度
(2)时间复杂度的系数、常数 、低阶
时间复杂度反应的是代码执行时间随数据规模增长的变化趋势,在实际软件开发中,排序的数据规模可能很小,在对比同一阶时间复杂度的排序算法性能的时候,就需要把系数、常数、低阶也一起考虑进去。
(3)比较次数和交换(或移动)次数
基于比较的排序算法的执行过程,会涉及两种操作,一种是元素比较大小,另一种是元素交换或移动。所以,如果我们在分析排序算法的执行效率的时候,应该把比较次数和交换(或移动)次数也考虑进去。
2.内存消耗
算法的内存消耗可以通过空间复杂度来衡量,空间复杂度O(1)的排序算法称为“原地排序”。
3.稳定性
在一组数据排序后,相同数据的前后顺序没有改变,那么就把这种排序称为“稳定的排序算法”,反之就叫做“不稳定的排序算法”。因为排序可能会有多种条件,不稳定排序可能会打乱之前的排序。
冒泡排序
在一组数据中,比较相邻的两个数,看是否满足大小关系,如果不满足,则交换,重复n次,直到最后到已排序区间。
如:一组数据4,2,3,1。每相邻的两个数比较,第一次冒泡后结果为2,3,1,4,一直到最后冒泡结束,过程如下图。

最后写成代码如下:
/// <summary>
/// 冒泡排序
/// </summary>
/// <param name="a">需要排序的数组</param>
/// <param name="n">数组长度</param>
void bubbleSort(int a[], int n) {
if (n <= 0) return;
for (int i = n - 1; i >= 0; --i) {
for (int j = 0; j < i; ++j) {
if (a[j] > a[j + 1]) {
int temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
}
冒泡排序分析
1.执行效率
最好时间复杂度为O(n),最坏时间复杂度为O(n2),平均时间复杂度为O(n2)。
2.内存消耗。
冒泡排序只涉及相邻数据的交换操作,生成常量的内存空间,空间复杂度为O(1),所以是“原地排序”。
3.稳定性。
在相邻两个数据交换中,只有数据不同且满足交换条件时才会交换,数据相同不会交换,保持原有顺序,所以是稳定的排序算法。
插入排序
一组数据中,分为已排序区间和未排序区间,将未排序区间中第一个数据从已排序区间的后往前比较,如果不满足大小关系,则将数据向后移,直到满足或者达到首项,将数据插入,此时已排序区间+1,未排序区间-1。重复操作直到未排序区间为空。过程如下图。

最后写成代码如下:
/// <summary>
/// 插入排序
/// </summary>
/// <param name="a">需要排序的数组</param>
/// <param name="n">数组长度</param>
void insertionSort(int a[], int n)
{
if (n <= 0) return;
for (int i = 0; i < n - 1; ++i) { //0 到 i 为已排序好的数据
int j;
int insert = a[i + 1];
for (j = i + 1; j > 0; --j) {
if (insert < a[j - 1]) {
a[j] = a[j - 1];
}
else {
break;
}
}
a[j] = insert;
}
}
插入排序分析
1.执行效率
最好时间复杂度为O(n),最坏时间复杂度为O(n2),平均时间复杂度为O(n2)。
2.内存消耗。
插入排序只涉及数据的移动和插入操作,生成常量的空间,空间复杂度为O(1),所以是“原地排序”。
3.稳定性。
数据的移动过程中,只有数据不同且满足交换条件时才会交换,数据相同不会移动,保持原有顺序,所以是稳定的排序算法。
选择排序
选择排序有点类似插入排序,也是分为已排序区间和未排序区间,每一次遍历未排序区间,选择相应条件的数据和已排序区间末尾的数据交换。重复操作直到未排序区间为空。过程如下图。

最后写成代码如下:
/// <summary>
/// 选择排序
/// </summary>
/// <param name="a">需要排序的数组</param>
/// <param name="n">数组长度</param>
void selectionSort(int a[], int n)
{
int index;
for (int i = 0; i < n - 1; ++i)
{
index = i;
for (int j = i + 1; j < n; ++j)
{
if (a[index] > a[j])
{
index = j;
}
}
int temp = a[index];
a[index] = a[i];
a[i] = temp;
}
}
选择排序分析
1.执行效率
最好时间复杂度为O(n2),最坏时间复杂度为O(n2),平均时间复杂度为O(n2)。
2.内存消耗。
选择排序只涉及数据的交换操作,生成常量空间,空间复杂度为O(1),所以是“原地排序”。
3.稳定性。
数据在交换过程中可能打乱原有的顺序,所以不是稳定的排序算法。
归并排序
归并排序的核心就是将需要排序的数组分成两部分,然后将这两部分分别排序,最后将这两部分和成一个数组。在分成两个部分中,我们继续将数组分成两个部分,直到最后一个部分只有一个数据为止。过程如下图。

最后写成代码如下:
void Merge(int a[], int l, int m, int r) {
int len = r - l + 1; //临时数组长度
int* temp = new int[len];
int i = 0;
int left = l;
int right = m + 1;
while (left <= m && right <= r) {
temp[i++] = a[left] <= a[right] ? a[left++] : a[right++];
}
while (left <= m)
temp[i++] = a[left++];
while (right <= r)
temp[i++] = a[right++];
for (int j = 0; j < len; ++j) {
a[l + j] = temp[j];
}
delete[] temp;
}
/// <summary>
/// 归并排序
/// </summary>
/// <param name="a">需要排序的数组</param>
/// <param name="left">左下标</param>
/// <param name="right">右下标</param>
void MergeSort(int a[], int left, int right) {
if (left >= right) return;
int mid = (right - left) / 2 + left;
MergeSort(a, left, mid);
MergeSort(a, mid + 1, right);
Merge(a, left, mid, right);
}
归并排序分析
1.执行效率
我们假设对 n 个元素进行归并排序需要的时间是 T(n),那分解成两个子数组排序的时间都是 T(n/2)。我们知道,merge() 函数合并两个有序子数组的时间复杂度是 O(n)。所以,套用前面的公式,归并排序的时间复杂度的计算公式就是
T(1) = C; n=1时,只需要常量级的执行时间,所以表示为C。
T(n) = 2*T(n/2) + n; n>1
T(n) = 2*T(n/2) + n
= 2*(2*T(n/4) + n/2) + n = 4*T(n/4) + 2*n
= 4*(2*T(n/8) + n/4) + 2*n = 8*T(n/8) + 3*n
= 8*(2*T(n/16) + n/8) + 3*n = 16*T(n/16) + 4*n
......
= 2^k * T(n/2^k) + k * n
......
T(n) = 2kT(n/2k)+kn。当 T(n/2^k)=T(1) 时,也就是 n/2^k=1,我们得到 k=log2n 。我们将 k 值代入上面的公式,得到 T(n)=Cn+nlog2n 。如果我们用大 O 标记法来表示的话,T(n) 就等于 O(nlogn)。所以归并排序的时间复杂度是 O(nlogn)。
2.内存消耗。
归并排序需要创建临时数组用于存储数据,合并完成才会释放,所以空间复杂度为O(n),不是原地排序。
3.稳定性。
合并的条件为<=,所以在数据相等的情况下,不会交换顺序,所以是稳定的排序算法。
快速排序
快排和归并排序有点类似,他会选择一个点作为pivot(分区点),遍历数组将小于pivot的数据放在左边,大于pivot的数据放在右边,将分成两区间的数据重复以上操作,直到区间为1。

最后写成代码如下:
int Parition(int a[], int low, int high) {
int pivot = a[high];
int i = low;
for (int j = low; j < high; ++j)
{
//j指向当前遍历元素,如果大于等于pivot,继续向前
//如果小于当前元素,则和i指向的元素交换
if (a[j] < pivot) {
swap(a[j], a[i]);
i++;
}
}
swap(a[i], a[high]);
return i;
}
/// <summary>
/// 快速排序
/// </summary>
/// <param name="a">需要排序的数组</param>
/// <param name="low">左下标</param>
/// <param name="high">右下标</param>
void QuickSort(int a[], int low, int high) {
if (low < high)
{
int q = Parition(a, low, high);
QuickSort(a, low, q - 1);
QuickSort(a, q + 1, high);
}
}
快速排序分析
1.执行效率
快速排序算法虽然最坏情况下的时间复杂度是 O(n2),但是平均情况下时间复杂度都是 O(nlogn)。
2.内存消耗。
快排只需要交换数据,不会产生多余的空间,所以空间复杂度为O(1),是原地排序
3.稳定性。
交换过程中可能打乱顺序,所以是不稳定排序。
本文介绍了分析排序算法的三个维度:执行效率、内存消耗和稳定性。并对冒泡排序、插入排序、选择排序、归并排序和快速排序进行分析,包括各算法的实现过程、时间复杂度、空间复杂度以及是否为稳定排序。
596

被折叠的 条评论
为什么被折叠?



