排序算法是算法最最最基础的内容,希望自己的基础可以扎实
package com.company;
import java.util.Arrays;
public class Sort {
/**
* 归并排序
* 为什么可以O(nlogn),因为所有的比较都没有被浪费,但冒泡或者其他排序就会浪费很多比较的过程
*
* @param arr
* @param L
* @param R
*/
public static void process(int[] arr, int L, int R) {
if (L == R) {
return;
}
int M = L + ((R - L) >> 1);
// 左侧排好
process(arr, L, M);
// 右侧排好
process(arr, M + 1, R);
// 然后最终合并到一起?这里没有需要mid
merge(arr, L, M, R);
}
public static void merge(int[] nums, int L, int M, int R) {
// 辅助空间(外排序)
int[] help = new int[R - L + 1];
// 给help使用的,用来填数字
int i = 0;
// 用来指示数组下标
int p1 = L;
int p2 = M + 1;
//进入循环的前提条件是不发生越界
while (p1 <= M && p2 <= R) {
// 这个写法真的太精妙了,其实就是用两个数组不断拷贝的过程
help[i++] = nums[p1] <= nums[p2] ? nums[p1++] : nums[p2++];
}// 然后最终会有一个先越界;就看谁先耗完,谁没越界,谁就吧自己的数目全部拷贝到help里面
while (p1 <= M) {
help[i++] = nums[p1++];
}
while (p2 <= R) {
help[i++] = nums[p2++];
}
// 把help拷贝到arr;
for (i = 0; i < help.length; i++) {
nums[L + i] = help[i];
}
}
/**
* 求小和,数组每个数左边比她小的之和/右边多少个数比它大
*/
public static int littleSum(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return process2(arr, 0, arr.length - 1);
}
// 既要排序,又要求小和
public static int process2(int[] nums, int l, int r) {
// 首先把不需要求小和的情况写下来,就是数组长度为0,为什么不能用nums?其实我觉得可以
// 还是要注重模块化实现,就是说每个方法仅仅完成一小部分的功能
if (l == r) {
return 0;
}
int m = l + ((r - l) >> 1);
return (// 左侧产生的小和
process2(nums, l, m)
// 右侧产生小和
+ process2(nums, m + 1, r)
// 合并产生的小和
+ merge2(nums, l, m, r));
}
// 什么时候作为参数传入呢?是不是因为前面写了m,所以后面就可以直接作为参数传入?
public static int merge2(int[] nums, int l, int m, int r) {
// 辅助空间(外排序)
int[] help = new int[r - l + 1];
// 指针指向help数组下标
int i = 0;
int p1 = l;
int p2 = m + 1;
// 储存结果集
int littleSum = 0;
// 都不越界的时候
while (p1 <= m && p2 <= r) {
// 只有左组比右小,才能增加小和;增加的是当前右组比p1大的数目,它乘以当前左组的数就是小和增加的量。
littleSum += nums[p1] < nums[p2] ? (r - p2 + 1) * nums[p1] : 0;
// 右组大于等于左组,先拷贝右组
help[i++] = nums[p1] < nums[p2] ? nums[p1++] : nums[p2++];
while (p1 <= m) {
help[i++] = nums[p1++];
}
while (p2 <= r) {
help[i++] = nums[p2++];
}
for (i = 0; i < help.length; i++) {
nums[l + 1] = help[i];
}
}
return littleSum;
}
/**
* 求逆序对(这个确实考很多)
*/
/**
* 选择排序
*
* @param nums
*/
public static void selectSort(int[] nums) {
// 剔除不需要排序的方法
if (nums == null || nums.length < 2) {
return;
}
for (int i = 0; i < nums.length - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < nums.length; j++) {
if (nums[j] < nums[minIndex]) {
minIndex = j;
}
}
swap(nums, i, minIndex);
}
}
/**
* 数组中有一个数出现奇数次,其他都出现偶数次,请问怎么找到这个数?
*/
public static void yihuo(int[] nums, int a, int b) {
int eor = 0;
for (int i = 0; i < nums.length; i++) {
eor = eor ^ nums[i];
// 最后的eor就是出现了奇数次,因为剩下的两个一模一样的都被异或掉了
}
}
/**
* 数组中有2个数出现奇数次,其他都出现偶数次,请问怎么找到这个数?
*/
public static void yihuo2(int[] nums) {
//设目标数字为ab,其他都为偶数次
int eor = 0;
for (int num : nums) {
eor ^= num;
// 最后的eor就是a^b;且a!=b,且他们在某一位的数字是不一样的
}
int lastOne = eor & (~eor + 1);
int rightOne = 0;
for (int num : nums) {
if ((lastOne & num) == 0) {
// 这个地方还是不能直接让num==rightOne;这是因为符合条件的num不止一个?
rightOne ^= num;
}
}
int anotherOne = eor ^ rightOne;
System.out.println(rightOne);
System.out.println(anotherOne);
}
/**
* 插入排序
*
* @param nums
*/
public static void insert(int[] nums) {
// 想让从0-i上面是有序的
for (int i = 0; i < nums.length; i++) {
// J>=0,代表它不越界;nums[j]>nums[j+1] 代表需要交换位置(妙啊)
for (int j = i - 1; j >= 0 && nums[j] > nums[j + 1]; j--) {
swap(nums, nums[j], nums[j + 1]);
}
}
}
public static void swap(int[] nums, int a, int b) {
// 异或:相同为0;不同为1的运算;也可以理解为无进位相加
// 同一批数异或结果一定是一样的,哪怕顺序都不一样
// 使用异或就不需要交换
// 能这么干的前提是,ab在内存中属于不同的区域,也就是a位置必须不等于b位置,不然这个位置上面的数字就等于0了
// 平时就好好的按照正常的写,来个temp变量,因为以自己的水平还不能保证a和b永远都不一样
int temp = nums[a];
nums[a] = nums[b];
nums[b] = temp;
}
/**
* getMax 使用递归的方法求数组最大值
*/
public static int getMax(int[] nums) {
return process4(nums, 0, nums.length - 1);
}
public static int process4(int[] nums, int l, int r) {
int mid = l + ((r - l) << 1);
int left = process4(nums, l, mid);
int right = process4(nums, mid + 1, r);
return Math.max(left, right);
}
/**
* quickSort,认为最右边的是number,<=number放左边,>放右边
* 荷兰国旗的递归版本
* 快排最差情况的原因:划分值打在最差的地方;打在中间,时间复杂度最好;因此必须随机选择这个target
*/
public static void quickSort(int[] nums) {
if (nums == null || nums.length < 2) {
return;
}
quickSort(nums, 0, nums.length - 1);
}
public static void quickSort(int[] nums, int l, int r) {
// 递归开始条件
if (l < r) {
swap(nums, l + (int) Math.random() * (r - l + 1), r);
// 自己思考一下
int[] partition = partition(nums, l, r);
quickSort(nums, l, partition[0]);
quickSort(nums, partition[1] + 1, r);
}
}
public static int[] partition(int[] nums, int l, int r) {
int lpivot = l - 1;
int rpivot = r;
// 还有数据没有排序
while (l < rpivot) {
if (nums[l] < nums[r]) {
swap(nums, ++lpivot, l++);
} else if (nums[l] > nums[r]) {
swap(nums, rpivot--, l);
} else {
l++;
}
}
swap(nums, rpivot, r);
return new int[]{lpivot + 1, rpivot};
}
/**
* 堆排序,堆结构比较重要;堆在逻辑概念上是完全二叉树的结构;
* 完全二叉树的定义:不满的话从左到右填满
* 把堆填满然后删除为0;就是排序的过程
*/
public static void HeapSort(int[] nums) {
if (nums == null || nums.length < 2) {
return;
}
// 把数组搞成大根堆
for (int i = 0; i < nums.length; i++) {
heapInsert(nums, i);
}
int heapSize = nums.length;
swap(nums, 0, --heapSize);
while (heapSize > 0) {
heapify(nums, 0, heapSize);
swap(nums, 0, --heapSize);
}
}
public static void heapInsert(int[] nums, int index) {
while (nums[index] > nums[(index - 1) / 2]) {
swap(nums, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
public static void heapify(int[] nums, int index, int heapSize) {
int left = index * 2 + 1;
// 说明我有孩子,判断左孩子有没有越界;因为先填充左再填充右,左孩子的下标比右孩子小,所以只要有左孩子就有孩子
while (left < heapSize) {
// left+1 右孩子下标,<heapSize 证明它存在;
// 两个孩子谁的下标最大,把它赋值给largest
int largest = left + 1 < heapSize && nums[left + 1] > nums[left] ? left + 1 : left;
// 父亲和孩子谁的值大,谁把自己的下标给largest
largest = nums[largest] > nums[index] ? largest : index;
if (largest == index) {
break;
}
swap(nums, largest, index);
index = largest;
left = index * 2 + 1;
}
}
public static void main(String[] args) {
// 和数据量无关的就是常数操作
// 时间复杂度,其实自己也不是特别的熟悉;一般先看指标,如果指标一样就通过实践判
// 如果指标一样就通过实践判
int[] nums = new int[]{3, 1, 2, 9, 7, 4};
HeapSort(nums);
System.out.println(Arrays.toString(nums));
}
}