递归 ==> 迭代
递归就是系统帮自己压栈:所有递归都可以用迭代解决
系统压栈用的空间比较大。自己压栈性能会好很多。
public static int getMax(int[] arr, int L, int R){
if(L == R){ //结束递归
return arr[L];
}
int mid = L + (R - L) / 2; //左边界
int maxLeft = getMax(arr, L, mid);
int maxRight = getMax(arr, mid + 1, R); //防止左边界不收缩。mid + 1
return Math.max(maxLeft, maxRight);
}
先递归在求解。
交换
public static void swap(int [] nums, int i, int j){
nums[i] = nums[i] ^ nums[j];
nums[j] = nums[i] ^ nums[j];
nums[i] = nums[i] ^ nums[j];
}
public static void swap(int [] nums, int i, int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
1. 快速排序
排序中最重要的
期望时间复杂度:
Θ
\Theta
Θ(nlgn)
最坏时间复杂度:
Θ
\Theta
Θ(
n
2
n^2
n2)
空间复杂度:
O
(
l
g
n
)
O(lgn)
O(lgn) ~
O
(
n
)
O(n)
O(n)
QUICKSORT(A,p,r)
if p < r
q = PARTITION(A, p, r)
QUICKSORT(A, p, q - 1)
QUICKSORT(A, q + 1, r)
为了排序一个数组A的全部元素, 初始调用是QUICKSOER(A, 1, A.length).
- 算法的关键部分是PARTITION过程,它实现了对子数组A[q…r]的原址重排。
PARTITION(A, p, r)
x = A[r]
i = p -1
for j = p to r-1
if A[j] <= x
i = i + 1
exchange A[i] with A[j]
exchange A[i + 1] with A[r]
return i + 1
i+1和j之间的元素是比pivot element 大的元素
快速排序的随机化版本
PARTITION(A, p, r)
x = A[r]
i = p -1
for j = p to r-1
if A[j] <= x
i = i + 1
exchange A[i] with A[j]
exchange A[i + 1] with A[r]
return i + 1
RANDOMIZED-PARRTITION(A, p ,r)
i = RANDOM(p, r)
exchange A[r] with A[i]
return PARTOTION(A, p , r)
QUICKSORT(A,p,r)
if p < r
q = RADOMMIZED-PARTITION(A, p, r)
QUICKSORT(A, p, q - 1)
QUICKSORT(A, q + 1, r)
代码如下:常规思路
public static void quickSort(int[] nums){
if(nums == null || nums.length == 0)
return;
quickSort1(nums, 0, nums.length - 1);
}
/**
* 时间复杂度:
* 最好:O(nlogn)
* 最坏: O(n^2)
* 平均: O(nlogn)
* 空间复杂度:O(logn ~ n)
* 稳定性: 不稳定
* @param nums
* @param left
* @param right
*/
public static void quickSort1(int[] nums, int left, int right) {
if(left < right) {
swap(nums, left, left + (int)(Math.random() *(right - left + 1));
int pivot = partition(nums, left, right);
quickSort1(nums, left, pivot - 1);
quickSort1(nums, pivot + 1, right);
}
}
public static int partition(int[] nums, int left, int right) {
int pivotIndex = left;
int pivot = nums[pivotIndex];
int l = left + 1;
int r = right;
// System.out.println("pivot: " + pivot + " 当前结果: " + Arrays.toString(nums));
// System.out.println("pivotIndex: " + pivotIndex + "; l,r: " + l + " " + r);
while(l < r) {
if(nums[l] > pivot && nums[r] < pivot){
swap(nums, l++, r--);
// System.out.println("排序过程:" + Arrays.toString(nums));
}
if(nums[l] <= pivot) l++;
if(nums[r] >= pivot) r--; //与pivot小的元素交换,返回r
}
swap(nums, pivotIndex, r);
// System.out.println("此轮结束:" + Arrays.toString(nums));
// System.out.println();
return r;
}
快排优化代码如下:(荷兰国旗问题的延申)
public static void newQuickSort(int[] nums){
if(nums == null || nums.length == 0)
return;
newQuickSort1(nums, 0, nums.length - 1);
}
public static void newQuickSort1(int[] nums, int left, int right){
if(left < right){
swap(nums, left + (int)(Math.random() *(right - left + 1)), right);
int[] p = partition(nums, left, right);
newQuickSort1(nums, left, p[0] - 1);
newQuickSort1(nums, p[1] + 1, right);
}
}
//划分的数:nums[right]
//小于:左边界:0 右边界less 0~less
//等于:左边界less + 1, 右边界left less + 1 ~ left
//待定区域:left + 1 ~ more - 1
//大于: more ~ right - 1
//左后一步:swap(more, right) nums[right]是划分数组的数,最后在more的左边界
//所以最后返回的是left + 1, more.(more为等于nums[right]->划分的数
public static int[] partition(int[] nums, int left, int right){ //用left划分,省一个变量
int less = left - 1; //less代表左边小于nums[right]的索引
int more = right; //more代表右边大于nums[right]的索引,右边right为比较的数,不能交换
while(left < more){
if(nums[left] < nums[right]){
swap(nums, ++less, left++);
} else if(nums[left] > nums[right]){
swap(nums, --more, left); //交换more前面一个数
} else{
left++;
}
}
swap(nums, more, right); //more的索引是大于nums[right]
return new int[]{less + 1, more};
}
//划分的数nums[right]
//小于:0~less
//等于:less+1 ~ cur - 1
//待定:cur: cur ~ more - 1
//大于: more - 1 ~ right - 1
//最后一步 swap(more, right) ==> 下雨边界:0~ less, 等于边界:less+1 ~ more 大于边界:more + 1 ~ right
public static int[] partition2(int[] nums, int left, int right){
int less = left - 1;
int more = right;
int num = nums[right];
while(left < more){
if(nums[left] < num){
swap(nums, ++less, left++);
} else if(nums[left] > num){
swap(nums, --more, left);
} else{
left++;
}
}
swap(nums, more, right);
return new int[]{less + 1, more};
}
最初版本划分Hoare划分
Hoare-PARTITION(A, p, r)
x = A[p]
i = p + 1
j = r
while TRUE
wile A[j] > x
j = j - 1
while A[i] < x
i = i + 1
if i < j
exchange A[i] with A[j]
return j
例题:荷兰国旗问题
给定一个数组arr,和一个数num, 请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。
返回的是等于区域的边界
public class NetherlandsFlags {
public static int[] partition(int [] nums, int left, int right, int num){
int less = left - 1;
int more = right + 1;
int cur = left; //可以用left 代替cur
while(cur < more){
if(nums[cur] < num)
swap(nums, ++less, cur++);
else if(nums[cur] > num)
swap(nums, --more, cur); // 大于nums的数放在后面,换回来的元素大小不确定
else
cur++;
}
return new int[]{less + 1, more - 1};
}
}
public static int[] partition(int[] nums, int left, int right){
int less = left - 1;
int more = right;
while(left < more){
if(nums[left] < nums[right]){
swap(nums, ++less, left++);
} else if(nums[left] > nums[right]){
swap(nums, --more, left);
} else{
left++;
}
}
swap(nums, more, right);
return new int[]{less + 1, more};
}
2. 归并排序
时间复杂度:
Θ
\Theta
Θ(nlgn)
空间复杂度:
O
(
n
)
O(n)
O(n) (辅助数组占用的空间,固定的)
MERGE-SORT(A, p, r)
if p < r
q = (p + r)/2
MERGE-SORT(A, p, q)
MERGE-SORT(A, q + 1, r)
MERGE(A, p, q, r) //合并
MERGE(A, p, q, r)
n1 = q - p + 1
n2 = r - q
L[n1], R[n2]
for i = 0 to n1
L[i] = A[p + i]
for j = 0 to n2
L[j] = A[q + j + 1]
i = 0; j = 0
for k = p to r
if(L[i] <= R[j]
A[k] = L[i]
i = i + 1
else
A[k] = A[j]
j = j + 1
while i < n1 - 1
A[k++] = A[p + i]
i = i + 1
while j < n2-1
A[k++] = A[q+1 + j]
j = j + 1
public static void mergeSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
mergeSort(arr, 0, arr.length - 1);
}
public static void mergeSort(int[] arr, int left, int right) {
if (left < right) { //继续条件
int mid = left + ((right - left) >> 1);
mergeSort(arr, left, mid); //划分
mergeSort(arr, mid + 1, right); //划分
merge(arr, left, mid, right); //求解
}
return 0;
}
// public static int mergeSort(int[] nums, int left, int right){
// if(left == right) {//递归结束条件
// return 0;
// }
// int mid = left + (right - left) / 2;
// int sumLeft = mergeSort(nums, left, mid);
// int sumRight = mergeSort(nums, mid + 1, right);
// return sumLeft + sumRight + merge(nums, left, mid, right);
// }
public static void merge(int[] arr, int left, int mid, int right) {
int[] help = new int[right - left + 1];
int i = 0;
int p1 = left;
int p2 = mid + 1;
while (p1 <= mid && p2 <= right) {
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= mid) {
help[i++] = arr[p1++];
}
while (p2 <= right) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[left + i] = help[i];
}
}
模板
mergeSort(int[] A, int left, int right){
if(left == right)
return 0;
mid = left + (right - left) / 2; //取左边界
mergeSort(A, left, mid);
mergeSort(A, mid + 1,right);
merge(A, left, mid, right); //求解
}
merge(int[] arr, int left, int mid, int right) {
int[] help = new int[right - left + 1];
int i = 0;
int p1 = left;
int p2 = mid + 1;
while (p1 <= mid && p2 <= right) {
// your code, 求解方法
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= mid) {
help[i++] = arr[p1++];
}
while (p2 <= right) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[left + i] = help[i];
}
}
例题小和问题:在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求解一个数组的小和。
右边数组比左边某一个某数大的数目, 某数乘以数目。依次分批求解
代码:方法
public static int smallSum(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return mergeSort(arr, 0, arr.length - 1);
}
public static int mergeSort(int[] arr, int l, int r) {
if (l == r) {
return 0;
}
int mid = l + ((r - l) >> 1);
return mergeSort(arr, l, mid) + mergeSort(arr, mid + 1, r) + merge(arr, l, mid, r);
}
public static int merge(int[] arr, int l, int m, int r) {
int[] help = new int[r - l + 1];
int i = 0;
int p1 = l;
int p2 = m + 1;
int res = 0;
while (p1 <= m && p2 <= r) {
res += arr[p1] < arr[p2] ? (r - p2 + 1) * arr[p1] : 0; //求解代码
help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= m) {
help[i++] = arr[p1++];
}
while (p2 <= r) {
help[i++] = arr[p2++];
}
for (i = 0; i < help.length; i++) {
arr[l + i] = help[i];
}
return res;
}
正确的参考方法
// for test
public static int comparator(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
int res = 0;
for (int i = 1; i < arr.length; i++) {
for (int j = 0; j < i; j++) {
res += arr[j] < arr[i] ? arr[j] : 0;
}
}
return res;
}
对数器检查算法是否正确
对数器产生随机数组
// for test
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); //包含负数,不减则为正数
}
return arr;
}
// for test
public static int[] copyArray(int[] arr) {
if (arr == null) {
return null;
}
int[] res = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
res[i] = arr[i];
}
return res;
}
// 判断排序数组是否一样
public static boolean isEqual(int[] arr1, int[] arr2) {
if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
return false;
}
if (arr1 == null && arr2 == null) {
return true;
}
if (arr1.length != arr2.length) {
return false;
}
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
// 打印数组
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
// 测试
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 100;
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
int[] arr1 = generateRandomArray(maxSize, maxValue);
int[] arr2 = copyArray(arr1);
if (smallSum(arr1) != comparator(arr2)) {
succeed = false;
printArray(arr1);
printArray(arr2);
break;
}
}
System.out.println(succeed ? "Nice!" : "Fucking fucked!");
}
逆序对个数的问题
从大到小排序,找出比nums[p1]小的元素的个数
public static int reverse(int[] nums){
if(nums == null || nums.length < 2)
return 0;
return mergeSort(nums, 0, nums.length - 1);
}
public static int mergeSort(int[] nums, int left, int right){
if(left == right){ //结束条件
return 0;
}
int mid = left + (right - left) / 2;
int sumLeft = mergeSort(nums, left, mid);
int sumRight = mergeSort(nums, mid + 1, right);
return sumLeft + sumRight + merge(nums, left, mid, right);
}
public static int merge(int[] nums, int left, int mid, int right){
int[] help = new int[right - left + 1];
int i = 0;
int p1 = left;
int p2 = mid + 1;
int sum = 0;
while(p1 <= mid && p2 <= right){// 左右数组已经是从大到小排序了,找出比nums[p1]的数量
sum += (nums[p1] > nums[p2] ? (right - p2 + 1) : 0);
help[i++] = nums[p1] > nums[p2] ? nums[p1++] : nums[p2++]; //从大到小排序,先确定大的元素
}
while(p1 <= mid){
help[i++] = nums[p1++];
}
while(p2 <= right){
help[i++] = nums[p2++];
}
for(i = 0; i < help.length; i++)
{
nums[left + i] = help[i];
}
return sum;
}
public static int[] generatorArray(int maxSize, int maxValue){
int[] arr = new int[(int)((maxSize + 1) * Math.random())];
for(int i = 0; i < arr.length; i++){
arr[i] = (int)((maxValue + 1) * Math.random()) - (int)((maxValue + 1) * Math.random());
}
return arr;
}
public static int[] copyArray(int [] arr){
if(arr == null || arr.length == 0)
return null;
int[] temp = new int[arr.length];
for(int i = 0; i < arr.length; i++){
temp[i] = arr[i];
}
return temp;
}
public static int rightMethod(int[] nums){
if(nums == null || nums.length < 2)
return 0;
int sum = 0;
for(int i = 1; i < nums.length; i ++) //第i个数,前面有几个比他大的数
for(int j = 0; j < i; j++) {
sum += nums[j] > nums[i] ? 1 : 0;
}
return sum;
}
public static boolean isEqual(int sum1, int sum2){
return sum1 == sum2 ? true : false;
}
public static void printArrary(int[] nums){
if(nums == null || nums.length == 0 )
return;
for(int i = 0; i < nums.length; i++){
System.out.print(nums[i] + " ");
}
System.out.println();;
}
public static void main(String[] args) {
int maxSize = 100;
int maxValue = 100;
int times = 5000;
boolean success = true;
for(int i = 0; i < times; i++){
int[] arr1 = generatorArray(maxSize, maxValue);
int[] arr2 = copyArray(arr1);
int sum1 = reverse(arr2);
int sum2 = rightMethod(arr1);
if(!isEqual(sum1, sum2)){
System.out.println("reveser" + sum1);
success = false;
printArrary(arr1);
printArrary(arr2);
break;
}
}
System.out.println(success ? "nice!" : "wrong");
}
总结:快速排序是先排序在划分,所以后面的排序过程可以不要划分的数;归并排序是先划分后排序,所以每一个数都要在划分的子数组里面,而且要防止陷入死循环(防止死递归爆栈问题):取左中位数就要左边界收缩,取右中位数就要右边界收缩。
左边界:
mid = left + (right - left) / 2;
mid = (left + right) >>> 1;
左边界收缩:
left = mid + 1
我们看极端的情况,left 和 high 都是整型最大值的时候,注意,此时 3232 位整型最大值它的二进制表示的最高位是 00,它们相加以后,最高位是 11 ,变成负数,但是再经过无符号右移 >>>(重点是忽略了符号位,空位都以 00 补齐),就能保证使用 + 在整型溢出了以后结果还是正确的。
右边界:
mid = left + (right - left + 1) / 2;
mid = (left + right + 1) >>> 1;
右边界收缩:
right = mid - 1