排序算法比较的几个点:
- 时间复杂度
- 空间复杂度
- 排序算法稳定性
排序算法稳定性的定义:
假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。
以下按照自己的想法总结的内容:
想看详细可以观看下面的博客,有详细的流程图:
十大排序算法-快排-希尔-堆排-归并-冒泡-桶排-选择-插入-计数-基数-1_天津 唐秙的博客-CSDN博客
算法的复杂度和稳定性
排序算法 | 平均时间复杂度 | 最坏的时间复杂度 | 最好的时间复杂度 | 空间复杂度 | 稳定性 |
冒泡排序 | O(n^2) | O(n^2) | O(n) | O(1) | 稳定 |
选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | 数组不稳定、链表稳定 |
插入排序 | O(n^2) | O(n^2) | O(n) | O(1) | 稳定 |
快速排序 | O(n*log2(n)) | O(n^2) | O(n^2) | O(log2n) | 不稳定 |
计数排序 | O(n+m) | O(n+m) | O(n+m) | O(n+m) | 稳定 |
堆排序 | O(n*log2(n)) | O(n*log2(n)) | O(nlog2(n)) | O(1) | 不稳定 |
希尔排序 | O(n*log2(n)) | O(n^2) | O(nlog2(n)) | O(1) | 不稳定 |
归并排序 | O(n*log2(n)) | O(n*log2(n)) | O(nlog2(n)) | O(n) | 稳定 |
桶排序 | O(n+k) | O(n^2) | O(n^2) | O(n+k) | 稳定 |
基数排序 | O(k*n) | O(k*n) | O(k*n) | O(k+n) | 稳定 |
冒泡排序:
实现思路:从第一个数据开始,拿该数据和其他数据轮番比较,(从小到大排序)如果该数据大于其他数据的话,则交换数据
//升序
//从前往后排
void Bubbling_sort(vector<int>& p)//冒泡排序
{
for (int i = 0; i < p.size()-1; i++)//从第一个数据开始遍历,最后一个不需要遍历所以-1
{
for (int j = i+1; j < p.size(); j++)//遍历的次数为数组长度减去已找到的数据
{
if (p[i] > p[j ])
{
swap(p[i], p[j]);
}
}
}
}
//从后往前排
void Bubbling_sort(vector<int>& p)//冒泡排序
{
for (int i = 0; i < p.size()-1; i++)//从第一个数据开始遍历,最后一个不需要遍历所以-1
{
for (int j = 0; j < p.size()- 1-i; j++)//遍历的次数为数组长度减去已找到的数据
{
if (p[j] > p[j + 1])
{
swap(p[j], p[j + 1]);
}
}
}
}
时间复杂度:O(n^2) 空间复杂度:O(1) 排序算法稳定
选择排序:
实现思路:从第一个数据开始,用一个数据来存储下标,找到最小数的下标,然后交换这两个数。
//升序
void Select_Sort(vector<int>& p)//选择排序
{
int min;//存储下标
for (int i = 0; i < p.size()-1; i++)//只需要遍历i-1次
{
min = i;//假设第一个数为最小
for (int j = i + 1; j < p.size(); j++)
{
if (p[j] < p[min])//找到小值
{
min = j;//更换下标
}
}
if (min != i)//如果下标不匹配
{
swap(p[i],p[min]);//交换数值
}
}
}
时间复杂度:O(n^2) 空间复杂度:O(1) 排序算法不稳定(链表的话稳定)
不稳定的体现:
插入排序:
实现思路:假设第一个已排序,从第二个数据开始,与前面的数据进行比较,如果比前面的数据小,则交换两个数据,如果比前面的数据大,则结束。
//升序
void Insert_Sort(vector<int>& p)//插入排序
{
for (int i = 1; i < p.size(); i++)//从第二个数据开始
{
if (p[i] < p[i - 1])//当前面的数大于该数时
{
int data = p[i];//获取插入的数据
int n = i-1;//获取i-1的位置
p[n+1] = p[n];
while (n > 0 && data < p[n-1])
{
p[n] = p[n - 1];//把数据前移
n--;//位置往后移
}
p[n] = data;//找到位置插入
}
}
}
时间复杂度:O(n^2) 空间复杂度:O(1) 排序算法稳定
快速排序:
实现思路:(分治算法基础上设计出来的一种排序算法)
- 先取一个值P作为中轴,
- 把比它小的数放到左边
- 把比它大的数放到右边
- 重复对左右两个区域进行1-3步。
void Quick_Sort(vector<int>& p, int begin, int end)//快速排序
{
if (begin >= end)//结束条件
return;
int head = begin;//头指针
int tail = end;//尾指针
int data = p[begin];//以头节点为中轴
while (head < tail)//当头小于尾时执行
{
//从右边开始找小的数
while (head<tail && p[tail]>=data) { tail--; }//如果尾部的数大于中枢的值,跳过,指向上一个
p[head] = p[tail];//把值给赋给头指针
//从左边开始找大的数
while (head < tail && p[head]<= data) { head++; }//头部的值小于中枢的值,跳过,指向下一个
p[tail] = p[head];//把值给赋给头指针
}
//当两个指针相等时就是中枢节点的位置
p[head] = data;
Quick_Sort(p, begin, head - 1);//递归左半部分
Quick_Sort(p, head + 1,end );//递归右半部分
}
时间复杂度:O(nlog2n) 空间复杂度:O(log2n)) 排序算法不稳定
计数排序:
实现思路为:
- 获取数组中的最大最小值,获取两个数的差值
- 开辟一个数组,大小为 :差值+1 初始化为0
- 利用新数组来存储,元素的个数
- 得到个数后,从头开始输出数组元素并赋值给原数组
void Count_Sort(vector<int>& p)//计数排序
{
int min = 0;//存储最小值
int max = 0;//存储最大值
for (int i = 0; i < p.size(); i++)
{
if (p[i] < min)min = p[i];//找最小值
if (p[i] > min)max = p[i];//找最大值
}
int num = max - min;//存储差值,用来创建数组
vector<int>count(num + 1, 0);//用来存储元素个数
for (int i = 0; i < p.size(); i++)
{
count[p[i - min]]++;//统计元素个数
}
int j = 0;
for (int i = 0; i < p.size();)
{
while (j <= num && count[j])
{
count[j--];//个数减一
p[i] = j + min;//把数值放到数值中
i++;
}
j++;
}
}
时间复杂度:O(n+m) 空间复杂度:O(n+m) 排序算法稳定
堆排序:
推荐一个视频方便理解:
实现思路:
- 建堆 小到大排序(建大堆),大到小排序(建小堆)
- 替换第一个和最后一个数据,把树的元素个数减1
- 重复1-2,直到只剩一个元素
建堆的一些概念:
void heapify(vector<int>& p, int n, int i)//建堆,n为数组长度,i为待维护节点下标
{
int mid = i;//
int lchild = i * 2 + 1;//i的左节点
int rchild = i * 2 + 2;//i的右节点
//获取三点的最大值
if (lchild < n && p[mid] < p[lchild])
{
mid = lchild;
}
if (lchild < n && p[mid] < p[rchild])
{
mid = rchild;
}
//如果最大值不为父节点
if (mid != i)
{
swap(p[mid], p[i]);//交换数值,使父节点成为最大值
heapify(p, n, mid);//继续比较完成建堆
}
}
void Heap_Sort(vector<int>& p, int n)
{
//建堆
int i;
//由于n的父节点为(n-1)/2 相当于(n-1-1)/2=n/2-1
for (i = n / 2 - 1; i >= 0; i--)//从后往前建堆
{
heapify(p, n, i);
}
//排序
for (i = n - 1; i >= 0; i++)
{
swap(p[0], p[i]);//交换第一和最后一个值
heapify(p, i, 0);//调整位置
}
}
时间复杂度:O(n*log2n) 空间复杂度:O(1) 排序算法不稳定
希尔排序:
实现思路:
是插入排序的增强版,希尔排序将数组分隔成n组来进行插入排序,每一次,将数据排的相对有序。
void shell_Sort(vector<int>& p)//希尔排序
{
int n = p.size();//获取数组长度
//设置增量,每次循环/2
for (int i = n / 2; i > 0; i /= 2)
{
//起始值从i开始,进行插入排序
for (int j=i; j < n; j++)
{
int k = i;
int temp = p[k];
while (k - i >= 0 && p[k - i] > temp)
{
p[k] = p[k - i];
k = k - i;
}
p[k] = temp;
}
}
}
时间复杂度:O(n*log2n) 空间复杂度:O(1) 排序算法不稳定
归并算法:
方法为:
- 先把长度为n的数据分为两块
- 通过递归对左右半区进行拆分
- 拆分完后,分别对左右两边的数据,进行对比(排序),放入临时数组中
- 然后拷贝到数组中
排序算法:归并排序【图解+代码】_哔哩哔哩_bilibili
void merge(vector<int>& n, vector<int>& m, int left , int mid, int right)合并
{
//左边第一个元素
int x = left;
//右边第一个元素
int y = mid + 1;
//记录临时数组下标
int z = left;
while (x <= mid && y <= right)
{
if (n[x] < n[y])
{
m[z++] = n[x++];
}
else
{
m[z++] = n[y++];
}
}
//左边还有剩余元素
while (x <= mid)
{
m[z++] = n[x++];
}
//右边还有剩余元素
while (y <= right)
{
m[z++] = n[y++];
}
//把数据复制给数组
while (left <= right)
{
n[left] = m[left];
left++;
}
}
void msort(vector<int>& n, vector<int>& m, int left, int right)
{
if (left < right)
{
int mid = left + (right - left) / 2;//取中间点
//递归拆分左边
msort(n, m, left, mid);
//递归拆分右边
msort(n, m, mid + 1, right);
//合并
merge(n,m,left,mid,right);
}
}
void merge_Sort(vector<int>&p,int n)//归并排序
{
vector<int>G(n, 0);//辅助数组
msort(p, G, 0, n - 1);//归并入口
}
桶排序:
类似于哈希表,解决冲突的方法为地址法,把每个头节点当作一个桶。
把数据分别放入对应的桶中,然后从第一个桶开始,取数据放到数组中
基数排序:
- 取得数组中的最大数,并取得位数。
- arr为原始数组,从最低位开始取每个位组成radix数组。
- 对radix进行计数排序(利用计数排序适用于小范围数的特点)