您的位置:首页 > Web前端

剑指Offer学习总结-最小的k个数

2018-01-23 18:38 274 查看

剑指Offer学习总结-最小的k个数

本系列为剑指Offer学习总结,主要是代码案例的分析和实现:

书籍链接:http://product.dangdang.com/24242724.html

原作者博客:http://zhedahht.blog.163.com/blog/static/254111742011101624433132/

原作者博客链接有完整的项目代码下载。

最小的k个数

题目

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

第一眼看到的解法:

直接给数组进行排序,排序完之后,获取前k个数字。我们选用快速排序来进行排序。

作者的快速排序和常规的稍有区别 选取参照值的时候进行了随机分配,并且第一比较过程中将分界值放到了末尾。

不太好理解 大家多看下注释我的注释,模拟下代码的过程。

void GetLeastNumbers_Solution1(int* input, int n, int* output, int k)
{
if(input == NULL || output == NULL || k > n || n <= 0 || k <= 0)
return;

int start = 0;
int end = n - 1;
int index = Partition(input, n, start, end);
while(index != k - 1)
{
if(index > k - 1)
{
end = index - 1;
index = Partition(input, n, start, end);
}
else
{
start = index + 1;
index = Partition(input, n, start, end);
}
}

for(int i = 0; i < k; ++i)
output[i] = input[i];
}

int Partition(int data[], int length, int start, int end)
{
if(data == NULL || length <= 0 || start < 0 || end >= length)
throw new std::exception("Invalid Parameters");

//随机分配 下标确定分界值
int index = RandomInRange(start, end);

//将分界值值放到末尾
Swap(&data[index], &data[end]);

int small = start - 1;//当期小于分界值而且最靠近分界值的数字的位置下标
for(index = start; index < end; ++ index)
{
if(data[index] < data[end])
{
++ small;
if(small != index)
Swap(&data[index], &data[small]);
}
}

++ small;
Swap(&data[small], &data[end]);

return small;
}


但是这样做在那n-k 个数的排序上浪费了资源。我们需要引入一个结构用来存储前k个数字,并且使得前k个数总是最大的数在第一个,

这样每次遇到一个数值需要和前k个数中排在第一位的那个最大数比较就可以了。

红黑树的解法

我们这里来分析一下这里需要用什么数据结构比较合适。

我们首先需要一个容器来存储这k个数字,我们需要对容器进行什么操作?

如果容器的容量小于k则直接插入,如果容器满了,我们仍然需要插入

我们需要判断当前容器中最大数值和待插入数值的大小,如果待插入数值较小,则进行插入

因此当容器满了之后, 我们要做 3 件事情: 一是在 k 个整数中找到最大数;

二是有可能在这个容器中删除最大数: 二是有可能要插入一个新的数字。

如果用一个二叉树来实现这个数据容器, 那么我们能在 O(bgk)时间内实现这二步操作。

因此对于 n 个输入数字而言, 总的时间效率就是O(nlogk)

我们可以选择用不同的二叉树来实现这个数据容器。 由于每次都需要找到 k 个整数中的最大数字,

我们很容易想到用最大堆。在最大堆中, 根结点的值总是大于它的子树中任意结点的值。

于是我们每次可以在 O(1)得到已有的 k 个数字的最大值, 但需要 O(logk)时间完成删除及插入操作。

我们还可以采用红黑树来实现我们的容器。 红黑树通过把结点分为红、 黑两种颜色并根据一些规则

确保树在一定程度上是平衡的,从而保证在红黑树中查找、 删除和插入操作都只需要 O(logk)时间

在 STL中 set 和 multiset 都是基于红黑树实现的。

下边很容易理解仔细看一下就好。

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

void GetLeastNumbers_Solution2(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);
}
}
}
}


最大堆的插入删除操作

STL中没有最大堆的实现

template <typename T>
Class MaxHeap(){
public:
MaxHeap(int maxSize);
~MaxHeap();
bool Insert(T element);
bool DeleteMax();
private:
T *elements;
int MaxSize = 0;
int size = 0;
}

//创建堆,记住堆的根节点是element[1]
//这样做的目的,是为了可以使用i/2来访问其父结点,可以使用2i表示其左结点。
template <typename T>
MaxHeap::MaxHeap(int maxSize){
MaxSize = maxSize;
elements = new T[maxSize+1];
size = 0;
}

//插入节点,其实就是从末节点开始,往上找,直到找一个地方,让新元素放进去后,比它的父节点小。
//找的过程中,找过的节点下移到它的子女所在的平台,为了给新结点腾地方
//找到这个地方后,就把新结点安进去
template <typename T>
bool MaxHeap::Insert(T ele){
if(size == MaxSize){
return false;
}
size ++;
int i = size;
while(i > 1){
if(elements[i/2] >= ele)
break;    //找到合适的位子了
element[i] = element[i/2];    //没找到,把父节点往子节点上移,腾位置
i = i/2;
}
element[i] = ele;
return true;
}

//删除节点,就是将根节点删除,但是我们知道,接下来必须调整,因为数组中间不能有空缺。
//调整的过程其实就是将数组末尾的那个元素找个地方放。根节点原来的空位不行,就把空位往下挪,
//一直挪到这个空位放末尾节点合适了,所谓合适,就是末尾节点放在这里比其子女都大,
//(就算中间没有合适的位置,移到叶节点,必然合适) ,把末尾节点放进去。
template <typename T>
bool MaxHeap::DeleteMax(){
if(size == 0){
return false;
}
T temp = elements[size];    //末尾节点记下来。
size--;
int i = 1;
while(2*i <= size){    //当i变成叶节点,就会跳出循环
int j = 2 * i;
if(j+1 <= size && elements[j+1] > elements[j])    //别忘右子节点可能不存在的情况
j++;    //如果存在右子节点,而且比左子节点大,我们就和右子节点比 (就是验证是否temp比左右子节点都大罢了,所以和大的比)
if(element[j] <= temp)    //符合条件,左右子节点都比temp,也就是末节点大,那么空位置找到了,退出循环
break;
elements[i] = elements[j];        //不符合条件,继续往下腾位置
i *= 2;
}
elementp[i] = temp;    //就算一直找到最后也没找到,这个时候i已经是某一个叶节点,而且这个叶节点的值已经转移到父节点上,我们把temp付给它就可以

return true;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: