1.首先介绍最简答的冒泡排序算法,顾名思义就是像水中冒泡一样,每次把当前混乱集合中的第一个数据依次和后面的数据进行比较,然后依次往后面行进。那么每次排一个数据,则需要n-1次排序,每次要和n-1个数据比较
void Sort(int *number,int numberSize)
{
int i,j,temp;
for(i = 0;i < numberSize-1;i++) //需要n-1趟排序(最后一个数据自然就会有顺序了)
{
for(j = 0;j < numberSize-i-1;j++) //因为前面的数据和n-1个数据比较了,那么当前数据一定和后面的有序集合的数据
{ //已经有大小关系了,那么只需要比较混乱集合即可
if(number[j] > number[j+1]) //从小到大的排序
{
temp = number[j];
number[j] = number[j+1];
number[j+1] = temp;
}
}
}
}
显然这段代码的复杂度是O(n^2)时间复杂度。该冒泡算法还可以进行一些小的优化,但是时间复杂度级别还是接近O(n^2)。
2.直接插入排序,该算法是基于这么一个思想。我给定一串有序的数据,我现在要插入一个数据并且我还想保持数据的顺序。那么我采取的做法就是从前往后或者从后往前挨个比较,然后插入就行。这样对于一串无序的数,我们可以假设第一个数据是有序的,其余的数据属于(n-1)次输入的数据,也就是调用了n-1次插入排序。最后混乱集合就变成了有序集合。
void Sort(int *number,int numberSize)
{
int i =0,j,Soldier; //这个是存放当前的比较数,为了方便后面减少交换次数的小技巧
for(i = 1;i < numberSize;i++)
{
Soldier = number[i];
for(j = i-1;j >= 0;j--)
{
if(Soldier < number[j])
{
number[j+1] = number[j]; //减少交换次数
}
else
{
number[j+1] = Soldier;
break;
}
}
if(j < 0)
number[0] = Soldier;
}
}
该算法虽然时间复杂度还是在O(n^2)但是却是进行了很多的优化,我们不妨来计算一下,假设输入(n+1)个数据,那么最坏的情况就是初始顺序和我们要排列的顺序相反,完全逆序。那么第一次需要排列1次,第二次2次,。。。。依次类推。那么最后是
(1+2+3+4+。。。。+n) = O(n^2);但是实际上是O(n*(n+1)/2),还是比冒泡的直接O(n^2)要快上不少的。
3.希尔排序,其实希尔排序就是上面直接插入排序的优化版本。但是希尔算法非常好的利用了直接插入算法的长处,对于尽可能有序的数列进行插入排序的时候可以达到O(n)时间复杂度这个特性,所以希尔算法就是先将数据进行分组,具体怎么分组这个大家可以网上百度,有非常多的分组方法,因为并没有一个定论怎么样分组是最优的。我们这里采用分组数是原始数据个数不停的二分。
void FUZHU_Sort(int *number,int numberSize,int step)
{
int i,j,k,Soldier;
for(k = 0;k < step;k++)
{
for(i = k+step;i < numberSize;i+=step) //这里和直接插入排序是有区别的,因为并不是0,1,2这样连着排序的,而是
{ //0,0+step,0+step+step这样排序的
Soldier = number[i];
for(j = i-step;j >= 0;j-=step)
{
if(Soldier < number[j])
{
number[j+step] = number[j];
}
else
{
number[j+step] = Soldier;
break;
}
}
if(j < 0)
number[k] = Soldier; //多组的起始位置
}
}
}
void Shell_Sort(int *number,int numberSize)
{
int step = numberSize/2;
while(step > 0)
{
FUZHU_Sort(number,numberSize,step);
step = step/2;
}
}
4.归并排序,归并排序的思想是,把带排序的数据不停的折半,最后折半到每一半都只有一个数据,那么这个数据肯定是有序的,然后再把左右的归并,归并就是用一个循环来挨个比较两边的数据,小(大)的进入一个临时数组,最后比较完毕再把临时数组放回去。
void Merge_Sort(int *number,int numberSize,int low,int high)
{
if(low == high)
return;
int mid = (low+high)/2;
//分治
Merge_Sort(number,numberSize,low,mid);
Merge_Sort(number,numberSize,mid+1,high);
//归并
int i = low,j = mid+1;
int *temp = (int *)malloc(sizeof(int)*(high-low+1));
int count = 0;
while(i <= mid && j <= high)
{
if(number[i] < number[j])
temp[count++] = number[i++];
else
temp[count++] = number[j++];
}
//还有没做完的:
while(i <= mid)
temp[count++] = number[i++];
while(j <= high)
temp[count++] = number[j++];
for(i = low;i <= high;i++)
number[i] = temp[i-low];
}
归并排序的分治归并的状态方程:T(n) = 2 * T(n/2)+O(n)很容易推出其时间复杂度是O(nlogn)(主定理法是可以直接看出来的,详细见前面的时间复杂度的分析。)
5.快速排序,思想是每次选取一个基值,然后将每个数和这个数比较,大于它的放后面,小于它的放前面。然后从这个点断开分成两边继续重复这个操作,直到最后一个数。其实也是分治法。
void quick_Sort(int *number,int numberSize,int low,int high)
{
if(low == high)
return;
//先排序后分治
int i = low,j = high; //首尾计数器
int mid = (low+high)/2;
int base_number = number[low]; //每次以第一个作为基准
int temp;
while(i < j)
{
while(number[j] >= base_number && j > i)
j--;
if(j <= i)
break;
else
{
temp = number[j];
number[j] = number[i];
number[i] =temp;
}
while(number[i] < base_number && i < j)
i++;
if(i >= j)
break;
else
{
temp = number[j];
number[j] = number[i];
number[i] =temp;
}
}
for(int k = low;k < numberSize;k++)
{
if(number[k] == base_number)
break;
}
quick_Sort(number,numberSize,low,k);
quick_Sort(number,numberSize,k+1,high);
}
本段代码是设置了一个首位计数器,先从尾部开始,大于基数不管,一旦小于基数就和首指针指向的数交换,然后首指针开始运作,一旦首指针指向的数大于基数,那么就和尾指针指向的数交换,然后尾指针开始运作,最后到首尾指针重合结束一次。快速排序的时间复杂度不难看出是O(nlogn)因为分治分方程基本是一样的和前面的。
以上都是一些简单的常用的排序,当然都是有很大优化空间的,这些可以自己思考,然后一些稍微复杂的排序将会在后序更新。