一、 位运算、算法是什么、简单排序
1.1位运算
1.2算法是什么
1.2.1 算法
1)有具体的问题 2)有设计解决这个问题的具体流程 3)有评价处理流程的可量化指标
1.2.2 算法的分类
1)分类当然非常多
2)对于新手学习特别重要的一个分类: 1 明确知道怎么算的流程 2 明确知道怎么尝试的流程
1.3简单排序
1.3.1选择排序
0N-1上选出最小值放到0位置 1N-1上选出最小值放到1位置 2~N-1上选出最小值放到2位置
public class Code01_SelectionSort {
public static void selectionSort(int[] arr){
if( arr == null || arr.length < 2){
return;
}
int N = arr.length;
// 0 ~ n-1
// 1 ~ n-1
// 2 ~ n-1
for(int i = 0; i < N ;i++){
int minValueIndex = i;
for(int j = i+1; j < N; j++){
minValueIndex = arr[j] > arr[minValueIndex] ? minValueIndex : j;
}
swap(arr,i,minValueIndex );
}
}
public static void swap(int[] arr,int i,int j){
int temp =arr[j];
arr[j] =arr[i];
arr[i] =temp;
}
}
1.3.2 冒泡排序
0N-1上选出最小值放到0位置 1N-1上选出最小值放到1位置 2~N-1上选出最小值放到2位置
public class code02_bubbleSort{
public static void bubbleSort(int[] arr){
if(arr == null || arr.length < 2){
return;
}
int N = arr.length;
// 0 ~ n-1
// 0 ~ n-2
// 0 ~ n-3
for(int end = N-1 ; end >= 0; end--){
// 0 ~ end 这一坨进行冒泡排序
// 0 1 1 2 2 3 3 4 end-1 end
for(int second = 1; second < end ; second++){
if(arr[second] < arr[second -1]){
swap(arr,second-1,second);
}
}
}
}
public static void swap(int[] arr,int i,int j){
int temp =arr[j];
arr[j] =arr[i];
arr[i] =temp;
}
}
1.3.3插入排序
0N-1上选出最小值放到0位置 1N-1上选出最小值放到1位置 2~N-1上选出最小值放到2位置
public class code03_insertSort{
public static void insertSort(int[]arr){
if(arr==null || arr.length < ){
return;
}
int N = arr.length;
// 0 ~ 1
// 0 ~ 2
// 0 ~ 3
// 0 ~ n-1
for( int end = 1 ; end <= N-1 ; end++){
// int newNumIndex = end;
// while (newNumIndex - 1 >= 0 && arr[newNumIndex - 1] > arr[newNumIndex]) {
// swap(arr,newNumIndex-1, newNumIndex);
// newNumIndex--;
// }
for(int pre = end -1; pre >= 0 && arr[pre] > arr[pre+1]; pre--){
swap(arr,pre,pre+1);
}
}
}
}
二、前缀和数组、对数器和随机行为
1)数据结构是存储、组织数据的方式
2)精心选择的数据结构可以带来更高的运行或者存储效率
3)数据结构是很多算法得以进行的载体
1)数组 便于寻址,不便于增删数据 2)链表 便于增删数据,不便于寻址
2.1前缀数组
假设有一个数组arr,用户总是频繁的查询arr中某一段的累加和 你如何组织数据,能让这种查询变得便利和快捷?
public class RangSum(){
private int[] preSun;
public RangSum(int[] arr){
preSum = new int[arr.length];
preSum[0] = arr[0];
for(int i= 1; i<N; i++){
preSum[i] = preSum[i-1] + arr[i];
}
}
public int rangSum(int L, int R){
return L == 0 ? preSum[R] : preSum[R] - preSum[L - 1];
}
}
2.2 介绍随机函数
已经1-5 随机求 1-7 随机
public int f1(){
// 返回一个 1-5 的等概率数
return (int)(Math.random() * 5) + 1;
}
public int f2(){
int ans = 0;
do{
ans= f1();
}while(ans==3);
// 返回 0 和1 的等概率数
return ans <3 ? 0 : 1;
}
public int f3(){
// 返回 000 - 111 的等概率数
return (f2() << 2) + (f2() << 1) + (f2() << 0)
}
public int f4(){
int ans =0;
do{
ans = f3();
}while(ans == 7);
// 返回 0 - 6 的等概率数
return ans ;
}
public int g(){
// 返回 1 - 7 的等概率数
return f4()+1;
}
2.3对数器的使用
选择、冒泡、插入排序的对数器验证
// 返回一个数组arr,arr长度[0,maxLen-1],arr中的每个值[0,maxValue-1]
public static int[] lenRandomValueRandom(int maxLen, int maxValue) {
// 生成数组的随机长度
int len = (int) (Math.random() * maxLen);
int[] ans = new int[len];
// 生成数组里面的值 : 里面的值是随机生成的
for (int i = 0; i < len; i++) {
ans[i] = (int) (Math.random() * maxValue);
}
return ans;
}
public static int[] copyArray(int[] arr) {
int[] ans = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
ans[i] = arr[i];
}
return ans;
}
// arr1和arr2一定等长 判断数组是否是有序的
public static boolean isSorted(int[] arr) {
if (arr.length < 2) {
return true;
}
int max = arr[0];
for (int i = 1; i < arr.length; i++) {
if (max > arr[i]) {
return false;
}
max = Math.max(max, arr[i]);
}
return true;
}
三、二分、复杂度、动态数组、哈希表和有序表
3.1 二分法
3.1.1 有序数组中找到num
public static boolean find(int[] arr, int num){
if(arr == null || arr.length == 0){
return false;
}
int L = 0;
int R = arr.length - 1;
while( L <= R){
int mid = ( L + R ) / 2 ;
if(arr[mid] == num){
return true;
}else if(arr[mid] > num){
R = mid -1 ;
}else{
L = mid +1 ;
}
}
return false;
}
3.1.2 有序数组中找到>=num最左的位置
public static int mostLeftMoreNum(int[] arr, int num){
if(arr == null || arr.length == 0){
return - 1;
}
int L = 0;
int R = arr.length - 1;
int ans = -1;
while(L <= R ){
int mid = (L + R) / 2 ;
if(arr[mid] >= num){
ans = mid;
R = mid - 1;
}else{
L = mid +1;
}
}
return ans;
}
3.1.3 有序数组中找到<=num最右的位置
public static int nearestIndex(int[] arr, int value) {
int L = 0;
int R = arr.length - 1;
int ans = -1; // 记录最右的对号
while (L <= R) {
int mid = L + ((R - L) >> 1);
if (arr[mid] <= value) {
ans = mid;
L = mid + 1;
} else {
R = mid - 1;
}
}
return ans;
}
}
3.1.4 局部最小值问题
public static int oneMinIndex(int[] arr) {
if(arr==null || arr.length == 0){
return -1;
}
if(arr.length == 1){
return 0;
}
int N = arr.length;
if(arr[0]<arr[1]){
return 0;
}
if(arr[N -1] < arr[N -2]){
return N-1;
}
int L = 0;
int R = N - 1;
while(L < R - 1){
int mid = (L+R) / 2;
if( arr[mid] < arr[mid+1] && arr[mid] < arr[mid-1] ){
return mid;
}else{
if(arr[mid] > arr[mid-1]){
R = mid -1;
}else {
L = mid + 1;
}
}
}
return arr[L] < arr[R] ? L : R;
}
3.2 时间复杂度
⑴ 找出算法中的基本语句;
算法中执行次数最多的那条语句就是基本语句,通常是最内层循环的循环体。
⑵ 计算基本语句的执行次数的数量级;
只需保留f(n)中的最高次幂正确即可,可以忽略所有低次幂和最高次幂的系数。
⑶ 用大Ο记号表示算法的时间性能。
将基本语句执行次数的数量级放入大Ο记号中。大O用来表示上界的,当用它作为算法的最坏情况运行时间的上界,就是对任意数据输入的运行时间的上界
如果算法中包含嵌套的循环,则基本语句通常是最内层的循环体,如果算法中包含并列的循环,则将并列循环的时间复杂度相加。例如:
for (i=1; i<=n; i++)
x++;
for (i=1; i<=n; i++)
for (j=1; j<=n; j++)
x++;
第一个for循环的时间复杂度为Ο(n),第二个for循环的时间复杂度为Ο(n²),则整个算法的时间复杂度为Ο(n+n²)=Ο(n²)。
注、加法原则:T(n)=O(f(n))+O(g(n))=O(max(fn,gn))
常见的算法时间复杂度由小到大依次为:
Ο(1)<Ο(log2n)<Ο(n)<Ο(nlog2n)<Ο(n²)<Ο(n³)<…<Ο(2^n)<Ο(n!)<O(n^n)
Ο(1)表示基本语句的执行次数是一个常数,一般来说,只要算法中不存在循环语句,其时间复杂度就是Ο(1)。Ο(log2n)、Ο(n)、Ο(nlog2n)、Ο(n2)和Ο(n3)称为多项式时间,而Ο(2n)和Ο(n!)称为指数时间。计算机科学家普遍认为前者是有效算法,把这类问题称为P类问题,而把后者称为NP问题。
3.3 动态数组
- 由于数组长度是默认长度为10,那么当数组存满元素,就需要对该数组进行扩容操作。
- 因为数组是无法动态增加的,就需要创建一个新的数组,并且数组容量一般都是原数组容量的1.5呗,然后将原数组的元素循环放入新数组中,这就是动态扩容
private void ensureCapacity(int capacity) {
// 获取当前数组的容量
int oldCapacity = elements.length;
// 当前存储的元素个数 < 当前数组容量, 直接返回
if (oldCapacity >= capacity) {
return;
}
// 创建新的新容量为旧容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//根据新的容量创建新数组
E[] newElements = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
// 拷贝原数组元素到新数组
newElements[i] = elements[i];
}
// 引用新数组
elements = newElements;
System.out.println("size=" + oldCapacity + ", 扩容到了" + newCapacity);
}