非比较排序--- 计数排序、基数排序、桶排序

计数排序

计数排序是一种稳定的排序算法。计数排序是最简单的特例,由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存,实用性不高。例如:计数排序是用来排序0-100之间的数字的最好的算法;另外,计数排序可以用在基数排序中的算法来排序数据范围很大的数组。

计数排序的时间复杂度为O(n+k)n为数组大小,k为值域大小。

算法的步骤如下:

1、找出待排数组的最大与最小的元素

2、统计数组中每个值为x的元素出现的次数,存入数组cnt的第第x项

3、对所有的计数累加(从cnt数组第一个元素开始,每一项和前面相加。。。也就是前缀和)

4、反向填充目标数组;将每个元素t放在新数组的第cnt[t]项,每放一个元素就将cnt[t]减1

算法实现(c++):

void countSort(int *a,int *b,int n)
{
	
	int _max = *max_element(a,a+n);
	int _min = *min_element(a,a+n);

	int range = _max - _min + 1;
	int *cnt = new int[range];
	memset(cnt,0,sizeof(int)*range);

	for(int i = 0;i < n;++i)//计数
	{
		cnt[a[i] - _min]++;
	}

	for(int i = 1;i<range;++i)//计数累加
	{
		cnt[i] += cnt[i-1];
	}
	for(int i = n-1;i>=0;--i)//填充
	{
		b[--cnt[a[i]-_min] ] = a[i];
	}
	
	// int index = 0;//这里是直接填充
	// for(int i = 0;i<range;++i)
	// {
	// 	while(cnt[i]--)
	// 	{
	// 		a[index++] = i+_min;
	// 		//cout << i+_min << endl;
	// 	}
	// }  

	
	delete[] cnt;
}
int main() 
{
	int a[5] = {3,2,1,4,5};
	int *b  = new int[5];
	countSort(a,b,5);
	for(int i = 0;i<5;++i)
		cout << b[i];
    return 0;
}

基数排序

基数排序将所有待比较值(正整数)统一为同样的数位长度,数位较短的数前面补0,然后从最低位开始,依次进行依次排序。这样从最低位排序一直到最高位排序完成以后,数列就变成了一个有序数列。

  假设我们有一些二元组(a,b),要对它们进行以a为首要关键字,b的次要关键字的排序。我们可以先把它们先按照首要关键字排序,分成首要关键字相同的若干堆。然后,在按照次要关键值分别对每一堆进行单独排序。最后再把这些堆串连到一起,使首要关键字较小的一堆排在上面。按这种方式的基数排序称为MSD(Most Significant Dight)排序。第二种方式是从最低有效关键字开始排序,称为LSD(Least Significant Dight)排序。首先对所有的数据按照次要关键字排序,然后对所有的数据按照首要关键字排序。要注意的是,使用的排序算法必须是稳定的,否则就会取消前一次排序的结果。由于不需要分堆对每堆单独排序,LSD方法往往比MSD简单而开销小。下文介绍的方法全部是基于LSD的。

      基数排序的简单描述就是将数字拆分为个位十位百位,每个位依次排序。因为这对算法稳定要求高,所以我们对数位排序用到上一个排序方法计数排序。因为基数排序要经过d (数据长度)次排序, 每次使用计数排序, 计数排序的复杂度为 On),  d 相当于常量和N无关,所以基数排序也是 O(n)。基数排序虽然是线性复杂度, 即对n个数字处理了n次,但是每一次代价都比较高, 而且使用计数排序的基数排序不能进行原地排序,需要更多的内存, 并且快速排序可能更好地利用硬件的缓存, 所以比较起来,像快速排序这些原地排序算法更可取(大多数教材是采用计数的,因为本身也就开大小为10的数组就行了)。对于一个位数有限的十进制数,我们可以把它看作一个多元组,从高位到低位关键字重要程度依次递减。可以使用基数排序对一些位数有限的十进制数排序

算法实现(C++):

int radixSort(vector<int>& nums)
{
    if (nums.empty() || nums.size() < 2)
        return 0;

    int maxVal = *max_element(nums.begin(), nums.end());

    int exp = 1;                                 // 1, 10, 100, 1000 ...
    int radix = 10;                              // base 10 system

    vector<int> aux(nums.size());

    /* LSD Radix Sort */
    while (maxVal / exp > 0) {                   // Go through all digits from LSD to MSD
        vector<int> count(radix, 0);

        for (int i = 0; i < nums.size(); i++)    // Counting sort
            count[(nums[i] / exp) % 10]++;

        for (int i = 1; i < count.size(); i++)   // you could also use partial_sum()
            count[i] += count[i - 1];

        for (int i = nums.size() - 1; i >= 0; i--)
            aux[--count[(nums[i] / exp) % 10]] = nums[i];

        for (int i = 0; i < nums.size(); i++)
            nums[i] = aux[i];
        
        // for(auto x:nums)
        //  cout << x << endl;
        // cout << endl;
        
        exp *= 10;
    }

}

桶排序

桶就是一个数据结构,每个桶存储一个区间的数。依然有一个待排序的整数序列A,最小值不小于o,最大值不超过K。

假设我们有M个桶,第i个桶Bucket[i]存储i*K/M至(i+1)*K/M之间的数。桶排序步骤如下:

  1. 扫描序列A,根据每个元素的值所属的区间,放入指定的桶中(顺序放置)。
  2. 对每个桶中的元素进行排序,什么排序算法都可以,插入排序最好,近乎有序的情况下,插入排序效果最好。
  3. 依次收集每个桶中的元素,顺序放置到输出序列中。

算法实现(c++)

void insertSort(std::vector<int> &v)
{
    int n = v.size(),i,j;
    for(int i = 1; i<n;++i)
    {
        int p = v[i];
        for(j = i;j>0 && v[j-1] > p;--j)
            v[j] = v[j-1];
        v[j] = p;
    }
}
void bucketSort(std::vector<int> &v)
{
    int _min = *min_element(v.begin(),v.end());
    int _max = *max_element(v.begin(),v.end());

    int bucketNum = 11;//11个桶
    vector< vector<int> > buckets(bucketNum);

    for(auto x: v)
    {
        int index = x/10;//分桶规则自定
        buckets[index].push_back(x);
    } 
    int index = 0;
    for(int i = 0;i<bucketNum;++i)
    {
        insertSort(buckets[i]);//用插入最好
        for(auto &x:buckets[i])
            v[index++] = x;
    }

}

总结:

排序算法 时间复杂度 空间复杂度  
计数排序 O(N+K) O(N+K) 稳定排序
基数排序 O(N) O(N) 稳定排序
桶排序 O(N+K) O(N+K) 稳定排序

      从整体上来说,计数排序,桶排序都是非基于比较的排序算法,而其时间复杂度依赖于数据的范围,桶排序还依赖于空间的开销和数据的分布。而基数排序是一种对多元组排序的有效方法,具体实现要用到计数排序或桶排序。

     相对于快速排序、堆排序等基于比较的排序算法,计数排序、桶排序和基数排序限制较多,不如快速排序、堆排序等算法灵活性好。但反过来讲,这三种线性排序算法之所以能够达到线性时间,是因为充分利用了待排序数据的特性,如果生硬得使用快速排序、堆排序等算法,就相当于浪费了这些特性,因而达不到更高的效率。

发布了257 篇原创文章 · 获赞 36 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/sgh666666/article/details/100811970