爆肝整理!堪称全网最详细的十大常用经典排序算法总结!!!
写在开头,本文经过参考多方资料整理而成,全部参考目录会附在文章末尾。很多略有争议性的细节都是在不断查阅相关资料后总结的,具有一定普适性。
总表:
相关解释:
稳定:如果原本序列中a在b前面且a=b,排序后a仍在b前面,顺序不变;
不稳定:如果原本序列中a在b前面且a=b,排序后a可能在b后面,顺序可能发生改变;
内排序:所有排序操作均在内存中完成;
外排序:由于数据量太大,将其放入磁盘中,排序过程中需要磁盘与内存之间的数据传输;
时间复杂度:一个排序算法在执行过程中所耗费的时间量级的度量;
空间复杂度:一个排序算法在运行过程中临时占用存储空间大小的度量;
附加:
本表格已上传至个人资源,并设置了1积分下载(欢迎支持啦!!),有需要的朋友可以自取;
该表格已同步上传至网盘,附网盘下载链接:
链接:https://pan.baidu.com/s/1Ov7yFpZnbQy1cf_fBCGyEA
提取码:p400
规律性记忆:
归纳总结:
1. 稳定性记忆方法——“快希选堆”不稳定。
2. 需要使用额外空间的四种排序——基数、计数、桶排、归并。
3. 常用时间复杂度大小关系:O(1)<O(logn)<O(n)<O(n logn)<O(n²)<O(n³)<O(2^n)<O(n!)<O(n^n)。
4. 空间复杂度为O(1) 的排序:冒泡、插入、选择、希尔、堆排;
5. 时间复杂度最优情况可达O(n)的排序:冒泡、插入、桶排;
6. 最优、平均、最差时间复杂度一致的排序:选择、堆排、归并、基数、计数;
7. 最差情况下时间复杂度仍为O(n logn)的排序:堆排、归并;
8. 最差情况下时间复杂度仍在线性时间范围内的排序:计数;
排序方法选取规则:
1. 如果待排序列中数据含有大量重复值——优先使用计数排序;
2. 如果待排序列中数据近乎有序——优先使用插入排序;
3. 如果待排序列中数据取值范围有限——优先使用计数排序;
4. 如果待排序列中数据要求稳定——优先使用归并排序;
5. 如果待排序列需要使用链表——优先链表归并、链表快排;
6. 如果待排序列中数据无法全部装到内存——优先使用外部排序;
十大排序算法详解:
一、冒泡排序(Bubble Sort)
遍历所有的数据,每次对相邻元素进行两两比较,如果顺序和预先规定的顺序不一致,则进行位置交换;这样一次遍历会将最大或最小的数据上浮到顶端,之后再重复同样的操作,直到所有的数据有序。数据是反序时,耗费时间最长O(n²);数据是正序时,耗费时间最短O(n)。
平均时间复杂度为O(n²),空间复杂度为O(1),是一种稳定的排序算法。
附算法实现源码:
//冒泡排序
template <class T>
void BubbleSort(T data[],int n)
{
int flag=0;
for(int i=0;i<n;i++)
{
flag=0;
for(int j=1;j<n-i;j++)
{
if(data[j]<data[j-1])
{
flag=1;
T t=data[j];
data[j]=data[j-1];
data[j-1]=t;
}
}
if(flag==0)
return;
}
}
二、快速排序(Quick Sort)
快速排序采用分治法。首先从数列中挑出一个元素作为中间值。依次遍历数据,所有比中间值小的元素放在左边,所有比中间值大的元素放在右边。然后按此方法对左右两个子序列分别进行递归操作,直到所有数据有序。最理想的情况是,每次划分所选择的中间数恰好将当前序列几乎等分(均匀排布),整个算法的时间复杂度为O(n logn)。 最坏的情况是,每次所选的中间数是当前序列中的最大或最小元素(正序和逆序都是最坏),整个排序算法的时间复杂度为O(n²)。
平均时间复杂度为O(n logn),空间复杂度为O(logn),是一种不稳定的排序算法。
附算法实现源码:
//快速排序
template <class T>
int Partition(T data[],int left,int right)
{
T pivot=data[left];
while(left<right)
{
while(left<right&&data[right]>pivot)
right--;
data[left]=data[right];
while(left<right&&data[left]<=pivot)
left++;
data[right]=data[left];
}
data[left]=pivot;
return left;
}
template <class T>
void QuickSort(T data[],int left,int right)
{
if(left<right)
{
int p=Partition(data,left,right);
QuickSort(data,left,p-1);
QuickSort(data,p+1,right);
}
}
三、选择排序(Selection Sort)
遍历所有数据,先在数据中找出最大或最小的元素,放到序列的起始;然后再从余下的数据中继续寻找最大或最小的元素,依次放到序列中直到所有数据有序。原始数据的排列顺序不会影响程序耗费时间O(n²),相对费时,不适合大量数据排序。
平均时间复杂度为O(n²),空间复杂度为O(1),是一种不稳定的排序算法。
附算法实现源码:
//选择排序
template <class T>
void SelectionSort(T data[],int n)
{
for(int i=1;i<n;i++)
{
int k=i-1;
for(int j=i;j<n;j++)
{
if(data[j]<data[k])
{
k=j;
}
}
if(k!=i-1)
{
T t=data[k];
data[k]=data[i-1];
data[i-1]=t;
}
}
}
四、插入排序(Insertion Sort)
将前i个(初始为1)数据假定为有序序列,依次遍历数据,将当前数据插入到前述有序序列的适当位置,形成前i+1个有序序列,依次遍历完所有数据,直至序列中所有数据有序。数据是反序时,耗费时间最长O(n²);数据是正序时,耗费时间最短O(n)。适用于部分数据已经排好的少量数据排序。
平均时间复杂度为O(n²),空间复杂度为O(1),是一种稳定的排序算法。
附算法实现源码(直接插入+折半插入):
//直接插入排序
template <class T>
void InsertionSort(T Data[],int n)
{
int p,i;
for(p=1;p<n;p++)
{
T temp=Data[p];
i=p-1;
while(i>=0&&Data[i]>temp)
{
Data[i+1]=Data[i];
i--;
}
Data[i+1]=temp;
}
}
//折半插入排序
template <class T>
void BinaryInsertionSort(T Data[],int n)
{
int left,mid,right,p;
for(p=1;p<n;p++)
{
T temp=Data[p];
left =0;
right=n-1;
while(left<=right)
{
mid=(left+right)/2;
if(Data[mid]>temp)
right=mid-1;
else
left=mid+1;
}
for(int i=p-1;i>=left;i--)
Data[i+1]=Data[i];
Data[left]=temp;
}
}
五、希尔排序(Shell Sort)
希尔排序也称递减增量排序,是对插入排序的改进,以牺牲稳定性的方法提高效率。基本思路是先将整个数据序列分割成若干子序列分别进行直接插入排序,待整个序列中的记录基本有序时,再对全部数据进行依次直接插入排序,直至所有数据有序。希尔排序算法的性能与所选取的分组长度序列有很大关系,复杂度下界为O(n log²n),在中等规模的数据中表现良好。
平均时间复杂度为O(n^3/2),空间复杂度为O(1),是一种不稳定的排序算法。
附算法实现源码:
//希尔排序
template <class T>
void ShellSort(T Data[],int n)
{
int d=n/2;
while(d>=1)
{
for(int k=0;k<d;k++)
{
for(int i=k+d;i<n;i+=d)
{
T temp=Data[i];
int j=i-d;
while(j>=k&&Data[j]>temp)
{
Data[j+d]=Data[j];
j-=d;
}
Data[j+d]=temp;
}
}
d=d/2;
}
}
六、堆排序(Heap Sort)
堆排序利用堆这种近似完全二叉树的数据结构进行排序。堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。以最大堆为例,堆中的最大值总是位于根节点。首先将待排序的n个数据构造为大根堆,将顶端数据与末尾数据进行交换并将堆的尺寸减一,然后剩余n-1个数据再次构造为大根堆,再次交换,再次缩减,直至所有数据有序。建堆复杂度为O(n),调整堆复杂度为O(n logn)。最优的情况为所有叶节点铺满最底层,最差情况所有叶节点都没铺满,对复杂度的影响在常数级别,时间复杂度均为O(n logn)。
平均时间复杂度为O(n logn),空间复杂度为O(1),是一种不稳定的排序算法。
附算法实现源码:
//堆排序
template <class T>
void SiftDown(int left, int n, T Data[])
{
int i = left;
int j = 2*i + 1;
T temp = Data[i];
while(j < n)
{
if((j < n - 1)&&(Data[j] < Data[j+1])) j++;
if(temp < Data[j])
{
Data[i] = Data[j];
i = j;
j = 2*j + 1;
}
else break;
}
Data[i] = temp;
}
template <class T>
void BuildHeap(int n, T Data[])
{
for (int i = n/2-1; i >= 0; i--)
SiftDown(i, n, Data);
}
template <class T>
void Remove(T Data[], int n)
{
SiftDown(0, n, Data);
}
template <class T>
void HeapSort(T Data[], int n)
{
BuildHeap(n, Data);
for(int i = n-1; i > 0; i--)
{
T t = Data[0];
Data[0] = Data[i];
Data[i] = t;
Remove(Data, i);
}
}
七、归并排序(Merge Sort)
归并排序采用分治法,基本思想为将已有序的子序列合并,得到完全有序的序列。以二路归并为例,首先将整个数据样本拆分为两个子样本, 并分别对它们进行排序,拆分后的两个子样本序列,再继续递归的拆分为更小的子数据样本序列, 再分别进行排序, 直到最后数据序列长度为1无法拆分,最后将同一级别下的子数据样本两两合并在一起,直到所有数据有序。归并排序的终极优化版本为TimSort,最好情况下可将时间复杂度降至O(n)。还有一种改进的原地归并算法可牺牲部分时间效率将空间复杂度降至O(1)。
平均时间复杂度为O(n logn),空间复杂度为O(n),是一种稳定的排序算法。
附算法实现源码:
//归并排序
template <class T>
void Merge(T data[],int start,int mid,int end)
{
int len1=mid-start+1;
int len2=end-mid;
int i,j,k;
T* left=new T[len1];
T* right=new T[len2];
for(i=0;i<len1;i++)
{
left[i]=data[i+mid+1];
}
for(i=0;i<len2;i++)
{
right[i]=data[i+mid+1];
}
i=0,j=0;
for(k=start;k<end;k++)
{
if(i==len1||j==len2)
break;
if(left[i]<=right[j])
data[k]=left[i++];
else
data[k]=right[j++];
}
while(i<len1)
{
data[k++]=left[i++];
}
while(j<len2)
{
data[k++]=left[j++];
}
delete[] left;
delete[] right;
}
template <class T>
void MergeSort(T data[],int start,int end)
{
if(start<end)
{
int mid=(start+end)/2;
MergeSort(data,start,mid);
MergeSort(data,mid+1,end);
Merge(data,start,mid,end);
}
}
八、基数排序(Radix Sort)
基数排序是一种基于非比较的整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个不同数位分别比较。原始基数排序的算法思想是将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后从最低位开始依次进行一次排序。直到所有数据有序。基数排序时间复杂度为O(n×k),其中n为数据个数,k为数据位数。主要分为两种实现方法MSD(最高位优先)和LSD(最低位优先)。
平均时间复杂度为O(n×k),空间复杂度为O(n+k),是一种稳定的排序算法。
附算法实现源码:
//基数排序
const int RADIX=10;
template <class T>
struct LinkNode
{
T data;
LinkNode* next;
};
template <class T>
struct TubNode
{
LinkNode<T>* rear;
LinkNode<T>* front;
};
template <class T>
TubNode <T>* Distribute(T data[],int n,int ith)
{
TubNode<T>* tube = new TubNode<T>[RADIX];
memset(tube,0,sizeof(TubNode<T>)*RADIX);
LinkNode<T>* t;
for(int i=0;i<n;i++)
{
T v=data[i];
int j=ith-1;
while(j--)
v=v/RADIX;-
v=v%RADIX;
t=new LinkNode<T>;
t->data=data[i];
t->next=NULL;
if(tube[v].front)
{
tube[v].rear->next=t;
tube[v].rear=t;
}
else
{
tube[v].front=tube[v].rear=t;
}
}
return tube;
}
template <class T>
void Collect(T data[],TubNode<T>*tube)
{
LinkNode<T>*t,*p;
int index=0;
for(int i=0;i<RADIX;i++)
{
p=t=tube[i].front;
while(t)
{
data[index++]=t->data;
t=t->next;
delete p;
p=t;
}
}
delete[] tube;
}
template <class T>
void RadixSort(T data[],int n,int keys)
{
TubNode<T>* tube;
for(int i=0;i<keys;i++)
{
tube=Distribute<T>(data,n,i+1);
Collect<T>(data,tube);
}
}
九、计数排序(Counting Sort)
计数排序是一种基于非比较的线性时间复杂度的排序方法,计数排序要求输入的数据必须是有确定范围K的整数。假定输入的元素是 n 个 0 到 k 之间的整数,它的运行时间是 Θ(n + k)。首先要找出待排序的数组中的最大元素和最小元素,然后统计数组中每个值为i的元素出现的次数存入数组Count[i],在之后对所有的计数进行累加(从数组Count第一个元素开始,每一项与前一项相加),最后反向填充目标数组(具体操作为:将每个元素i放在新数组的第Count[i]项,每放一个元素对应Count[i]减去一)。适用于对最大值不是很大的整型元素序列进行排序的情况。最好最坏复杂度都处在线性范围O(n+k)。
平均时间复杂度为O(n+k),空间复杂度为O(k),是一种稳定的排序算法。
附算法实现源码:
//计数排序
template <class T>
void CountingSort(T in_data[], T out_data[], int length,int k)
{
T *temp = new T[k];
for (int i = 0; i < k; i++)
{
temp[i] = 0;
}
for (int i = 0; i < length; i++)
{
temp[in_data[i]] += 1;
}
for (int i = 1; i < k; i++)
{
temp[i] = temp[i] + temp[i - 1];
}
for (int i = length - 1; i >= 0; i--)
{
out_data[temp[in_data[i]]-1] = in_data[i];
temp[in_data[i]] -= 1;
}
delete[]temp;
}
十、桶排序(Bucket Sort)
桶排序属于计数排序的升级强化版,同时利用了分治的思想,将待排数据划分到一定数量的有序的桶里,然后再对每个桶中的数据进行排序(桶排序的稳定性取决于桶内排序算法是否稳定),最后再将各个桶里的数据有序的合并到一起。最理想的情况下,输入数据可以被均匀的分配在每一个桶中,时间复杂度可以降到O(n);最坏的情况为所有数据在同一个桶中进行排序,且使用了复杂度较高的排序算法,此时时间复杂度会变为O(n²)。为了使桶排序更加高效,需要做到以下两点:①在额外空间充足的情况下,尽量增加桶的数量。②使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中。
平均时间复杂度为O(n+k),空间复杂度为O(n+k),是一种稳定的排序算法。
附算法实现源码:
//桶排序
template <class T>
void Bucket_sort(T data[], int n, int max)
{
T *buckets;
if (data==NULL || n<1 || max<1)
{
return;
}
if ((buckets=(T *)malloc(max*sizeof(T)))==NULL)
{
return;
}
memset(buckets, 0, max*sizeof(T));
for (int i = 0; i < n; i++)
{
buckets[data[i]]++;
}
for (int i = 0, j = 0; i < max; i++)
{
while( (buckets[i]--) >0 )
{
data[j++] = i;
}
}
free(buckets);
}
相关代码后续会上传至网盘及个人资源,有需要者可自行下载。当然也可私信或留言博主私下获取!!!
链接:https://pan.baidu.com/s/1y9QuUpYLqzWxrQkD89tsSw
提取码:6blj
参考目录:
首先感谢参考文献中各位大佬的整理、总结与分享。如果在文章中出现侵犯您的版权或隐私的行为,请私信或留言联系博主,博主会在第一时间进行回复并做出相应修改。再次感谢参考目录中出现的各位大佬,Thanks~~
排序:https://baike.baidu.com/item/%E6%8E%92%E5%BA%8F
冒泡排序:https://baike.baidu.com/item/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F
插入排序:https://baike.baidu.com/item/%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F
选择排序:https://baike.baidu.com/item/%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F
快速排序:https://baike.baidu.com/item/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95
希尔排序:https://baike.baidu.com/item/%E5%B8%8C%E5%B0%94%E6%8E%92%E5%BA%8F
堆排序:https://baike.baidu.com/item/%E5%A0%86%E6%8E%92%E5%BA%8F
桶排序:https://baike.baidu.com/item/%E6%A1%B6%E6%8E%92%E5%BA%8F
计数排序:https://baike.baidu.com/item/%E8%AE%A1%E6%95%B0%E6%8E%92%E5%BA%8F
基数排序:https://baike.baidu.com/item/%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F
归并排序:https://baike.baidu.com/item/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F
十大经典排序算法总结:https://jingyan.baidu.com/article/d3b74d64900eb05f77e609b7.html
上官致远_十大经典排序算法:https://zhuanlan.zhihu.com/p/41923298
blackboydec_排序算法时间复杂度、空间复杂度、稳定性比较:https://blog.csdn.net/yushiyi6453/article/details/76407640
排序算法时间复杂度以及空间复杂度:https://blog.csdn.net/liwei123liwei123/article/details/77868195
茶还是咖啡_八大排序总结:https://www.jianshu.com/p/8edba972b4b0
_code_x_十大常见排序算法的复杂度与代码实现:https://www.jianshu.com/p/4753b10a482c
今天的文章十大常用经典排序算法总结图_十大算法「建议收藏」分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/74559.html