一、选择排序
- 插入排序的最坏和平均时间复杂度均为
)
#include<iostream>
using namespace std;
//实现选择排序
//最坏情况和平均情况时间复杂度均为O(n^2)
//原地排序,空间复杂度为O(1)
int main()
{
int arr[10] = { 23, 6, 1, 6, 45, 20, 15, 36, 5, 24 };
int n = sizeof(arr)/sizeof(int);
for (int i = 0; i < n; i++)
{
int max = arr[0];
int index = 0;
for (int j = 1; j < n - i; j++)
{
if (max < arr[j])
{
max = arr[j];
index = j;
}
}
swap(arr[index], arr[n-i-1]);
}
for (int i = 0; i < n; i++)
{
cout << arr[i] << " ";
}
return 0;
}
二、插入排序
- 最坏情况的时间复杂度与选择排序相同(对一个从大到小的数组进行从小到大的排序)
- 平均情况时间复杂度比选择排序的代价大约少一半(
)
#include<iostream>
using namespace std;
//插入排序
int main()
{
int arr[10] = { 23, 6, 1, 6, 45, 20, 15, 36, 5, 24 };
int n = sizeof(arr) / sizeof(int);
for (int i = 1; i < n; i++) //第一个元素已经被作为基准,故从第二个元素开始
{
int temp = arr[i];
int j = i - 1;
if (temp < arr[j])
{
while (j >= 0 && temp < arr[j])
{
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = temp;
}
}
for (int i = 0; i < n; i++)
{
cout << arr[i] << " ";
}
return 0;
三、快速排序
- 最坏情况的时间复杂度也为
,即对一个有序的数组进行排序,需要进行N次PARTITION,每次PARTITION的时间复杂度是
。
- 平均时间复杂度为。
时间复杂度递归方程满足:,
由Master定理,可得
。
#include<iostream>
using namespace std;
//快速排序
int PARTITION(int a[], int l, int r)
{
int pivot = a[l];//基准元素
while (l < r)
{
/*
由于我们是一次一次地进行元素位置调整,故不会发生
元素覆盖
*/
//整理基准元素的右边元素
while (l<r && a[r]>=pivot)
{
r--;
}
a[l] = a[r];
//整理基准元素的左边元素
while (l<r && a[l]<=pivot)
{
l++;
}
a[r] = a[l];
}
//找到基准元素正确的位置后放入
a[r] = pivot;
return r;
}
void Qick_sort(int a[], int l, int r)
{
if (l < r)
{
int pivot = PARTITION(a ,l, r);
//左递归
Qick_sort(a, l, pivot - 1);
//右递归
Qick_sort(a, pivot + 1, r);
}
}
int main()
{
int arr[10] = { 23, 6, 1, 6, 45, 20, 15, 36, 5, 24 };
int n = sizeof(arr) / sizeof(int);
Qick_sort(arr, 0, n - 1);
for (int i = 0; i < n; i++)
{
cout << arr[i] << " ";
}
return 0;
}
四、合并排序
在快速排序的基础上,递归的进行排序,不在规定基准元素pivot,而是直接等半划分,无论是最坏还是平均的时间复杂度均为,但牺牲了
的空间复杂度。
五、堆排序
堆的定义
- 堆结构特性:堆节点是一颗完全二叉树。
- 堆偏序特性:堆节点中存储的元素满足父节点的值大于所有子节点。
堆的修复
- 在堆顶元素被取走后,先将堆底元素放入堆顶元素,后进行浮动修复。
- 堆修复的时间复杂度代价为
。
堆的构建
- 堆构建的时间复杂度代价为
。
#include <iostream>
#include <algorithm>
using namespace std;
void max_heapify(int arr[], int start, int end) {
//父节点下标和子节点下标
int dad = start;
int son = dad * 2 + 1;
//堆修复
while (son <= end) { //若子节点指标在范围内才做比较
//比较左节点和右节点大小,若右节点更大,右节点更加可能与根节点进行交换。
if (son + 1 <= end && arr[son] < arr[son + 1])
son++;//成为为右节点
//如果父节点大于子节点中的最大值代表满足堆偏序关系,直接while循环
if (arr[dad] > arr[son])
return;
//否则交换父子继续进行堆修复
else {
swap(arr[dad], arr[son]);
dad = son;
son = dad * 2 + 1;
}
}
}
void heap_sort(int arr[], int len) {
//构建一个初始堆,从最后一个开始,保证了子节点的一定满足堆偏序特性。
for (int i = len / 2 - 1; i >= 0; i--)
max_heapify(arr, i, len - 1);
//将最大值不断地与后面的节点进行交换
for (int i = len - 1; i > 0; i--) {
swap(arr[0], arr[i]);
//进行堆修复,只用修复任意一个子树
max_heapify(arr, 0, i - 1);
}
}
int main() {
int arr[] = { 23, 6, 1, 6, 45, 20, 15, 36, 5, 24 };
int len = (int)sizeof(arr) / sizeof(*arr);
heap_sort(arr, len);
for (int i = 0; i < len; i++)
cout << arr[i] << ' ';
cout << endl;
return 0;
}
时间复杂度分析
- 最坏情况时间复杂度和平均时间复杂度均为
。
六、基于比较排序的排序算法下界
- 比较排序的最坏情况时间复杂度的下界为
。
- 比较排序的平均情况时间复杂度的下界为
。