1. 局部最小和二分法
一个数组从0到N位置一定存在局部最小值,即x(m-1)>x(m)<x(m+1), 不停寻找局部最小位置作为二分点。
2. 求中点
(1)mid =(L+R)/2
常见写法,但(L+R)容易发生溢出
(2)mid = L+(R-L)/2
(3)mid = L+(R-L)>>1
右移操作比除2更快
3. 归并排序的合并
p1<M和p2<R是判断越界
4. 小和问题
等同于对于每个数,给右边的数的贡献。例如,1的右边有四个数比它大,则1的贡献是4个1;3的右边有两个,贡献是2个3……
此时求解小和等同于归并排序的merge操作。
例如,我们可以先求1和3的数组的小和,即把1和3看成两个有序数组merge,得到1个1;
接着把数组13和4merge,得到1个1和1个3,依次类推
代码:
5. 逆序对问题
与小和问题一样
6. 荷兰国旗问题
对于问题一:
例如arr=[3 5 6 7 4], num=5
设置一个x,代表小于等于区,初始x=0,
从i=0遍历到最后:
(1)arr[i]<=num, arr[i]和arr[x+1]交换,然后x++,i++
(2)arr[i]>num,i++
对于问题二:
由荷兰国旗问题可以推出快排。
7. 堆结构
-
堆事实上就是一种完全二叉树
-
堆排序的空间复杂度是最小的
-
优先级队列就是堆(java中PriorityQueue就是默认小根堆,也可以通过修改比较器变成大根堆)
-
堆和数组的对应关系:
对应下标:
规则是对于i位置的节点:
左孩子:2i+1
右孩子:2i+2
父节点:(i-1)/2 -
大根堆:
最大值在根节点,每个父节点都比它孩子节点大
(1)上移操作(heapInsert):在叶子节点插入一个数(即在数组最尾端放一个数字),通过不断上移形成新的大根堆
(2)下移操作(heapify):某个父节点变成一个更小的数,不断将数字和其最大的孩子比较交换,重新构建大根堆;
代码中heapsize是防止越界,如果左孩子小于heapsize代表节点下方还有孩子节点,有时候我们不考虑整个数组,只考虑前几个数字
(4)堆排序
先构建一个大根堆,每次删除数组中的最大值(交换根节点和最后一个节点),然后断掉新的最后节点,然后剩下部分重新构建大根堆
构建大根堆的方法有两种:
(1)一个一个插入,从上到下构建大根堆
(2)首先构造一个二叉树(无序),然后自下而上构造大根堆,比方法(1)更好
堆排序应用:
假设一个数组排序,每个数字正确排放的位置都不会移动超过k步,怎么排序?
从i=0位置开始,每次取i到(i+k)位置数字,将其构建成小根堆,i位置此时一定是正确值,依次往后操作
8. 桶排序
pass
9. 排序问题总结
-
快排是最优选,如果有空间限制再选堆,需要稳定性选归并。
-
坑:很难,经典快排的划分不具有稳定性,需要进行论文级别的研究
-
有时候需要结合NlogN和N^2的算法: