1、排序算法简介
1.1 相关概念
稳定性:稳定或不稳定;
待排序的序列中存在值相等的元素,经过排序之后,相等元素之间原有的先后顺序不变为稳定,否则为不稳定;
例如:1,2,2,5,9,4,3
将上述数字从小到大排列,两个2的位置不会发生变化则为稳定。
1.2 基础的排序算法
注:表中时间复杂度忽略了系数,实际应用中应考虑系数。
算法 | 时间复杂度 | 稳定性 |
---|---|---|
冒泡排序 | O( n 2 n^2 n2) | 稳定 |
选择排序 | O( n 2 n^2 n2) | 不稳定 |
插入排序 | O( n 2 n^2 n2) | 稳定 |
希尔排序 | O( n l o g n nlogn nlogn) | 不稳定 |
归并排序 | O( n l o g n nlogn nlogn) | 稳定 |
快速排序 | O( n l o g n nlogn nlogn) | 不稳定 |
计数排序 | O(n) | 稳定 |
桶排序 | O(n) | 稳定 |
基数排序 | O(n) | 稳定 |
后续较为复杂的数据结构目的无非是为了方便数据的增删改查,所以了解基础的排序算法有助于更好的理解和学习后续的内容。
今天先来了解基础中的基础:三种简单排序。
2、冒泡排序
2.1 思路
每次冒泡操作都会对相邻的两个元素进行比较,看是否满足大小关系要求。如果不满足就让它俩互换。一次冒泡会让至少一个元素移动到它应该在的位置,重复 n 次,就完成了 n 个数据的排序工作。
举例: 对数组{ 5,16,3,11,8,1,3 }
这几个数冒泡排序,要求从小到大。
第一次冒泡图示:
图示为数第一次冒泡排序的过程,本次结束后最大的数已经在A[6],则下一次排序只需遍历A[0]~A[5],以此类推,直到剩下A[0],则排序结束。
综上所述,若对大小为n的数组进行冒泡排序,共需要进行n次,每次需要遍历n、n-1、n-2… 1 个元素。
2.2 cpp代码
新建类
class Simplesort
{
public:
void BubbleSort(int *Arr,int n);//冒泡排序
void PrintResult(int* Arr, int n);//打印
};
函数定义
void Simplesort::PrintResult(int* Arr, int n)
{
if (n <= 1 || Arr == nullptr)
{
return;
}
for (int i = 0; i < n; i++)
{
cout << setw(4) << Arr[i];
}
cout << endl;
}
void Simplesort::BubbleSort(int* Arr, int n)
{
if (n <= 1 || Arr == nullptr)
{
return;
}
bool iHasSwap = true;//当前遍历有数据交换发生,若没有,则表示数据已有序,无需再遍历
for (int i = 0; i < n; i++)
{
iHasSwap = false;
for (int j = 0; j < n - i - 1; j++)
{
if (Arr[j] > Arr[j + 1])//交换位置
{
int temp = Arr[j + 1];
Arr[j + 1] = Arr[j];
Arr[j] = temp;
iHasSwap = true;
}
}
if (false == iHasSwap)
{
break;
}
}
}
测试
int main()
{
Simplesort mysort;
int testA[7] = { 5,16,3,11,8,1,3 };
cout << "before BubbleSort" << endl;
mysort.PrintResult(testA, 7);
mysort.BubbleSort(testA,7);
cout << "after BubbleSort" << endl;
mysort.PrintResult(testA, 7);
}
结果
2.3 性能分析
2.3.1 空间复杂度:
只需要申请常量级临时空间作交换使用,为O(1);
2.3.2 稳定性:
相同大小元素不会交换位置,稳定;
2.3.3 时间复杂度:
最好:
逆序度为0,遍历一遍就推出,为O(n);
最坏:
有序度为0,每个元素都要遍历,根据2.1节分析,需要遍历
(
n
−
1
)
+
(
n
−
2
)
+
.
.
.
.
+
1
=
n
(
n
−
1
)
2
次
(n-1)+(n-2)+....+1= \frac{n(n-1)}{2} 次
(n−1)+(n−2)+....+1=2n(n−1)次,为
O
(
n
2
)
O(n^2)
O(n2);
平均:
逆序度、有序度各一半,平均需要
n
(
n
−
1
)
4
次
\frac{n(n-1)}{4} 次
4n(n−1)次,为
O
(
n
2
)
O(n^2)
O(n2)。
3、插入排序
3.1 思路
将数组中的数据分为两个区间,已排序区间和未排序区间。初始已排序区间只有一个元素,为数组的第一个元素。未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。重复这个过程,直到未排序区间中元素为空,算法结束。
举例: 对数组{ 5,16,3,11,8,1,3 }
这几个数插入排序,要求从小到大。
插入排序图示:
将当前元素从无序区间插入到有序区间后,从插入位置起,向后的所有元素都要向后移动1位,正好覆盖了移动前的位置。
3.2 cpp代码
函数声明
class Simplesort
{
public:
void BubbleSort(int *Arr,int n);
void InsertSort(int* Arr, int n);
void PrintResult(int* Arr, int n);
};
函数定义
void Simplesort::InsertSort(int* Arr, int n)
{
if (n <= 1 || Arr == nullptr)
{
return;
}
for (int i = 1; i < n; i++)//数组第一个元素直接放在有序空间,所以从第二个元素开始
{
int value = Arr[i];
int j = i - 1;
for (; j>=0; j--)
{
if (Arr[j] > value)//j开始的元素依次向后移动
{
Arr[j + 1] = Arr[j];
}
else
{
break; //如果在已排序区间内找到<=插入值的数,则退出
}
}
Arr[j + 1] = value;//value插在原来j的位置
}
}
测试
int main()
{
Simplesort mysort;
int testA[7] = { 5,16,3,11,8,1,3 };
cout << "before Sort" << endl;
mysort.PrintResult(testA, 7);
mysort.InsertSort(testA,7);
cout << "after Sort" << endl;
mysort.PrintResult(testA, 7);
}
3.3 性能分析
3.3.1 空间复杂度:
无需额外申请临时空间作交换使用,为O(1);
3.3.2 稳定性:
相同大小元素不会交换位置,稳定;
3.3.3 时间复杂度:
最好:
逆序度为0,遍历一遍就推出,为O(n);
最坏:
有序度为0,每个元素都要遍历,根据2.1节分析,需要遍历
(
n
−
1
)
+
(
n
−
2
)
+
.
.
.
.
+
1
=
n
(
n
−
1
)
2
次
(n-1)+(n-2)+....+1= \frac{n(n-1)}{2} 次
(n−1)+(n−2)+....+1=2n(n−1)次,为
O
(
n
2
)
O(n^2)
O(n2);
平均:
逆序度、有序度各一半,平均需要
n
(
n
−
1
)
4
次
\frac{n(n-1)}{4} 次
4n(n−1)次,为
O
(
n
2
)
O(n^2)
O(n2)。
4、选择排序
4.1 思路
类似插入排序,也分已排序区间和未排序区间。但是选择排序每次会从未排序区间中找到最小的元素,将其放到已排序区间的末尾。
举例: 对数组{ 3,16,5,11,8,3,1 }
这几个数插入排序,要求从小到大(数字顺序与上述举例不同)。
选择排序图示:
选取未排序区中最小值,插入已排序的末尾,实现方式是与未排序区间的第一个元素交换位置。
4.2 cpp代码
函数定义:
void Simplesort::SelectSort(int* Arr, int n)
{
if (n <= 1 || Arr == nullptr)
{
return;
}
int min;
for (int i = 0; i < n-1; i++)//初始已排序区间为0,最后一个数无需排序,所以i范围0~n-1
{
min = Arr[i]; //选取未排序空间的第一个数作比较
for (int j = i; j < n; j++)//从i开始到n为未排序空间,遍历所有元素找最小值
{
if (Arr[j] < min)
{
swap(min, Arr[j]);//最小值更新数据
}
}
Arr[i] = min;//最小值放入已排序区间的末尾
}
}
测试:
int main()
{
Simplesort mysort;
int testA[7] = { 3,16,5,11,8,3,1 };
cout << "before Sort" << endl;
mysort.PrintResult(testA, 7);
mysort.SelectSort(testA, 7);
cout << "after Sort" << endl;
mysort.PrintResult(testA, 7);
}
4.3 性能分析
4.3.1 空间复杂度:
需申请临时空间作交换使用,同冒泡排序,为O(1);
4.3.2 稳定性:
相同大小元素会交换位置,不稳定(此案例的两个3会变化,图示的步骤a到步骤b);
4.3.3 时间复杂度:
最好、最坏、平均都需要挨个遍历找最小值,因此都为 O ( n 2 ) O(n^2) O(n2)。