冒泡排序
冒泡排序是一种极其简单的排序算法,也是我所学的第一个排序算法。它重复地走访过要排序的元素,依次比较相邻两个元素,如果他们的顺序错误就把他们调换过来,直到没有元素再需要交换,排序完成。这个算法的名字由来是因为越小(或越大)的元素会经由交换慢慢“浮”到数列的顶端。
冒泡排序算法的运作如下:
1)比较相邻的元素,如果前一个比后一个大,就把它们两个调换位置。
2)对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
3)针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较
/////////////////////////////
//冒泡排序(倒序,从后往前冒)
//时间复杂度:O(N^2) 空间复杂度:O(1)
//稳定排序
///////////////////////////////
void BubleSort(int arr[ ],size_t size)
{
//判断size是否合法
if( size<=1)
{
return ;
}
yy
size_t cur=size-1;
for( ;cur>bound;cur--)
{
if(arr[cur-1]>arr[cur])
{
Swap(&arr[cur-1],&arr[cur]);
}
}
}//end of bound
return ;
}
选择排序
选择排序也是一种简单直观的排序算法。它的工作原理很容易理解:初始时在序列中找到最小(大)元素,放到序列的起始位置作为已排序序列;然后,再从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾。以此类推,直到所有元素均排序完毕。
注意选择排序与冒泡排序的区别:冒泡排序通过依次交换相邻两个顺序不合法的元素位置,从而将当前最小(大)元素放到合适的位置;而选择排序每遍历一次都记住了当前最小(大)元素的位置,最后仅需一次交换操作即可将其放到合适的位置。
////////////////////////////////
///选择排序 不稳定排序
//时间复杂度:O(N^2) 空间复杂度 O(1)
//////////////////////////////
void SelectSort(int arr[ ],size_t size)
{
//判断size是否合法
if( size<=1)
{
return ;
}
//[ 0,bound]有序区间
//[ bound,size]无需区间
size_t bound=0;
//第一重循环,每循环一次,有序区间加1
for( ;bound<size;bound++)
{
size_t cur=bound+1;
for(;cur<size;cur++)
{
//如果擂主值大于cur的值,则更换擂主的值
if( arr[bound]<arr[cur])
{
Swap(&arr[bound],&arr[cur]);
}
}
}
return ;
}
选择排序宏观图
插入排序
插入排序是一种简单直观的排序算法。它的工作原理非常类似于我们抓扑克牌
对于未排序数据(右手抓到的牌),在已排序序列(左手已经排好序的手牌)中从后向前扫描,找到相应位置并插入。
插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。
具体算法描述如下:
1)从第一个元素开始,该元素可以认为已经被排序
2)取出下一个元素,在已经排序的元素序列中从后向前扫描
3)如果该元素(已排序)大于新元素,将该元素移到下一位置
4)重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
5)将新元素插入到该位置后
6)重复步骤2~5
插入排序宏观图
//////////////////////
//插入排序
//时间复杂度O( N^2 ),空间复杂度O(1)稳定排序
//////////////////////
void InsertSort(int arr[ ],size_t size )
{
if( size<=1)
{
return ;
}
//[ 0,bound]有序区间
//[bound,size]带排序区间
//插入排序把有序区间当作一个有序链表
//然后把bound位置的元素插入到合适位置
size_t bound=1;
for(;bound<size;bound++)
{
//此时保存起来用于后面的搬运
//一旦arr[bound]被保存起来就可以被修改
int bound_value=arr[bound];
//此处cur辅助我们进行搬运的下标
//会从后往前遍历,找到合适的位置存放bound_value的值
size_t cur=bound;
for( ;cur>0;cur--)
{
//此处,我们拿线性表的最后一个元素和bound_value比较
if(arr[cur-1]>arr[bound])
{
//此时进行搬运
arr[cur]=arr[cur-1];
}
else
{
//说明已经找到合适位置
break;
}
}//end for( ;cur>0;cur--)
//安置bound_value
arr[cur]=bound_value;
}
}
插入排序不适合对于数据量比较大的排序应用。但是,如果需要排序的数据量很小,比如量级小于千,那么插入排序还是一个不错的选择。 插入排序在工业级库中也有着广泛的应用,在STL的sort算法和stdlib的qsort算法中,都将插入排序作为快速排序的补充,用于少量元素的排序(通常为8个或以下)
堆排序
堆排序是指利用堆这种数据结构所设计的一种选择排序算法。堆是一种近似完全二叉树的结构(通常堆是通过一维数组来实现的),并满足性质:以最大堆(也叫大根堆、大顶堆)为例,其中父结点的值总是大于它的孩子节点。
我们可以很容易的定义堆排序的过程:
1) 由输入的无序数组构造一个最大堆,作为初始的无序区
2)把堆顶元素(最大值)和堆尾元素互换
3)把堆(无序区)的尺寸缩小1,并调用heapify(A, 0)从新的堆顶元素开始进行堆调整
4)重复步骤2,直到堆的尺寸为1
堆排序是不稳定的排序算法,不稳定发生在堆顶元素与A[i]交换的时刻。
比如序列:{ 9, 5, 7, 5 },堆顶元素是9,堆排序下一步将9和第二个5进行交换,得到序列 { 5, 5, 7, 9 },再进行堆调整得到{ 7, 5, 5, 9 },重复之前的操作最后得到{ 5, 5, 7, 9 }从而改变了两个5的相对次序。堆排序是不稳定的排序算法,不稳定发生在堆顶元素与A[i]交换的时刻。
比如序列:{ 9, 5, 7, 5 },堆顶元素是9,堆排序下一步将9和第二个5进行交换,得到序列 { 5, 5, 7, 9 },再进行堆调整得到{ 7, 5, 5, 9 },重复之前的操作最后得到{ 5, 5, 7, 9 }从而改变了两个5的相对次序。
/////////////////////////////
//下沉函数
void AdjustDown(int arr[ ],int size,int root)
{
int parent=root;
int child=root*2+1;
while(child<size)
{
//使child指向较大的那个孩子
if(child+1<size&&arr[child+1]>arr[child])
{
++child;
}
//如果此时child的值比parent大,
//就需要交换child的值和parent的值
//把大值往上换,并向下继续调整
if(arr[child]>arr[parent])
{
Swap(&arr[child],&arr[parent]);
parent=child;
child=parent*2+1;
}
//parent比child节点值还大,跳出循环
else
{
break;
}
}
}
//////////////////////////////////
//堆排序
//时间复杂度:O(NlogN)
//空间复杂度:O(1) 不稳定排序
/////////////////////////////////
void HeapSort(int arr[ ],int size)
{
if( size<=1)
{
return ;
}
//建堆
int i=size/2-1;
for( ;i>=0;--i )
{
AdjustDown(arr,size,i);
}
//定义一个end,[0,end) 表示未排序的堆区间
int end=size-1;
while( end>0)
{
Swap(&arr[0],&arr[end]);
AdjustDown(arr,end,0);
--end;
}
}