概述

算法=输入+程序+输出

  1. 针对某个特定问题的解决方案
    1. 先有解决问题的主体思路
    2. 功能分解,逐步求精
  2. 算法是有好坏之分的 ,主要使用O进行标识

    1. 时间复杂性:算法执行所需要的时间
    2. 空间复杂性:算法执行所需要的空间内存

      基础算法

  3. 排序

    1. 简单排序:冒泡、选择、插入
    2. 高级排序:希尔、归并、计数、快速、堆
  4. 查询
    1. 二分查找法

image.png

算法的特征

  1. 有穷性
  2. 有效性
  3. 确定性
  4. 输入性
  5. 输出性

(两输两有一确定)

算法的基本要求

  1. 正确性
  2. 可读性
  3. 健壮性
  4. 时间复杂度
  5. 空间复杂度

(时空正可健)

数据结构的概述

  1. 数据结构:就是在数据中存储的方式

    1. 在内存中物理存储方式
      1. 数组
      2. 链表
    2. 逻辑存储方式
      1. 线性表
      2. 队列
    3. 树结构
      1. 二叉树
      2. 查询树
      3. 平衡树
      4. 红黑树
      5. B树
    4. 哈希表
    5. 排序算法

      image.png
  2. 三种简单排序的区别,时间复杂性:O(n^2)

    1. 冒泡
      1. 两个临近的数值进行对比,将大的数值放到集合尾部
      2. 遍历次数多,交换次数多
    2. 选择
      1. 从未排序的集合中选择最小或者最大值,放入到已经排序好的集合中
      2. 遍历次数多,但是交换的次数少
    3. 插入
      1. 从未排序的集合中获取一个值,然后从已经排序好了的集合中找到插入的位置进行插入
      2. 遍历次数少,交换次数多
  3. 高级排序(速度比较快速)

    1. 快速排序
    2. 计数排序
    3. 希尔排序
    4. 归并排序
    5. 堆排序

      查询算法

  4. 传统的查询算法需要遍历整个集合,时间复杂性O(n)

  5. 使用二分查找法之后,时间复杂度O(logN)

    1. 二分查找法只要针对有序的集合

      1. 获取数组的中间值下标
      2. 将中间值和目标值进行对比
      3. 判断大小选择左右查找image.png
      4. 基本代码

        1. public static void binarySeach(int[] arrs,int target,int start,int end){
        2. //1,获取数组的中间值下标
        3. int middle = (start+end)/2;
        4. //2,将中间值和目标进行比较
        5. if(target == arrs[middle]){
        6. System.out.println("找到目标,下标是:"+middle);
        7. }else if(target > arrs[middle]){
        8. //从右边开始找
        9. binarySeach(arrs,target,middle+1,end);
        10. }else{
        11. //从左边开始找
        12. binarySeach(arrs,target,start,middle-1);
        13. }
        14. }
      5. 添加递归退出条件

image.pngimage.png

  1. if(end<start){//递归退出条件
  2. //没有找到目标
  3. System.out.println("没有找到目标");
  4. return;
  5. }
  1. 6. 带返回结果的二分查找法
  1. public static Integer binarySeach(int[] arrs,int target,int start,int end){
  2. if(end<start){//递归退出条件
  3. //没有找到目标
  4. //System.out.println("没有找到目标");
  5. return null;
  6. }
  7. //1,获取数组的中间值下标
  8. int middle = (start+end)/2;
  9. //2,将中间值和目标进行比较
  10. if(target == arrs[middle]){
  11. //System.out.println("找到目标,下标是:"+middle);
  12. return middle;
  13. }else if(target > arrs[middle]){
  14. //从右边开始找
  15. return binarySeach(arrs,target,middle+1,end);
  16. }else{
  17. //从左边开始找
  18. return binarySeach(arrs,target,start,middle-1);
  19. }
  20. }

两种物理结构

  1. 数组
    1. 是内存中的连续空间,在创建的时候大小是固定的
    2. 一般编程语言都直接支持
    3. 通过下标可以直接获取数组相对应的值
    4. 会有扩容的问题
  2. 链表
    1. 在内存中不是连续空间,大小也不是固定的
    2. 可以通过下标查找数值但是需要从开始链表一个一个查询
    3. 插入和删除速度很快,没有扩容的问题,但是查询速度慢
  3. 链表的基本实现

    1. 创建一个节点类

      1. //给链表设计一个节点
      2. public class Node<T>{
      3. T data;//存储的数据
      4. Node next;//指向下一个节点的引用
      5. public Node(T data, Node next) {
      6. this.data = data;
      7. this.next = next;
      8. }
      9. public T getData() {
      10. return data;
      11. }
      12. public void setData(T data) {
      13. this.data = data;
      14. }
      15. }
    2. 实现新增和查询

      1. //新增方法
      2. public void add(T t){
      3. if(head==null){
      4. head = new Node(t,null);
      5. }else{
      6. Node temp = head;
      7. //找到最后一个节点
      8. while(temp.next!=null){
      9. temp = temp.next;
      10. }
      11. //将新增的节点放入到最后一个节点的next引用
      12. temp.next = new Node(t,null);
      13. }
      14. size++;
      15. }
      16. //查询方法
      17. public T get(int pos){
      18. if(pos>=size){
      19. throw new IndexOutOfBoundsException();
      20. }
      21. int cnt = 0;
      22. Node<T> temp = head;//从头部开始
      23. //向下移动
      24. while(cnt != pos){
      25. temp = temp.next;
      26. cnt++;
      27. }
      28. return temp.getData();
      29. }
    3. 指定位置的新增

      1. public void add(int pos, T t) {
      2. //创建新的节点
      3. Node newNode = new Node(t, null);
      4. if(pos == 0){//在头部插入
      5. //先将新的节点的next指向头部
      6. newNode.next = head;
      7. head = newNode;
      8. }else{
      9. //当前位置的
      10. Node current = getNode(pos-1);
      11. //将新节点的下一个位置,执行当前节点的下一个位置
      12. newNode.next = current.next;
      13. //将当前位置的节点指向新增的节点
      14. current.next = newNode;
      15. }
      16. size++;
      17. }
    4. 指定位置的删除

      1. //删除指定位置的数据
      2. public void remove(int i) {
      3. if(i==0){
      4. //将头节点直接移动到下一个节点
      5. head = head.next;
      6. }else{
      7. //找到节点
      8. Node current = getNode(i);
      9. Node preNode = getNode(i-1);
      10. //前面的节点,直接指向下后面的节点
      11. preNode.next = current.next;
      12. }
      13. //删除
      14. size--;
      15. }

      栈与队列

  4. 栈:先进后出

    1. 自定义实现栈

      1. public class MyStack<T> {
      2. MyLinkedList<T> datas = new MyLinkedList<>();
      3. //压入栈
      4. public void push(T t){
      5. //放在链表的尾部
      6. datas.add(t);
      7. }
      8. //出栈
      9. public T pop(){
      10. //获取头部
      11. T t = datas.get(datas.size-1);
      12. datas.remove(datas.size-1);
      13. return t;
      14. }
      15. }
    2. jdk中自带的Stack类

      1. Stack<String> myStack = new Stack<>();
      2. myStack.push("aa");
      3. myStack.push("bb");
      4. myStack.push("cc");
      5. System.out.println(myStack.pop());
      6. System.out.println(myStack.pop());
  5. 队列:先进先出

    1. 自定义实现

      1. MyQueue<String> queue = new MyQueue<>();
      2. queue.add("aa");
      3. queue.add("bb");
      4. queue.add("cc");
      5. System.out.println(queue.poll());
      6. System.out.println(queue.poll());
    2. jdk自带的实现

      1. //数组实现
      2. Queue<String> queue = new ArrayDeque<>();
      3. //链表实现
      4. queue = new LinkedList<>();

      树结构

  6. 是一种非线性结构

    1. 既有数组查询的效率,又有链表增删的性能
  7. 树的基本特点

    1. 每颗树有且仅仅有一个根节点没有父节点
    2. 除了根节点,所有其他节点只有一个父节点
    3. 每个节点都可以有多个子节点,也可以没有子节点
    4. 最多只有两个子节点的树就是二叉树

      链表实现树

  8. 实现树节点

    1. public class Node<T>{
    2. T data;//数据
    3. Node<T> left;//左子节点
    4. Node<T> right;//右子节点
    5. }
  9. 创建一颗树

    1. //树的根节点
    2. Node root;
    3. //创建一颗树
    4. public void createTree(){
    5. //创建根节点
    6. root = new Node(1);
    7. //左子节点
    8. Node left = new Node(2);
    9. root.setLeft(left);
    10. //右边子节点
    11. Node right= new Node(3);
    12. root.setRight(right);
    13. //左子左节点
    14. Node leftLeft = new Node(4);
    15. left.setLeft(leftLeft);
    16. //左子右节点
    17. Node leftRight = new Node(5);
    18. left.setRight(leftRight);
    19. //右子左节点
    20. Node rightLeft = new Node(6);
    21. right.setLeft(rightLeft);
    22. //右子右节点
    23. Node rightRight = new Node(7);
    24. right.setRight(rightRight);
    25. }
  10. 树的遍历

    1. 先序遍历:根节点-》左节点-》右节点

      1. //实现先序遍历
      2. public void preTrival(Node node){
      3. if(node ==null){
      4. return;
      5. }
      6. //直接输出根节点
      7. System.out.print(node.data+",");
      8. //遍历左子节点
      9. preTrival(node.left);
      10. //遍历右子节点
      11. preTrival(node.right);
      12. }
    2. 中序遍历:左节点-》根节点-》右节点

      1. //实现中序遍历
      2. public void midTrival(Node node){
      3. if(node==null){
      4. return;
      5. }
      6. //遍历左子节点
      7. midTrival(node.left);
      8. //打印根节点
      9. System.out.print(node.data+",");
      10. //遍历右子节点
      11. midTrival(node.right);
      12. }
    3. 后序遍历:左节点-》右节点-》根节点

      1. //实现后序遍历
      2. public void postTrival(Node node){
      3. if(node==null){
      4. return;
      5. }
      6. //遍历左子节点
      7. postTrival(node.left);
      8. //遍历右子节点
      9. postTrival(node.right);
      10. //打印根节点
      11. System.out.print(node.data+",");
      12. }

      数组实现树

  11. 基本规则

    1. 使用数组存储
    2. 只能构造完全二叉树
    3. 左子节点 = 当前节点下标*2 +1
    4. 右子节点 = 当前节点下标*2 +2
  12. 遍历

    1. //实现先序遍历
    2. public void preTrival(int index){
    3. if(index>= datas.length){
    4. return;
    5. }
    6. //打印根节点
    7. System.out.print(datas[index]+",");
    8. //遍历左子节点
    9. preTrival(2*index+1);
    10. //遍历右子节点
    11. preTrival(2*index+2);
    12. }
    13. //实现先序遍历
    14. public void midTrival(int index){
    15. if(index>= datas.length){
    16. return;
    17. }
    18. //遍历左子节点
    19. midTrival(2*index+1);
    20. //打印根节点
    21. System.out.print(datas[index]+",");
    22. //遍历右子节点
    23. midTrival(2*index+2);
    24. }
  13. 堆排序

    1. 使用数组树
    2. 构造一个大顶堆(所有的父类节点都比子节点要大,最大的值就会在根节点)
    3. 在构造完毕大顶堆之后,将根节点和最后一个叶子节点进行交换,并且脱离树结构
    4. 再将树结构继续构造大顶堆,然后再和最后一个叶子节点交换,直到排序完毕

      查询树

  14. 树既有链表插入删除的优势,又有数组的查询的效率

  15. 一般的树是不具备快速查询的能力的,只有二分查找树,可以天然的支持二分查找法,以快速实现数据的查询
  16. 查找树特点
    1. 左子树的节点要比右边子树要小
    2. 在构造查询树的时候,有可能会出现左右失去平衡的问题
  17. 二叉平衡树(AVL)
    1. 是一个特殊的查找树
    2. 在构造树的时候,会通过旋转算法,使得树达到平衡
    3. 当左子树和右子树的高度差大于1的时候,树就失去了平衡,需要旋转来达到平衡
    4. 由于平衡失去平衡的规则限制太高,会导致树在旋转的时候,次数增加,从而影响性能
  18. 红黑树(RBTree)

    1. 也是一颗特殊的查找树
    2. 主要将树节点的颜色分为黑色和红色
    3. 任意节点到每个叶子节点的的黑色节点的数量是一致,如果不一致,那么则失去平衡进行旋转
    4. 无论树的层有多高,每次旋转次数不会超过3次

      多路查询树

  19. 二叉查找树的问题,在于数据很多时候,树高度会很高,从而影响的性能

  20. 多路查找树,不像二叉树之后两子节点,它具备多个子节点,可以有效降低树的高度提高查询效率
  21. 多路查找树就是B树,B+树就是在B的基础,将所以的叶子节点通过链表链接起来了

    霍夫曼树

  22. 是带权重的树

  23. 主要用压缩算法

    哈希表

  24. 哈希表的查询速度非常,可以到达O(1)

  25. 主要原理
    1. 创建一个Entry用于存储数据的key 和 value值
    2. 主要是通过hash散列算法,计算出插入的元素所在位置
    3. 然后根据计算出的问题,将Entry插入到对应数组中
  26. 基本实现

    1. public class MyHashTable<K,V> {
    2. Entry<K,V>[] elements ;//用于存储Entry的数组
    3. public MyHashTable(){
    4. //创建Entry的数组
    5. elements = new Entry[10];
    6. }
    7. //计算所在的位置,散列算法
    8. public int hash(K k){
    9. return k.hashCode()%elements.length;
    10. }
    11. //插入
    12. public void put(K k ,V v){
    13. //使用K执行相关散列算法,获取存储在位置
    14. int pos = hash(k);
    15. //创建Entry放入到指定的位置
    16. elements[pos] = new Entry<>(k,v);
    17. }
    18. //获取
    19. public V get(K k){
    20. //通过散列算法获取位置
    21. int pos = hash(k);
    22. //通过位置获取Entry
    23. Entry<K,V> entry = elements[pos];
    24. return entry.getValue();
    25. }
    26. //定义一个Entry用于存储key和value
    27. public static class Entry<K,V>{
    28. K key;
    29. V value;
    30. public Entry(K key, V value) {
    31. this.key = key;
    32. this.value = value;
    33. }
    34. public K getKey() {
    35. return key;
    36. }
    37. public void setKey(K key) {
    38. this.key = key;
    39. }
    40. public V getValue() {
    41. return value;
    42. }
    43. public void setValue(V value) {
    44. this.value = value;
    45. }
    46. }
    47. }
  27. 散列冲突问题

    1. 由于数组长度是有限的,在计算entry插入的位置的时候,必然会出现冲突的问题
    2. 解决方案1:开放寻址法
      1. 在发生冲突的时候,通过重新计算,或者是扩容的方式,来解决冲突的问题
      2. 好处可以保证entry是在数组中,方便序列化
      3. 缺点,会浪费更多空间,而且在重写计算的时候可能会影响效率
      4. 适合数据量不大的数据
    3. 解决方案2:链表式

      1. 在发生冲突的时候,在冲突的位置上,通过链表添加新的数据
      2. 好处是插入的时候不需要考虑数组扩容的问题
      3. 坏处是链表逐步增大,会导致性能问题
      4. 将entry转换成链表

        1. public static class Entry<K,V>{
        2. K key;
        3. V value;
        4. Entry<K,V> next;//指向下个节点的引用
        5. }
      5. 修改插入和查询方法 ```java //插入 public void put(K k ,V v){ //使用K执行相关散列算法,获取存储在位置 int pos = hash(k);

        //判断是否发生碰撞 if(elements[pos]==null){ //创建Entry放入到指定的位置 elements[pos] = new Entry<>(k,v); }else{ Entry temp = elements[pos]; //链表的遍历 while(temp.next!=null){

        1. temp = temp.next;

        } //插入到链表下一个为null的位置 temp.next = new Entry<>(k,v); }

  1. }
  2. //获取
  3. public V get(K k){
  4. //通过散列算法获取位置
  5. int pos = hash(k);
  6. //通过位置获取Entry
  7. Entry<K,V> entry = elements[pos];
  8. //判断Entry的key的值是否一致
  9. if(entry.getKey().equals(k)){
  10. return entry.getValue();
  11. }else{
  12. //遍历链表,根据key判断
  13. while(entry.next!=null){
  14. entry = entry.next;
  15. //判断key值是否相同
  16. if(entry.getKey().equals(k)){
  17. return entry.getValue();
  18. }
  19. }
  20. }
  21. return null;
  22. }
  1. 5. JDK中的哈希表
  2. 1. jdk早期哈希表主要是Hashtable
  3. 1. 之后为了提高效率,jdk提供HashMap
  4. 1. 没有使用锁了
  5. 1. 可以对空值进行处理
  6. 1. 提高的了hash算法的效率
  7. 1. 在处理哈希冲突的时候,也使用链表法,但是在链表数量超过8个,会将链表转换成红黑树
  8. <a name="SVhKX"></a>
  9. ## 图
  10. 1. 图是一种网状结构,和树结构最大区别在于图可以有多个父类节点
  11. 1. 图主要由顶点 (Vertex) 和边 (Edge) 构成
  12. 1. 边是顶点和顶点之间的连线
  13. 1. 边可以是有向的, 也可以是无向的.(比如A --- B, 通常表示无向. A --> B, 通常表示有向)2
  14. 1. 图可以带权重,例如A->B花费的时间
  15. 1. 路径是一个顶点到另一个顶点所经过的边的序列
  16. 2. 通过二维数组定义图
  17. ```java
  18. public class MyGraph {
  19. //存储顶点
  20. Vertex[] vertices;
  21. //需要一个矩阵用于存储边
  22. int[][] adjMat;
  23. int verNum;//顶点的数量
  24. public MyGraph(int num){
  25. //初始化数组存储顶点
  26. vertices = new Vertex[num];
  27. //初始化边
  28. adjMat = new int[num][num];
  29. verNum=0;
  30. }
  31. //添加顶点
  32. public void addVertex(String label){
  33. //创建订单对象
  34. Vertex vertex = new Vertex(label);
  35. vertices[verNum++] = vertex;
  36. }
  37. //添加边
  38. public void addEdge(String start,String end){
  39. int sIndex =0;//start这个顶点的坐标
  40. int eIndex =0;
  41. //遍历顶点
  42. for (int i=0;i<vertices.length;i++){
  43. if(vertices[i].label.equals(start)){
  44. //第一个顶点的坐标
  45. sIndex =i;
  46. }
  47. //找到第二个顶点的坐标
  48. if(vertices[i].label.equals(end)){
  49. eIndex =i;
  50. }
  51. }
  52. //定义边
  53. adjMat[sIndex][eIndex] = 1;
  54. adjMat[eIndex][sIndex] = 1;
  55. }
  56. //定义顶点类
  57. public static class Vertex{
  58. String label;//存储顶点的标签
  59. public Vertex(String label){
  60. this.label= label;
  61. }
  62. }
  63. }
  1. 两种遍历方式

    1. 深度优先遍历:其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次(可以栈来实现)

      1. Stack stack = new Stack<>();
      2. //当前值已经被遍历了
      3. vertices[pos].isVisited = true;
      4. int current = pos;
      5. //将位置入栈
      6. stack.push(pos);
      7. System.out.println(vertices[pos].label);
      8. out:while(!stack.empty()) {
      9. for (int i=0;i< vertices.length; i++) {
      10. if(adjMat[current][i]==1&&vertices[i].isVisited==false) {
      11. //入栈
      12. stack.push(i);
      13. vertices[i].isVisited= true;
      14. current = i;
      15. System.out.println(vertices[current].label);
      16. continue out;
      17. }
      18. }
      19. //没有找到可以连接的位置的时候,那么就出栈
      20. stack.pop();
      21. //出栈后要重新设置栈顶的位置
      22. if(!stack.empty()) {
      23. current = (int) stack.peek();
      24. }
      25. }
    2. 广度优先遍历:其过程检验来说是对每一层节点依次访问,访问完一层进入下一层,而且每个节点只能访问一次(队列实现)

      1. //广度优先遍历
      2. public void bfs(int pos) {
      3. //创建队列
      4. Queue queue = new LinkedList<>();
      5. queue.add(pos);//入队列
      6. System.out.println(vertices[pos].label);//打印
      7. vertices[pos].isVisited = true;
      8. int current = pos;
      9. while(!queue.isEmpty()) {
      10. current = (int) queue.poll();//出列
      11. for (int i=0;i< vertices.length; i++) {
      12. if(adjMat[current][i]==1&&vertices[i].isVisited==false) {
      13. //入队列
      14. queue.add(i);//入队列
      15. System.out.println(vertices[i].label);//打印
      16. vertices[i].isVisited = true;
      17. }
      18. }
      19. }
      20. }