剑指offer-面试题30:最小的K个数

题目:输入n个数,找出其中最小的K个数。例如输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1,2,3,4。

思路:这道题目是典型的top K问题。两种方法:

(1)如果允许改变数列,半快速排序,是基准值正好为第K个数,那么基准值左边的都是小于它的,即可得到最小的K个数(求最大的过程类似),时间复杂度O(n)。

(2)不允许改变数列,采用维护数组的方法,建立一个最大堆。遍历数列,如果数字小于堆顶,那么堆顶很明显不是最小的K个数,更新堆顶,直到所有数字都已经与堆顶比较,堆上的数字即为最小的K个数。

半快速排序在面试题29里讲过,在此不再讨论。见连接:http://blog.csdn.net/moses1213/article/details/51072029

堆的实现可以用数组,也可以利用STL里的multiset容器,下面是分别用两种实现过程的求解代码:

1.用multiset实现

typedef multiset<int, greater<int> >    intSet;
typedef multiset<int, greater<int> >::iterator setIterator;

void GetLeastNumbers(Const vector<int>& data, intSet& leastNumbers, int k)
{
    leastNumbers.clear();
    
    if(k < 1 || data.size() < k)
        return;
        
    vector<int>::const_iterator iter = data.begin();
    for(; iter != data.end(); ++iter)
    {
        if(leastNumbers.size() < k)
            leastNumbers.insert(*iter);
            
        else
        {
            setIterator iterGreatest = leastNumbers.begin();
            
            if(*iter < *(leastNumbers.begin())
            {
                leastNumbers.erase(iterGreatest);
                leastNumbers.insert(*iter);
            }
        }
    }
}

2.用数组实现

如果数组从0开始计数(也有从1开始),顺序存储堆的结点,那么对于结点i,它的左右子结点和父亲有这样的关系:

左儿子:2*i+1

右儿子:2*i+2

父亲:    (i-1)/2

建堆过程:上滤建堆。因为数组总是在末尾插入新的元素,这时候比较它和父亲的大小,比父亲小则交换,逐步上移,直到大于父结点。

插入过程:和建堆过程类似,上滤法。

维护二叉堆的过程是每次和堆顶元素比较,此时需要采取的是下滤过程,因此这里需要上滤和下滤两个例程。

void Swap(int& a, int& b)
{
    int tmp = a;
    a = b;
    b = tmp;
}


void SiftUp(int* a, int n)
{
    int c;
    for(int i = n - 1; i > 0 && a[i] > a[c=(i-1)/2]; i = c)
        Swap(a[i], a[c]);
}


void SiftDown(int* a, int n)
{
    int c;
    for(int i = 0; (c = 2*i+1) <= n-1; i = c)
    {
        if(c+1 <= n-1 && a[c+1] > a[c])
            ++c;
        if(a[i] >= a[c])
            break;
        Swap(a[i], a[c]);
    }
}
TopK算法:

void TopK(int *a, int n, int k)  
{  
    int *heap = new int[k];  
    for(int i = 0; i < k; ++i)  
        heap[i] = a[i];          //堆初始化数据  
    for(int i = 1; i < k; ++i)  
        SiftUp(heap, i+1);       //建堆
    
    for(int i = k; i < n; ++i)   //与堆顶元素比较,如果小于堆顶则替换堆顶  
    {  
        if(a[i] < *heap)  
        {  
            *heap = a[i];  
            SiftDown(heap, k);  
        }  
    }  
    for(int i = 0; i < k; ++i)  
        cout << heap[i] << " ";  
    cout << endl;  
    delete[] heap;
}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值