大家好呀!我是小笙,本节是我对排序算法的一个总结
各种排序算法
稳定性:一组数列在排序的时候,相同的值的相对位置是否发生变化
选择排序
概述:将某一个数列中最大的值或者最小的值与该数列的第一个进行交换,然后缩小数列范围(不包括第一个)依次反复则可以排好序
时间复杂度:o(n^2)
空间复杂度:o(1)
public class SelectionSort {
public static void main(String[] args) {
int[]nums = new int[]{1,8,67,3,5};
sort(nums);
System.out.println(Arrays.toString(nums));
}
/**
* 选择排序 升序排序
* @param nums 数组
*/
public static void sort(int[] nums){
int len = nums.length;
if(nums == null || len < 2){
return;
}else{
for (int i = 0; i < len-1; i++) {
int minIndex = i;
for (int j = i+1; j < len; j++) {
minIndex = nums[j] < nums[minIndex]? j:minIndex;
}
int temp = nums[minIndex];
nums[minIndex] = nums[i];
nums[i] = temp;
}
}
}
}
冒泡排序
概述:类似与泡泡浮出水面,比如将最大值的值向右端浮动,浮动过程中出现左边的值大于右边的值,则需要进行交换,确保最大值最后落在最右端,依次反复
时间复杂度:o(n^2)
空间复杂度:o(1)
public class BubbleSort {
public static void main(String[] args) {
int[]nums = new int[]{1,8,67,3,5};
sort(nums);
System.out.println(Arrays.toString(nums));
}
/**
* 冒泡排序 升序排序
* @param nums 数组
* 可以进行优化: 如果内循环一次发现没有发生交换操作,则可以认为已经排好序并跳出循环
*/
public static void sort(int[] nums){
int len = nums.length;
if(nums == null || len < 3){
return;
}else{
for (int i = len-1; i >= 0; i--) {
for (int j = 0; j < i; j++) {
if(nums[j] > nums[j+1]){
int temp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = temp;
}
}
}
}
}
}
插入排序
概述:实现很像冒泡排序,注意两次循环方向,插入排序是同向的,冒泡是反向的,插入无非就是从第二数开始,插入到前面的数列中也能保持有序,在数组上的实现也就只能是层层交换,但是如果是链表可能就更好理解什么是插入概念?
时间复杂度:o(n^2)
空间复杂度:o(1)
public class InsertSort {
public static void main(String[] args) {
int[]nums = new int[]{1,8,67,3,5};
sort(nums);
System.out.println(Arrays.toString(nums));
}
/**
* 直接插入排序 升序
* @param nums 数组
*/
public static void sort(int[] nums){
int len = nums.length;
if(nums == null || len < 2){
return;
}else{
for (int i = 1; i < len; i++) {
// 注意:看起来像冒泡,但是因为数组插入的时候, ,所以通过比较前移来实现也是一样的效果
for(int j = i;j >= 0 && nums[j] < nums[j-1];j--){
int temp = nums[j];
nums[j] = nums[j-1];
nums[j-1] = temp;
}
}
}
}
}
希尔排序
希尔排序实质上是一种分组插入方法,它的基本思想是: 对于n个待排序的数列,取一个小于n的整数step(step被称为步长)将待排序元素分成若干个组子序列,所有距离为step的倍数的记录放在同一个组中;然后,对各组内的元素进行直接插入排序。 这一趟排序完成之后,每一个组的元素都是有序的。然后减小step的值,并重复执行上述的分组和排序。重复这样的操作,当step=1时,整个数列就是有序的
时间复杂度:希尔排序的时间复杂度与增量(即,步长step的选取有关。例如,当增量为1时,希尔排序退化成了直接插入排序,此时的时间复杂度为o(N²),而Hibbard增量的希尔排序的时间复杂度为o(N3/2)
空间复杂度:o(1)
public class ShellSort {
// 测试
public static void main(String[] args) {
int a[] = {80,30,60,40,20,10,50,70};
System.out.println(Arrays.toString(a));
shellSort(a, a.length);
System.out.println(Arrays.toString(a));
}
/**
* 希尔排序
* @param a 待排序的数组
* @param n 数组的长度
*/
public static void shellSort(int[] a, int n) {
// gap为步长,每次减为原来的一半。
for (int step = n / 2; step > 0; step /= 2) {
// 共gap个组,对每一组都执行直接插入排序
for (int i = 0 ;i < step; i++)
groupSort(a, n, i, step);
}
}
/**
* 对希尔排序中的单个组进行排序
* @param a 待排序的数组
* @param n 数组总的长度
* @param i 组的起始位置
* @param step 组的步长
*/
public static void groupSort(int[] a, int n, int i,int step) {
for (int j = i + step; j < n; j += step) {
// 如果a[j] < a[j-step],则寻找a[j]位置,并将后面数据的位置都后移。
if (a[j] < a[j - step]) {
int tmp = a[j];
int k = j - step;
while (k >= 0 && a[k] > tmp) {
a[k + step] = a[k];
k -= step;
}
a[k + step] = tmp;
}
}
}
}
归并排序
概述:分成2部分,分别排好序,在通过比较大小归并到统一的数组中
时间复杂度: o(nlogn)
空间复杂度:o(n)
// 思路
// 1.整体就是一个简单递归,左边排好序、右边排好序、让其整体有序
// 2.让其整体有序的过程里用了外排序方法
// 3. 利用master公式来求解时间复杂度
public class MergeSort {
public static void main(String[] args) {
int[]nums = new int[]{1,8,67,3,5,3,4,56,67};
// 可以选择数组的一段进行排序
sort(nums,4,nums.length-1);
System.out.println(Arrays.toString(nums));
}
/**
* 归并算法(升序排序)
* 递归算法(分治)
*/
public static void sort(int[] nums,int L,int R){
if(L == R){
return;
}else{
// 中点位置
int mid = L + ((R-L) >> 1);
sort(nums,L,mid);
sort(nums,mid+1,R);
merge(nums,L,mid,R);
}
}
/**
* 归并数据
*/
public static void merge(int[] nums,int L,int M,int R){
int[] arr = new int[R-L+1];
// 数组的下标
int index = 0;
// L ~ M 的数组下标
int p0 = L;
// M+1 ~ R 的数组下标
int p1 = M + 1;
while(p0 <= M && p1 <= R){
arr[index++] = nums[p0] > nums[p1]?nums[p1++]:nums[p0++];
}
while(p0 <= M){
arr[index++] = nums[p0++];
}
while(p1 <= R){
arr[index++] = nums[p1++];
}
for (int i = 0; i < R-L+1; i++) {
nums[L+i] = arr[i];
}
}
}
求最小数和
题目理解:就是遍历数组,依次累积左侧小于当前位置的数字
public class SmallSum {
/**
* 对等数法来测试归并方法的正确性
*/
public static void main(String[] args) {
int testNum = 5000000;
while(testNum-- >= 0){
int[] nums = random(10,10);
if(SimpleSum(nums,0,nums.length-1) != mergeSort(nums,0,nums.length-1)){
System.out.println("sorry,test error!");
return;
}
System.out.println("测试" + (5000000 - testNum) + "组数");
}
System.out.println("right,you are great!");
}
/**
* 暴力解法
*/
public static int SimpleSum(int[] nums,int L,int R){
int sum = 0;
for (int i = L; i < R; i++) {
for (int j = i+1; j <= R; j++) {
if(nums[j] > nums[i]){
sum += nums[i];
}
}
}
return sum;
}
/**
* 归并排序的过程计算最小和
*/
public static int mergeSort(int[] nums,int L,int R){
if(L == R){
return 0;
}else{
int mid = L + ((R-L) >> 1);
return mergeSort(nums,L,mid) + mergeSort(nums,mid+1,R) + merge(nums,L,mid,R);
}
}
/**
* 归并数据
*/
public static int merge(int[] nums,int L,int M,int R){
int[] arr = new int[R-L+1];
int index = 0;
int p0 = L;
int p1 = M+1;
// 归并数据时候求最小和
int sum = 0;
while(p0 <= M && p1 <= R){
if(nums[p1] <= nums[p0]){
arr[index++] = nums[p1++];
}else{
sum += nums[p0]*(R-p1+1);
arr[index++] = nums[p0++];
}
}
while(p0 <= M){
arr[index++] = nums[p0++];
}
while(p1 <= R){
arr[index++] = nums[p1++];
}
for (int i = 0; i < arr.length; i++) {
nums[i+L] = arr[i];
}
return sum;
}
/**
* 随机生成 size 个 1 ~ value 值的数组
* @param size 数组长度
* @param value 数组值
*/
public static int[] random(int size,int value){
int[] nums = new int[size];
for (int i = 0; i < size-1; i++) {
nums[i] = (int)(Math.random()*value + 1);
}
return nums;
}
}
数组的逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数
举例:
输入: [7,5,6,4]
输出: 5
解释:逆序对 [7,5] [7,6] [7,4] [5,4] [6,4] 五对
通过归并的形式解决
class Solution {
public int reversePairs(int[] nums) {
if(nums.length < 2){
return 0;
}
return mergeSort(nums,0,nums.length-1);
}
public static int mergeSort(int[] nums,int L,int R){
if(L == R){
return 0;
}else{
int mid = L + ((R-L) >> 1);
return mergeSort(nums,L,mid) + mergeSort(nums,mid+1,R) + merge(nums,L,mid,R);
}
}
public static int merge(int[] nums,int L,int M,int R){
int[] arr = new int[R-L+1];
int index = 0;
int p0 = L;
int p1 = M+1;
int count = 0;
while(p0 <= M && p1 <= R){
if(nums[p1] < nums[p0]){
count += R-p1+1;
System.out.println(count);
arr[index++] = nums[p0++];
}else{
arr[index++] = nums[p1++];
}
}
while(p0 <= M){
arr[index++] = nums[p0++];
}
while(p1 <= R){
arr[index++] = nums[p1++];
}
for (int i = 0; i < arr.length; i++) {
nums[i+L] = arr[i];
}
return count;
}
}
快速排序
快排 1.0 :根据最后数字划分两个范围 >= < 时间复杂度:o(n^2)
快排 2.0 :根据最后数字划分三个范围 > = < 时间复杂度:o(n^2)
快排 3.0 :随机选择一个数组中的数字和最后一个数进行交换来划分三个范围,根据概率运算得到时间复杂度: o(nlogn)
空间复杂度:o(logn)
public class QuickSort {
public static void main(String[] args) {
int[]nums = new int[]{1,8,67,3,5};
sort(nums,0,nums.length-1);
System.out.println(Arrays.toString(nums));
}
/**
* 快速排序
*/
public static void sort(int[] nums,int L,int R){
// 不能等于 == ,可能存在 L 小于 R 的情况
if(L >= R){
return;
}else{
// 随机数,并且与最后一个数交换
int rand = L + (int)(Math.random()*(R-L+1));
int temp = nums[rand];
nums[rand] = nums[R];
nums[R] = temp;
// 分别划分值的左右范围
int[] partition = partition(nums, L, R);
sort(nums,L,partition[0]-1);
sort(nums,partition[0]+1,R);
}
}
/**
* 划分三等份
*/
public static int[] partition(int[] nums,int L,int R){
int less = L-1;
int more = R;
while(L < more){
if(nums[L] < nums[R]){
less++;
int temp = nums[less];
nums[less] = nums[L];
nums[L] = temp;
L++;
}else if(nums[L] > nums[R]){
more--;
int temp = nums[more];
nums[more] = nums[L];
nums[L] = temp;
}else{
L++;
}
}
int temp = nums[more];
nums[more] = nums[R];
nums[R] = temp;
return new int[]{less + 1,more};
}
}
堆排序
堆结构就是用数组实现的完全二叉树结构
完全二叉树中如果每棵子树的最大值都在顶部就是大根堆
完全二叉树中如果每棵子树的最小值都在顶部就是小根堆
大根堆排序
public class HeapSort {
public static void main(String[] args) {
int[]nums = new int[]{1,6,67,3,5,4,3,2,76};
// // 形成大根堆
for (int i = 0; i < nums.length; i++) {
heapInsert(nums,i);
}
// 堆排序
int size = nums.length;
while(size > 0){
swap(nums,0,--size);
heapify(nums,0,size);
}
System.out.println(Arrays.toString(nums));
}
/**
* 插入数据形成堆
*/
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;
}
}
/**
* 取出最大值
* 堆化(大根堆)
* @param index 父节点的索引
* @param size 堆的总长度
*/
public static void heapify(int[] nums,int index,int size){
int L = index * 2 + 1;
// 判断是否有子节点
while(L < size){
// 比较左右子节点的大小(首先判断是否有右节点,有的话再进行比较)记录索引
int max = L+1 < size && nums[L+1] > nums[L]? L+1:L;
// 父节点和其中一个比较大的子节点进行比较
max = nums[index] > nums[max]?index:max;
// 如果父节点最大,则停止循环
if(max == index){
break;
}else{
swap(nums,index,max);
index = max;
L = index * 2 + 1;
}
}
}
/**
* 交换对应索引下数组的值
*/
public static void swap(int[] nums,int index1,int index2){
int temp = nums[index1];
nums[index1] = nums[index2];
nums[index2] = temp;
}
}
堆结构的heapInsert.与heapify操作 堆结构的增大和减少 优先级队列结构,就是堆结构