积跬步至千里——算法强化训练(6)利用基数排序解决O(n)问题

来源:互联网 发布:java代码换行 编辑:程序博客网 时间:2024/05/17 00:57

基数排序:

基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用,基数排序法是属于稳定性的排序,其时间复杂度为O (nlog(r)m),其中r为所采取的基数,而m为堆数——百度百科

举个简单的例子:

第一步
以LSD为例,假设原来有一串数值如下所示:
73, 22, 93, 43, 55, 14, 28, 65, 39, 81
首先根据个位数的数值,在走访数值时将它们分配至编号0到9的桶子中:
0
1 81
2 22
3 73 93 43
4 14
5 55 65
6
7
8 28
9 39
第二步
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
81, 22, 73, 93, 43, 14, 55, 65, 28, 39
接着再进行一次分配,这次是根据十位数来分配:
0
1 14
2 22 28
3 39
4 43
5 55
6 65
7 73
8 81
9 93
第三步
接下来将这些桶子中的数值重新串接起来,成为以下的数列:
14, 22, 28, 39, 43, 55, 65, 73, 81, 93
这时候整个数列已经排序完毕;如果排序的对象有三位数以上,则持续进行以上的动作直至最高位数为止


具体的算法题:

1、一个大小为n的数组,里面的数都属于范围[0, n-1],有不确定的重复元素,找到至少一个重复元素,要求O(1)空间和O(n)时间。

思路:n个数,都在0——n-1之间,所以假设n个桶,把数字扔到对应桶里面,如果桶里面数字和要扔进去的一样就找到了重复的。比如 2 4 5 1 2 0 六个数字,a[0] = 2,则和2号桶交换 5 4 2 1 2 0,那么2是就位了,a[0]=5,和5号桶交换,编程0 4 2 1 2 5,a[0]=0了,0、2、5三个桶都是对的了,a[1]=4,和4号桶交换,变成0 2 2 1 4 5,a[1]=2,要和2号桶交换,2号里面有个2,咿,找到重复了。

int FindTheRepeat(int data[],int n){for (int i =0; i < n; ++i){while (i!=data[i]){if(data[i]==data[data[i]]) return data[i];swap(data[i],data[data[i]]);}}return -1;}

上面的写法需要不断的交换,挺费时间的,现在主要是想区分一个元素是不是又被访问了,如果是就是重复了,可以考虑给每个数加上n 还是2 4 5 1 2 0,a[0]=2 把a[2]+6,变成2 4 11 1 2 0,a[1]=4,a[4]+6,变成2 4 11 1 8 0,a[2]=11>6,则a[a[2]-6]+6,变成2 4 11 1 8 6,a[3]=1,则a[1]+6,变成8 4 11 1 8 6 ,a[4]=8>6,a[a[4]-6]=11>6,说明a[2]之前已被人加过了,那么a[4]-6就是重复的。

int FindTheRepeat(int data[],int n){for (int i= 0;i<n;++i){int index = data[i]>n?data[i]-n:data[i];if(data[index]>=n) return index;data[index] += n;}}


2、给定一个无序的整数数组,怎么找到第一个大于0,并且不在此数组的整数。比如[1,2,0]返回3,[3,4,-1,1]返回    2,[1, 5, 3, 4, 2]返回6,[100, 3, 2, 1, 6,8, 5]返回4。要求使用O(1)空间和O(n)时间
思路:从前到后,如果一个数在0—n之间,就放到对应的坑里面,如果不在这个范围,或者坑里的元素和要放进去的相等,就不用放,全部放完了,从前到后,找到第一个不连续的就好了。
100 3 2 1 6 8 5为例,a[0]=100 不在范围,不动,a[1]=3,交换a[3] 100 1 2 3 6 8 5,a[1]=1,继续往后走,a[2]=2,继续后走,a[3]=3,继续,a[4]=6,交换,100 1 2 3 5 8 6,a[4]=5,继续交换,100 1 2 3 8 5 6,a[4]=8,超过范围,不动,继续走a[5]=5 a[6]=6,走完一遍,第二遍,从前到后,1 2 3都有,4没有,返回4

int FindFirstNumberNotExistenceInArray(int a[], int n)  {  int i;  // 类似基数排序,当a[i]>0且a[i]<N时保证a[i] == i + 1  // a[i] != a[a[i] - 1]判断是为了防止1 1 1这种死循环for (i = 0; i < n; i++)  while (a[i] > 0 && a[i] <= n && a[i] != i + 1 && a[i] != a[a[i] - 1])  Swap(a[i], a[a[i] - 1]);  // 查看缺少哪个数  for (i = 0; i < n; i++)  if (a[i] != i + 1)  break;  return i + 1;  }


0 0