1、冒泡排序

冒泡排序总的平均时间复杂度为image.png

  • 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  • 对每一对相邻元素做同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
  • 针对所有的元素重复以上的步骤,除了最后一个。
  • 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

    1. public static void bubbleSort(int arr[]) {
    2. for(int i =0 ; i<arr.length-1 ; i++) {
    3. for(int j=0 ; j<arr.length-1-i ; j++) {
    4. if(arr[j]>arr[j+1]) {
    5. int temp = arr[j];
    6. arr[j]=arr[j+1];
    7. arr[j+1]=temp;
    8. }
    9. }
    10. }
    11. }

    2、快速排序

    时间复杂度为O (nlogn)

  • 首先设定一个分界值,通过该分界值将数组分成左右两部分

  • 将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值
  • 然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理
  • 重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了
    1. /**
    2. * @Description 快速排序
    3. * @Param [data, start, end]
    4. * @Return java.lang.Integer[]
    5. * @Author WJ
    6. * @Date 2021/8/29 3:17
    7. **/
    8. @Test
    9. public Integer[] quickSort(Integer[] data, Integer start, Integer end) {
    10. if (start >= end) {
    11. return data;
    12. }
    13. Integer low = start;
    14. Integer high = end;
    15. Integer temp = data[start];
    16. while (low < high) {
    17. while (low < high && data[high] >= temp) {
    18. high--;
    19. }
    20. data[low] = data[high];
    21. while (low < high && data[low] < temp) {
    22. low++;
    23. }
    24. data[high] = data[low];
    25. }
    26. data[low] = temp;
    27. quickSort(data, start, low - 1);
    28. quickSort(data, low + 1, end);
    29. return data;
    30. }

3、归并排序

时间复杂度:O(n log n)

  • 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
  • 设定两个指针,最初位置分别为两个已经排序序列的起始位置
  • 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
  • 重复步骤3直到某一指针超出序列尾
  • 将另一序列剩下的所有元素直接复制到合并序列尾

    1. public static int[] mergeSort(int[] nums, int l, int h) {
    2. if (l == h)
    3. return new int[] { nums[l] };
    4. int mid = l + (h - l) / 2;
    5. int[] leftArr = mergeSort(nums, l, mid); //左有序数组
    6. int[] rightArr = mergeSort(nums, mid + 1, h); //右有序数组
    7. int[] newNum = new int[leftArr.length + rightArr.length]; //新有序数组
    8. int m = 0, i = 0, j = 0;
    9. while (i < leftArr.length && j < rightArr.length) {
    10. newNum[m++] = leftArr[i] < rightArr[j] ? leftArr[i++] : rightArr[j++];
    11. }
    12. while (i < leftArr.length)
    13. newNum[m++] = leftArr[i++];
    14. while (j < rightArr.length)
    15. newNum[m++] = rightArr[j++];
    16. return newNum;
    17. }

    ```java private static void mergeSort(int[] arrays, int lefts, int rights, int[] newArray) {

    1. if (lefts < rights) {
    2. int mid = (lefts + rights) / 2;
    3. mergeSort(arrays, lefts, mid, newArray);
    4. mergeSort(arrays, mid + 1, rights, newArray);
    5. mergearray(arrays, lefts, mid, rights, newArray);
    6. }

    }

public static void mergearray(int a[], int first, int mid, int last, int temp[]) {
    int i = first, j = mid + 1;
    int m = mid, n = last;
    int k = 0;

    while (i <= m && j <= n) {
        if (a[i] <= a[j]) {
            temp[k++] = a[i++];
        } else {
            temp[k++] = a[j++];
        }
    }

    while (i <= m) {
        temp[k++] = a[i++];
    }

    while (j <= n) {
        temp[k++] = a[j++];
    }

    for (i = 0; i < k; i++) {
        a[first + i] = temp[i];
    }
}
<a name="c1AEA"></a>
# 4、广度优先搜索BFS
**BFS(Breath First Search)广度优先搜索。**<br />何为BFS宽搜?即由一点逐级往下搜索,以图为例,由顶点v1可以延伸到v2、v3,再由v2可以延伸到v1、v4、v5,被搜索过的顶点不用再次搜索,直到整个图的节点被搜完一遍。<br />**优缺点**

- 空间复杂度是呈现出指数增长。
- 不存在爆栈的危险。
- 可以搜索最短路径、最小等问题

![image.png](https://cdn.nlark.com/yuque/0/2021/png/21439354/1628500488206-535c9b56-b324-4a2c-8c57-47fbf3924961.png#clientId=ud563d1b2-0968-4&from=paste&id=u7c48d237&margin=%5Bobject%20Object%5D&name=image.png&originHeight=200&originWidth=410&originalType=url&ratio=1&size=5027&status=done&style=none&taskId=u418e0903-9144-4e94-bd1c-52165b4a7d8)
```java
    public static void printBFS(int[][] graph,int s){
        boolean[] visited  = new boolean[graph.length];
        Queue<Integer> queue = new LinkedList<>();
        queue.add(s);
        visited[s] = true;

        int[] bfs = new int[graph.length];
        int j = 0;
        while (!queue.isEmpty()){

            int vertex = queue.poll();
            int[] nodes = graph[vertex];

            for(int i = 0; i < nodes.length; i++){
                if(!visited[i] && nodes[i]!=0){
                    queue.add(i);
                    visited[i] = true;
                }
            }

            bfs[j] = vertex;
            j++;
            System.out.print(vertex+" ");
        }


    }

5、深度优先搜索DFS

DFS(Deep First Search)深度优先搜索。
何为DFS深搜?即先一条道走到黑,不管中途遇到什么岔路口都不停,直到这条道没后面的路了,再回到前一个岔路口,继续一条道走到黑。不断回溯,直到全部节点都被搜完。
image.png
优缺点

  • 空间复杂度:与深度成正比,假设深度为n,则为O(n)
    • 可能会存在爆栈的危险。
  • 不能搜最短路径,最小等问题

      public static void  printDFS(int[][] graph,int s,int e) {
          Stack<Integer> stack = new Stack<Integer>();
          stack.push(s);
          boolean visited[] = new boolean[6];//默认为false
          visited[s] = true;
    
          while(!stack.isEmpty()){
              int vertex = stack.pop();
    
              int[] nodes = graph[vertex];
              for(int i =0; i<nodes.length;i++){
                  if(!visited[i] && nodes[i]>0){ //还未访问
                      stack.push(i);
                      visited[i] = true;
                  }
              }
    
              System.out.print(vertex+" ");
          }
      }
    

    7、二分查找

    条件:

  • 有序

  • 无重复元素

复杂度分析

  • 时间复杂度:O(logN)。
  • 空间复杂度:O(1)。
      public int search(int[] nums, int target) {
          // 避免当 target 小于nums[0] nums[nums.length - 1]时多次循环运算
          if (target < nums[0] || target > nums[nums.length - 1]) {
              return -1;
          }
          int left = 0, right = nums.length - 1;
          while (left <= right) {
              int mid = left + ((right - left) >> 1);
              if (nums[mid] == target)
                  return mid;
              else if (nums[mid] < target)
                  left = mid + 1;
              else if (nums[mid] > target)
                  right = mid - 1;
          }
          return -1;
      }
    

    8、贪心算法

    所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解。
    贪心算法没有固定的算法框架,算法设计的关键是贪心策略的选择。必须注意的是,贪心算法不是对所有问题都能得到整体最优解,选择的贪心策略必须具备无后效性,即某个状态以后的过程不会影响以前的状态,只与当前状态有关。
    所以对所采用的贪心策略一定要仔细分析其是否满足无后效性。

    9、滑动窗口

10、回溯法

11、动态规划

将原问题拆解成若干个子问题,同时保存子问题的答案,使得每个子问题只求解一次,最终获得原问题的答案。
记忆化搜索:自顶向下
动态规划:自底向上

11、斐波那契数列

public int fbnq(int n){
        if(n == 0){
            return 0;
        }
        if (n == 1){
            return 1;
        }
        return fbnq(n-1)+fbnq(n-2);
    }

//记忆化搜索
int[] data = new int[n+1];
public int fbnqPlus(int n){
        if(n == 0){
            return 0;
        }
        if (n == 1){
            return 1;
        }
        if(data[n] == 0){
            data[n] = fbnq(n-1)+fbnq(n-2);
        }
        return data[n];
    }
//动态规划
public int fbnqPlusPlus(){
    data[0] = 0;
    data[1] = 1;
    for(int i = 2;i<=n;i++){
        data[i] = data[i-1]+data[i-2];
    }
    return data[n];
}

13、01背包

image.png

    private static int package01(int[] w, int[] v, int index, int c) {
        if (index < 0 || c <= 0) {
            return 0;
        }
        int res = package01(w, v, index - 1, c);
        if (c >= w[index]) {
            res = Math.max(res, v[index] + package01(w, v, index - 1, c - w[index]));
        }
        return res;
    }
//记忆化搜索
static int[][] data = new int[w.length][c + 1];
private static int package01Plus(int[] w, int[] v, int index, int c) {
        if (index < 0 || c <= 0) {
            return 0;
        }
        if(data[index][c]!= -1){
            return data[index][c];
        }
        int res = package01(w, v, index - 1, c);
        if (c >= w[index]) {
            res = Math.max(res, v[index] + package01(w, v, index - 1, c - w[index]));
        }
        data[index][c] = res;
        return res;
    }
//动态规划
static int[][] data = new int[w.length][c + 1];
    private static int package01PlusPlus(int[] w, int[] v, int c) {
        if (w.length == 0 || v.length == 0){
            return 0;
        }
        for (int j = 0; j < c; j++) {
            data[0][j] = j>=w[0]?v[0]:0;
        }
        for (int i = 1; i < w.length; i++) {
            for (int j = 0; j <= c; j++) {
                data[i][j] = data[i-1][j];
                if (j>=w[i]){
                    data[i][j] = Math.max(data[i][j],v[i]+data[i-1][j-w[i]]);
                }
            }
        }
        return data[w.length-1][c];
    }

14、N皇后问题

15、LRU

15.1、巧用LinkedHashMap完成LRU算法

import java.util.LinkedHashMap;

public class LRUCache {

    private LinkedHashMap<Integer, Integer> cache;

    public LRUCache(int capacity) {
        cache = new LinkedHashMap<Integer, Integer>(capacity, 0.75f, true){

            private static final long serialVersionUID = 1L;


            @Override
            protected boolean removeEldestEntry(java.util.Map.Entry<Integer, Integer> eldest) {
                return size() > capacity;
            }

        };
    }

    public int get(int key) {
        return cache.getOrDefault(key, -1);
    }

    public void put(int key, int value) {
        cache.put(key, value);
    }

    @Override
    public String toString() {
        return cache.toString();
    }

}

15.2、手写LRU算法

class LRUCache2{
    class Node<K, V>{//双向链表节点
        K key;
        V value;
        Node<K, V> prev;
        Node<K, V> next;

        public Node() {
            this.prev = this.next = null;
        }
        public Node(K key, V value) {
            super();
            this.key = key;
            this.value = value;
        }
    }

    //新的插入头部,旧的从尾部移除
    class DoublyLinkedList<K, V>{
        Node<K, V> head;
        Node<K, V> tail;

        public DoublyLinkedList() {
            //头尾哨兵节点
            this.head = new Node<K, V>();
            this.tail = new Node<K, V>();
            this.head.next = this.tail;
            this.tail.prev = this.head;
        }

        public void addHead(Node<K, V> node) {
            node.next = this.head.next;
            node.prev = this.head;
            this.head.next.prev = node;
            this.head.next = node;
        }

        public void removeNode(Node<K, V> node) {
            node.prev.next = node.next;
            node.next.prev = node.prev;
            node.prev = null;
            node.next = null;

        }

        public Node<K, V> getLast() {
            if(this.tail.prev == this.head)
                return null;
            return this.tail.prev;
        }

    }

    private int cacheSize;
    private Map<Integer, Node<Integer, Integer>> map;
    private DoublyLinkedList<Integer, Integer> doublyLinkedList;


    public LRUCache2(int cacheSize) {
        this.cacheSize = cacheSize;
        map = new HashMap<>();
        doublyLinkedList = new DoublyLinkedList<>();
    }

    public int get(int key) {
        if(!map.containsKey(key)) {
            return -1;
        }

        Node<Integer, Integer> node = map.get(key);

        //更新节点位置,将节点移置链表头
        doublyLinkedList.removeNode(node);
        doublyLinkedList.addHead(node);

        return node.value;
    }

    public void put(int key, int value) {

        if(map.containsKey(key)) {

            Node<Integer, Integer> node = map.get(key);
            node.value = value;
            map.put(key, node);


            doublyLinkedList.removeNode(node);
            doublyLinkedList.addHead(node);
        }else {

            if(map.size() == cacheSize) {//已达到最大容量了,把旧的移除,让新的进来
                Node<Integer, Integer> lastNode = doublyLinkedList.getLast();
                map.remove(lastNode.key);//node.key主要用处,反向连接map
                doublyLinkedList.removeNode(lastNode);
            }

            Node<Integer, Integer> newNode = new Node<>(key, value);
            map.put(key, newNode);
            doublyLinkedList.addHead(newNode);
        }
    }    
}