Java数据结构与算法初级篇之数组、集合和散列表

版权声明:本文出自门心叼龙的博客,转载请注明出处。 https://blog.csdn.net/geduo_83/article/details/86385566

 源码下载地址:https://download.csdn.net/download/geduo_83/10913510

之前没有写过关于数据结构的文章,那么今天我们将在本文中介绍最基础、最简单的数据结构。
数组,作为数据结构中最基础的一个存储方式,是我们学习一切数据结构、算法的基石。大部分的数据结构可以用数组来实现。接下来我们会介绍数组的概念、存储结构、特点和使用场景。
集合算是升级版的数组,在本文当中我们会介绍集合的概念、特点、实现以及的它的适用场景。
散列表又叫哈希表,在很多语言中都是在数组的基础上实现的,当然散列表也有其他的实现形式,本文我们会介绍散列表的基本概念、特点、实现方式等。

1. 前言 2

2. 数组 3

    2.1 概念 3

    2.2 特点 3

    2.3 存储结构 3

    2.4 使用场景 3

    2.5 常见算法 4

        2.5.1 冒泡排序 4

        2.5.2 选择排序 6

        2.5.3 桶排序 7

        2.5.4 数组中是否有重复元素 9

        2.5.5 删除数组中的重复元素 10

        2.5.6 两数求和 12

        2.5.7 求从左上到右下的路径数 15

        2.5.8 求左上到右下的路径最小值 17

3. 集合 18

    3.1 概念 18

    3.2 特点 19

        1.它的长度是可变的 19

        2.他会浪费一定内存空间 19

        3.数据的拷贝会浪费一定的时间 19

    3.3 适用场景 19

    3.4 常见的算法 19

        3.4.1 自定义实现一个集合 19

        3.4.2 删除集合中的偶数 21

    3.5 性能分析 23

4. 散列表 24

5. 小结 26

1. 前言

之前没有写过关于数据结构的文章,那么今天我们将在本文中介绍最基础、最简单的数据结构。

数组,作为数据结构中最基础的一个存储方式,是我们学习一切数据结构、算法的基石。大部分的数据结构可以用数组来实现。接下来我们会介绍数组的概念、存储结构、特点和使用场景。

集合算是升级版的数组,在本文当中我们会介绍集合的概念、特点、实现以及的它的适用场景。

散列表又叫哈希表,在很多语言中都是在数组的基础上实现的,当然散列表也有其他的实现形式,本文我们会介绍散列表的基本概念、特点、实现方式等。

2. 数组

2.1 概念

数组是内存中有序的元素序列

2.2 特点

定长、按顺序访问、索引快、插入删除慢

2.3 存储结构

2.4 使用场景

数组其实是非常简单的一个数据结构,用起来也比较简单,他是其他所有数据结构的基础,所以只有掌握好数组,才能学习好其他的数据结构和算法。

什么时候使用数组,通过数据的特点我们就可以想到,数据的长度是固定的,所以不会出现长度变化的业务上比较适合使用数组

如果我们在app开发中非常常见的菜单按钮,它的个数一般都是固定的不会发生变化,如下图这个界面有首页、报表、消息、我的,我们就可以用数组来存储。

2.5 常见算法

2.5.1 冒泡排序

package A数组.A001冒泡排序;

/**
 * Description: <冒泡排序><br>
 * Author: 门心叼龙<br>
 * Date: 2018/11/21<br>
 * Version: V1.0.0<br>
 * Update: <br>
 */
public class MainAlgorithm {
  public static void main(String[] arg) {
    int[] arr = {1, 3, 10, 1, 34, 5, 21};
    sortBubbling(arr);
    int i = 0;
    while (i < arr.length) {
      System.out.println(arr[i]);
      i++;
    }
  }

  // 冒泡排序:两个循环,通过两两相比,进行排序
  private static void sortBubbling(int[] arr) {
    // 第一轮确定最后一个,第二轮确定倒数第二个...
    for (int i = 0; i < arr.length - 1; i++) {
      for (int j = 0; j < arr.length - i - 1; j++) {
        // 两两相比,就像鱼吐水泡一样...
        if (arr[j] > arr[j + 1]) {
          int temp = arr[j + 1];
          arr[j + 1] = arr[j];
          arr[j] = temp;
        }
      }
    }
  }
}

2.5.2 选择排序

package A数组.A002选择排序;

/**
 * Description: <选择排序><br>
 * Author: 门心叼龙<br>
 * Date: 2018/11/21<br>
 * Version: V1.0.0<br>
 * Update: <br>
 */
public class MainAlgorithm {
  public static void main(String[] arg) {
    int[] arr = {1, 3, 10, 1, 34, 5, 21};
    sortChange(arr);
    int i = 0;
    while (i < arr.length) {
      System.out.println(arr[i]);
      i++;
    }
  }

  // 选择排序,选择第一个元素和剩下的n-1个比较
  private static void sortChange(int[] arr) {
    // 第一轮确定第一个元素,第二轮确定第二个元素
    for (int i = 0; i < arr.length; i++) {
      for (int j = i + 1; j < arr.length; j++) {
        // 选择第一i个元素和剩余的元素进行比较
        if (arr[i] > arr[j]) {
          int temp = arr[i];
          arr[i] = arr[j];
          arr[j] = temp;
        }
      }
    }
  }
}

2.5.3 桶排序

package A数组.A003桶排序;

/**
 * Description: <桶排序><br>
 * Author: 门心叼龙<br>
 * Date: 2018/11/21<br>
 * Version: V1.0.0<br>
 * Update: <br>
 */
public class MainAlgorithm {
  public static void main(String[] arg) {
    int[] arr = {1, 3, 10, 1, 34, 5, 21};
    sortBucket(arr);
    // int i = 0;
    // while (i < arr.length) {
    // System.out.println(arr[i]);
    // i++;
    // }
  }

  // 桶排序,声明一个以 最大元素+1 为长度的数组,遍历原数组,桶数组计数
  private static void sortBucket(int[] arr) {
    int[] arr1 = new int[34 + 1];
    for (int i = 0; i < arr.length; i++) {
      arr1[arr[i]]++;
    }
    for (int i = 0; i < arr1.length; i++) {
      int count = arr1[i];
      while (count > 0) {
        System.out.println(i);
        count--;
      }
    }
  }
}

2.5.4 数组中是否有重复元素

package A数组.A004数组是否有重复元素;

import java.util.HashSet;

/**
 * Description: <数组是否有重复元素><br>
 * Author: 门心叼龙<br>
 * Date: 2018/11/21<br>
 * Version: V1.0.0<br>
 * Update: <br>
 */
public class MainAlgorithm {
  public static void main(String[] arg) {
    int[] arr = {11, 3, 10, 11, 34, 5, 21};
    System.out.println(checkRepeat(arr));
  }

  // 查找一个数组里面有没有重复元素
  private static boolean checkRepeat(int[] arr) {
    // 1.声明一个散列表表
    // 2.遍历这个数组
    // 3.对遍历的元素依次进行判断,如果散列表里面没有就往散列表里面塞,有就直接退出了
    HashSet<Integer> hashSet = new HashSet<>();
    for (int i = 0; i < arr.length; i++) {
      if (hashSet.contains(arr[i])) {
        return true;
      } else {
        hashSet.add(arr[i]);
      }
    }
    return false;
  }
}

2.5.5 删除数组中的重复元素

package A数组.A005删除数组重复元素;

import java.util.Arrays;

/**
 * Description: <删除数组重复元素><br>
 * Author: 门心叼龙<br>
 * Date: 2018/11/21<br>
 * Version: V1.0.0<br>
 * Update: <br>
 */
public class MainAgorithm {
  public static void main(String[] arg) {
    int[] arr = {1, 3, 10, 1, 34, 5, 21};
    arr = removeRepeat(arr);
    int i = 0;
    while (i < arr.length) {
      System.out.println(arr[i]);
      i++;
    }
  }

  // 查找一个数组里面有没有重复元素,如果有则删除重复元素
  private static int[] removeRepeat(int[] arr) {
    // 取出第一个元素和剩余的其他元素进行对比
    // 一旦发现相等,则后面的元素都往前移动一个,移动完毕数组
    loop: for (int i = 0; i < arr.length; i++) {
      for (int k = i + 1; k < arr.length; k++) {
        // 如果相等则后面的元素同意往前移动
        if (arr[i] == arr[k]) {
          int head = k;
          while (head < arr.length - 1) {
            arr[head] = arr[head + 1];
            head++;
          }
          // 对数组进行压缩处理
          arr = Arrays.copyOf(arr, arr.length - 1);
          i = 0;
          // 压缩完毕,重头开始执行
          continue loop;
        }
      }
    }
    return arr;
  }
}

2.5.6 两数求和

package A数组.A006两数求和;

import java.util.Arrays;

/**
 * Description: <><br>
 * Author: 门心叼龙<br>
 * Date: 2018/11/21<br>
 * Version: V1.0.0<br>
 * Update: <br>
 */
public class MainAlgorithm {
  public static void main(String[] args) {
    int[] arr = {1, 23, 12, 2, 11, 22};
    int[] res = get(arr, 33);
    System.out.println(Arrays.toString(res));
  }
  static class Data implements Comparable<Data>{
    int index;
    int data;

    public Data(int index, int data) {
      this.index = index;
      this.data = data;
    }

    @Override
    public int compareTo(Data o) {
      return data - o.data;
    }

    @Override
    public String toString() {
      return "Data{" + "index=" + index + ", data=" + data + '}';
    }
  }
  // 指定一个目标数,查找其中两个数之和等于目标数的下标
  public static int[] get(int[] arr, int sum) {
    int[] result = {0, 0};
    // 1.首先对原数组排序
    Data[] arr1 = new Data[arr.length];
    int i = 0;
    while( i < arr.length){
      arr1[i] = new Data(i,arr[i]);
      i++;
    }

    Arrays.sort(arr1);
    System.out.println(Arrays.toString(arr1));
    // 2.声明两个指针,head,tail
    int head = 0;
    int tail = arr.length - 1;
    while (head < tail) {
      if ((arr1[head].data + arr1[tail].data) == sum) {
        result[0] = arr1[head].index;
        result[1] = arr1[tail].index;
        return result;
      } else if ((arr1[head].data + arr1[tail].data) < sum) {
        head++;
      } else {
        tail--;
      }
    }
    return null;
  }
}

2.5.7 求从左上到右下的路径数

package A数组.A007左上到右下路径数;

/**
 * Description: <在一个m*n的矩形方格中, 机器人在最左上的格子里,一次只能挪动一步,只能往右往下移动 目标地点在最右下方的方格里,问共有几条路径 ><br>
 * Author: 门心叼龙<br>
 * Date: 2018/11/23<br>
 * Version: V1.0.0<br>
 * Update: <br>
 */
public class MainAlgorithm {
  public static void main(String[] args) {

    int path = getPath(3, 3);
    System.out.println(path);
    int[][] arr = new int[2][3];
    System.out.println("length:"+arr.length);
  }

  // 动态规划问题
  public static int getPath(int m, int n) {
    int[][] arr = new int[m][n];
    // 将第一行都赋值为1
    for (int i = 0; i < n; i++) {
      arr[0][i] = 1;
    }
    // 将第一列都赋值为1
    for (int i = 0; i < m; i++) {
      arr[i][0] = 1;
    }
    // 给其他单元格赋值
    for (int i = 1; i < m; i++) {
      for (int j = 1; j < n; j++) {
        arr[i][j] = arr[i - 1][j] + arr[i][j - 1];
      }
    }
    return arr[m - 1][n - 1];
  }
}

2.5.8 求左上到右下的路径最小值

package A数组.A008左上到右下路径中的最小值;

/**
 * Description: <在一个m*n的矩形方格中, 机器人在最左上的格子里,一次只能挪动一步,只能往右往下移动 目标地点在最右下方的方格里,问共有几条路径,求路径中的最小值><br>
 * Author: 门心叼龙<br>
 * Date: 2018/11/23<br>
 * Version: V1.0.2<br>
 * Update: <br>
 */
public class MainAlgorithm {
  public static void main(String[] args) {
    int[][] grid = new int[][] {{1, 1, 8}, {3, 1, 4}, {6, 2, 1}};
    int minvalue = getMinvalue(grid);
    System.out.println(minvalue);
  }

  private static int getMinvalue(int[][] grid) {
    int[][] result = new int[grid.length][grid[0].length];
    // 给result[0][0]赋值
    result[0][0] = grid[0][0];
    // 给第一行赋值
    for (int i = 1; i < result[0].length; i++) {
      result[0][i] = result[0][i - 1] + grid[0][i];
    }
    // 给第一列赋值
    for (int i = 1; i < result.length; i++) {
      result[i][0] = result[i - 1][0] + grid[i][0];
    }
    // 给其他元素赋值
    for (int i = 1; i < result.length; i++) {
      for (int j = 1; j < result[0].length; j++) {
        result[i][j] = Math.min(result[i - 1][j], result[i][j - 1]) + grid[i][j];
      }
    }
    return result[result.length - 1][result[0].length - 1];
  }
}

3. 集合

3.1 概念

大家知道数据的致命缺点就是长度固定,如果我们要存储的数据长度不固定,该怎么办?这时候就要用集合了,其实集合也是基于数组实现的,不过它是一个变长数组,想放入多少就可以放入多少。集合就是一个变长数组或者叫做动态数组。

3.2 特点

1.它的长度是可变的

2.他会浪费一定内存空间

3.数据的拷贝会浪费一定的时间

 3.3 适用场景

集合的适用场景非常多,如果博客的文章列表、评论列表等,只要有列表就有集合的身影。

3.4 常见的算法

3.4.1 自定义实现一个集合

现在我们不妨画个流程图方便大家理解集合的工作原理:

代码实现如下:

package B集合.A001自定义实现一个ArrayList;

import java.util.Arrays;

/**
 * Description: <自定义实现一个ArrayList><br>
 * Author: 门心叼龙<br>
 * Date: 2018/11/19<br>
 * Version: V1.0.0<br>
 * Update: <br>
 */
public class MyArrayList {
  private int[] arr;
  private int initsize = 5;
  private int size = 0;

  public MyArrayList() {
    arr = new int[initsize];
  }

  public int get(int index) {
    return arr[index];
  }

  public boolean add(int value) {
    // 说明此时数组已经满了,要扩容了
    if (size == arr.length) {
      System.out.println("数组要扩容了...");
      arr = Arrays.copyOf(arr, size * 2);
    }
    arr[size++] = value;
    return true;
  }

  public boolean remove(int value) {
    if (arr.length > 0) {
      loop: for (int i = 0; i < arr.length; i++) {
        if (arr[i] == value) {
          int temp = i;
          while (temp < arr.length - 1) {
            arr[temp] = arr[temp + 1];
            temp++;
          }
          arr[--size] = 0;
          break loop;
        }
      }
    }
    return true;
  }

  public int size() {
    return size;
  }
}

3.4.2 删除集合中的偶数

package B集合.A002删除集合中的偶数;

import java.util.ArrayList;
import java.util.Iterator;

/**
 * Description: <删除集合中的偶数><br>
 * Author: 门心叼龙<br>
 * Date: 2018/11/21<br>
 * Version: V1.0.0<br>
 * Update: <br>
 */
public class MainAlgorithm {
  public static void main(String[] arg) {
    ArrayList<Integer> list = new ArrayList() {
      {
        add(1);
        add(2);
        add(3);
        add(4);
      }
    };
    removeEvenNumber(list);
    int i = 0;
    while (i < list.size()) {
      System.out.println(list.get(i));
      i++;
    }
  }

  // 删除集合中的偶数元素
  private static void removeEvenNumber(ArrayList<Integer> myArrayList) {
    Iterator<Integer> iterator = myArrayList.iterator();
    while (iterator.hasNext()) {
      Integer next = iterator.next();
      if (next % 2 == 0) {
        iterator.remove();
      }
    }
  }
}

3.5 性能分析

在算法中,每种算法的性能指标一般都有两个,即时间复杂度和空间复杂度。

时间复杂度:它定量的描述了该算法的运行时间。常常用大写的O表示。

空间复杂度:是对一个算法在运行过程中临时占用的存储空间大小的度量。

虽然集合这个变长数组比普通的数组高级一些,但是本质上它还是基于数组实现的,所以它和数组的性能差不多。

对于数组的操作,并不像我们看到的那么直观,计算机需要根据我们具体操作的位置,从头到尾一个一个地寻找到指定的位置,所以我们在数组中增加元素、修改元素、获取元素等操作的时间复杂度都为O(n)。

变长数据也有性能损耗的问题,如果插入的元素发现其中固定的数组的长度不够,则需要建立一个新的更长的数组,还要拷贝元素到新的数组,这都会造成性能损耗。

4. 散列表

4.1 概念

前面我们讲了数组和集合,他们都有一个共同的特点,他们在内存中的存储顺序是有序的,如果数据量很大我们需要在数组或者集合中查找一个元素,或者在数组或集合的头部添加或者删除一个元素,它的性能就会大大降低。

此时散列表就应运而生了,散列表是一种以空间换时间的数据结构。

让我们想一下,若在手机的通信录中查找一个人,那我们应该不会从第1个人一直的查找下去,因为这样实在是太慢了。我们其实是这样做的:首先看这个人的名字的首字母是什么,比如姓赵,那么我们会点击字母z,列表里以字母z开始的人名都会迅速的查找出来,就很快的查找到我们要查找的那个人。

还有我们在查字典的时候,需要查找一个单词,肯定不会从头翻到尾,而是通过这字的首字母,查找到对应的那一页,这样可以速度的跳到那个字所在的页面。

其实这里就用到了散列表的思想。

散列表又叫哈希表,能通过给定的关键字的值直接访问到具体对应的值的一个数据结构。也就是说,通过关键字映射到一个表中的位置来直接访问记录,以加速访问的速度。

而这个关键字就是我通常所说的key,把对应的记录称为value,所以可以说也是通过这个key访问一个映射表来得到value的值,而这个映射表就是所说的散列表,也叫哈希表。

4.2 哈希算法

刚才我们说到,通过关键字映射到一个表中的位置来直接访问记录,这个映射到表中的位置就是通过哈希算法来实现的,目前这个哈希算法的实现的方法比较多,主要有以下一种:

1.直接寻址法

2.数字分析法

3.平方取中法

4.随机数法

5.除留取余法

4.3 哈希冲突

会有这样一种情况,有多个不同的Key通过哈希函数可能会得到相同的地址,这样就会造成对数据的覆盖、丢失。那么解决哈希冲突的处理方式也有很多种,主要有以下几种方法:

1.开放地址法

2.再哈希法

3.链接地址法

4.4 存储结构

一个好的散列表设计,除了需要选择一个性能较好的哈希函数,还要选择一个好的冲突解决方式。这里我们选择除留取余法作为哈希算法,选择链接地址法作为冲突的处理方式。

4.5 特点

散列表有两种用法,一种是key的值和Value的值一样,一般我们称这种数据结构为Set集合;如果Key的值和Value的值不一样,我们称这种情况为Map。

1.增啥改查的速度都很快

2.无序

4.6 适用场景

1.数据缓存

2.快速查找

4.7 性能分析

散列表的访问,如果没有碰撞,那么我们完全可以认为对元素的访问的时间复杂度为O(1)

但是实际上不可能没有碰撞,Java中使用链表来解决,而碰撞之后的访问需要遍历链表,所以时间的复杂度将变为O(L),其中L为链表的长度。

5. 小结

数组,作为数据结构中最为基础的、常用的一个结构。而集合与散列表他们都是在数组的基础上进行稍微高级点的扩展的数据结构,通过对比这三种数据结构,我们更加清楚他们之间的区别和应用场景了,对数组的应用有了更加深入的理解。

源码下载地址:https://download.csdn.net/download/geduo_83/10913510

问题反馈

在使用中有任何问题,欢迎反馈给我,可以用以下联系方式跟我交流

关于作者

  var geduo_83 = {
    nickName  : "门心叼龙",
    site : "http://www.weibo.com/geduo83"
  }

猜你喜欢

转载自blog.csdn.net/geduo_83/article/details/86385566