数据结构与算法JC班-左程云第一节课笔记(认识复杂度、对数器、二分法与异或运算)

第1节 认识复杂度、对数器、二分法与异或运算

程序=算法+数据结构,既然算法这么重要,每个人写出来的算法又不一样,那么怎么算是一个好的算法呢?

1、评估算法优劣的核心指标是什么?

  • 时间复杂度(流程决定)
  • 额外空间复杂度(流程决定)
  • 常数项时间(实现细节决定)

(1)常数项时间

什么是常数项时间?

你比如数组的寻址操作,这就是一个常数项时间操作,每次执行时间都是固定时间,与数据量的大小无关,这样的操作就属于常数项时间操作。、

这里再举一个例子!你们不是Java里面经常用到LinkedList吗?这个在底层属于双向链表,如果你要list.get(5),那么并不会像数组寻址那样直接计算偏移量,而是一个一个地去遍历,到了合适的位置才取值,这个就不是常数项时间。

常见的常数时间的操作

  • 常见的算数运算(+、-、*、/、%等)
  • 常见的位运算(>>[带符号位,统一右移,然后看符号位是什么就用什么补]、>>>[不带符号位]、<<、|、&、^等)
  • 赋值、比较、自增、自减操作等
  • 数组寻址操作

(2)时间复杂度

什么是时间复杂度?

了解常数时间有什么用?时间复杂度就是来衡量你这个算法中基本常数操作执行的次数,看到底和数据量大小之间存在什么样的关系?很难理解吧?我们现在用选择排序(思想:每次选择出数组中最小的数值,然后和最小位置的数字进行交换,然后最小位置+1,直到选择排序完毕)来介绍时间复杂度。这里视频中讲了选择排序的常数操作num=XN^2 + YN + Z,想一下为什么?时间复杂度为o(N^2),只需要最高次幂。

如何确定算法流程的总操作数量与样本数量之间的表达式关系?

  1. 想象该算法流程所处理的数据状况,要按照最差的情况来。
  2. 把整个流程彻底拆分为一个个基本动作,保证每个动作都是常数时间的操作。
  3. 如果数据量为N,看看基本动作的数量和N是什么关系。

时间复杂度怎么表达?

当完成了表达式的建立后,只要把最高阶项留下即可。低阶项都去掉,高阶项的系数也要去掉,记为o(忽略掉系数的高阶项)

时间复杂度的意义?

当我们要处理的样本量很大很大的时候,我们会发现低阶项什么都不是最重要的了,每一项的系数是什么也不是最重要的,真正重要的是最高阶项是什么。时间复杂度是衡量算法流程复杂程度的一种指标,这个指标只与数据量有关,与过程之外的优化无关(一会再讲这一句)。

// 到了这里我们就把选择排序、冒泡排序、插入排序都讲了吧!这三个的时间复杂度都是o(n^2)

class Test {
    // 选择排序思想:每次选择出数组中最小的数值,然后和最小位置的数字进行交换,然后最小位置+1,直到选择排序完毕
    public static void selectSort(int[] arr) {
        if (arr == null || arr.length <= 1) {
            return;
        }
        // 这里用i来表示下标
        for (int i = 0; i < arr.length - 1; i++) {
            int min_index = i;
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[min_index] > arr[j]) {
                    min_index = j;
                }
                // 交换一次后我现在是当前最小的,我还要看后面有没有更小的,所以继续循环
                swap(arr, i, min_index);
            }
        }
    }

    // 冒泡排序思想:0-1谁大谁往后,1-2谁大谁往后,....,4-5谁大谁往后,这样一轮后,最大的值在最后面;第二轮开始....第三轮开始....,最后排序完毕。
    public static void bubbleSort(int[] arr) {
        if (arr == null || arr.length <= 1) {
            return;
        }
        // 0 ~ N-1
        // 0 ~ N-2
        // 0 ~ N-3
        // 0 ~ 1
        for (int e = arr.length - 1; e > 0; e--) {
            // 0 1
            // 1 2
            // 2 3
            // e-1 e
            for (int i = 0; i < e; i++) {
                if (arr[i] > arr[i + 1]) {
                    swap(arr, i, i + 1);
                }
            }
        }
    }

    // 插入排序思想:先让0~1有序,再让0~2有序,.....,最后再让0~N-1有序
    public static void insertSort(int[] arr) {
        if (arr == null || arr.length <= 1) {
            return;
        }
        for(int i = 1;i < arr.length;i++){
            for(int j = i - 1;j >= 0 && arr[j] > arr[j+1];j--){
                swap(arr,j,j+1);
            }
        }
    }

    public static void swap(int[] arr, int i, int j) {
        int temp;
        temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public static void printArr(int[] arr) {
        for (int e : arr) {
            System.out.print(e + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
    }
}

时间复杂度的排名?【看到logN,默认以2为底】

o(1) < o(logN) < o(N) < o(N*logN) < o(N^2) < … < o(N^k) < o(2^n) < … < o(k^n) < o(n!)

(3)额外空间复杂度

什么是空间复杂度?

你要实现一个算法流程,在实现算法流程的过程中,你需要开辟一些空间来支持你的算法流程。作为输入参数的空间,不算额外空间,作为输出结果的空间,也不算额外空间。因为这些都是必要的,和现实目标有关的,所以都不算。但是除此之外,你的流程如果还需要开辟空间才能让你的流程继续下去,这部分空间就是额外空间,如果你的流程只需要开辟有限几个变量,额外空间复杂度就是o(1)。

(4)最优解

什么是最优解?

那么我们就可以判断出来什么是最优解?最优解就是在在时间复杂度的指标上要尽可能得低,先满足了时间复杂度最低这个指标之后,使用最少的空间的算法流程,叫做这个问题的最优解。一般来说最优解都是忽略掉常数项这个因素的,因为这个因素只决定了实现层次的优化和考虑,而和怎么解决整个问题的思想无关。

2、认识对数器。

  1. 你想要测的方法a。
  2. 实现复杂度不好但是容易实现的方法b。
  3. 实现一个随机样本产生器。
  4. 把方法a和方法b跑相同的随机样本,看看得到的结果是否一样。
  5. 如果有一个随机样本使得比对结果不一致,打印样本进行人工干预,改对方法a和方法b。
  6. 当样本数量很多时比对测试依然正确,可以确定方法a已经正确。
package class01;

import java.util.Arrays;

public class Code01_SelectionSort {

	public static void selectionSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		// 0 ~ N-1
		// 1~n-1
		// 2
		for (int i = 0; i < arr.length - 1; i++) { // i ~ N-1
			// 最小值在哪个位置上  i~n-1
			int minIndex = i;
			for (int j = i + 1; j < arr.length; j++) { // i ~ N-1 上找最小值的下标 
				minIndex = arr[j] < arr[minIndex] ? j : minIndex;
			}
			swap(arr, i, minIndex);
		}
	}

	public static void swap(int[] arr, int i, int j) {
		int tmp = arr[i];
		arr[i] = arr[j];
		arr[j] = tmp;
	}

	// for test
	public static void comparator(int[] arr) {
		Arrays.sort(arr);
	}

	// for test
	public static int[] generateRandomArray(int maxSize, int maxValue) {
		// Math.random()   [0,1)  
		// Math.random() * N  [0,N)
		// (int)(Math.random() * N)  [0, N-1]
		int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
		for (int i = 0; i < arr.length; i++) {
			// [-? , +?]
			arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
		}
		return arr;
	}

	// for test
	public static int[] copyArray(int[] arr) {
		if (arr == null) {
			return null;
		}
		int[] res = new int[arr.length];
		for (int i = 0; i < arr.length; i++) {
			res[i] = arr[i];
		}
		return res;
	}

	// for test
	public static boolean isEqual(int[] arr1, int[] arr2) {
		if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
			return false;
		}
		if (arr1 == null && arr2 == null) {
			return true;
		}
		if (arr1.length != arr2.length) {
			return false;
		}
		for (int i = 0; i < arr1.length; i++) {
			if (arr1[i] != arr2[i]) {
				return false;
			}
		}
		return true;
	}

	// for test
	public static void printArray(int[] arr) {
		if (arr == null) {
			return;
		}
		for (int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}

	// for test
	public static void main(String[] args) {
		int testTime = 500000;
		int maxSize = 100;
		int maxValue = 100;
		boolean succeed = true;
		for (int i = 0; i < testTime; i++) {
			int[] arr1 = generateRandomArray(maxSize, maxValue);
			int[] arr2 = copyArray(arr1);
			selectionSort(arr1);
			comparator(arr2);
			if (!isEqual(arr1, arr2)) {
				succeed = false;
				printArray(arr1);
				printArray(arr2);
				break;
			}
		}
		System.out.println(succeed ? "Nice!" : "Fucking fucked!");

		int[] arr = generateRandomArray(maxSize, maxValue);
		printArray(arr);
		selectionSort(arr);
		printArray(arr);
	}

}

3、认识二分法。

下面用一些题来认识二分。

// 在一个有序数组中,找某个数是否存在
package class01;

public class Code04_BSExist {

	public static boolean exist(int[] sortedArr, int num) {
		if (sortedArr == null || sortedArr.length == 0) {
			return false;
		}
		int L = 0;
		int R = sortedArr.length - 1;
		int mid = 0;
		// L..R
		while (L < R) {
			// mid = (L+R) / 2;
			// L 10亿  R 18亿
			// mid = L + (R - L) / 2
			// N / 2    N >> 1
			// X*2+1可以表示为( X<<2 | 1 )
			mid = L + ((R - L) >> 1); // mid = (L + R) / 2
			if (sortedArr[mid] == num) {
				return true;
			} else if (sortedArr[mid] > num) {
				R = mid - 1;
			} else {
				L = mid + 1;
			}
		}
		return sortedArr[L] == num;
	}
}
// 在一个有序数组中,找>=某个数最左侧的位置
package class01;
import java.util.Arrays;

public class Code05_BSNearLeft {

	// 在arr上,找满足>=value的最左位置
	public static int nearestIndex(int[] arr, int value) {
		int L = 0;
		int R = arr.length - 1;
		int index = -1; // 记录最左的对号
		while (L <= R) {
			int mid = L + ((R - L) >> 1);
			if (arr[mid] >= value) {
				index = mid;
				R = mid - 1;
			} else {
				L = mid + 1;
			}
		}
		return index;
	}

	// for test
	public static int test(int[] arr, int value) {
		for (int i = 0; i < arr.length; i++) {
			if (arr[i] >= value) {
				return i;
			}
		}
		return -1;
	}

	// for test
	public static int[] generateRandomArray(int maxSize, int maxValue) {
		int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
		for (int i = 0; i < arr.length; i++) {
			arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
		}
		return arr;
	}
	
	// for test
	public static void printArray(int[] arr) {
		if (arr == null) {
			return;
		}
		for (int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}

	public static void main(String[] args) {
		int testTime = 500000;
		int maxSize = 10;
		int maxValue = 100;
		boolean succeed = true;
		for (int i = 0; i < testTime; i++) {
			int[] arr = generateRandomArray(maxSize, maxValue);
			Arrays.sort(arr);
			int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
			if (test(arr, value) != nearestIndex(arr, value)) {
				printArray(arr);
				System.out.println(value);
				System.out.println(test(arr, value));
				System.out.println(nearestIndex(arr, value));
				succeed = false;
				break;
			}
		}
		System.out.println(succeed ? "Nice!" : "Fucking fucked!");
	}
}
// 在一个有序数组中,找<=某个数最右侧的位置
package class01;
import java.util.Arrays;

public class Code05_BSNearRight {

	// 在arr上,找满足<=value的最右位置
	public static int nearestIndex(int[] arr, int value) {
		int L = 0;
		int R = arr.length - 1;
		int index = -1; // 记录最右的对号
		while (L <= R) {
			int mid = L + ((R - L) >> 1);
			if (arr[mid] <= value) {
				index = mid;
				L = mid + 1;
			} else {
				R = mid - 1;
			}
		}
		return index;
	}

	// for test
	public static int test(int[] arr, int value) {
		for (int i = arr.length - 1; i >= 0; i--) {
			if (arr[i] <= value) {
				return i;
			}
		}
		return -1;
	}

	// for test
	public static int[] generateRandomArray(int maxSize, int maxValue) {
		int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
		for (int i = 0; i < arr.length; i++) {
			arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
		}
		return arr;
	}

	// for test
	public static void printArray(int[] arr) {
		if (arr == null) {
			return;
		}
		for (int i = 0; i < arr.length; i++) {
			System.out.print(arr[i] + " ");
		}
		System.out.println();
	}

	public static void main(String[] args) {
		int testTime = 500000;
		int maxSize = 10;
		int maxValue = 100;
		boolean succeed = true;
		for (int i = 0; i < testTime; i++) {
			int[] arr = generateRandomArray(maxSize, maxValue);
			Arrays.sort(arr);
			int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
			if (test(arr, value) != nearestIndex(arr, value)) {
				printArray(arr);
				System.out.println(value);
				System.out.println(test(arr, value));
				System.out.println(nearestIndex(arr, value));
				succeed = false;
				break;
			}
		}
		System.out.println(succeed ? "Nice!" : "Fucking fucked!");
	}

}
// 局部最小值问题
package class01;
public class Code06_BSAwesome {

	public static int getLessIndex(int[] arr) {
		if (arr == null || arr.length == 0) {
			return -1; // no exist
		}
		if (arr.length == 1 || arr[0] < arr[1]) {
			return 0;
		}
		if (arr[arr.length - 1] < arr[arr.length - 2]) {
			return arr.length - 1;
		}
		// 到了这里,我们就明白趋势了。先下后上,所以一定会存在最小值。
		int left = 1;
		int right = arr.length - 2;
		int mid = 0;
		while (left < right) {
			mid = (left + right) / 2;
			if (arr[mid] > arr[mid - 1]) {
				right = mid - 1;
			} else if (arr[mid] > arr[mid + 1]) {
				left = mid + 1;
			} else {
				return mid;
			}
		}
		return left;
	}
}

4、认识异或运算。

异或运算(^):相同则0,不同则1;同或运算(⊙):相同则1,不同则0。

能长时间记住的概率接近0%,所以,异或运算就记成无进位相加(无进位是什么意思?就是二进制去相加,即便两者都是1,相加结果是2,不要进位,也就是得到0且不要进位)。

(1)异或运算的性质

0^N == N、N^N == 0。

异或运算满足交换律和结合律。

上面的两个性质用无进位相加来理解就非常得容易。

(2)应用

public class Test {

    public static void main(String[] args) {
        // 如何不需要额外的变量即可实现两个数字的交换,但是特别注意,只有两个变量用的不是同一个内存的时候才可以这样干!
        int a = 10;
        int b = 4;
        a = a ^ b;
        b = a ^ b;
        a = a ^ b;
        // b = a ^ b ^ b = a ^ 0 = a; 运用交换律和结合律
        // a = a ^ b = a ^ b ^ a ^ b ^ b = a ^ a ^ b ^ b ^ b = b; 运用交换律和结合律
        System.out.println("a=" + a + ",b=" + b);
    }
}
public class Test {

    // 一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这个数字?
    public static void printOddTimesNum1(int[] arr){
        int eor = arr[0];
        for(int i = 1; i < arr.length;i++){
            eor ^= arr[i];
        }
        System.out.println(eor);
    }
    public static void main(String[] args) {
        
        int[] arr = new int[]{1,1,2,2,3,3,4};
        printOddTimesNum1(arr);
    }
}
public class Test {

    // 怎么把一个整数,提取出最右侧的1来
    public static int func(int number){
        // 00000011 01010000
        // 11111100 10101111 (取反)
        // 11111100 10110000 (加1)
        // 00000000 00010000 (与)
        return number & ((~number) + 1);
    }
    public static void main(String[] args) {

        int num = 234;
        System.out.println(func(num));
    }
}
public class Test {

    // arr中,有两种数,出现奇数次
    public static void printOddTimesNum2(int[] arr) {
        int eor = 0;
        for (int i = 0; i < arr.length; i++) {
            eor ^= arr[i];
        }
        // eor = a ^ b
        // eor != 0
        // eor必然有一个位置上是1
        // 0110010000
        // 0000010000
        int rightOne = eor & (~eor + 1); // 提取出最右的1
        int onlyOne = 0; // eor'
        for (int i = 0 ; i < arr.length;i++) {
            //  arr[1] =  111100011110000
            // rightOne=  000000000010000
            if ((arr[i] & rightOne) != 0) {
                onlyOne ^= arr[i];
            }
        }
        System.out.println(onlyOne + " " + (eor ^ onlyOne));
    }

    public static void main(String[] args) {
    }
}
public class Test {

    // 看一个数字的二进制样式中有几个1
    public static int func(int num){
        int count = 0;
        while(num != 0){
            int rightOne = num & ((~num) + 1);
            count++;
            num ^= rightOne;
        }
        return count;
    }
    public static void main(String[] args) {
        int num = 234;
        System.out.println(func(num));
    }
}

你可能感兴趣的:(数据结构与算法基础班-左程云,数据结构,算法,排序算法)