各种排序不为人知的真相(二)

归并排序利用了分治思想(将原问题分解成若干类似于原问题的子问题,递归对子问题求解,然后合并子问题的解得到原问题的解)进行排序。

分解: 分解待排序序列,把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;
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值