排序
-
插入类排序
1、直接插入排序 O(n2) O(n) O(n2)
每次将一个待排序元素按照关键字大小插入到已经排序的序列中去。
void insert(int a[],int n)
{
int i,j;
int temp;
for(i=1;i<=n;i++) //从第二个元素开始,因为第一个元素肯定是有序的
{
temp=a[i]; //temp存储a[i]防丢失
j=i-1;
while(j>=0 && temp<a[j]) //在i之前的元素已经是有序的
{
a[j+1] = a[j];
j--; //一个个往后移
}
a[j+1]=temp;
}
}
-
折半插入排序 O(n2) O(n) O(n2)
折半查找法寻找元素插入位置。与1最大不同在于,1用的是顺序查找法。
-
希尔排序
缩小增量排序。实质就是分组插入排序。把记录按步长分组,对每组记录采用直接插入排序方法进行排序。随着步长逐渐减小,所分成的组包含的记录越来越多,当步长的值减小到
1 时,完成排序。一个好的增量序列有以下特征:最后一个增量必须为1;尽量避免序列中的值,尤其是相邻的值,互为倍数。
-
交换类排序
1、冒泡排序 O(n2) O(n) O(n2)
结束条件是一趟排序中未发生元素交换。
基本原理:依次比较两个相邻的数,若是按从大到小排序,将大数放前,小数放后,直到比较到最后两位数。重复上述步骤,直到一趟排序中未发生元素交换为止。
//普通冒泡
void bubble_sort(int a[],int n)//n为数组a的元素个数
{
//一定进行N-1轮比较
for(int i=0; i<n-1; i++)
{
//每一轮比较前n-1-i个,即已排序好的最后i个不用比较
for(int j=0; j<n-1-i; j++)
{
if(a[j] > a[j+1])
{
int temp = a[j];
a[j] = a[j+1];
a[j+1]=temp;
}
}
}
}
//优化实现
void bubble_sort_better(int a[],int n)//n为数组a的元素个数
{
//最多进行N-1轮比较
for(int i=0; i<n-1; i++)
{
bool isSorted = true;
//每一轮比较前n-1-i个,即已排序好的最后i个不用比较
for(int j=0; j<n-1-i; j++)
{
if(a[j] > a[j+1])
{
isSorted = false;
int temp = a[j];
a[j] = a[j+1];
a[j+1]=temp;
}
}
if(isSorted) break; //如果没有发生交换,说明数组已经排序好了
}
}
2、快速排序 O(nlog2n) O(nlog2n) O(n2)
分治的思想。分治法通常有3步:Divide(分解子问题的步骤)、Conquer(递归解决子问题的步骤)、Combine(子问题解求出来后合并成原问题解的步骤)。而求解递归式的三种方法有:
(1)替换法:主要用于验证递归式的复杂度。
(2)递归树:能够大致估算递归式的复杂度,估算完后可以用替换法验证。
(3)主定理:用于解一些常见的递归式。
快排中,基准的左边全是比它小的,右边都是大的。三种取基准的方法:第一个或最后一个元素,中间元素,随机元素。
待排序列越接近有序算法效率越低。当每次划分时,若都能分成两个等长的子序列时,效率会达到最大。最理想的方法是,选择的基准恰好能把待排序序列分成两个等长的子序列。
递归进行的,递归需要栈的辅助,因此辅助空间为O(log2n)。
void quiksort(int a[],int start,int end)
{
int low = start;
int high= end;
int key = a[low];
if( start < end)
{
while(low <high && key<=a[high])
high--; //如果high所在的值不比key小,就往左移动
a[low] = a[high]; //遇到比key小的high值,就把a[high]值给a[low],a[low]原本的值已经在key里面了
while(low < high && a[low] <= key)
low++;
a[high]= a[low];
a[low]=key; //此时的low的左边全是比key小的,右边全是比key大的,把key值放大当前low位置
quiksort(a,start,low-1);//左边重复
quiksort(a,low+1,end);//右边重复
}
else
return;
}
-
选择类排序
-
-
简单选择排序
从头至尾顺序扫描未排序序列,选最小的元素与第一个进行交换。
-
堆排序 O(nlog2n) O(nlog2n) O(nlog2n)
从无序序列所确定的完全二叉树的第一个非叶子节点开始,从右向左,从下往上,对每个节点进行调整(大顶堆,小顶堆),直到无序序列中只剩下一个元素。
优点:a)最坏情况下时间复杂度也是O(nlog2n),这是它相对快排最大的优点。
b)空间复杂度为O(1),这是在所有时间复杂度为O(nlog2n)中最小的。
适用于元素很多的场合,比如100万个元素中选前10个最大的。
堆排序相对快速排序:1、最好最坏情况下时间复杂度都为O(nlog2n),不会出现快排最坏的O(n2)
2、堆排序所需的辅助空间少,是O(1),而快排是O(log2n)
4. 归并排序
O(nlog2n) O(nlog2n) O(nlog2n)
采用分治法的思想。将n个元素的序列划分为两个序列,再将两个序列划分为4个序列,直到每个序列只有一个元素,最后,再有序序列两两归并成一个有序的序列。
归并排序时间复杂度与初始序列无关。都是O(nlog2n)。空间复杂度为O(n)
内存空间不足的时候,能够并行计算的时候使用归并排序。
//归并,将有二个有序数列a[start…mid]和a[mid+1…end]合并。把结果放到temp里面
void Merge(int a[],int temp[], int start, int mid, int end)
{
int i = start, j=mid+1, k = start;
while(i!=mid+1 && j!=end+1)
{
if(a[i] >a[j])
temp[k++] = a[j++]; //从小到大排序,a[j]放入temp[k],然后j,k都后移一位
else
temp[k++] = a[i++];
}
while(i != mid+1)
temp[k++] = a[i++];
while(j != end+1)
temp[k++] = a[j++];
for(i=start; i<=end; i++)
a[i] = temp[i];
}
//内部使用递归
void MergeSort(int a[], int tempArr[], int start, int end)
{
int mid;
if(start < end)
{
mid = (start+ end) / 2;
MergeSort(a, temp, start, mid);
MergeSort(a, temp, mid+1, end);
Merge(a, temp, start, mid, end);
}
}
-
基数排序
多关键字排序。两种,最高位优先、最低位优先。
适用场景:序列中元素个数很多,但是组成元素的关键字的取值范围比较小。比如取值范围0~9.
总结:
1、平均时间复杂度为O(nlog2n) 快些归队
快速 希尔 归并 堆排序
2、不稳定 快些选
快速 希尔 选择类排序(简单选择、堆排序)
3、特殊的空间复杂度 其他都是O(1) 快速O(log2n) 归并O(n)
4、一趟保证一个元素到底最终位置 交换类 选择类
5、比较次数与初始序列无关 简单选择 折半插入
6、排序趟数与初始序列有关 交换类排序
7、元素基本有序(正序) 直接插入 冒泡
8、当数据规模n较小 直接插入排序 简单选择排序
9、当数据规模n较大,采用时间复杂度为O(nlog2n)的排序方法(快些归队)