1.插入排序
原理:
遍历i个元素,对于第i个元素,依次与前面的元素比较大小,如果前面的元素大于第i个元素,则调换两元素位置,直到第i个元素小于前一个元素值。(以顺序为例)
代码实现:
void insertsort(int arr[],int length){
for(int i=1;i<length;i++)//第一个元素(arr[0])默认为有序了,所以i从第二个元素开始遍历
{
for(int j=i;j>=1&&arr[j-1]>arr[j];j--){
int temp=arr[j];
arr[j]=arr[j-1];
arr[j-1]=temp;
}
}
}
优点:当原序列前半部分刚好已经按顺序排列时插入排序比较高效。
时间复杂度:最坏情况下为O(N*N),此时待排序列为逆序,或者说接近逆序
最好情况下为O(N),此时待排序列为升序,或者说接近升序。
空间复杂度:O(1)
2.希尔排序
原理:
其本质为优化后的插入排序,插入排序是元素与前一个元素比较大小,步幅为1,希尔排序则拉大了步幅,再将步幅逐渐缩小到1。
代码实现:
void shellSort(int arr[],int length,int n)//n为“步幅”
{
while(n>1)
{
for(int i=n;i<length;i++){
for(int j=i;j>=n&&arr[j-n]>arr[j];j--)
{
int temp=arr[j];
arr[j]=arr[j-n];
arr[j-n]=temp;
}
}
n/=2;//每次循环步幅整除2。
}
}
优点:
当排序的数据很多时,希尔排序比插入排序更高效,遍历次数更少。
时间复杂度平均:O(N^1.3)
空间复杂度:O(1)
3.选择排序
原理:
对于第i个元素,从其后面的所有元素中找出最大(小)值与第i个元素比较大小,如果第i个元素小,则交换两者位置。
代码实现:
void SelectSort(int arr[],int length)
{
int max,temp,num;
for(int i=0;i<length-1;i++){
for(int j=i+1;j<length;j++){
max=arr[i+1],num=i+1;//num记录下标
if(max<arr[j]){
max=arr[j];
num=j;
}
if(arr[i]<max){//比较大小,交换位置.
temp=arr[i];
arr[i]=max;
arr[num]=temp;
}
}
}
优点:
思路简单,容易理解。
优化:
每次遍历时,我们可以找出最大值的同时也找出最小值,这样能使排序效率高出一倍。
优化代码的实现:
void SelectSort(int arr[],int length)
{
int Max_num,Min_num;
for(int i=0;i<length-1;i++){
for(int j=i+1;j<length;j++){
Max_num=i+1,Min_num=i+1;//num记录下标
if(arr[Max_num]<arr[j]){
Max_num=j;
}
if(arr[Min_num]>arr[j]){
Min_num=j;
}
if(arr[i]<arr[Max_num]){//比较大小,交换位置.
int temp=arr[i];
arr[i]=arr[Max_num];
arr[Max_num]=temp;
}
if(arr[length-1-i]>arr[Min_num]){
int temp=arr[length-1-i];
arr[length-1-i]=arr[Min_num];
arr[Min_num]=temp;
}
}
}
时间复杂度:最坏情况:O(N^2)
最好情况:O(N^2)
空间复杂度:O(1)
4.冒泡排序
原理:
两两比较交换,将最大值放到序列的末尾。
代码实现:
void BubbleSort(int *arr,int length)
{
int count=length;
while(count--){
for(int i=0;i<count;i++){
int flag=0;
if(arr[i]>arr[i+1]){
int temp=arr[i+1];
arr[i+1]=arr[i];
arr[i]=temp;
flag=1;
}
if(flag==0) break;//判断是否进行了交换,若没有,则说明已经是有序的了,不用再循环下去。
}
}
}
优点:
代码可读性高,适合小白,好理解,运行稳定,加入flag后的代码时间复杂度降低。
时间复杂度:最坏情况:O(N^2)
最好情况:O(N)
空间复杂度:O(1)
5.快速排序
原理:
采用分而治之的思想,设定一个轴点(从第一个元素开始),左右两边分别进行递归,每一轮递归后在轴点左边的都比轴点小,在轴点右边的都比轴点大。
每次递归过程中,先从右往左遍历,遇到比轴点小的元素时停止,并将两个元素交换位置,然后从左往右遍历,遇到比轴点大的元素停止,交换两点位置,又回到从左往右的新一轮递归中。当左右遍历相遇时,轴点左边都比轴点小,轴点右边都比轴点大。然后再依次对轴点左右两边的子序列进行快速排序。
代码实现:
void QuickSort(int arr[],int high,int low)
{
if(low<high){
int pos_pivot=Partition(arr,high,low);//Partition函数返回的是轴点的下标。
QuickSort(arr,pos_pivot-1,low);
QuickSort(arr,high,pos_pivot+1);
}//递归思想
}
int Partition(int *arr,int high,int low)
{
int pivot=arr[low];//先拿出轴点的值,用pivot储存
while(low<high){
//先从high向轴点:
while(pivot<=arr[high]&&high>low) high--;
//直到遇到比轴点小的元素,将arr[high]的元素拿出来放到arr[low]位置(轴点位置也被交换):
arr[low]=arr[high];
//再从low向轴点:
while(pivot>=arr[low]&&high>low) low++;
//直到遇到比轴点大的元素,将arr[low]的元素拿出来放到arr[high]位置(轴点位置也被交换):
arr[high]=arr[low];
}
//此时low=high,arr[low]无元素,所以要把pivot储存的值(即轴点的值)放回到arr[low]
arr[low]=pivot;
return low;//返回轴点下标。
}
优点:
时间复杂度相较其他排序大大降低。(但是不够稳定,难以理解)
时间复杂度:
快速排序的过程类似于二叉树其高度为logN,每层约有N个数。
6.堆排序
原理:
堆可分为大根堆与小根堆,其本质上是一棵完全二叉树。
大根堆每个父节点大于等于孩子节点:
。
小根堆每个父节点小于等于孩子节点:
以建小根堆为例,选出左右孩子节点中小的那个,与父节点交换,即“小的向上浮,大的往下沉”。(建大根堆则相反)
代码实现(以小根堆为例):
//构建小根堆函数,映射到数组arr[]中.
void rootpiles(int* arr, int n, int root)
{
int parent = root;
int child = 2*parent + 1;
//如果孩子节点下标大于数组大小,则循环结束
while (child < n)
{
//child为左右孩子节点中小的那个
if (arr[child + 1] < arr[child]&& child + 1 <n)//防止没有右孩子节点
{
++child;
}
//小的向上浮,大的向下浮
if (arr[child] < arr[parent])
{
int tem = arr[parent];
arr[parent] = arr[child];
arr[child] = tem;
parent = child;
child = 2*parent + 1;
}
else//如果child>parent则已满足小堆,跳出循环。
{
break;
}
}
}
//堆排序
void HeapSort(int* arr, int n)
{
//建小根堆
for (int i = (n -1 -1) / 2; i >= 0; --i)
{
rootpiles(arr, n, i);
}
int end = n - 1;
//把最小的换到最后一个位置,不把最后一个数看作堆里的
//每次选出剩下数中最小的
//从后往前放
while (end > 0)
{
swap(arr[end],arr[0]);//选出第二小的节点
rootpiles(arr, end, 0);
--end;
}
}
优点:
时间复杂度小,排序较为高效。
时间复杂度:最坏的情况及满二叉树,且每个节点都需要调整,此时建堆的时间复杂度为O(N);
向下调整算法的时间复杂度为O(log2N);
所以堆排序的时间复杂度为O(N*log2N);
总结:
本篇文章罗列出了6种较为常用的排序算法。不同的算法有不同的思维方式和原理,不同排序算法也有着不同的特性和优点。有人会说,只会一种排序算法不就可以了,何必再学其他算法。但我觉得,学不同的排序算法实际上也是在学习不同的思维方式,让自己思维更开阔,更重要的是,能让我们得以触碰,感受算法的无限奥秘与魅力。
本人也是从大学才开始学习编程,目前还是一名大一的小白,学校教了c语言,我就自己自学数据结构与算法,自学过程中挫折是必然的,有时候想破脑袋都想不出怎么写,但是,每次自己理解并解决了一个困扰自己很久的问题时产生的成就感让我觉得之前的努力都是值得的。选择计算机就要做好不断学习的打算,路漫漫其修远兮,吾将上下而求索。文章中若有任何错误,欢迎大家指正!