这里记录了一些基础的排序方法
1.选择排序法
选排的算法步骤如下:
第一步,在未排序的n个数(a[0]~a[n-1])中找到最小数,将它与a[0]交换
第二步,在剩下的未排序的n-1个数中(a[1]~a[n-1])中找到最小数,将它与a[1]交换
。。。。。。
第n-1步,在剩下未排序的2个数中(a[n-2]~a[n-1])中找到最小数,将它与a[n-2]交换
不难发现,选择排序就是不断选出最小数放在数组前端
代码实现:
for(k=0;k<n-1;k++)//枚举排序次数
{
j=k;//最小值所在的下标
for(i=k+1;i<n;i++)
{
if(a[i]<a[j]) j=i;//选择出了小的
}
tmp=a[j];
a[j]=a[k];
a[k]=tmp;//交换,把这个小的值放在前面
}
时间复杂度:比较次数O(n^2),比较次数与关键字的初始状态无关,总的比较次数N=(n-1)+(n-2)+...+1=n*(n-1)/2。交换次数O(n),最好情况是,已经有序,交换0次;最坏情况交换n-1次,逆序交换n/2次。交换次数比冒泡排序少多了,由于交换所需CPU时间比比较所需的CPU时间多,n值较小时,选择排序比冒泡排序快,是一种不稳定的排序算法
2.冒泡排序法
原理:
比较相邻的元素,如果第一个比第二个大,则交换两者的值
对每一对元素作上述操作,从开始的一对到结尾的一对
针对所有的元素作上述操作,除了最后一个
这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端(升序或降序排列),就如同碳酸饮料中二氧化碳的气泡最终会上浮到顶端一样,故名“冒泡排序”。
代码实现:
void work(int a[],int n)
{
int i,j,t;
for(i=1;i<n;i++)
{
for(j=0;j<n-i;j++)//如果是a[0]则需要n-1次,如果是a[1]需要n-2次,,
{
if(a[j]>a[j+1])
{
t=a[j];
a[j]=a[j+1];
a[j+1]=t;
}
}
}
}
时间复杂度:最好的时间复杂度为O(n),平均时间复杂度为O(n2)
3.插入排序法
原理:
基本操作是将一条记录插入到已经排序好的有序表中,从而得到一个新的,记录数量增1的有序表
首先在当前有序区a[1..i-1]中查找a[i]的正确插入位置k(1≤k≤i-1);然后将a[k..i-1]中的记录均后移一个位置,腾出k位置上的空间插入a[i]。
注意:若a[i]的关键字大于等于a[1..i-1]中所有记录的关键字,则a[i]就是插入原位置。
一种查找比较操作和记录移动操作交替地进行的方法。具体做法:
将待插入记录a[i]的关键字从右向左依次与有序区中记录a[j](j=i-1,i-2,…,1)的关键字进行比较:
① 若a[j]的关键字大于a[i]的关键字,则将R[j]后移一个位置;
②若a[j]的关键字小于或等于a[i]的关键字,则查找过程结束,j+1即为a[i]的插入位置。
关键字比a[i]的关键字大的记录均已后移,所以j+1的位置已经腾空,只要将a[i]直接插入此位置即可完成一趟直接插入排序
算法中引进的附加记录a[0]称监视哨或哨兵(Sentinel)。
哨兵有两个作用:
① 进人查找(插入位置)循环之前,它保存了a[i]的副本,使不致于因记录后移而丢失a[i]的内容;
② 它的主要作用是:在查找循环中"监视"下标变量j是否越界。一旦越界(即j=0),因为a[0].可以和自己比较,循环判定条件不成立使得查找循环结束,从而避免了在该循环内的每一次均要检测j是否越界(即省略了循环判定条件"j>=1")。
for(i=2;i<n;i++)
{
a[0]=a[i];//哨兵
j=i-1;
while(a[0]<a[j])
{
a[j+1]=a[j];
j--;
}
a[j + 1]=a[0];
}
void pri_sort(int *b, int n)
{
int i;
for(i=0;i<n;i++)
{
if(i==n-1)
printf("%d\n",b[i]);
else
printf("%d ",b[i]);
}
}
时间复杂度:如果目标是把n个元素的序列升序排列,那么采用插入排序存在最好情况和最坏情况。最好情况就是,序列已经是升序排列了,在这种情况下,需要进行的比较操作需(n-1)次即可。最坏情况就是,序列是降序排列,那么此时需要进行的比较共有n(n-1)/2次。插入排序的赋值操作是比较操作的次数加上 (n-1)次。平均来说插入排序算法的时间复杂度为O(n^2)。
4.快速排序法(对冒泡排序的改进)
通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列
(1)首先设定一个分界值,通过该分界值将数组分成左右两部分。
(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。
(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
(4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
在百度上找到的形象的模拟图
代码实现:
int quick_sort(int a[],int low,int high)
{
int tmp=0,i,index,begin,end;
if(low<high)
{
tmp=a[low];
begin=low;
end=high;
while(low<high)
{
while(low<high&&a[high]>=tmp)
{
high--;
}
a[low]=a[high];
while(low<high&&a[low]<=tmp)
{
low++;
}
a[high]=a[low];
}
a[low]=tmp;
index=low;
quick_sort(a,begin,index-1);
quick_sort(a,index+1,end);
}
时间复杂度:
快速排序的一次划分算法从两头交替搜索,直到low和high重合,因此其时间复杂度是O(n);而整个快速排序算法的时间复杂度与划分的趟数有关。
理想的情况是,每次划分所选择的中间数恰好将当前序列几乎等分,经过log2n趟划分,便可得到长度为1的子表。这样,整个算法的时间复杂度为O(nlog2n)。
最坏的情况是,每次所选的中间数是当前序列中的最大或最小元素,这使得每次划分所得的子表中一个为空表,另一子表的长度为原表的长度-1。这样,长度为n的数据表的快速排序需要经过n趟划分,使得整个排序算法的时间复杂度为O(n2)。
5.桶排序
思想:桶排序的大体思路就是先将数组分到有限个桶中,再对每个桶中的数据进行排序,可以说是鸽巢排序的一种归纳结果(对每个桶中数据的排序可以是桶排序的递归,或其他算法,在桶中数据较少的时候用插入排序最为理想)。
待更新。。。
文献资料参考来源:
https://baike.baidu.com/item/%E5%86%92%E6%B3%A1%E6%8E%92%E5%BA%8F/4602306?fr=aladdin
https://baike.baidu.com/item/%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F/7214992?fr=aladdin#7
https://www.cnblogs.com/1328497946TS/p/11042804.html感谢这位大佬的博客讲解