待更新
算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 是否稳定排序 | 是否可以用于链式结构 | 适用情况 |
---|---|---|---|---|---|---|
快速排序 | O(nlog2n) | 数组有序时O(n^2) | O(log2n)~O(n) | 否 | 很难用于链式 | n较大,且数组无序 |
归并排序 | O(nlog2n) | O(nlog2n) | O(n+log2n) | 是 | 是 | 归并排序比较占内存,但是排序效率高,适用于n较小的情况 |
简单排序 | O(n^2) | O(n^2) | O(1) | 是 | 是 | 最朴实的排序,效率非常低 |
冒泡排序 | O(n^2) | O(n^2) | O(1) | 是 | 是 | 改进的冒泡排序最好的情况为O(n) |
简单选择排序 | O(n^2) | O(n^2) | O(1) | 否 | 是 | 简单选择排序比冒泡排序要快,在元素本身占较大空间时,比直接插入快 |
堆排序 | O(nlogn) | O(nlogn) | O(1) | 否 | 否 | 堆排序是简单选择的改进,不适合待排序个数较少的情况 |
直接插入排序 | O(n^2) | O(n^2) | O(1) | 是 | 是 | 直接插入比简单选择排序要快,如果元素基本有序,可以使用插入排序 |
折半插入排序 | O(n^2) | O(n^2) | O(1) | 是 | 是 | 查找部分由原来的顺序查找,变成了折半查找,所以不管待排序的数是否比有序序列中的最后一个元素的值大还是小,都要从中间开始比较,所以其适用于序列初始无序的情况 |
希尔排序 | O(nlogn~O(n^2)) | O(n^2) | O(1) | 否 | 否 | 最好情况为O(n^1.3),性能取决于增量的大小,是插入排序的改进 |
基数排序 | O(d(n+rd)) | O(d(n+rd)) | O(n+rd) | 是 | 是 | · |
下面的图片转载自牛客网,侵删
1.快速排序
快速排序的思想在于通过一次交换来解决多个元素逆序的问题,要解决多个元素逆序问题就不能和冒泡一样只交换相邻元素的位置,所以快速排序会跨着跳。
不同于使用两个low和high两个下标来回对比枢轴交换位置,剑指offer上面的方法形式上更简单
它的思想在于,把枢轴放在最后一个元素,从左往右开始比较枢轴,如果元素比枢轴小,则将元素移动到左侧,所以需要一个small_index来记录小于枢轴的元素的数量。
如果vec[i]<vec[end]且small_index!=i,则交换vec[i]和vec[samll_index]的位置。这样一来small_index位置左侧的元素,必定小于vec[end].
在遍历完元素之后,枢轴的位置还在end位置,而small_index之前的元素都是小于枢轴的,所以交换vec[small_index]和vec[end]就可以完成一趟快速排序。
后续只需要递归完成快速排序就可以。
int get_Parition(vector<int>& vec,int start,int end) {
//std::rand();
int small_index = start;
for (int i = start; i <=end;++i) {
if (vec[i]<vec[end])
{
if(small_index!=i){
std::swap(vec[i], vec[small_index]);
}
++small_index;
}
}
std::swap(vec[small_index], vec[end]);
return small_index;
}
void my_quick_sort(vector<int>& vec,int start ,int end) {
if (start<end) {
int parition = get_Parition(vec, start, end);
my_quick_sort(vec, start, parition - 1);
my_quick_sort(vec, parition + 1, end);
}
}
//修改版
void q_sort_core(vector<int>& vec,int begin,int end) {
int v;
while (begin < end) {
v = get_pivo(vec, begin, end);
//if (begin<v-1) {
q_sort_core(vec, begin, v - 1);
//}
begin = v + 1;
}
}
书上说是尾递归优化,但是看起来并不是的,尾递归的优化要在return那里
这里可以减少递归次数的原因在于,这里在进行后半段排序的时候,检测了一遍
begin<end所以可以减少递归次数。
2.归并排序
归并排序利用了完全二叉树的结构思想,只要进行log2n次归并就可以完成排序
它的思想在于将数组分为两个子数组,将两个子数组排序后合并为一个数组,使得这个数组变得有序。
因为他会两两比较元素的大小所以归并排序是稳定的排序,但是归并排序每次合并的时候都需要一个额外的空间来保存合并后的数组,这个额外的空间等于数据中元素的大小,所以归并需要占用比较多的内存,当数据量巨大的时候就不太适用了。
归并排序的核心思想:
1.将当前数组拆分为两个子数组
2.将两个子数组排序
3.将两个子数组归并为一个数组
//归并排序
void merge_sort_core(vector<int>& vec, int begin, int end);
void MergeSort(vector<int>& vec) {
if (vec.size()==0) {
return;
}
merge_sort_core(vec, 0, vec.size() - 1);
}
void merge_sort_core(vector<int>& vec,int begin,int end) {
//如果拆分到只剩下一个值,则返回
if (begin==end) {
return;
}
//将当前的数组,拆分为两个部分
int mid = (begin + end) / 2;
merge_sort_core(vec, begin, mid);
merge_sort_core(vec, mid + 1, end);
//上面的代码表示将两个子数组已经合并好了,下面把上面的两个子数组合并
//由于两个子数字是有序的,将两个有序的合并后到一起需要一段辅助空间
vector<int> copy_vec(end-begin+1);
//将两个子数组中的值传入到辅助数组中
int arr_1_index = begin;
int arr_2_index = mid + 1;
int copy_index = 0;
while (arr_1_index<=mid&&arr_2_index<=end) {
if (vec[arr_1_index]<=vec[arr_2_index]) {
copy_vec[copy_index] = vec[arr_1_index];
++arr_1_index;
}
else {
copy_vec[copy_index] = vec[arr_2_index];
++arr_2_index;
}
++copy_index;
}
//将剩余的部分都加入
while (arr_1_index<=mid) {
copy_vec[copy_index] = vec[arr_1_index];
++arr_1_index;
++copy_index;
}
while (arr_2_index <= end) {
copy_vec[copy_index] = vec[arr_2_index];
++arr_2_index;
++copy_index;
}
//将排序好的部份拷贝给原来的数组
std::copy(copy_vec.begin(), copy_vec.end(), vec.begin() + begin);
}
3.简单排序
/*
最简单的排序,但是不是冒泡
每一个数字j都和当前的i进行对比
如果当前的i大于j则交换二者
所以一轮次交换之后,可以得到一个最小的值
时间复杂度为O(n^2)
*/
void simple_sort(vector<int>& vec) {
for (int i = 0; i < vec.size();++i) {
for (int j = i + 1; j < vec.size();++j) {
if (vec[i]>vec[j]) {
swap(vec[i],vec[j]);
}
}
}
cout << endl;
}
4. 冒泡排序
/*
冒泡排序
冒泡顾名思义是从下往上冒,所以比较元素的时候是,从下往上的开始比较的
如果一个算法,已经是有序的
1,2,3,4,5,6,7,8,9
排序还是会一直往下走,从9到1,从9-2。
因为序列已经是有序的了,所以再次比较其实就是浪费性能,所以可以改进
时间复杂度为O(n^2)
*/
void bubble_sort(vector<int>& vec) {
for (int i = 0; i < vec.size();++i) {
bool is_sort = true;
for (int j = vec.size()-2; j >= i;--j) {
if (vec[j]>vec[j+1]) {
swap(vec[j],vec[j+1]);
is_sort = false;
}
}
if (is_sort) {
return;
}
}
}
5.简单选择排序
/*
简单选择排序的思想是,从一个无序的序列中,选择一个最小的值
插入到有序序列的最后面
时间复杂度为O(n^2)
*/
void simple_select_sort(vector<int>& vec) {
for (int i = 0; i < vec.size();++i) {
int min_index = i;
for (int j = i+1; j < vec.size();++j) {
if (vec[min_index]>vec[j]) {
min_index = j;
}
}
if (min_index!=i) {
swap(vec[min_index],vec[i]);
}
}
}
6. 直接插入排序
/*
插入排序的思想是从选择无序序列中的第一个元素,插入到有序序列中
在寻找插入位置的时候,可以使用二分查找
*/
void insert_sort(vector<int>& vec) {
for (int i = 0; i < vec.size()-1;++i) {
if (vec[i]>vec[i+1]) {
//保存当前的值
int value = vec[i + 1];
//前面的值往后覆盖
int j = i+1;
//使用j>0的方法,可以保证j-1不会越界
for (; j > 0&&value < vec[j-1];--j) {
vec[j] = vec[j-1];
}
//找到这个位置,然后插进去
vec[j] = value;
}
}
}
7. 折半插入排序
void binary_insert_sort(vector<int>& vec) {
for (int i = 1; i < vec.size();++i) {
//利用折半查找
int begin = 0;
int end = i;
int mid ;
//寻找需要插入的点,mid
while (begin<=end) {
mid = begin + (end - begin) / 2;
//cout << vec[mid] << endl;
if (vec[i]>vec[mid]) {
begin = mid + 1;
}
else {
end = mid - 1;
}
}
int value = vec[i];
int j = i;
//把从mid开始的点都移动到后面去
for (;j>mid ;--j) {
vec[j] = vec[j-1];
}
//mid位置的值,赋值给当前的value
vec[mid] = value;
}
}
8. 堆排序
如果是升序排列,则构造大根堆。
如果是降序排列,则构造小根堆。
这里是构造大根堆。
//
//完全二叉树根节点和左右子树的关系,需要从1开始计算,但是数组的下标是从0开始的。
void heap_adjust(vector<int>& vec,int index,int length) {
index += 1;
int i = index * 2;
int temp = vec[index - 1];
for (; i <= length;i*=2) {
//选择左右子树中,较大值的节点
if (i<length&&vec[i]>vec[i-1]) {
i += 1;
}
if (temp<vec[i-1]) {
vec[i / 2 - 1] = vec[i - 1];
}
else {
break;
}
}
vec[i / 2 - 1] = temp;
}
void heap_sort(vector<int>& vec) {
//1. 构建堆
for (int i = vec.size() / 2; i >=0;--i) {
heap_adjust(vec, i, vec.size());
}
//2.堆排序
for (int i = vec.size() - 1; i > 0;--i) {
std::swap(vec[0], vec[i]);
heap_adjust(vec, 0, i - 1);
}
}