归并排序利用了分治思想(将原问题分解成若干类似于原问题的子问题,递归对子问题求解,然后合并子问题的解得到原问题的解)进行排序。
分解: 分解待排序序列,把n个记录不断二分直到分成单个记录。
解决: merge对子序列排序。
合并: 合并已经排好序的序列。
整个排序过程如图:
归并排序在合并的时候是比较两个子序列的第一个元素,选较小的那个放入临时数组,然后指针前移,继续比较两个子序列的第一个元素,直到某个序列插入完毕,把另一个序列的剩余元素插入到临时数组,当完成最后一组二分子序列的排序的时候,整个序列排序完毕。源码:
void merge(vector<int> list, int start, int middle, int end, vector<int> tmp)
{
int i = start, j = middle + 1, k = 0;
while (i <= middle && j <= end)
{
if (list[i] < list[j])
tmp[k++] = list[i++];
else
tmp[k++] = list[j++];
}
while (i <= middle)
{
tmp[k++] = list[i++];
}
while (j <= end)
{
tmp[k++] = list[j++];
}
for (k = 0; k <= (end - start); k++)
list[start + k] = tmp[k];
}
void mergesort(vector<int> list, vector<int> tmp, int start, int end)
{
if (start < end){
int middle = (start + end) / 2;
mergesort(list, tmp, start, middle);
mergesort(list, tmp, middle + 1, end);
merge(list, start, middle, end, tmp);
}
}
基数排序属于分配排序。分配排序的思想是:不靠比较关键字来排序,而是通过空间使其时间复杂度达到线性阶O(n),简单的说就是以空间换时间。
在说基数排序之前先说一下同为分配排序的桶排序。比如现在有8个数{3,6,2,7,8,9,1,2},那么我们需要9个桶,即一个9位的数组,初值为0,遍历待排序序列,每出现一次桶计数加一,最后遍历输出这个数组,计数为几输几次,可以看出当数据的大小跨度非常大的时候,需要申请的数组空间也会变得非常大。
基数排序相当于一个多维的桶排序,他以位数为关键字,比如同样对范围[0,50]的序列排序,需要用到51个桶,而基数排序只需要[0,9]10个桶,分别个位十位用两次。我们用一个例子说明一下基数排序的排序过程:
比如我们现在有一个无序序列{1234, 4356, 278, 2222, 19}
1) 按个位数对序列排序:
2222, 1234, 4356, 278, 19
2) 按十位排(相同的按上一步顺序排):
19, 2222, 1234, 4356, 278
3) 按百位排:
19, 2222, 1234, 278, 4356
4) 按千位排:
19, 278, 1234, 2222, 4356
基数排序的时间复杂度是O(d*(n+r)),其中d是位数,r是基数,n是序列个数。因为不存在比较的操作,所以最好最坏情况都是这个。
源码:
//求数组中数的最大位数以确定循环次数
int maxbit(vector<int> list)
{
int d = 1;
int p = 10;
for (int i = 0; i < list.size(); i++)
{
while (list[i] >= p)
{
p *= 10;
++d;
}
}
return d;
}
void radixsort(vector<int> list)
{
int d = maxbit(list);
//count数组用于统计针对某位排序的时候桶中的记录数
int count[10];
int radix = 1;
vector<int> tmp(list.size(), 0);
//d次排序
for (int i = 0; i < d; i++)
{
//清空计数器
for (int j = 0; j < 10; j++)
{
count[j] = 0;
}
//针对radix看list中元素对应位数字是几就在对应计数器加一
for (int j = 0; j < list.size(); j++)
{
int k = (list[j] / radix) % 10;
count[k]++;
}
//确定这几个桶中元素在此次排序中实际应该在的位置
for (int j = 1; j < list.size(); j++)
{
count[j] += count[j - 1];
}
//从后往前存tmp是因为每个桶中可能不止一个元素,之前是从前往后统计的count所以这里要从后往前
//因为放弃比较,所以使用count的计数来作为判断大小的依据
for (int j = list.size() - 1; j >= 0; j--)
{
tmp[count[list[j] / radix % 10] - 1] = list[j];
count[list[j] / radix % 10]--;
}
//把排好序的tmp再赋值给原数组
for (int j = 0; j < list.size(); j++)
{
list[j] = tmp[j];
}
radix *= 10;
}
}