剑指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; }
相关文章推荐
- 剑指Offer学习总结-把数组排成最小的数
- 剑指Offer学习总结-旋转数组的最小数字
- 剑指Offer学习总结-不用加减乘除做加法
- 剑指Offer学习总结-不能被继承的类
- 剑指Offer学习总结-链表中倒数第 k 个结点
- 剑指Offer学习总结-栈的压入、 弹出序列
- 剑指Offer学习总结-调整数组顺序使奇数位于偶数前面
- 剑指Offer学习总结-二叉树的镜像
- 剑指Offer学习总结-和为S的两个数字
- 剑指Offer学习总结-圆圈中最后剩下的数字
- 【剑指Offer学习】【面试题8 : 旋转数组的最小数字】
- 剑指Offer学习总结-二进制中1的个数
- 剑指Offer学习总结-复杂链表的复制
- 剑指Offer学习总结-翻转单词顺序
- 剑指Offer学习之面试题8 : 旋转数组的最小数字
- 剑指Offer学习总结-二维数组寻找数字
- 剑指Offer学习总结-替换空格
- 剑指Offer学习总结-数值的整数次方
- 剑指Offer学习总结-在O(1)时间删除链表结点
- 【剑指Offer学习】【面试题33:把数组排成最小的数】