package com.zuoshen.jichurumen.class02;
import java.util.PriorityQueue;
/**
* @author ShiZhe
* @create 2022-02-24 9:06
*/
public class code01 {
/**
* 归并排序
* 思想是分而治之
* 时间复杂度O(N*logN),额外空间复杂度O(N)
* @param arr
*/
public static void mergeSort(int[] arr, int left, int right) {
if (arr == null || arr.length < 2) {
return;
}
if (left == right) {
return;
}
int mid = left + ((right - left) >> 1);
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
/**
* 归并排序算法步骤:
* 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
* 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
* 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
* 重复步骤 3 直到某一指针达到序列尾;
* 将另一序列剩下的所有元素直接复制到合并序列尾。
* @param arr
* @param left
* @param mid
* @param right
*/
public static void merge(int[] arr, int left, int mid, int right) {
// 辅助空间,将2个有序序列比较后合成一个有序序列
int[] help = new int[right - left +1];
// 辅助空间下标
int i = 0;
// 第一个有序序列的指针
int one = left;
// 第二个有序序列的指针
int two = mid + 1;
// 利用2个指针比较大小
while (one <= mid && two <= right) {
help[i++] = arr[one] < arr[two] ? arr[one++] :arr[two++];
}
while (one <= mid) {
help[i++] = arr[one++];
}
while (two <= right) {
help[i++] = arr[two++];
}
for (i = 0; i < help.length; i++) {
arr[left++] = help[i];
}
}
/**
* 小和问题
* 在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。
* 例子:[1,3,4,2,5] 1左边比1小的数,没有; 3左边比3小的数,1; 4左边比4小的数,1、3; 2左边比2小的数,1; 5左边比5小的数,1、3、4、2; 所以小和为1+1+3+1+1+3+4+2=16
* 转换思路:求每一个数左边比当前小的数,也就等同于求每个数右边比当前大的数的个数
*/
public static int smallSum(int[] arr, int left, int right) {
if (arr == null || arr.length < 2){
return 0;
}
if (left == right) {
return 0;
}
int mid = left + ((right - left) >> 1);
return smallSum(arr, left ,mid)
+ smallSum(arr,mid + 1, right)
+ smallSumMergeSort(arr, left, mid ,right);
}
public static int smallSumMergeSort(int[] arr, int left, int mid, int right) {
int[] help = new int[right - left + 1];
int i = 0;
int one = left;
int two = mid + 1;
// 和
int res = 0;
while (one <= mid && two <= right) {
// 这一步最重要,左边有序序列的某值比右边有序序列的某值小,那右边有序序列某值之后的所有值都比左边某值大
res = arr[one] < arr[two] ? res+arr[one] * (right - two + 1) :res;
help[i++] = arr[one] < arr[two] ? arr[one++] :arr[two++];
}
while (one <= mid){
help[i++] = arr[one++];
}
while (two <= right){
help[i++] = arr[two++];
}
for (i = 0; i < help.length; i++) {
arr[left++] = help[i];
}
return res;
}
/**
* 堆排序
* 第一步:生成大根堆
* 第二步:把堆的最大值和堆末尾的值交换,然后减少堆的大小之后,再去调整堆,一直周而复始
* @param arr
*/
public static void heapSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
// 生成大根堆
for (int i = 0; i < arr.length; i++) {
heapInsert(arr, i);
}
int size = arr.length;
swap(arr, 0, --size);
// 调整,注意循环条件,使用异或调整,最后一个不换
while (size > 1) {
heapify(arr, 0, size);
swap(arr, 0, --size);
}
}
/**
* 将一个数组生成为大根堆
* 时间复杂度为O(N)
* @param arr
* @param index
*/
public static void heapInsert(int[] arr, int index) {
// 当一个孩子的值大于父亲节点的值得时候交换,注意数组的下标从0开始
// 左孩子是2 * i + 1,右孩子是2 * i + 2
// 父亲节点是(i - 1) / 2
// 注意(0 - 1) >> 1 = -1
// while (arr[index] > arr[(index -1) >> 1]) {
// swap(arr, index, (index -1) >> 1);
// index = (index -1) >> 1;
// }
// (0 - 1) / 2 = 0
while (arr[index] > arr[(index - 1) / 2]) {
swap(arr, index, (index - 1) /2);
index = (index - 1)/2 ;
}
}
/**
* 调整
* 时间复杂度为O(N*logN)
* @param arr
* @param index
* @param size
*/
public static void heapify(int[] arr, int index, int size) {
// index的左孩子
int left = index * 2 + 1;
while (left < size) {
// 左孩子和右孩子中比较大小,得到大的下标
int bigIndex = (left + 1) < size && arr[left] < arr[left + 1] ? left + 1 : left;
// 孩子中大的与index相比较大小,得到最大的下标
bigIndex = arr[bigIndex] > arr[index] ? bigIndex : index;
// index为最大值,不需要调整,跳出循环
if (bigIndex == index) {
break;
}
swap(arr, index, bigIndex);
index = bigIndex;
left = index * 2 + 1;
}
}
/**
* 数组异或交换
* 当输入的i和j相同时,交换结果均为0
* @param arr
* @param i
* @param j
*/
public static void swap(int[] arr, int i, int j) {
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
/**
* 数组辅助交换
* @param arr
* @param i
* @param j
*/
public static void swap2(int[] arr,int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
/**
* 对近乎有序的数组进行排序,近乎有序是,如果排好序的话,指每个元素移动的距离不超过k
* @param arr
* @param k
*/
public static void sortedArrDistanceLessK(int[] arr, int k) {
// PriorityQueue类提供堆数据结构的功能。默认是升序排列
PriorityQueue<Integer> heap = new PriorityQueue<>();
int index = 0;
// 将前k个放入堆中,排列有序
for (; index < Math.min(arr.length, k); index++) {
heap.add(arr[index]);
}
int i = 0;
// 依次添加数组元素和将队首输出,保持堆中个数为k
for (; index < arr.length; i++, index++) {
heap.add(arr[index]);
arr[i] = heap.poll();
}
// 将队列中最后k个依次输出
while (!heap.isEmpty()) {
arr[i++] = heap.poll();
}
}
/**
* 荷兰国旗问题
* 给定一个数组arr,和一个数num,
* 请把小于num的数放在数组的左边,
* 等于num的数放在数组的中间,
* 大于num的数放在数组的右边。
* 要求额外空间复杂度O(1),时间复杂度O(N)
* @param arr
* @param left
* @param right
* @param num
* @return
*/
public static int[] netherlandsFlag(int[] arr, int left, int right, int num) {
// less指向小于num的下标
int less = left - 1;
// more指向大于num的下标
int more = right + 1;
while (left < more) {
if (arr[left] < num) {
swap2(arr, ++less, left++);
} else if (arr[left] > num) {
swap2(arr, --more, left);
} else {
left++;
}
}
// 返回的是等于num值得数组下标区间
return new int[] {less + 1, more -1};
}
/**
* 快速排序:对荷兰国旗问题的再应用,将数组最后一个值作为划分值
* 划分值越靠近两侧,复杂度越高;划分值越靠近中间,复杂度越低,不改进的快速排序时间复杂度为O(N^2)
* 改进的快排就是等概率随机选取一个值作为划分值
* @param arr
* @param left
* @param right
*/
public static void quickSort(int[] arr, int left, int right) {
if (arr == null || arr.length < 2) {
return;
}
if (left < right) {
// 将某个值与最后的值做交换,等概率随机选取一个值作为划分值
swap2(arr, left + (int) (Math.random() * (right - left + 1)), right);
// 划分为3部分
int[] p = partition(arr, left, right);
// 递归调用
quickSort(arr, left, p[0] - 1);
quickSort(arr, p[1] + 1, right);
}
}
public static int[] partition(int[] arr, int left, int right) {
int less = left - 1;
int more = right;
while (left < more) {
if (arr[left] < arr[right]) {
swap2(arr, ++less, left++);
} else if (arr[left] > arr[right]) {
swap2(arr, -- more, left);
}
else {
left++;
}
}
swap2(arr, more, right);
// 注意这里将最后的值作为划分值,进行交换后,返回的p[1]应该为more,而不是more-1
return new int[] {less + 1, more};
}
public static void main(String[] args) {
// 归并排序
int[] arr1 = {45, 4563, 14512, 222, 225, 24, 215, 45, 56315, 4534};
mergeSort(arr1, 0, arr1.length - 1);
for (int i = 0; i < arr1.length; i++) {
System.out.println(arr1[i]);
}
// 小和问题
int[] arr2 = {1, 3, 4, 2, 5};
int small = smallSum(arr2, 0, arr2.length - 1);
System.out.println(small);
// 堆排序
int[] arr3 = {45, 4563, 14512, 222, 225, 24, 215, 45, 56315, 4534};
heapSort(arr3);
for (int i = 0; i < arr3.length; i++) {
System.out.println(arr3[i]);
}
// 注意:(0 - 1) / 2 = 0
System.out.println((0-1)/2);
// 注意:(0 - 1) >> 1 = -1
System.out.println((0-1)>> 1);
// 堆排序扩展
int[] arr4 = {1, 3, 2, 4, 5, 6, 8, 7, 10, 9};
sortedArrDistanceLessK(arr4, 2);
for (int i = 0; i < arr4.length; i++) {
System.out.println(arr4[i]);
}
// 荷兰国旗问题
int[] arr5 = {0, 2, 0, 0, 2, 2, 1, 2, 1, 0};
int[] partition = netherlandsFlag(arr5, 0, arr5.length - 1, 1);
for (int i = 0; i < arr5.length; i++) {
System.out.println(arr5[i]);
}
for (int i = 0; i < partition.length; i++) {
System.out.println(partition[i]);
}
// 快排
int[] arr6 = {45, 4563, 14512, 222, 225, 24, 215, 45, 56315, 4534};
quickSort(arr6, 0, arr6.length -1);
for (int i = 0; i < arr6.length; i++) {
System.out.println(arr6[i]);
}
}
}