计数排序和基数排序


计数排序

计数排序顾名思义,要计算数值。我们先来看一道题

面试题 01.01. 判定字符是否唯一

实现一个算法,确定一个字符串 s 的所有字符是否全都不同。
这道题就会体现到计数思路。想要查明是不是都相同,我们可以先遍历一遍,记录每一个字符出现的次数,如果有出现过两次的,那就不是相同。但是题为字符串,应该怎么记录?次数是一个整形数值,我们先创建一个整形数组,然后开始遍历字符串。如果用 i 来做下标,出现同样的字符应该怎样判断,毕竟相同字符出现的位置是随机的,所以我们要让所有字符都能有自己唯一的下标。这里我找的就是字符的ANSCII码值,每个字符减去字符1,得到自己独有的下标,然后这个下标的值+1即可。当然数组得提前初始化为0。

bool isUnique(char* astr){
    int ptr[100] = {0};
    int i = 0;
    int j = 0;
    int len = strlen(astr);
    for(i = 0; i < len; i++)
    {
        j = astr[i] - '1';
        ptr[j] += 1;
        if(ptr[j] != 1)
        {
            return false;
        }
    }
    return true;
}

回到计数排序,我们要是想办法让所有的数据都有自己的唯一。思路确定后,我们直接看代码。

代码如下:

//时间O(N + range)
//空间O(N + range)
//适合范围集中的数据,只适合整形
void CountSort(int* a, int n)
{
	int max = a[0];
	int min = a[0];
	int i = 0;
	for (i = 1; i < n; i++)
	{
		if (a[i] > max)
		{
			max = a[i];
		}
		if (a[i] < min)
		{
			min = a[i];
		}
	}
	int range = max - min + 1;
	int* count = (int*)calloc(range, sizeof(int));
	if (count == NULL)
	{
		perror("malloc fail\n");
		exit(-1);
	}
	//统计次数
	for (i = 0; i < n; i++)
	{
		count[a[i] - min]++;
	}
	//排序
	int k = 0;
	int j = 0;
	for (j = 0; j < range; j++)
	{
		while (count[j])
		{
			a[k++] = j + min;
		}
	}
	free(count);
}

可以看到这里是先找到最大最小值,然后利用这两个值来确定每个值特定的值。

基数排序

基数排序的思路是找关键字,根据关键字来一次次缩短排序范围。基数排序有两种方法,MSD和LSD,不过不重要,只要记住它是根据关键字来排序就好。MSD是从最高位关键字开始排序,将序列分成若干子序列,对每个子序列按照次一位的关键字进行排序,再将序列分成若干子序列,重复这个操作,直到最次位的关键字;LSD正好相反,从最次位的关键字开始排序,排到最高位的关键字。

实际代码中和MSD LSD有不同。

现在有一组数据

278,109,063,930,589,184,505,269,008,083

对于这组数据,可以这样排序,列出来 0 到 9 的数字,根据每个数的个位数来放置,比如278放在8这个位置,这样一次排下来后,9这个位置有109 589 269三个位置,也有其他的位置有多个数,排完一次,按照 0 到 9 的顺序,把这些数据再放回去,然后根据十位数,再放置一次所有的数据,最后根据百位数再放置一遍,拿回来后数据就排好了。

按照这个思路,我们需要队列,需要有分发和回收数据的函数。下面代码中用到了C++的queue库,用C语言写的就需要自己手动实现队列,替换掉C++语句。

//基数排序
#define K 3  //关键字个数
#define RADIX 10  //基数的个数

queue<int> q[RADIX];

void PrintArray(int* a, int n)
{
    for (int i = 0; i < n; ++i)
    {
        printf("%d ", a[i]);
    }
    printf("\n");
}

int GetKey(int val, int k)//根据第几次取关键字,得到对应的值
{
    int key = 0;
    while (k >= 0)
    {
        key = val % 10;
        val /= 10;
        k--;
    }
    return key;
}

void Distribute(int a[], int left, int right, int k)
{
    for (int i = left; i < right; ++i)
    {
        int key = GetKey(a[i], k);
        q[key].push(a[i]);
    }
}

void Collect(int a[])
{
    int k = 0;
    for (int i = 0; i < RADIX; ++i)
    {
        while (!q[i].empty())
        {
            a[k++] = q[i].front();
            q[i].pop();
        }
    }
}

void RadixSort(int a[], int left, int right)//[left, right]
{
    for (int i = 0; i < K; ++i)
    {
        //分发数据
        Distribute(a, left, right, i);

        //回收数据
        Collect(a);
    }
}

int main()
{
    int arr[] = {278, 109, 63, 930, 589, 184, 505, 269, 8, 83};
    int n = sizeof(arr) / sizeof(arr[0]);
    PrintArray(arr, n);
    RadixSort(arr, 0, n - 1);
    PrintArray(arr, n);
    return 0;
}

结束。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值