常见的数据结构
概述
数据结构 : 其实就是存储数据和表示数据的方式。数据结构内容比较多,细细的学起来也是相对费功夫的,不可能达到一蹴而就。我们将常见的数据结构:堆栈、队列、数组、链表和红黑树 这几种给大家介绍一下,作为数据结构的入门,了解一下它们的特点即可。
栈
栈:stack,又称堆栈,它是运算受限的线性表,其限制是仅允许在表的一端进行插入和删除操作,不允许在其他任何位置进行添加、查找、删除等操作。 简单的说:采用该结构的集合,对元素的存取有如下的特点
- 先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。例如,子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的子弹。
-
栈的入口、出口的都是栈的顶端位置。
这里两个名词需要注意:
- 压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。
- 弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。
队列
队列:queue,简称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许在表的一端进行插入,而在表的另一端进行取出并删除。简单的说,采用该结构的集合,对元素的存取有如下的特点:
- 先进先出(即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素)。例如,小火车过山洞,车头先进去,车尾后进去;车头先出来,车尾后出来。
- 队列的入口、出口各占一侧。例如,下图中的左侧为入口,右侧为出口。
数组
数组:Array,是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。就像是一排出租屋,有100个房间,从001到100每个房间都有固定编号,通过编号就可以快速找到租房子的人。简单的说,采用该结构的集合,对元素的存取有如下的特点:
-
查找元素快:通过索引,可以快速访问指定位置的元素
- 增删元素慢
指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。如下图
指定索引位置删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中。如下图
链表
-
链表:linked list,由一系列结点node(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。我们常说的链表结构有单向链表与双向链表,那么这里给大家介绍的是单向链表。简单的说,采用该结构的集合,对元素的存取有如下的特点:
简单的说,采用该结构的集合,对元素的存取有如下的特点:
- 多个结点之间,通过地址进行连接。例如,多个人手拉手,每个人使用自己的右手拉住下个人的左手,依次类推,这样多个人就连在一起了。
- 查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素。
- 增删元素快:只需要修改链接下一个元素的地址值即可
树
树具有的特点:
- 每一个节点有零个或者多个子节点
- 没有父节点的节点称之为根节点,一个树最多有一个根节点。
- 每一个非根节点有且只有一个父节点
常见的树结构
- 二叉树:如果树中的每个节点的子节点的个数不超过2,那么该树就是一个二叉树。
- 平衡二叉树:它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树
- 红黑树:是一种自平衡的二叉查找树。作用:提高搜索效率
List接口
概述
java.util.List接口继承自Collection接口,是单列集合的一个重要分支,习惯性地会将实现了List接口的对象称为List集合。
List接口特点
- 它是一个元素存取有序的集合。例如,存元素的顺序是11、22、33。那么集合中,元素的存储就是按照11、22、33的顺序完成的)。
- 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
- 集合中可以有重复的元素。
List接口新增常用方法
List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法。
- public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。
- public E get(int index):返回集合中指定位置的元素。
- public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。
- public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回值的更新前的元素。
代码示例
package demo04;
import java.util.ArrayList;
import java.util.List;
public class Demo {
public static void main(String[] args) {
// 创建list集合,限制集合中元素的类型为String类型
List<String> list = new ArrayList<>();
// 往集合中添加一些元素
list.add("张三");
list.add("李四");
list.add("王五");
System.out.println(list);// [张三, 李四, 王五]
// 在索引为1的位置添加赵六
list.add(1, "赵六");
System.out.println(list);// [张三, 赵六, 李四, 王五]
// 获取索引为1的元素
System.out.println("索引为1的元素:"+list.get(1));// 索引为1的元素:赵六
// 删除索引为1的元素
String removeE = list.remove(1);
System.out.println("被删除的元素:"+removeE);// 被删除的元素:赵六
System.out.println(list);// [张三, 李四, 王五]
// 把索引为0的元素替换为田七
String setE = list.set(0, "田七");
System.out.println("被替换的元素:"+setE);// 被替换的元素:张三
System.out.println(list);// [田七, 李四, 王五]
}
}
List的常用子类
ArrayList类
- java.util.ArrayList集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList是最常用的集合。许多程序员开发时非常随意地使用ArrayList完成任何需求,并不严谨,这种用法是不提倡的。
LinkedList类
java.util.LinkedList集合数据存储的结构是链表结构。方便元素添加、删除的集合。
实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。这些方法我们作为了解即可:
LinkedList集合特有的方法:
- public void addFirst(E e):将指定元素插入此列表的开头。
- public void addLast(E e):将指定元素添加到此列表的结尾。
- public E getFirst( ):返回此列表的第一个元素。
- public E getLast( ):返回此列表的最后一个元素。
- public E removeFirst( ):移除并返回此列表的第一个元素。
- public E removeLast( ):移除并返回此列表的最后一个元素。
- public E pop( ):从此列表所表示的堆栈处弹出一个元素。
- public void push(E e):将元素推入此列表所表示的堆栈。
代码示例
package demo04;
import java.util.LinkedList;
public class Demo {
public static void main(String[] args) {
// 创建LinkedList集合,限制集合元素的类型为String类型
LinkedList<String> list = new LinkedList<>();
// 往集合中添加元素
list.add("张三");
list.add("李四");
list.add("王五");
System.out.println(list);// [张三, 李四, 王五]
// 在集合的首尾添加一个元素
list.addFirst("赵六");
list.addLast("田七");
System.out.println(list);// [赵六, 张三, 李四, 王五, 田七]
// 获取集合的首尾元素
String firstE = list.getFirst();
String lastE = list.getLast();
System.out.println("第一个元素是:"+firstE);// 第一个元素是:赵六
System.out.println("最后一个元素是:"+lastE);// 最后一个元素是:田七
// 删除首尾元素
String removeFirst = list.removeFirst();
String removeLast = list.removeLast();
System.out.println("被删除的第一个元素是:"+removeFirst);// 被删除的第一个元素是:赵六
System.out.println("被删除的最后一个元素是:"+removeLast);// 被删除的最后一个元素是:田七
System.out.println(list);// [张三, 李四, 王五]
// pop --->删除第一个元素
String popE = list.pop();
System.out.println("被删除的第一个元素是:"+popE);// 被删除的第一个元素是:张三
System.out.println(list);// [李四, 王五]
// push --->添加一个元素在开头
list.push("富贵");
System.out.println(list); // [富贵, 李四, 王五]
}
}
常见的实现类还有。Vector:底层数据结构动态数组。Stack:底层数据结构栈 .....
Set集合
概述
- Set接口是Collection的子接口,set接口没有提供额外的方法。但是比Collection接口更加严格了。Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个 Set 集合中,则添加操作失败。
- Set集合没有索引,所以遍历元素的方式就只有:增强for循环,或者迭代器。
- Set的常用实现类有:HashSet、TreeSet、LinkedHashSet。凡是实现了Set接口的类都叫做Set集合
import java.util.HashSet;
public class Test {
public static void main(String[] args) {
// 创建HashSet集合对象,限制集合元素的类型为Integer
HashSet<Integer> hashSet = new HashSet<>();
//添加数据
hashSet.add(1);
hashSet.add(2);
hashSet.add(3);
hashSet.add(1);
hashSet.add(16);
hashSet.add(6);
/*
HashSet集合: 元素存取无序,元素不可重复,元素无索引
*/
System.out.println(hashSet);// [16, 1, 2, 3, 6]
}
}
Set接口的常用子类
HashSet类
HashSet 是 Set 接口的典型实现,大多数时候使用 Set 集合时都使用这个实现类。 java.util.HashSet底层的实现其实是一个java.util.HashMap支持,然后HashMap的底层物理实现是一个哈希表。
哈希表底层结构
在JDK1.8之前,哈希表底层采用数组+链表实现,即使用数组处理冲突,同一hash值的链表都存储在一个数组里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。
而JDK1.8之后,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值8时,将链表转换为红黑树,这样大大减少了查找时间。简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的。如下图所示。
HashSet保证元素唯一原理:依赖hashCode()和equals()方法
- 创建一个默认长度为16的数组
- 当存储元素的时候,就会调用该元素的hashCode()方法计算该元素的哈希值,存储到数组对应的位置
- 判断该哈希值对应的位置上,是否有元素:
- 如果该哈希值对应的位置上,没有元素,就直接存储
- 如果该哈希值对应的位置上,有元素,说明产生了哈希冲突
- 产生了哈希冲突,就得调用该元素的equals方法,与该位置上的所有元素进行一 一比较。如果比较的时候,有任意一个元素与该元素相同,那么就不存储。 如果比较完了,没有一个元素与该元素相同,那么就直接存储
HashSet存储自定义类型元素
给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一.
自定义类
package demo02;
import java.util.Objects;
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
//其他代码省略
@Override
//重写 equals
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
@Override
//重写 hashCode
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
定义测试类
import java.util.HashSet;
public class Test {
public static void main(String[] args) {
// 创建HashSet集合对象,限制集合元素的类型为Student
HashSet<Student> hashSet = new HashSet<>();
//添加元素,我们认为属性值一样就是同一个对象
hashSet.add(new Student("张三",18));
hashSet.add(new Student("王五",18));
hashSet.add(new Student("李四",19));
hashSet.add(new Student("张三",18));
hashSet.add(new Student("赵六",15));
hashSet.add(new Student("张三",18));
System.out.println(hashSet);
/*
输出结果 如下
[Student{name='王五', age=18}, Student{name='张三', age=18}, Student{name='赵六', age=15}, Student{name='李四', age=19}]
*/
}
}
LinkedHashSet类
我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?在HashSet下面有一个子类java.util.LinkedHashSet,它是链表和哈希表组合的一个数据存储结构。LinkedHashSet是HashSet的子类,比父类多维护了元素的添加顺序。当且仅当,你既想要元素不可重复,又要保证元素的添加顺序时,再使用它。
import java.util.HashSet;
import java.util.LinkedHashSet;
public class Test {
public static void main(String[] args) {
// 创建LinkedHashSet集合对象,限制集合元素的类型为Integer
HashSet<Integer> set = new LinkedHashSet<>();
// 往集合中存储数据
set.add(1);
set.add(2);
set.add(3);
set.add(4);
set.add(5);
set.add(4);
/*
LinkedHashSet集合: 元素存取有序,元素无索引,元素不可重复(唯一)
采用哈希表+链表结构,由哈希表保证元素唯一,由链表保证元素存取有序
*/
System.out.println(set); //[1, 2, 3, 4, 5]
}
}
TreeSet类
TreeSet集合是Set接口的一个实现类,底层依赖于TreeMap,是一种基于红黑树的实现,其特点为:
- 元素唯一
- 元素没有索引
- 使用元素的自然排序对元素进行排序,或者根据创建 TreeSet 时提供的 Comparator 比较器 进行排序,具体取决于使用的构造方法。
构造方法
- public TreeSet():根据其元素的自然排序进行排序
- public TreeSet(Comparator<E> comparator): 根据指定的比较器进行排序 指定规则排序
如何排序?
- 自然排序:让待添加的元素类型实现Comparable接口,并重写compareTo方法
- 定制排序:创建Set对象时,指定Comparator比较器接口,并实现compare方法
代码示例
package demo02;
import java.util.Comparator;
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
// 按照默认规则排序---->默认升序
// 创建TreeSet集合,限制集合中元素的类型为Integer类型
TreeSet<Integer> set = new TreeSet<>();
// 往集合中存储数据
set.add(300);
set.add(100);
set.add(200);
set.add(500);
set.add(400);
set.add(400);
System.out.println(set);// [100, 200, 300, 400, 500]
// 按照指定规则排序---->降序
// 创建TreeSet集合,限制集合中元素的类型为Integer类型
TreeSet<Integer> set1 = new TreeSet<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
/*
指定排序规则:
前减后 升序
后减前 降序
前:第一个参数 后:第二个参数
*/
return o2 - o1;
}
});
// 往集合中存储数据
set1.add(300);
set1.add(100);
set1.add(200);
set1.add(500);
set1.add(400);
set1.add(400);
System.out.println(set1); //[500, 400, 300, 200, 100]
}
}
Collections工具类
Collections 是一个操作 Set、List 和 Map 等集合的工具类。Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作,还提供了对集合对象设置不可变、对集合对象实现同步控制等方法。
常用方法:
- public static void shuffle(List<?> list) :打乱集合顺序。
- public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。默认规则就是事先写好的规则。集合元素所属的类一定要实现Comparable接口,重写compareTo方法,在compareTo方法中指定排序规则
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Test {
public static void main(String[] args) {
// 创建List集合,限制集合中元素的类型为Integer类型
List<Integer> list = new ArrayList<>();
// 往集合中添加元素
list.add(3);
list.add(1);
list.add(2);
list.add(5);
list.add(4);
System.out.println("打乱顺序之前的集合:" + list);// 打乱顺序之前的集合:[3, 1, 2, 5, 4]
// 打乱顺序
Collections.shuffle(list); // 随机打乱顺序
System.out.println("打乱顺序之后的集合:" + list);// 打乱顺序之后的集合:[2, 1, 4, 5, 3]
// 将集合中元素按照默认规则排序
Collections.sort(list);
System.out.println("排序之后的集合:" + list); // 排序之后的集合:[1, 2, 3, 4, 5]
}
}
如果集合中的元素是我们自定义的类,要使用默认规则进行排序,就必须实现Comparator接口中的compare方法来指定排序规则,代码示例
public class Student implements Comparable<Student> {
private int age;
public Student(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
'}';
}
@Override
//排序规则
public int compareTo(Student o) {
/*
排序规则:
前减后 升序
后减前 降序
前: this 后:参数
*/
//按照年龄从大到小排序
return o.age - this.age;
}
//其他代码省略
}
测试类
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Demo {
public static void main(String[] args) {
// 创建List集合,限制集合中元素的类型为Student类型
List<Student> list = new ArrayList<>();
// 往集合中添加元素
list.add(new Student(3));
list.add(new Student(2));
list.add(new Student(311));
list.add(new Student(23));
list.add(new Student(0));
System.out.println("排序之前的集合:"+list);
// 输出结果: 排序之前的集合:[Student{age=3}, Student{age=2}, Student{age=311}, Student{age=23}, Student{age=0}]
// 将集合中元素按照默认规则排序
Collections.sort(list);
System.out.println("排序之后的集合:"+list);
//输出结果 :排序之后的集合:[Student{age=311}, Student{age=23}, Student{age=3}, Student{age=2}, Student{age=0}]
}
}
- public static <T> void sort (List<T> list,Comparator<? super T> ):将集合中元素按照指定规则排序。
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Test {
public static void main(String[] args) {
// 创建List集合,限制集合中元素的类型为Integer类型
List<Integer> list = new ArrayList<>();
// 往集合中添加元素
list.add(3);
list.add(1);
list.add(2);
list.add(5);
list.add(4);
System.out.println("打乱顺序之前的集合:" + list);// 打乱顺序之前的集合:[3, 1, 2, 5, 4]
// 将集合中元素按照默认规则排序
Collections.sort(list);
System.out.println("排序之后的集合:" + list); // 排序之后的集合:[1, 2, 3, 4, 5]
//对集合中的元素按指定规则排序
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
// 指定排序规则
// 前减后 升序
// 后减前 降序
// 前: 第一个参数o1 后:第二个参数o2
return o2-o1;
}
});
System.out.println("排序之后的集合:" + list); // 排序之后的集合:[5, 4, 3, 2, 1]
}
}
其他方法:
- public static void reverse(List<?> list):反转指定列表List中元素的顺序。
- public static void swap(List<?> list,int i,int j):将指定 list 集合中的 i 处元素和 j 处元素进行交换
- public static int frequency(Collection<?> c,Object o):返回指定集合中指定元素的出现次数
- public static <T> void copy(List<? super T> dest,List<? extends T> src):将src中的内容复制到dest中
Collections工具类中还有许许多多的方法,我们可以根据实际需求去API中查询使用
可变参数
前提:在JDK1.5之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致,我们可以对其简化.
public class Test {
public static void main(String[] args) {
getSum(1, 2, 3, 4);
}
//定义了可变参数的方法
public static void getSum(int... number) {
// 使用:把number可变参数当成数组使用
int sum = 0;
for (int i : number) {
sum += i;
}
System.out.println("方法参数和:" + sum); //方法参数和:10
}
}
注意事项:
- 可以变参数可以接收的参数数量是0 到n 个
- 可变参数可以接收该类型的数组
- 一个方法只能有一个可变参数
/*
编译报错,因为一个方法,只能有一个可变参数
public static void method1(int... nums,String... strs){
}*/
- 如果方法中有多个参数,可变参数要放到最后。
/*
编译报错,因为如果方法有多个参数,可变参数一定要放在末尾
public static void method2(int... nums,String str){
}
*/
在Collections中也提供了添加一些元素方法:
- public static <T> boolean addAll(Collection<T> c, T... elements) :往集合中添加一些元素。
import java.util.ArrayList;
import java.util.Collections;
public class Test {
public static void main(String[] args) {
// 创建ArrayList集合,限制集合元素的类型为Integer类型
ArrayList<Integer> arrayList = new ArrayList<>();
// 往list集合中添加批量元素
Collections.addAll(arrayList, 1111111, 2, 3, 45, 6, 7, 8);
System.out.println(arrayList); //[1111111, 2, 3, 45, 6, 7, 8]
}
}