一、时间复杂度
常数时间的操作 举例
时间复杂度O(1)
package com.timecomplexity;
import org.junit.Test;
/**
* @author wty
* @date 2022/11/24 23:50
*/
public class Exercise01 {
@Test
public void test(){
int t = 3+5;
int p = t >> 1;
int []array = new int[1000000];
int []b = new int[1000000 + 2];
}
}
非常数时间的操作 举例
非常数时间的操作,是和数据量有关的操作
时间复杂度O(N)
@Test
public void test2(){
LinkedList<Integer> linkedList = new LinkedList<>();
for (int i = 0; i < 100; i++) {
linkedList.add(i);
}
// 获取链表元素就不是常数时间的操作
System.out.println(linkedList.get(99));
}
以选择排序为基准看时间复杂度
package com.selectedsort;
import org.junit.Test;
/**
* 选择排序
*
* 时间复杂度:O(N^2)
*
* @author wty
* @date 2022/11/24 23:58
*/
public class SelectedSort {
@Test
public void test(){
int array[] = new int[]{1,4,3,6,7};
// 看
// i [0,3]
for (int i = 0; i < array.length - 1; i++) {
int minIndex = i;
// j [1,4]
for (int j = i + 1; j < array.length; j++) {
// 比较
minIndex = array[j] > array[minIndex] ? minIndex: j;
}
更新
swap(array,i,minIndex);
}
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
}
public void swap(int array[],int x,int y){
int temp = 0;
temp = array[x];
array[x] = array[y];
array[y] = temp;
}
}
计算时间复杂度的步骤
N代表数组的长度:
第一次: N*(看+比+更新)+1(交换)
第二次: (N-1)(看+比+更新)+1(交换)
第三次: (N-2)(看+比+更新)+1(交换)
……
第N次:1*(看+比+更新)+1(交换)
上面求和:
(N+N-1+N-2……+1)*(看+比+更新)+N
用等差数列数列的通项公式表示
(aN^2+bN+C)(看+比+更新)+N
(a(看+比+更新))N^2+(b*(看+比+更新))N+C*(看+比+更新)+N
不要低阶项,也不要高阶的系数
得到时间复杂度是N^2
如何评价算法流程的好坏
(1).先比较时间复杂度
(2).当时间复杂度相同的时候,用做实验的形式确定常数项,确定好坏
常数时间操作的系数比较
原因:常数时间的操作时间也不相同,原因是常数项系数不同,以下举例3+5和3|5通过做实验看出。
package com.selectedsort;
import org.junit.Test;
/**
* @author wty
* @date 2022/11/25 19:44
*/
public class Fundation {
@Test
public void test(){
long begin = System.currentTimeMillis();
for (int i = 0; i < Integer.MAX_VALUE; i++) {
int a = 3;
int b = 5;
int c = (a+b);
}
long end = System.currentTimeMillis();
System.out.println("3+5的时间"+(end-begin));
long begin1 = System.currentTimeMillis();
for (int i = 0; i < Integer.MAX_VALUE; i++) {
int a = 3;
int b = 5;
int c = (a|b);
}
long end1 = System.currentTimeMillis();
System.out.println("3|5的时间"+(end1-begin1));
}
}
运行结果:
3+5的时间 3
3|5的时间 2
比较看出,虽然3+5和3|5都是常数时间的操作,但是明显3|5的速度更快
二、空间复杂度
选择排序
package com.selectedsort;
import org.junit.Test;
/**
* 选择排序
* 时间复杂度:O(N^2)
* 空间复杂度:O(1)
*
* @author wty
* @date 2022/11/25 19:57
*/
public class ExerciseSelected {
@Test
public void test() {
int array[] = {1, 3, 7, 8, 2, 3, 5, 7, 8, 10, 2, 5};
selectedSort(array);
}
public void selectedSort(int array[]) {
int length = array.length;
for (int i = 0; i < length - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < length; j++) {
minIndex = array[minIndex] < array[j] ? minIndex : j;
}
int temp = array[minIndex];
array[minIndex] = array[i];
array[i] = temp;
}
for (int i = 0; i < length; i++) {
System.out.print(array[i] + " ");
}
}
}
冒泡排序
package com.bubble;
import org.junit.Test;
import java.util.Arrays;
/**
* 时间复杂度;O(N^2)
* 空间复杂度;O(1)
*
* @author wty
* @date 2022/11/25 20:04
*/
public class BubbleSort {
@Test
public void test() {
int array[] = {9,8,7,6,5,4,3,2,1};
bubbleSort(array);
System.out.println(Arrays.toString(array));
}
public void bubbleSort(int array[]) {
int length = array.length;
boolean flag = false;
for (int i = 0; i < length - 1; i++) {
for (int j = 0; j < length - 1 - i; j++) {
if (array[j] > array[j+1]){
int temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
flag = true;
}
}
// 优化冒泡排序
if (flag == false){
break;
}else {
flag = false;
}
}
}
}
插入排序
package com.insert;
import org.junit.Test;
import java.util.Arrays;
/**
* @author wty
* @date 2022/11/26 0:50
*/
public class InsertSorted {
@Test
public void test(){
int array[] = {5,3,1,2,4};
insertSort(array);
//System.out.println(Arrays.toString(array));
}
public void insertSort(int array[]){
for (int i = 1; i < array.length; i++) {
for (int j = i-1; j>=0 && array[j+1] < array[j] ; j--) {
swap(array,j,j+1);
}
System.out.println("第"+i+"轮执行完后的顺序是:");
System.out.println(Arrays.toString(array));
}
}
public void swap(int array[],int x,int y){
int temp = array[x];
array[x] = array[y];
array[y] = temp;
}
}
三、二分查找
在一个有序数组中,找某个数是否存在
package com.binarysearch;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
/**
* 二分查找某个数,在数组是否存在
*
* @author wty
* @date 2022/11/27 17:42
*/
public class BinarySearch03 {
@Test
public void test(){
int array[] = {1,2,3,4,4,5};
int i = binarySearch03(array, 0, array.length - 1, 1);
System.out.println(i);
List<Integer> list = binarySearch04(array, 0, array.length - 1, 1);
System.out.println(list);
}
public int binarySearch03(int array[],int left,int right,int findVal){
int mid = (left + right)/2;
if (left > right){
return -1;
}
if (array[mid] > findVal){
//继续向左边查找
right = mid - 1;
return binarySearch03(array,left,right,findVal);
}else if (array[mid] == findVal){
return mid;
}else {
//继续向右边查找
left = mid + 1;
return binarySearch03(array,left,right,findVal);
}
}
/**
* 如果说查找的元素存在重复,那么需要返回多个
* @param array
* @param left
* @param right
* @param findVal
* @return
*/
public List<Integer> binarySearch04(int array[], int left, int right, int findVal){
int mid = (left + right)/2;
if (left > right){
return new ArrayList<Integer>();
}
if (array[mid] > findVal){
//继续向左边查找
right = mid - 1;
return binarySearch04(array,left,right,findVal);
}else if (array[mid] == findVal){
// 3,4,4,5
// 朝左边遍历
ArrayList<Integer> list = new ArrayList<>();
int temp = mid - 1;
while (true) {
if (temp >= 0 && array[temp] == findVal){
list.add(temp);
}else {
break;
}
--temp;
}
list.add(mid);
// 朝右边遍历
temp = mid + 1;
while (true) {
if (temp <= array.length - 1 && array[temp] == findVal){
list.add(temp);
}else {
break;
}
++temp;
}
return list;
}else {
//继续向右边查找
left = mid + 1;
return binarySearch04(array,left,right,findVal);
}
}
}
在一个有序数组中,找>=某个数最左边的位置
package com.binarysearch;
import org.junit.Test;
/**
* @author wty
* @date 2022/11/27 22:19
*/
public class BinarySearch06 {
@Test
public void test(){
// >= 2 的最左边的元素的位置
int array[] = {0,0,1,1,2,2,2,2,3,3,4,4,5};
int search = search(array, 0, array.length - 1, 0);
System.out.println(search);
}
//在一个有序数组中,找>=某个数最左边的位置
public int search(int array[],int left, int rigth,int findVal){
int mid = (left + rigth) /2;
if (array[mid] > findVal){
// 朝左边遍历
return search(array,left,mid - 1,findVal);
}else if(array[mid] == findVal){
int temp = mid - 1;
int index = mid;
while (true) {
if (temp >=0 && array[temp] == findVal){
index = temp;
}else {
break;
}
--temp;
}
return index;
}else {
// 朝右边遍历
return search(array,mid + 1, rigth,findVal);
}
}
}
在一个有序数组中,找<=某个数最右边的位置
package com.binarysearch;
import org.junit.Test;
/**
* @author wty
* @date 2022/11/27 22:19
*/
public class BinarySearch06 {
@Test
public void test(){
// <=2 的最右边的元素的位置
int array[] = {0,0,1,1,2,2,2,2,3,3,4,4,5};
int search01 = search01(array, 0, array.length - 1, 5);
System.out.println("最右边的元素的位置:"+search01);
}
//在一个有序数组中,找<=某个数最右边的位置
public int search01(int array[],int left, int rigth,int findVal){
int mid = (left + rigth) /2;
if (array[mid] > findVal){
// 朝左边遍历
return search(array,left,mid - 1,findVal);
}else if(array[mid] == findVal){
int temp = mid + 1;
int index = mid;
while (true) {
if (temp <=array.length -1 && array[temp] == findVal){
index = temp;
}else {
break;
}
++temp;
}
return index;
}else {
// 朝右边遍历
return search(array,mid + 1, rigth,findVal);
}
}
}
时间复杂度排名
O(1) 优于O(logN) 优于O(N)优于O(N²) 优于O(N³)优于O(N^k)优于O(2ⁿ)优于O(2 ^ k)优于O(n!)
局部最小值
(1)如果 array[0] < array[1] 则0位置上是局部最小
(2)如果 array[N-1] < array[N] 则N位置上是局部最小
(3)如果 array[i] < array[i-1] && array[i] < array[i+1] 则i位置上是局部最小
代码示例:解决顶峰问题
符合下列属性的数组 arr 称为 山峰数组(山脉数组) :
arr.length >= 3
存在 i(0 < i < arr.length - 1)使得:
arr[0] < arr[1] < … arr[i-1] < arr[i]
arr[i] > arr[i+1] > … > arr[arr.length - 1]
给定由整数组成的山峰数组 arr ,返回任何满足 arr[0] < arr[1] < … arr[i - 1] < arr[i] > arr[i + 1] > … > arr[arr.length - 1] 的下标 i ,即山峰顶部。
示例 1:
输入:arr = [0,1,0]
输出:1
示例 2: 输入:arr = [1,3,5,4,2]
输出:2
示例 3: 输入:arr = [0,10,5,2]
输出:1
示例 4: 输入:arr = [3,4,5,1]
输出:2
示例 5: 输入:arr = [24,69,100,99,79,78,67,36,26,19]
输出:2
package com.binarysearch;
import org.junit.Test;
/**
* 顶峰问题
*
* @author wty
* @date 2022/11/27 22:42
*/
public class BinarySearch07 {
@Test
public void test(){
int array[] = {24,69,100,99,79,78,67,36,26,19};
int i = new Solution30().peakIndexInMountainArray(array);
System.out.println(i);
}
}
class Solution30 {
public int peakIndexInMountainArray(int[] arr) {
return search(arr,1,arr.length-2);
}
public int search(int array[],int left,int right){
int mid = (left + right) /2;
if (left > right){
return left;
}
if (array[mid] > array[mid + 1]){
return search(array,left,mid - 1);
}else {
return search(array,mid + 1,right);
}
}
}
优化时间复杂度的方法
- 数据状况
- 问题性质
四、异或运算的性质
异或:无进位相加
同或:不同为0相同为1
满足交换律和结合律
a∧b = b∧a
a∧b∧c = a∧(b∧c)
一个数组中有一种数出现了奇数次,其它数都出现了偶数次,找这个一种数
package com.xor;
import org.junit.Test;
/**
* 异或:一个数组中有一种数出现了奇数次,其它数都出现了偶数次,找这个一种数
*
* @author wty
* @date 2022/11/28 14:45
*/
public class XorExercise04 {
@Test
public void test(){
// 1, 2, 2, 3, 3, 4, 4
int array[] = {1,1, 2, 2, 3, 3, 4, 4,5,5,5};
int eor = 0;
for (int i = 0; i < array.length; i++) {
eor = eor ^ array[i];
}
System.out.println(eor);
}
}
交换两个数,不引入第三个变量
package com.xor;
import org.junit.Test;
/**
* 异或:一个数组中有一种数出现了奇数次,其它数都出现了偶数次,找这个一种数
*
* @author wty
* @date 2022/11/28 14:45
*/
public class XorExercise04 {
/**
* 交换2个数
*/
@Test
public void test2(){
int a = 3;
int b = 4;
a = a ^ b;
b = a ^ b;
a = a ^ b;
System.out.println(a);
System.out.println(b);
}
}
一个数组中有两种数出现了奇数次,其它数都出现了偶数次,找这个两种数
package com.xor;
import org.junit.Test;
import java.util.Arrays;
/**
* 一个数组中有两种数出现了奇数次,其它数都出现了偶数次,找这个两种数
*
* @author wty
* @date 2022/11/28 14:46
*/
public class XorExercise05 {
@Test
public void test(){
int array[] = {1,2,10,4,1,4,3,3};
int[] ints = new Solution40().singleNumbers(array);
System.out.println(Arrays.toString(ints));
}
}
class Solution40 {
public int[] singleNumbers(int[] nums) {
int temp = 0;
for (int i = 0; i < nums.length; i++) {
temp = temp ^ nums[i];
}
// temp = 2 ∧ 10
int rightOne = temp & (~temp + 1);
int error = 0;
for (int i = 0; i < nums.length; i++) {
if ((rightOne & nums[i]) != 0){
error = error ^ nums[i];
}
}
int array [] = new int[2];
array[0] = error;
array[1] = error ^ temp;
return array;
}
}
五、对数器
package com.duishuqi;
import org.junit.Test;
import java.util.Arrays;
/**
* 针对选择排序的对数器
*
* @author wty
* @date 2022/11/28 15:59
*/
public class Duishuqi {
@Test
public void dsq(){
// 测试次数
int testTime = 500000;
// 数组长度
int maxSize = 100;
// 数组元素的最大值
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
int array1[] = generateRandomArray(maxSize,maxValue);
int array2[] = copyArray(array1);
selectedSort(array1);
comparator(array2);
if (!isEqual(array1,array2)){
succeed = false;
printArray(array1);
printArray(array2);
break;
}
}
System.out.println(succeed ? "succeed" : "failure");
int arr[] = generateRandomArray(maxSize,maxValue);
printArray(arr);
selectedSort(arr);
printArray(arr);
}
/**
* 比较器--和Arrays中的排序
* @param array
*/
public static void comparator(int array[]){
Arrays.sort(array);
}
/**
* 比较
* @param array1
* @param array2
*/
public static boolean isEqual(int array1[], int array2[]){
if ((array1 == null && array2 != null) || (array2 == null && array1 != null)){
return false;
}
if (array1 == null && array2 == null){
return true;
}
if (array1.length != array2.length){
return false;
}
for (int i = 0; i < array1.length; i++) {
if (array1[i] != array2[i]){
return false;
}
}
return true;
}
/**
* 数组的拷贝
* @param array
* @return
*/
public static int[] copyArray(int array[]){
if (array == null){
return null;
}
int[] res = new int[array.length];
for (int i = 0; i < array.length; i++) {
res[i] = array[i];
}
return res;
}
/**
* 产生随机的一个数组
* @param maxSize
* @param maxValus
* @return
*/
public static int[] generateRandomArray(int maxSize,int maxValus){
// Math.random() 范围 [0,1)
// Math.random() * A 范围 [0,A)
// Math.random() * (A + 1) 范围 [0,A+ 1]
int array[] = new int[(int)((maxSize + 1) * Math.random())];
for (int i = 0; i < array.length; i++) {
array[i] = (int)((maxValus + 1) * Math.random()) - (int)(maxValus * Math.random());
}
return array;
}
/**
* 打印数组
* @param array
*/
public static void printArray(int array[]){
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + " ");
}
System.out.println();
}
/**
* 选择排序
* @param array
*/
public static void selectedSort(int array[]) {
int length = array.length;
for (int i = 0; i < length - 1; i++) {
int minIndex = i;
for (int j = i + 1; j < length; j++) {
minIndex = array[minIndex] < array[j] ? minIndex : j;
}
int temp = array[minIndex];
array[minIndex] = array[i];
array[i] = temp;
}
}
}
详解递归函数和常见递归函数的复杂度估计
六、归并排序
package com.mergesort;
import org.junit.Test;
import java.util.Arrays;
/**
* 时间复杂度O(N*logN)
* 空间复杂度:O(N)
*
* @author wty
* @date 2022/11/29 11:37
*/
public class MergeSort03 {
@Test
public void test(){
int array[] = {7,2,5,1,3,8,0,0,0};
process(array,0,array.length - 1);
System.out.println(Arrays.toString(array));
}
public void process(int array[],int left,int right){
if (left == right){
return;
}
int mid = left + ((right - left) >>1);
process(array,left,mid);
process(array,mid +1,right);
merge(array,left,mid,right);
}
/**
*
* @param array 原数组
* @param left
* @param mid
* @param right
*/
public void merge(int array[],int left,int mid,int right){
int p1 = left;
int p2 = mid + 1;
int array_copy[] = new int[right - left + 1];
int k = 0;
// 保证在数组内不越界情况下赋值
while (p1 <= mid && p2 <= right){
array_copy[k++] = array[p1] <= array[p2] ? array[p1++] : array[p2++];
}
// 数组越界的时候,另外一部分进行赋值的操作
while (p2 <= right){
array_copy[k++] =array[p2++];
}
while (p1 <= mid){
array_copy[k++] =array[p1++];
}
for (int i = 0; i < array_copy.length; i++) {
array[i + left] = array_copy[i];
}
}
}
小和问题
package com.mergesort;
import org.junit.Test;
/**
* 小和问题
*
* @author wty
* @date 2022/11/29 18:26
*/
public class Merge06 {
@Test
public void test() {
int array[] = {1,3,4,2,5};
int process = process(array, 0, array.length - 1);
System.out.println(process);
}
public int process(int array[],int left,int right) {
if (left == right || array.length == 0){
return 0;
}
int mid = left +((right - left) >>1);
return process(array,left,mid)
+process(array,mid + 1,right)
+merge(array,left,mid,right);
}
public int merge(int array[],int left,int mid,int right) {
int p1 = left;
int p2 = mid + 1;
int copy_array[] = new int[right - left + 1];
int k = 0;
int sum = 0;
// 数组范围中的
while (p1 <= mid && p2 <= right){
if (array[p1] < array[p2]){
sum += array[p1] * (right - p2 + 1);
copy_array[k++] = array[p1++];
}else {
copy_array[k++] = array[p2++];
}
}
// 数组范围之外的
while (p1 <= mid){
copy_array[k++] = array[p1++];
}
while (p2 <= right){
copy_array[k++] = array[p2++];
}
for (int i = 0; i < copy_array.length; i++) {
array[left + i] = copy_array[i];
}
return sum;
}
}
逆序对问题
package com.mergesort;
import org.junit.Test;
/**
* @author wty
* @date 2022/11/29 20:05
*/
public class Merge08 {
@Test
public void test() {
int array[] = {1,3,2,3,1};
int process = process(array, 0, array.length - 1);
int num = 0;
for (int i = 1; i < array.length; i++) {
num = num + i;
}
System.out.println(num - process);
}
public int process(int array[],int left,int right) {
if (left == right || array.length == 0){
return 0;
}
int mid = left +((right - left) >>1);
return process(array,left,mid)
+process(array,mid + 1,right)
+merge(array,left,mid,right);
}
public int merge(int array[],int left,int mid,int right) {
int p1 = left;
int p2 = mid + 1;
int copy_array[] = new int[right - left + 1];
int k = 0;
int sum = 0;
// 数组范围中的
while (p1 <= mid && p2 <= right){
if (array[p1] <= array[p2]){
sum += (right - p2 + 1);
copy_array[k++] = array[p1++];
}else {
copy_array[k++] = array[p2++];
}
}
// 数组范围之外的
while (p1 <= mid){
copy_array[k++] = array[p1++];
}
while (p2 <= right){
copy_array[k++] = array[p2++];
}
for (int i = 0; i < copy_array.length; i++) {
array[left + i] = copy_array[i];
}
return sum;
}
}
七、堆排序