对于数组这东西,编程编的久了。绝对不陌生,但是数组可以衍生出很多有意思的问题。比如找出一个整形数组中第K大的数;找出一个整形数组中第K大的数,输出它的位置等等。
找第K个大的数不难,只要是接触过快速排序的人。都知道可以用快排的一次划分步骤作为筛选条件。
可以很快的写出partition的方法,快排还在草稿中,以后讨论
int my_partition(int*& r,int begin,int end)
{
if(r == NULL || begin>end) return -1;
int i=begin,j=end;
while(i<j)
{
while(i<j && r[i]<r[j]) j--;
if(i<j)
{
swap(r[i],r[j]);
i++;
}
while(i<j && r[i]<r[j]) i++;
if(i<j)
{
swap(r[i],r[j]);
j--;
}
}
return i;
}
对于第一个问题找到第k大的数,有了partition后就很简单了。
int index=0;
int begin=0,end=n-1;
//快速排序一次划分,直到第K个划分值出现
//序号0包括在内,实质的下标为k-1
while(index != k-1)
{
index = my_partition(r,begin,end);
if(index > k-1){
end = index-1;
}
else if(index < k-1)
{
begin = index +1;
}
}
但是这样就会修改了数组的原始分布,如果是第二个问题,还要输出第K个大数字的位置,这就略显尴尬了。如果这个第K大的数字在数中还有重复,那样的话下标就有多个了。
当然了,学过数据结构的人也快知道如何在partition的基础上加点修改来解决,就是建立个哈希表的事情嘛。我们在partition之前,建立个关于数组内容和下标的哈希表,这样在partition后得到的第K个数字,在哈希表里面查找然后输出记录下来的下标。建哈希的时间O(n),partition的时间O(nlogk),查找输出的时间O(1),总的时间复杂度为O(nlogk),还挺可观的。辅助空间S(n^2)
map<int,vector<int> > hashmap;
int temp=0;
for(int i=0;i<n;i++)//建立哈希表,map的底层其实是红黑树,不是实质上的哈希,但能表达出这种关系 ,所以程序实质上的复杂度并不是上文那样
{
temp=r[i];
hashmap[temp].push_back(i);
}
那么问题来了,有没有不修改数组顺序的方法呢?也是有的。因为我们是要找第K个大的数,那么原数组中必然存在一个子集,是包含了前K个大的数字组成的数组。那么很容易想到用一个大小为K的辅助数组A来寻找这个原数组前K个大的数字组成的子数组。对于这个大小为K的辅助数组A,关注点在于每次都需要拿A中的最大元素和原数组中的从第K+1个到第N个元素比较。
如果每次提取都需要扫描一遍A,时间为O(n),显然是不必要的,没有利用到历史的搜索记录。可以想到使用堆就可以每次以O(1)的时间找到当前最大的数。那么思路就很明显了,在第K+1到第N个元素的比较中,如果出现了比当前A中的最大元素还小,那么就替换当前的最大元素,然后在进行一次重建堆,因为在A在初始建立堆后已经具有了堆的性质,因此以后每次重建堆需要的时间为O(logk)。时间:扫描数组O(n)。重建(n-k)次堆,因此调整堆的时间为O((n-k)logk)—>O(nlogk),输出在扫描一次数组O(n)。总的时间为O(2*n+nlogk)。也就是O(nlogk),辅助空间S(k)。总的来说在空间上要比用哈希的方法来的好。
vector<int> heap;
for(int i=0;i<k;i++)//先读取k个数放入待选容器
{
heap.push_back(r[i]);
}
//初始建立最大堆
make_heap(heap.begin(),heap.end());
for(int i=k;i<n;i++)
{
vector<int>::iterator it = heap.begin();
if(*it > r[i])
{
//发现比堆顶还小的数
*it = r[i];
//重新建立堆
make_heap(heap.begin(),heap.end());
}
}
cout<<"第k个大的数为:"<<heap[0]<<endl;
for(int i=0;i<n;i++)
{
if(heap[0] == r[i]) cout<<i<<" ";
}
从上面的两个例子来说,找第K大的数都可以用两种思路来思考,一种是建立索引,一种是找到前K个大的数字组成的子数组。一般来说,建立索引我们可以获得信息是最多的,很容易迁移至其他问题上,且效率基本不有很大的下降。比如说求第K个大的数字出现的次数,求前K个大的数字的和或者总和等等。但是如果数据量很多的话,辅助空间就略可怕了。
建立辅助数组的思路的话,我们能获得的信息仅仅是数组中的前K个大的数字都是哪一些。因此如果数据量非常大的话,且我们的主要目的在于查找的话,这个方法就非常的好了。