文章目录
1、冒泡排序
/**
* @Description:
* @ClassName algorithm
* @Author: 王瑞文
* @Date: 2021/3/24 19:31
*/
public class Test {
public static void main(String[] args) {
int arr[] = {1, 2, 3, 4, 5, 12, 32};
bubbleSort(arr);
for (int i : arr) {
System.out.print(i+" ");
}
}
//冒泡排序
public static void bubbleSort(int[] arr) {
for (int end = arr.length; end > 0; end--) {
boolean isSort = true;//优化的 === 前提是如果已经排好序的
for (int begin = 1; begin < end; begin++) {
if (arr[begin - 1] > arr[begin]) {
isSort = false;
int tem = arr[begin];
arr[begin] = arr[begin - 1];
arr[begin - 1] = tem;
}
}
if (isSort)
break;
}
}
}
优化
-
如果序列已经完全有序,可以提前终止冒泡排序
**第一种优化:**在第一个For循环中定义一个boolean值为true,在第一轮中 如果发生交换就把布尔值设置为false。等里边那层循环完了就知道是不是有序的了。上边有代码 -
如果序列尾部已经局部排序,可以记录最后一次交换的位置,减少比较次数
第二种优化: 记录最后一次的交换位置,减少比较次数
//冒泡排序
public static void bubbleSort(int[] arr) {
for (int end = arr.length; end > 0; end--) {
int sortIndex = 0;
for (int begin = 1; begin < end; begin++) {
if (arr[begin - 1] > arr[begin]) {
int tem = arr[begin];
arr[begin] = arr[begin - 1];
arr[begin - 1] = tem;
sortIndex = begin;
}
}
end = sortIndex;
}
}
复杂度
时间复杂度:
- 最坏(完全乱序)==(1+2+3+4+…+n-1)
- O(n²)
- 最好时间复杂度 扫描一次就发现了是拍好顺序的
- O(n)
空间复杂度:
- 没有用到别的堆或者递归调用
- O(1)
稳定性:
- 如果相等的2个元素,在排序前后的相对位置保持不变,那么这是稳定的排序算法
冒泡排序是稳定的且是原地算法
2、选择排序
从序列中找出最大的元素,然后与最末尾的元素交换位置
执行完一轮,最后一个位置的就是最大元素
排除最后一个位置继续找
package easy.排序.选择排序.简单的选择排序;
/**
* @Description:
* @ClassName algorithm
* @Author: 王瑞文
* @Date: 2021/3/24 22:19
*/
public class Test {
public static void main(String[] args) {
int arr[] = {1, 2, 3, 4, 12, 32,8,20,10};
for (int end = arr.length - 1; end > 0; end--) {
int maxIndex = 0;
for (int begin = 1; begin <= end; begin++) {
if (arr[maxIndex] < arr[begin]) {//compare 比较
maxIndex = begin;
}
}
// 交换位置
int temp = arr[maxIndex];
arr[maxIndex] = arr[end];
arr[end] = temp;
}
for (int i : arr) {
System.out.print(i+" ");
}
}
}
优化
可以使用堆来进行找最大值
堆的时间复杂度是O(log(n))
这样优化下来外边加一层循环时间复杂度就是O(nlog(n))
复杂度
时间复杂度:
- O(n²)
- 最好、最坏、都是O(n²)
空间复杂度:
- O(1)
属于稳定排序
选择排序交换次数要远远少于冒泡排序,平均性能优于冒泡排序
3、堆排序
堆排序可以认为是对选择排序的优化
在挑最大值的时候时间复杂度是O(log(n))
流程:
- 对数据进行原地建堆(heapify)
- 重复一下操作,直到堆的元素数量为1
- 交换堆顶元素与尾元素
- 堆元素数量减一
- 对0位置进行过一次siftDown操作
Top K 问题
- 什么是Top K问题
- 从海量数据中找出前K个数据
比如:从100万个整数中找出最大的100个整数
- Top K 问题的解法之一:可以用数据结构"堆来解决"
二叉堆
- 如果任意节点的值总是≥子节点的值,称为:最大堆、大根堆、大顶堆
- 如果任意节点的值总是≤子节点的值,称为:最小堆、小根堆、小顶堆
一般使用数组实现二叉堆
用数组实现它索引的规律:(n为元素数量,i为数组索引)
- 如果i=0,它是根节点
- 如果i>0,它父节点的编号为floor((i-1)/2)
- 如果2i+1≤n-1,它的左子节点编号为2i+1
- 如果2i+1>n-1,它无左子节点
- 如果2i+2≤n-1,它的右子节点编号为2i+2
- 如果2i+2>n-1,它无右子节点
那么如何建立一个二叉堆呢?
以数组的形式 主要就是在添加的时候就进行上滤操作去排号数组顺序那么接下来着重说一下添加八
添加
@Override
public void add(E element) {
elementNullCheck(element);//检查添加元素是否为空
ensureCapacity(size + 1); //数组是否需要扩容
elements[size++] = element;// 还需要检测是否满足最大/最小堆的特性
siftUp(size - 1);//最后于一个元素上滤 ** 主要是这个方法
}
//上滤
private void siftUp(int node) {
E element = elements[node];
while (node > 0) {
int parentIndex = (node - 1) >> 1;
E parent = elements[parentIndex];
//小于父节点位置合适,直接退出
if (compare(parent, element) >= 0) break;
//调换位置
elements[node] = parent;
node = parentIndex;
}
elements[node] = element;
}
删除
批量建堆
-
自上而下的上虑(除了根节点都下虑) O(nlog(n))
-
自下而上的下虑 (一半节点下虑) --效率高 O(n)
4、插入排序
package easy.排序.插入排序.简单插入排序;
/**
* @Description:
* @ClassName algorithm
* @Author: 王瑞文
* @Date: 2021/3/5 23:06
*/
public class Test {
static void insert(int[] nums) {
System.out.println("排序前:");
for (int num : nums) {
System.out.print(num + " ");
}
System.out.println("");
for (int begin = 1; begin < nums.length; begin++) {
int n = begin;
while (n > 0 && nums[n] < nums[n - 1]) {
int temp = nums[n];
nums[n] = nums[n - 1];
nums[n - 1] = temp;
n--;
}
}
System.out.println("排序后:");
for (int num : nums) {
System.out.print(num + " ");
}
}
public static void main(String[] args) {
insert(new int[]{2, 5, 6, 8, 1, 786, 54, 7, 27, 3, 556, 37});
}
}
时间复杂度
逆序对:
数组<2,3,8,6,1>的逆序对为:<2,1> 、< 3,1> <8,6> <8,1> <6,1>
插入排序的时间复杂度与逆序对的数量成正比
逆序对的数量越多,时间复杂度越高
如果一个逆序对达到最大说明数组时降序的,插入排序时间复杂度非常高!O(n²)
最坏情况,完全降序下就是O(n²)
最好情况,完全升序下就是O(n)
空间复杂度
O(1)
5、归并排序
执行流程
- 不断的将当前序列平均分割成两个子序列直到不能分割
- 不断地将2个子序列合并成一个有序序列
时间复杂度
归并排序花费时间:
T(n)=T(n/2)+T(n/2)+O(n)=O(X) 求X ==>T(n)=2*T(n/2)+O(n)
两边都除n
T(n)/n=T(n/2)(n/2)+O(1)
一系列操作之后可以算出来复杂度为O(nlogn)
package easy.排序.归并排序;
import utils.RandomArrays;
/**
* @Description:
* @ClassName algorithm
* @Author: 王瑞文
* @Date: 2021/3/27 20:37
*/
public class Test {
private static Integer array[] = RandomArrays.getRandomArray(10, 10, 200);
private static Integer leftArray[];
public static void sort() {
leftArray = new Integer[array.length >> 1];
sort(0, array.length);
}
/**
* 对begin到end的范围内的数据进行排序 [begin,end)
*
* @param begin
* @param end
*/
public static void sort(int begin, int end) {
if (end - begin < 2) return;
int mid = (begin + end) >> 1;
sort(begin, mid); //耗时 Tn(/2)
sort(mid, end);//耗时 Tn(/2)
merge(begin, mid, end);
}
/**
* 将[begin,mid)和[mid,end)范围的序列合并成一个有序序列
*
* @param begin
* @param mid
* @param end
*/
public static void merge(int begin, int mid, int end) {
int li = 0, le = mid - begin;//li是左边数组的begin le是左边数组的end
int ri = mid, re = end;//ri是右边数组的begin,re是右边数组的end
int ai = begin;//标识覆盖的位置
/**
* 备份左边数组
*/
for (int i = li; i < le; i++) {
leftArray[i] = array[begin + i];//这个并不一定是 0开始的
}
while (li < le) {//如果左边没有结束
if (ri < re && array[ri] < leftArray[li]) {//左边大
array[ai++] = array[ri++];
} else {
array[ai++] = leftArray[li++];//右边大
}
}
}
public static void main(String[] args) {
sort();
System.out.println("排序后:");
for (Integer integer : array) {
System.out.print(+integer + " ");
}
}
}
6、快速排序
- 主要是从序列中找轴点元素(pivot)
- 利用pivot将序列分割成两个子序列
- 对子序列继续进行上述操作直到子序列剩下一个元素
时间复杂度
- 每次轴点都是中间位置的情况下是最好的情况 T(n)=T(n/2)+T(n/2)+O(n)
- O(nlogn)
- 如果轴点左右元素极度不均匀 T(n)=T(n-1)+O(n)
- O(n²)
- 为了降低最坏情况的出现概率,一般采取的做法是随机选择轴点元素,然后把选择的轴点元素和begin的位置交换一下就可以了
空间复杂度
- O(logn)
不稳定排序
package easy.排序.交换排序.快速排序;
import utils.RandomArrays;
/**
* @Description:
* @ClassName algorithm
* @Author: 王瑞文
* @Date: 2021/3/24 19:31
*/
public class Test {
private static Integer array[] = RandomArrays.getRandomArray(10, 100, 200);
/**
* 对[begin,end)范围内的元素进行快速排序-->轴点构造的一个过程
*
* @param begin
* @param end
*/
private static void sort(int begin, int end) {
if (end - begin < 2) return;
//确定轴点位置
int pivotIndex = pivotIndex(begin, end);
//对左右子序列也快速排序 O(n)
sort(begin, pivotIndex);
sort(pivotIndex + 1, end);
}
/**
* 确定[begin) 范围的轴点位置 -->找出轴点应该在哪。
*
* @param begin
* @param end
* @return 轴点元素的下标
*/
private static int pivotIndex(int begin, int end) {
//备份begin位置的元素
int pivot = array[begin];
end--; //将end指向最后一个元素
//左右交换扫面 从右边开始
while (begin < end) {
while (begin < end) {
if (array[end] - pivot> 0) {//end元素比轴点元素大
end--;
} else { //end元素比轴点元素小
array[begin++] = array[end];
break;
}
}
while (begin < end) {
if (array[begin] - pivot < 0) {//begin元素比轴点元素小
begin++;
} else {//begin元素比轴点元素大
array[end--] = array[begin];
break;
}
}
}
//放入轴点元素
array[begin] = pivot;
//执行完之后肯定begin=end 所以返回begin或者end都可以
return begin;
}
public static void main(String[] args) {
sort(0, array.length);
System.out.println("排序后:");
for (Integer integer : array) {
System.out.print(integer + " ");
}
}
}
7、希尔排序
按列进行排序
时间复杂度
最坏情况下是O(n²)
最好是O(n)
空间复杂度
O(1) 原地排序
不稳定
package easy.排序.插入排序.希尔排序;
import utils.RandomArrays;
import java.util.ArrayList;
import java.util.List;
/**
* @Description:
* @ClassName algorithm
* @Author: 王瑞文
* @Date: 2021/3/6 21:46
*/
public class Test {
private static Integer[] array = RandomArrays.getRandomArray(10, 100, 200);
private static List<Integer> step = sellStep(array.length);
public static void sort() {
for (Integer integer : step) {
sort(integer);
}
}
/**
* 默认用希尔德步长序列
*
* @return
*/
private static List<Integer> sellStep(int step) {
List<Integer> steps = new ArrayList<>();
//16-> 8 ,4,2,1
while ((step >>= 1) > 0) {
steps.add(step);
}
return steps;
}
/**
* 分成step列进行排序
*
* @param step
*/
public static void sort(int step) {
for (int col = 0; col < step; col++) {//col是列数 对第cole列进行排序
//插入排序
// col、clo+step、col+2*step、col+3*step 第一个不需要排序所以从clp+step开始
for (int begin = col + step; begin < array.length; begin += step) {
int n = begin;
while (n > col && array[begin] < array[begin - step]) {
int temp = array[n];
array[n] = array[n - step];
array[n - step] = temp;
n--;
}
}
}
}
public static void main(String[] args) {
sort();
System.out.print("排序后: ");
for (Integer integer : array) {
System.out.print(integer+" ");
}
}
}
一些非比较排序
1.计数排序(非比较)
统计每个整数再序列中出现的次数,进而推导出每个证书再有序序列中的索引
- 非比较排序是利用空间换时间,时间复杂度可以比O(nlogn)更低
时间复杂度
平均、最好、最坏都是O(n+k)
空间排序
O(n+k)
不稳定排序
2.基数排序(非比较)
- 依次对个位数、十位数、百位数、千位数…进行排序
个十百千位的取值范围都是固定的0~9,所以可以使用计数排序对他们进行排序
时间复杂度
O(d*(n+k)):d是最大值的位数,k是进制。属于稳定排序
空间复杂度
O(n+k)
10、桶排序(非比较)
- 创建一定数量的桶(比如用数组、链表作为桶)
- 按照一定规则,将序列中的元素均匀分配到对应的桶
- 分别对桶进行单独排序
- 将非空桶进行合并
了解