根据两个元素的比较结果然后进行交换,主要是冒泡排序和快速排序。
冒泡排序
基本思想:若为升序,则从后往前通过两两交换将无序区最小的元素放到有序区最后一位,无序区长度每次少1,最多n-1次排完。
void bubbleSort(int arr[], int n)
{
int i, j;
bool flag;
for (i = 0; i < n - 1; ++i){//i限定有限区
flag = false;//本趟是否交换
for (j = n - 1; j > i; --j)//交换时不能进入有序区
if (arr[j] < arr[j - 1]){
swap(arr[j - 1], arr[j]);
flag = true;
}
if (flag == false)return;//没有交换,说明有序了
print(arr, n, i+1);
}
}
第0轮: 6 5 3 1 8 7 2 4//初始情况
第1轮: 1 6 5 3 2 8 7 4
第2轮: 1 2 6 5 3 4 8 7
第3轮: 1 2 3 6 5 4 7 8
第4轮: 1 2 3 4 6 5 7 8
第5轮: 1 2 3 4 5 6 7 8
空间复杂度:
O(1)
时间复杂度:
- 最好情况:顺序,则第一次冒泡比较了n-1次,移动0次,复杂度为 O(n)
- 最坏情况:逆序(和期望正好相反),则要冒n-1次泡,第i趟进行n-i次比较,且每次比较须移动3次实现交换,此时:
比较次数=∑i=1n−1(n−i)=n(n−1)2
移动次数=∑i=1n−13(n−i)=3n(n−1)2
故而,最坏情况复杂度为 O(n2) ,平均也为 O(n2) . - 稳定性:稳定。
快速排序
对冒泡排序的改进,选一个基准,每一趟比它小的去左边,比它大的去右边,然后左右两边递归实现,直到每一部分没有或者仅有一个元素。
void quickSort(int arr[], int low, int high)
{
if (low < high){
int pivotpos = partition(arr, low, high);
quickSort(arr, low, pivotpos - 1);
quickSort(arr, pivotpos + 1, high);
}
}
int partition(int arr[], int low, int high)
{
int pivot = arr[low];//以第一个作为基准值
while (low < high){
while (low < high&&arr[high] >= pivot) --high;
arr[low] = arr[high];
while (low < high&&arr[low] <= pivot) ++low;
arr[high] = arr[low];
}
arr[low] = pivot;
return low;
}
空间复杂度:需要一个递归工作栈,其容量与递归调用最大深度一致。
- 最好情况下,每次都能分一半,则只需递归 log2(n+1) 向上取整次,复杂度为 O(log2n)
- 最差情况下,每次分的两部分有一部分没有元素,则相当于要分n-1次,复杂度为 O(n)
- 平均: O(log2n)
时间复杂度:
- 最好情况下,每次能分一半,每次比较都是和所有的元素比较所以不变,那么总共复杂度为 O(nlog2n)
- 最差情况下,每次分的两部分有一部分没有元素,则需要 n−1 次分割,每次比较的时候都是和所有值比较,总共为 n∗(n−1) ,复杂度为 O(n2)
- 平均: O(nlog2n)
稳定性:不稳定。
对于时间复杂度和空间复杂度的平均值为什么是最好情况的复杂度而不是最差情况的复杂度,我的理解是:对于一个随机的数列的一个基准数,它的值位于中间的概率是比较高的,也就是说,一般情况下都是趋于最好情况而不是趋于最差情况,故…
各算法复杂度表
加上上一篇插入排序的内容,表扩充为:
算法 | 平均时间复杂度 | 最好时间复杂度 | 最差时间复杂度 | 空间复杂度 | 稳定性 | 备注 |
---|---|---|---|---|---|---|
直接插入 | O(n2) | O(n) | O(n2) | O(1) | 稳定 | |
折半插入 | O(n2) | O(nlog2n) | O(n2) | O(1) | 稳定 | |
shell | O(n1.3) | O(n) | O(n2) | O(1) | 不稳定 | 和增量序列有关 |
冒泡 | O(n2) | O(n) | O(n2) | O(1) | 稳定 | 子序列全局有序,不同于插入排序 |
快排 | O(nlog2n) | O(nlog2n) | O(n2) | O(log2n) | 不稳定 |