排序算法的实际应用场景很多,很多排序算法的思路我们可能大致理解,但是要你用代码实现却要花些功夫,个人认为对算法掌握程度最好就是用代码把它们实现一遍。最近正好学习到排序这里,所以就把几种常用的排序算法全部写一遍,你会发现之前只是理解算法的大致思路,很快就会忘记,当然下面的描述其中还有很多可以优化,提高运行效率的地方。当然了,同一种算法写法很有多种,有些地方可以简化。
冒泡排序应该是排序算法的入门算法,是一种比较简单的排序,容易理解。从第一个元素开始,依次与其他元素比较,遇见比他大的元素就交换两者的位置,这样一直重复n-1次(n表示数组的大小)
平均时间复杂度为O( n 2 n^2 n2)
void Swap(int a[],int i,int j)
{
int temp =a[i];
a[i]=a[j];
a[j]=temp;
}
void BubleSort(int data[],int n)
{
for(int i=1; i<n; i++)
{
for(int j=0; j<i; j++)
{
if(data[j]>data[i])
Swap(data,i,j);
}
}
插入排序就是将一个数据插入到已经排好序的有序数据中,从而得到一个新的、个数加一的有序数据,算法适用于少量数据的排序,时间复杂度为** O(n^2) 。是 稳定 的排序方法。
平均时间复杂度: O(n^2) **,空间复杂度 O(1)
具体步骤:从第二个元素开始,(在数组乱序的情况下,把数组的第一个元素作为已经排好序的有序数组),依次将n-1个元素插入到前面的有序数组中。每一次插入时,依次向前遍历,如果比前面的元素小,就交换两者位置,一直比较,直到将元素插入。
void InsertSort(int data[],int n)
{
for(int i=1; i<n; i++) //循环n-1次
{
int temp=data[i];
int j=i;
for(; j>0 && temp<data[j-1]; j--) //比前面的小就交换
{
data[j]=data[j-1]; //向后移动
}
data[j]=temp;
}
}
希尔排序(Shell’s Sort)是一种带间隔的插入是对插入排序算法的一种更高效的改进版本。希尔排序是非稳定排序算法。
平均时间复杂度:O(n^1.3),
最坏时间复杂度O( n 2 n^2 n2),最好时间复杂度** O(n) **空间复杂度 **O(1)**空间复杂度 O(1)
void ShellSort(int data[],int n) //改进的插入排序
{
for(int gap=(n-1)/3; gap>0; gap=(gap-1)/3) //Kuth序列h=3h+1 Kuth序列比间隔h每次缩小一半效率高些 {
for(int i=gap; i<n; i=i+gap) //以每个间隔做一次插入排序
{
for(int j=i; j>=gap; j=j-gap)
{
if(data[j]<data[j-gap])
Swap(&data[j],&data[j-gap]);
}
}
}
选定数组中任意一个元素作为** 轴 **,看作基本点(base case),将要排序的数组分割成独立的两部分,其中一部分的所有数据都比轴小,另外一部分的所有数据都比轴大,然后再重复此方法对左右两部分数据分别进行快速排序,整个排序过程可以递归进行,递归出口:数组只有一个元素时,就退出。从而使得整个数据变成有序序列。
void QuickSort(int data[],int left,int right)
{
if(left>=right) //递归终止的边界条件
return;
int flag=data[right]; //最后的值作为轴
int i=left;
int j=right;
while(i<j)
{
while(i<j && data[i]<=flag) i++;
while(j>i && data[j]>=flag) j--;
if(i<j)
Swap(data,i,j);
}
Swap(data,i,right);
QuickSort(data,left,i-1);
QuickSort(data,i+1,right);
}
归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,采用分治法(Divide and Conquer)。将已有序的子序列合并(** merge **),得到完全有序的序列。将已排好序的子序列合并成一个有序的数组,称之为归并排序。
步骤:开一个和原数组等长的新数组,遍历每一个子序列,比较它们的值。谁比较小,谁先放入大数组中,直到数组遍历完成。
归并排序的前提是需要两个已经排好顺序的数组,但一般数组都是乱序的,所以需要用到分治思想,先将原数组分隔成一份一份的,每一个元素是一个有序的"数组",将相邻两个数组归并排序,形成一个新的有序数组,然后依次合成,再做最后一次归并。
这就是我们的分治法—>将一个大问题分成很多个小问题进行解决,最后重新组合起来。
void MergeSort(int data[],int left,int right) //分治思想,前提是2个子数组必须排好序
{
if(left==right)
return;
int mid=left+(right-left)/2; //防止数据范围超出;
MergeSort(data,left,mid);
MergeSort(data,mid+1,right);
Merge(data,left,mid,right);
}
//做合并,前提是左右两个数组分别是有序的
void Merge(int data[],int lefpo,int mid,int rigpo)
{
int i=lefpo,j=mid+1,k=0;
Typedata a[rigpo-lefpo+1]; //临时数组
while(i<=mid && j<=rigpo)
a[k++]=(data[i]<=data[j]) ? data[i++] : data[j++];
while(i<=mid) //右边先到边界
a[k++]=data[i++];
while(j<=rigpo) //左边先到 边界
a[k++]=data[j++];
for(int m=0; m<=rigpo-lefpo; m++)
data[lefpo+m]=a[m];
}
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆分为:最大堆,最小堆。
最大堆:父亲节点的元素都要大于其孩子节点,最小堆:父亲节点权重小于其左右孩子节点。
基本思想:把待排序的元素按照大小排成最大堆的形式(父节点的元素要大于等于其子节点)这个过程叫做建堆,根据这个特性(大根堆根最大,小根堆根最小),就可以把根节点拿出来,然后再堆化下,再把根节点拿出来,,,,循环到最后一个节点,就排序好了。
下面代码实现的是最大堆(根节点是最大值)
void heapfy(int data[],int n,int i)
{
if(i>=n) //超出节点范围,边界条件
return;
int lchild=2*i+1,rchild=2*i+2; //父节点为i
int Max=i;
if(lchild<n && data[lchild]>data[Max] )
Max=lchild;
if(rchild<n && data[rchild]>data[Max])
Max=rchild;
if(Max!=i)
{
Swap(data,Max,i);
heapfy(data,n,Max);
}
}
void buildHeap(int data[],int n)
{
int last_node=n-1;
int last_parent=(last_node-1)/2; //从最后一个父节点往上heapfy
for(int i=last_parent; i>=0; i--)
{
heapfy(data,n,i);
}
}
void HeapSort(int data[],int n)
{
buildHeap(data,n); //建堆
for(int i=n-1;i>=0;i--)
{
Swap(data,0,n-1); //把最大的根节点换到后面来
heapfy(data,i,0); //交换后,每次调整后减掉最后一个
}
}
基数排序(radix sort)属于"分配式排序"(distribution sort),又称"桶子法"(bucket sort)或bin sort,通过关键字(每个元素的每一位上的值)将要排序的元素分配至某些"桶"(编号为0-9)中,以达到排序的作用,基数排序法是属于稳定的排序。
步骤:第一趟:将元素的个位数分配到桶子里面去,然后回收起来,此时数组元素已经按个位数从小到大都已经排好顺序了
第二趟:将上一趟得到的序列按元素十位数分别分配到桶子里面去,然后回收起来,此时数组元素的所有个位数和十位数都已经排好顺序了(如果没有十位数、则补0)…
依次类推向高位排
一共排i次(i是数组最大值的位数)
void radixSort(int data[],int n)
{
//先找出最大值,才能确定要根据位数做几次排序
int Max=data[0];
for(int i=0; i<n; i++)
{
if(data[i]>Max)
Max=data[i];
}
int i=1; //依次代表个、十、百...
do
{
int Count[10]= {0}; //每个元素0-9的次数,这里必须要初始化为0!!
int temp[n];
for(int k=0; k<n; k++) //依次求每个位上的数字,记录每个数字出现的次数
{
int div=data[k]/i%10;
Count[div]++;
}
for(int j=1;j<10; j++)
{
Count[j]=Count[j]+Count[j-1]; //累加计算每个数字的大小排名
}
for(int j=n-1; j>=0; j--) //注意!!!这里一定要逆序复制,否则排序出错。
{
int div=data[j]/i%10;
temp[--Count[div]]=data[j];
}
for(int j=0; j<n; j++) data[j]=temp[j]; //每一位上排一遍后将数组复制过来
i*=10;
}
while(Max/i>0);
}
算法 | 平均时间复杂度 | 最坏时间复杂度 | 最好时间复杂度 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|
冒泡排序Bubble | n 2 n^2 n2 | n 2 n^2 n2 | n 2 n^2 n2 | 1 | 稳定 |
插入排序Insertion | n 2 n^2 n2 | n 2 n^2 n2 | n n n | 1 | 稳定 |
快速排序Quick | n l o g 2 n nlog_2n nlog2n | n 2 n^2 n2 | n l o g 2 n nlog_2n nlog2n | l o g 2 n log_2n log2n | 不稳定 |
希尔排序Shell | n 1 . 3 n^1.3 n1.3 | n 2 n^2 n2 | n n n | 1 | 不稳定 |
归并排序Merge | n l o g 2 n nlog_2n nlog2n | n l o g 2 n nlog_2n nlog2n | n n n | n | 稳定 |
基数排序Radix | n ∗ k n*k n∗k | n ∗ k n*k n∗k | n ∗ k n*k n∗k | n+k | 稳定 |