文章目录
前言
本人的算法菜到扣脚了,因为写LeeCode写不下去了,所以来学学算法基础
文章目录 |
---|
《算法(第4版)—— Java》 |
放上一个查看运行时间的万能算法模板先
public class DemoApplication {
public static void main(String[] args) {
// 开始时间
long startTime = System.currentTimeMillis();
// ...go go go
// 结束时间
long endTime = System.currentTimeMillis();
long runTime = (endTime - startTime) / 1000;
System.out.println("程序运行时长 = " + runTime + "秒");
}
}
一、程序所带来的运算复杂度
我认为主要是讲述在某些场景下的数据结构的一个效率问题,主要是从时间/空间复杂度展开,一般分为最好、最坏以及平均情况
以下摘录LeeCode《图解算法数据结构》—— Krahets
时间复杂度
根据从小到大排列,常见的算法时间复杂度主要有
O(1) < O(logN) < O(N) < O(NlogN) < O(N2) < O(2N) < O(N!)
简单来说,使用算法进行问题解决的时候,输入的数据量恒定为 N,我们要操作 y 次才能得到我们最终想要的结果
- 最佳情况 Ω(1) : nums = [7, a, b, c, …] ,即当数组首个数字为 77 时,无论 nums 有多少元素,线性查找的循环次数都为 11 次;
- 最差情况 O(N) : nums = [a, b, c, …] 且 nums 中所有数字都不为 77 ,此时线性查找会遍历整个数组,循环 NN 次;
- 平均情况 Θ : 需要考虑输入数据的分布情况,计算所有数据情况下的平均时间复杂度;例如本题目,需要考虑数组长度、数组元素的取值范围等;
O(1)
常数阶,无论变量是多大,程序产生结果的运行时间都跟输入的变量没有关系
public static int compute(int x){
int i = 1;
// ...各种霹雳帕拉的操作
return i;
}
O(logN)
对数阶,对数阶与指数阶相反,对数阶是 “每轮排除一半的情况” 。对数阶常出现于「二分法」、「分治」等算法中
O(N)
线性阶,循环的次数跟输入的变量成线性正比,输入的数据越大,程序运行的时间越久
public static int compute(int x) {
int count = 0;
for (int i = 0; i < x; i++) {
count++;
}
return count;
}
O(N log N)
线性对数,代码中出现两层循环相互独立
,第一层和第二层时间复杂度分别为O(logN) 和O(N) ,则总体时间复杂度为O(N log N) 。
O(N²)
平方阶,两层嵌套循环,输入的数据将导致结果运行时间是平方时
public static int compute(int x) {
int count = 0;
for (int i = 0; i < x; i++) {
for (int j = 0; j < x; j++) {
count++;
}
}
return count;
}
O(2^N)
指数阶,常见于递归
/**
* 递归模板
* @param x 输入数据
* @return 结果返回
*/
public static int compute(int x) {
// 实际执行的计算操作
x += 10;
// 递归条件判断
if (x < 100) {
return compute(x);
} else {
return x;
}
}
O(N!)
阶乘阶,也是递归,对应数学上常见的 “全排列”,即给定 NN 个互不重复的元素,求其所有可能的排列方案
总结
空间复杂度
空间复杂度涉及的空间类型有
- 输入空间: 存储输入数据所需的空间大小;初始数据大小
- 暂存空间: 算法运行过程中,存储所有中间变量和对象等数据所需的空间大小;数据计算时占用空间
- 输出空间: 算法运行返回时,存储输出数据所需的空间大小;最终结果大小
根据从小到大排列,常见的算法空间复杂度有
O(1) < O(logN) < O(N) < O(N2) < O(2N)
但是目前我们都是考虑以空间换取时间,因为目前的存储技术来说存储空间是非常充裕的,当然也是因为少不了各种压缩算法的横空出世。
二、排序
1 工具类sort方法
最简单粗暴的一种,数据之间的直接比较排序,如果是对象数据类型则需要通过实现 Comparable
或 Comparator
接口完成数据间的比较,实现后可以通过数组工具类或者集合工具类中的sort方法进行排序。
-
Comparable - 在数据对象中实现该接口,可在排序时通过该接口方法逻辑进行排序。
import lombok.AllArgsConstructor; import lombok.Data; import java.util.ArrayList; import java.util.Collections; import java.util.function.Consumer; /** * 算法学习 * @author 李家民 */ public class DemoApplication { public static void main(String[] args) { // 开始时间 long startTime = System.currentTimeMillis(); go(); // 结束时间 long endTime = System.currentTimeMillis(); long runTime = (endTime - startTime) / 1000; System.out.println("程序运行时长 = " + runTime + "秒"); } public static void go() { // 基本数据 ArrayList<Person> arrayList = new ArrayList<Person>(); arrayList.add(new Person("1", 11)); arrayList.add(new Person("4", 14)); arrayList.add(new Person("5", 15)); arrayList.add(new Person("2", 12)); arrayList.add(new Person("6", 16)); arrayList.add(new Person("3", 13)); // 排序 Collections.sort(arrayList); // 遍历 arrayList.forEach(new Consumer<Person>() { @Override public void accept(Person person) { System.out.println(person); } }); } } @Data @AllArgsConstructor class Person implements Comparable<Person> { private String name; private Integer age; @Override public int compareTo(Person o) { if (this.age.equals(o.age)) { return 0; } else if (this.age > o.age) { return 1; } else if (this.age < o.age) { return -1; } return 0; } }
-
Comparator - 如果在数据对象中没有实现上述接口,且需要使用集合进行排序的情况下,则需要在sort方法中作为形参实现。
import lombok.AllArgsConstructor; import lombok.Data; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.function.Consumer; /** * 算法学习 * @author 李家民 */ public class DemoApplication { public static void main(String[] args) { // 开始时间 long startTime = System.currentTimeMillis(); go(); // 结束时间 long endTime = System.currentTimeMillis(); long runTime = (endTime - startTime) / 1000; System.out.println("程序运行时长 = " + runTime + "秒"); } public static void go() { // 基本数据 ArrayList<Person> arrayList = new ArrayList<Person>(); arrayList.add(new Person("1", 11)); arrayList.add(new Person("4", 14)); arrayList.add(new Person("5", 15)); arrayList.add(new Person("2", 12)); arrayList.add(new Person("6", 16)); arrayList.add(new Person("3", 13)); // 排序 Collections.sort(arrayList, new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { if (o2.getAge().equals(o1.getAge())) { return 0; } else if (o2.getAge() > o1.getAge()) { return 1; } else if (o2.getAge() < o1.getAge()) { return -1; } return 0; } }); // 遍历 arrayList.forEach(new Consumer<Person>() { @Override public void accept(Person person) { System.out.println(person); } }); } } @Data @AllArgsConstructor class Person { private String name; private Integer age; }
2 冒泡排序
数据循环过程中依次比较两个相邻的数据,数据小的一方放在左边,大的一方放在右边,代码如下
public static void bubbleSort(int arr[]) {
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
但是基本用不上,因为时间复杂度太高了
3 选择排序
大概的流程是
- 找到数组中最小的元素
- 把这个元素和数组的第一个元素换位
- 重复上面的过程,直到完成整个数组的排序
大概代码如下
public class DemoApplication {
public static void main(String[] args) {
// 开始时间
long startTime = System.currentTimeMillis();
go();
// 结束时间
long endTime = System.currentTimeMillis();
long runTime = (endTime - startTime) / 1000;
System.out.println("程序运行时长 = " + runTime + "秒");
}
public static void go() {
int[] arr = new int[]{3, 5, 1, 4, 7, 9, 2};
for (int i : comp(arr)) {
System.out.println(i);
}
}
public static int[] comp(int[] arr) {
// 循环
for (int i = 0; i < arr.length; i++) {
// 嵌套循环 在每次循环的数组中找到最小元素
for (int j = i; j < arr.length; j++) {
// 进行比较交换
if (arr[i] > arr[j]) {
int tempNum = arr[i];
arr[i] = arr[j];
arr[j] = tempNum;
}
}
}
return arr;
}
}
4 希尔排序
引用文章 |
---|
希尔排序 | 菜鸟教程 (runoob.com) |
第一步:将数组内数据间隔对半,两两数据为一组,提前进行组内排序
第二步:以此类推,第二次将间隔再次减半,再次进行一次组内排序,直到最终间隔为1时停止
/**
* 算法学习
* @author 李家民
*/
public class DemoApplication {
public static void main(String[] args) {
// 开始时间
long startTime = System.currentTimeMillis();
go();
// 结束时间
long endTime = System.currentTimeMillis();
long runTime = (endTime - startTime) / 1000;
System.out.println("程序运行时长 = " + runTime + "秒");
}
/**
* 希尔排序
*/
public static void go() {
int[] array = {49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 1};
System.out.println("排序之前:");
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
// 希尔排序
int size = array.length;
while (true) {
// 增量每次减半
size /= 2;
for (int i = 0; i < size; i++) {
// 这个循环里其实就是一个插入排序
for (int j = i + size; j < array.length; j += size) {
int k = j - size;
while (k >= 0 && array[k] > array[k + size]) {
int temp = array[k];
array[k] = array[k + size];
array[k + size] = temp;
k -= size;
}
}
}
if (size == 1) {
break;
}
}
System.out.println();
System.out.println("排序之后:");
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
System.out.println();
}
}
5 归并排序
引用文章 |
---|
图解排序算法(四)之归并排序 - dreamcatcher-cx - 博客园 (cnblogs.com) |
简单来说需求就是,将两个有序的数组合并成一个更大的有序数组,所以叫它归并排序,其核心是采用分治策略,下面这幅图是将两个有序的子数组先合并为一个,利用双指针进行比较排序。
但是我想试试另外一种玩法,不合并的前提下进行比较,但是实际原理上都差不多
/**
* 算法学习
* @author 李家民
*/
public class DemoApplication {
public static void main(String[] args) {
// 开始时间
long startTime = System.currentTimeMillis();
go();
// 结束时间
long endTime = System.currentTimeMillis();
long runTime = (endTime - startTime) / 1000;
System.out.println("程序运行时长 = " + runTime + "秒");
}
public static void go() {
// 两个有序的原数组
int[] arrA = new int[]{1, 3, 5, 7, 9, 12, 14, 16};
int[] arrB = new int[]{2, 4, 6, 8, 10, 11, 13, 15, 17, 18};
// length
int sizeA = arrA.length;
int sizeB = arrB.length;
int[] arrC = new int[sizeA + sizeB];
// 比较两个数组的长度
int max = 0;
int min = 0;
if (sizeA > sizeB) {
max = sizeA;
min = sizeB;
}
if (sizeA < sizeB) {
max = sizeB;
min = sizeA;
}
// 指针指向
int arrA_pointer = 0;
int arrB_pointer = 0;
// 开始排序
for (int i = 0; i < max + min; i++) {
if (arrA_pointer == min) {
// 最小的那个数组已经达到边界了 把剩下的元素扔过去
arrC[i] = arrB[arrB_pointer];
arrB_pointer++;
continue;
}
// 元素比较 暂不考虑相等的情况
if (arrA[arrA_pointer] > arrB[arrB_pointer]) {
arrC[i] = arrB[arrB_pointer];
arrB_pointer++;
} else if (arrA[arrA_pointer] < arrB[arrB_pointer]) {
arrC[i] = arrA[arrA_pointer];
arrA_pointer++;
}
}
// 遍历查看结果
for (int i : arrC) {
System.out.print(i + " ");
}
System.out.println("________");
}
}
6 快速排序
快速排序是C. A. R. Hoare在1962年提出的一种排序算法,它的核心思想就是分治处理,实际上,它与归并排序
是一个互补的关系,通过两个有序的子序列进行合并排序,后面经过后人的不断改进也衍生了很多种不同类的快速排序,下面写一个最具代表性的三向切分
的快速排序。
我先把JVM的内存提升一下…刚才十亿数据差点把我给炸没了
-Xms4240m -Xmx12288m
首先整一点数据出来
import java.util.Random;
/**
* 一批静态数据
* @author 李家民
*/
public class DeadData {
/**
* 制造一波内存数据
* @param size 数据数量
* @param rangeSize 数据值范围
* @return
*/
public static int[] memoryData(int size, int rangeSize) {
int x = 0;
int[] resultArrays = new int[size];
while (x < size) {
int tempNum = new Random().nextInt(rangeSize) + 1;
resultArrays[x] = tempNum;
x++;
}
System.out.println("resultArrays.length = " + resultArrays.length);
return resultArrays;
}
}
快速排序的核心代码
/**
* 快速排序
* @author 李家民
*/
public class QuickSort {
/**
* 三向切分
* @param data
* @param leftPoint
* @param rightPoint
*/
public static void sorted(int[] data, int leftPoint, int rightPoint) {
// 如果左下标翻过了右下标 则退出递归
if (leftPoint >= rightPoint) {
return;
}
// 数据定义
int value = data[leftPoint];
int leftIndex = leftPoint;
int rightIndex = rightPoint;
int moveIndex = leftPoint + 1;
// 循环条件:如果左边移动的指针没有越过右边移动的指针
while (moveIndex <= rightIndex) {
if (data[moveIndex] < value) {
swap(data, moveIndex++, leftIndex++);
} else if (data[moveIndex] > value) {
swap(data, moveIndex, rightIndex--);
} else {
moveIndex++;
}
}
// 递归
sorted(data, leftPoint, leftIndex - 1);
sorted(data, rightIndex + 1, rightPoint);
}
/**
* 左右数据交换通用代码
* @param data 数组
* @param pointA 下标A
* @param pointB 下标B
*/
public static void swap(int[] data, int pointA, int pointB) {
int tempNum = data[pointA];
data[pointA] = data[pointB];
data[pointB] = tempNum;
}
}
接下来跑起来测试一下
import java.util.Arrays;
/**
* 快速排序之三向切分
* @author 李家民
*/
public class RunSortsDemo {
public static void main(String[] args) {
int[] intS1 = DeadData.memoryData(500000000, 1000000000);
int[] intS2 = intS1.clone();
B(intS2);
A(intS1);
}
public static void A(int[] intS) {
// 开始时间
long startTime = System.currentTimeMillis();
go(intS);
// 结束时间
long endTime = System.currentTimeMillis();
long runTime = (endTime - startTime) / 1000;
System.out.println("自己写的算法 执行时长 = " + runTime + "秒");
}
public static void B(int[] intS) {
// 开始时间
long startTime = System.currentTimeMillis();
Arrays.sort(intS);
// 结束时间
long endTime = System.currentTimeMillis();
long runTime = (endTime - startTime) / 1000;
System.out.println("Arrays.sort 执行时长 = " + runTime + "秒");
}
/**
* 执行算法
*/
public static void go(int[] arr) {
QuickSort.sorted(arr, 0, arr.length - 1);
}
}
我只能说,完败
resultArrays.length = 500000000
Arrays.sort 执行时长 = 38秒
自己写的算法 执行时长 = 56秒
本想挑战一下Arrays工具类的排序,万万是没想到啊
文章目录 |
---|
快速排序算法_哔哩哔哩_bilibili |
over
7 堆排序
1
三、查找
1
1
1
总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容。