数据结构与算法
1、数据结构
- 数据结构:就是在计算机的缓存、内存、硬盘中如何组织管理数据的。重点在结构。
- 数据结构分为
- 逻辑结构:思想上的结构,线性表(数组、链表),图,树,栈,队列
- 物理结构:真实结构,紧密结构(顺序结构),跳转结构(链式结构)
-
紧密结构:以数组为例,优点:寻址快—>查找元素块;缺点:删除和增加元素效率低
-
跳转结构:以链表(单向、双向、循环链表)为例,优点:删除和插入元素效率高,查询元素效率低
-
2、集合
- 数组、集合都是对多个数据进行存储操作的,简称容器
- 这些数据的存储是内存层面的存储,而不是持久化存储(.txt、.avi、.jpg、数据库)。
- 数组:长度一旦确定,不可修改。一旦声明了类型,那就只能存放这一种类型的数据。删除、增加元素效率低;数组中实际元素的数量是没有办法获取的;存储有序,可重复。为了解决数组以上缺点,于是引入了集合
2.1 Collection
- 迭代器简要原理图
import java.util.*;
public class Test {
public static void main(String[] args) {
/**
* Collection接口的常用方法:
* 增加:add(E e) addAll(Collection<? extends E> c)
* 删除:clear() remove(Object o)
* 修改:
* 查看:iterator() size()
* 判断:contains(Object 0) equals(Object o) isEmpty()
*/
//创建对象;接口不能创建对象,利用实现类创建对象
Collection col = new ArrayList();
//调用方法
//集合只能存储引用类型的数据,不能是基本类型数据,基本类型自动装箱,对应包装类型
col.add(18);
col.add(12);
col.add(11);
col.add(17);
System.out.println(col/*.toString()*/);//[18, 12, 11, 17]
List list = Arrays.asList(new Integer[]{11, 15, 3, 7, 1});
col.addAll(list);
System.out.println(col);//[18, 12, 11, 17, 11, 15, 3, 7, 1]
// col.clear();
// System.out.println(col);//[]
// System.out.println("集合中的元素的数量为:" + col.size());//集合中的元素的数量为:0
// System.out.println("集合是否为空:" + col.isEmpty());//集合是否为空:true
boolean isRemove = col.remove(15);
System.out.println(col); //[18, 12, 11, 17, 11, 3, 7, 1]
System.out.println("集合中数据是否删除:" + isRemove); //true
Collection col2 = new ArrayList();
col2.add(18);
col2.add(12);
col2.add(11);
col2.add(17);
Collection col3 = new ArrayList();
col3.add(18);
col3.add(12);
col3.add(11);
col3.add(17);
System.out.println(col2.equals(col3)); //true
System.out.println(col2 == col3); //false 比较的是地址,地址一定不相等
System.out.println("是否包含元素:" + col3.contains(17));//true
//对集合遍历
//方式1:普通for循环
// Collection不能用普通for循环
//方式2:增强for循环
for(Object o : col){
System.out.println(o);
}
//方式3:iterator()
Iterator it = col.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
System.out.println("===============");
for(Iterator ite = col.iterator();ite.hasNext();){
System.out.println(ite.next());
}
}
}
2.1.1 List
import java.util.*;
public class Test {
public static void main(String[] args) {
/**
* List 接口的常用方法:
* 增加:add(int index,E element)
* 删除:remove(int index) remove(Object o)
* 修改:set(int index,E element)
* 查看:get(int index)
* 判断:
*/
//创建对象;接口不能创建对象,利用实现类创建对象
List list = new ArrayList();
list.add(18);
list.add(12);
list.add(11);
list.add(17);
list.add(2);
list.add("abc");
System.out.println(list); //[18, 12, 11, 17, 2, abc]
list.add(3,66);
System.out.println(list); //[18, 12, 11, 66, 17, 2, abc]
list.set(3,77);
System.out.println(list); //[18, 12, 11, 77, 17, 2, abc]
list.remove(2);
System.out.println(list); //[18, 12, 77, 17, 2, abc]
list.remove("abc");
System.out.println(list); //[18, 12, 77, 17, 2]
Object o = list.get(0); //18
System.out.println(o);
System.out.println("====================");
//List集合 遍历
//方式1:普通for循环
for(int i = 0;i < list.size();i++){
System.out.println(list.get(i));
}
System.out.println("====================");
//方式2:增强for循环
for (Object obj : list) {
System.out.println(obj);
}
System.out.println("====================");
//方式3:迭代器
Iterator it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
System.out.println("====================");
for(Iterator ite = list.iterator();ite.hasNext();){
System.out.println(ite.next());
}
}
}
2.1.1.1 ArrayList
- JDK1.7源码
- ArrayList实现List接口的失误,由于AbstractList父类实现了List接口,所以子类ArrayList不需要重新实现List接口,后期的版本也没有修正
- 底层是数组,在调用构造器的时候,数组长度初始化10,扩容的时候扩展为原数组的1.5倍
- 底层重要属性
- JDK1.7中,ArrayList对应的内存, ArrayList a1 = new ArrayList();调用空构造器
- 调用add()
- 当数组中的10个位置都满了的时候就开始进行数组的扩容,扩容长度为元素组的1.5倍
- JDK1.8源码
- 底层依旧是Object类型的数组,size是数组中有效长度
- ArrayList a1 = new ArrayList();在调用空构造器的时候,底层elementData数组的初始化为{}
- 调用add()
- 底层仍然是一个数组,在调用构造器的时候,底层数组为{},在调用add方法以后底层数组才重新赋值为新数组,新数组的长度为10,这样做节省了内存,在add后才创建长度为10的数组
- 底层依旧是Object类型的数组,size是数组中有效长度
2.1.1.2 Vector
- 底层Object数组,int类型属性标书数组中有效长度
- Vector v = new Vector();调用构造器
- 调用add()
- ArrayList实现类和Vector实现类的区别与联系
- 联系:底层都是数组的扩容,数组的优点:查询效率高;数组的缺点:删除、增加元素效率低,数组的特点:数据是可重复的
- 区别:ArrayList底层扩容长度为原数组的1.5倍,线程不安全,效率高;而Vector底层扩容长度为原数组的2倍,线程安全,效率低
2.1.1.3 LinkedList
import java.util.Iterator;
import java.util.LinkedList;
public class Test {
public static void main(String[] args) {
/**
* LinkedList接口的常用方法:
* 增加:addFirst(E e) addLast(E e) offer(E e) offerFirst(E e) offerLast(E e)
* 删除:poll() poolFirst() poolLast() JDK1.6以后新出的方法,提高代码的健壮性
* removeFirst() removeLast()
* 修改:
* 查看:element() getFirst() getLast() indexOf(Object o) lastIndexOf(Object o)
* peek() peekFirst() peekLast()
* 判断:
*/
//创建LinkedList集合对象
LinkedList<String> list = new LinkedList<>();
list.add("aaaa");
list.add("bbbb");
list.add("cccc");
list.add("dddd");
list.add("bbbb");
list.add("eeee");
list.add("ffff");
System.out.println(list);//[aaaa, bbbb, cccc, dddd, bbbb, eeee, ffff];LinkedList可以添加重复的数据
list.addFirst("jj");
list.addLast("hh");
System.out.println(list);//[jj, aaaa, bbbb, cccc, dddd, bbbb, eeee, ffff, hh]
list.offer("kk");//添加元素在末尾
System.out.println(list);//[jj, aaaa, bbbb, cccc, dddd, bbbb, eeee, ffff, hh, kk]
list.offerFirst("pp");
list.offerLast("rr");
System.out.println(list);//[pp, jj, aaaa, bbbb, cccc, dddd, bbbb, eeee, ffff, hh, kk, rr]
System.out.println(list.poll()/*删除头元素*/);//pp
System.out.println(list);//[jj, aaaa, bbbb, cccc, dddd, bbbb, eeee, ffff, hh, kk, rr]
System.out.println(list.pollFirst());//jj
System.out.println(list.pollLast());//rr
System.out.println(list.removeFirst());//aaaa
System.out.println(list.removeLast());//kk
// list.clear();
// System.out.println(list);//[]
// System.out.println(list.pollFirst());//null
// System.out.println(list.removeFirst()); //会报错,Exception in thread "main" java.util.NoSuchElementException,不友好
//普通for循环
for(int i = 0;i < list.size();i++){
System.out.println(list.get(i));
}
//增强for循环
for (String s : list) {
System.out.println(s);
}
//while循环的迭代器
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
//for循环的迭代器,此方式节省内存
for(Iterator<String> ite = list.iterator();it.hasNext();){
System.out.println(ite.next());
}
}
}
- LinkedList数据结构
- 物理结构:跳转结构
- 逻辑结构:线性表(链表)
- 双向链表
2.1.1.3.1 自定义LinkedList
public class Node {//结点类
//三个属性
//上一个元素的地址
private Node pre;
//当前存入的元素
private Object obj;
//下一个元素地址
private Node next;
public Node getPre() {
return pre;
}
public void setPre(Node pre) {
this.pre = pre;
}
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
@Override
public String toString() {
return "Node{" +
"pre=" + pre +
", obj=" + obj +
", next=" + next +
'}';
}
}
public class MyLinkedList {
//链中一定有一个首节点
Node first;
//链中一定有一个尾结点
Node last;
//计数器
int count = 0;
//提供一个构造器
public MyLinkedList(){}
//添加元素方法
public void add(Object o){
if(first == null){//证明添加的元素是第一个结点
Node n = new Node();
n.setPre(null);
n.setObj(o);
n.setNext(null);
//当前链中第一个节点变为n
first = n;
//当前链中最后一个节点变为n
last = n;
}else{
Node n = new Node();
n.setPre(last); //n的上一个结点
n.setObj(o);
n.setNext(null);
last.setNext(n);
last = n;
}
//链中元素数量加1
count++;
}
//得到集合中元素的数量
public int getSize(){
return count;
}
//通过下标得到元素
public Object get(int index){
//获取链表的头元素
Node n = first;
for(int i = 0;i < index;i++){
n = n.getNext();
}
return n.getObj();
}
}
class Test{
public static void main(String[] args) {
//创建一个集合对象
MyLinkedList ml = new MyLinkedList();
ml.add("aa");
ml.add("bb");
ml.add("cc");
System.out.println(ml.getSize());
System.out.println(ml.get(2));
}
}
2.1.1.3.2 LinkedList源码
- JDK1.8 底层链表是双向链表
2.1.1.4 迭代器
- iterator()、Iterator、Iterable关系
- hasNext(),next()的具体实现,增强for循环也是通过迭代器实现的
2.1.1.5 ListIterator迭代器
- 报并发修改异常:就是Iterator迭代器和list同时对集合进行操作造成的
import java.util.ArrayList;
import java.util.Iterator;
public class Test2 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
//在"cc"之后添加一个字符串"kk"
Iterator<String> it = list.iterator();
while(it.hasNext()){
if("cc".equals(it.next())){
list.add("kk"); //报并发修改异常;Exception in thread "main" java.util.ConcurrentModificationException
}
}
}
}
- 解决
并发修改异常
的办法:事情让一个"人"来做—>所以就引入了新的迭代器:ListIterator
import java.util.ArrayList;
import java.util.ListIterator;
public class Test2 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
//在"cc"之后添加一个字符串"kk"
ListIterator<String> it = list.listIterator();
while(it.hasNext()){
if("cc".equals(it.next())){
it.add("kk");
}
}
System.out.println(it.hasNext()); //false
System.out.println(it.hasPrevious()); //true
//逆向遍历
while(it.hasPrevious()){
System.out.println(it.previous());
}
System.out.println(it.hasNext()); //true
System.out.println(it.hasPrevious()); //false
System.out.println(list); //[aa, bb, cc, kk, dd, ee]
}
}
2.1.2 Set
- 唯一、无序
- 底层原理:数组+链表 = 哈希表
- 没有跟索引相关的方法
- 遍历方法:1、迭代器;2、增强for循环
2.1.2.1 HashSet底层原理图
- HashSet底层是HashMap来实现
-
int类型数据比较:将比较的数据做差,然后返回一个int类型的数据,将这个int类型的数值 按照=0 >0 <0
-
String类型数据比较
-
比较double类型数据
-
比较自定义类型数据
(1)、内部比较器
public class Student implements Comparable<Student>{
private int age;
private double height;
private String name;
public Student(int age, double height, String name) {
this.age = age;
this.height = height;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", height=" + height +
", name='" + name + '\'' +
'}';
}
@Override
public int compareTo(Student o) {
//按照年龄进行比较
// return this.age - o.getAge();
//按照身高比较
// return ((Double)this.height).compareTo((Double)o.getHeight());
//按照名字比较
return this.name.compareTo(o.getName());
}
}
public class Test01 {
public static void main(String[] args) {
//比较两个学生
Student s1 = new Student(10,160.5,"lili");
Student s2 = new Student(14,170.5,"nana");
System.out.println(s1.compareTo(s2));
}
}
(2)、外部比较器,内部比较器和外部比较器,推荐使用外部比较器,多态,扩展性好
import java.util.Comparator;
public class Student{
private int age;
private double height;
private String name;
public Student(int age, double height, String name) {
this.age = age;
this.height = height;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", height=" + height +
", name='" + name + '\'' +
'}';
}
}
class ComparatorImp1 implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
//比较年龄
return o1.getAge() - o2.getAge();
}
}
class ComparatorImp2 implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
//比较年龄
return o1.getName().compareTo(o2.getName());
}
}
class ComparatorImp3 implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
//年龄相同的情况下,比较身高
if((o1.getAge() ^ o2.getAge()) == 0){
return ((Double)o1.getHeight()).compareTo((Double) o2.getHeight());
}
return o1.getAge() - o2.getAge();
}
}
public class Test01 {
public static void main(String[] args) {
//比较两个学生
Student s1 = new Student(10,160.5,"lili");
Student s2 = new Student(14,170.5,"nana");
//比较年龄
Comparator com = new ComparatorImp1();
System.out.println(com.compare(s1, s2));
//比较姓名
Comparator com2 = new ComparatorImp2();
System.out.println(com2.compare(s1,s2));
//年龄相同,比较身高
Comparator com3 = new ComparatorImp2();
System.out.println(com2.compare(s1,s2));
}
}
2.1.2.2 LinkedHashSet
- 唯一,有序(按照输入顺序进行输出)
- 在HashSet的基础上,多了一个总的链表,这个总链表将放入的元素串在一起,方便有序的遍历
2.1.2.3 TreeSet
- 唯一,升序
- 底层是二叉树,在树中放入数据的时候,最重要的是比较,通过内部比较器或者外部比较器实现的比较
- Integer类型的数据,底层利用的是内部比较器
- String类型的数据,底层利用的是内部比较器
- 自定义类型的数据,对于自定义的数据必须实现比较器,底层可以用内部比较器和外部比较器
- TreeSet在进行遍历的时候得到的是升序的结果,是靠中序遍历实现
- 二叉树的遍历:
- 中序遍历:左 根 右
- 先序遍历:根 左 右
- 后序遍历:左 右 根
(1)、内部比较器
public class Student implements Comparable<Student>{
private int age;
private String name;
public Student(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
@Override
public int compareTo(Student o) {
return this.age - o.getAge();
}
}
import java.util.TreeSet;
public class Test01 {
public static void main(String[] args) {
//创建一个TreeSet
TreeSet<Student> ts = new TreeSet<>();
ts.add(new Student(10,"lili"));
ts.add(new Student(10,"lili"));
ts.add(new Student(8,"alili"));
ts.add(new Student(10,"clili"));
System.out.println(ts.size());
System.out.println(ts);
}
}
(2)、外部比较器
import java.util.Comparator;
public class Student{
private int age;
private String name;
public Student(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
class ComparatorImp implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.getAge() - o2.getAge();
}
}
import java.util.Comparator;
import java.util.TreeSet;
public class Test01 {
public static void main(String[] args) {
//创建一个TreeSet
Comparator<Student> com = new ComparatorImp();
TreeSet<Student> ts = new TreeSet<>(com);//一旦制定外部比较器,那么就会按照外部比较器来比较
ts.add(new Student(10,"lili"));
ts.add(new Student(10,"lili"));
ts.add(new Student(8,"alili"));
ts.add(new Student(10,"clili"));
System.out.println(ts.size());
System.out.println(ts);
}
}
(3)、利用匿名内部类的外部比较器
public class Student{
private int age;
private String name;
public Student(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
import java.util.TreeSet;
public class Test01 {
public static void main(String[] args) {
//创建一个TreeSet
//利用匿名内部类实现的外部比较器,那么就会按照外部比较器来比较
TreeSet<Student> ts = new TreeSet<>((o1,o2) -> o1.getAge() - o2.getAge());
ts.add(new Student(10,"lili"));
ts.add(new Student(10,"lili"));
ts.add(new Student(8,"alili"));
ts.add(new Student(10,"clili"));
System.out.println(ts.size());
System.out.println(ts);
}
}
2.2 Map
- 唯一,无序
- HashMap
2.2.1 HashMap源码
- 加载因子为什么是0.75?
- 加载因子设置为1:空间利用率得到了很大的满足,很容易碰撞,产生链表---->查询效率低
- 加载因子设置为0.5:碰撞的概率低,扩容,产生的链表的几率低,查询效率高,空间利用率太低。
- 主数组的长度为什么必须为 2^n:
- h & (length - 1)等效与h%length操作,等效的前提是:length必须是2的整数倍
- 防止哈希冲突,位置冲突
- h&(length - 1)与h%length等效的
2.2.2 TreeMap源码
2.3 Collections
- 工具类,不能创建对象,只能用类名调用
import java.util.ArrayList;
import java.util.Collections;
public class Test {
public static void main(String[] args) {
//Collections不能创建对象,因为构造器私有化了
//里面的属性和方法都是被static修饰,可以直接用类名去调用
ArrayList<String> list = new ArrayList<>();
list.add("aa");
list.add("cc");
list.add("bb");
list.add("dd");
//addAll
Collections.addAll(list,"fee","fff","ggg");
System.out.println(list);//[aa, cc, bb, dd, fee, fff, ggg]
Collections.addAll(list,new String[]{"pp","oo","gg"});
System.out.println(list);//[aa, cc, bb, dd, fee, fff, ggg, pp, oo, gg]
//binarySearch必须在有序的集合中查找:所以集合先要排序
Collections.sort(list);
System.out.println(list);//[aa, bb, cc, dd, fee, fff, gg, ggg, oo, pp]
//binarySearch
System.out.println(Collections.binarySearch(list, "cc"));//2
//copy
ArrayList<String> list2 = new ArrayList<>();
Collections.addAll(list2,"tt","ss");
Collections.copy(list,list2);//将list2的内容替换到list上去;
System.out.println(list);//[tt, ss, cc, dd, fee, fff, gg, ggg, oo, pp]
System.out.println(list2);//[tt, ss]
//fill
Collections.fill(list2,"yyy");
System.out.println(list2);//[yyy, yyy]
}
}