数据结构与算法

58 阅读2分钟

一、 位运算、算法是什么、简单排序

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);
}

3.4 哈希表和有序表