基础数据结构总结

数据结构复习

前言
数据结构(hashmap基本原理,currenthashmap基本原理,二叉树,平衡二叉树,红黑树,单例模式手写,
工厂模式手写,快速排序手写,堆排序概念,jvm堆列栈概念,JVM垃圾回收概念(算法看自己想不先做,说出来加分)
,java集合全部概念,什么是链表,java实现链表相关,java多线程,线程锁概念,线程池概念,
所有排序算法时间复杂度(要先懂什么是时间复杂度))

HashMap

hashmap基本原理:
HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体
底层基本就是数组+链表
根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,
那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。
如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。
1.8以后引入红黑树
约定前面的数组结构的每一个格格称为桶
约定桶后面存放的每一个数据称为bin
size
size表示HashMap中存放KV的数量(为链表和树中的KV的总和)。

capacity
capacity译为容量。capacity就是指HashMap中桶的数量。默认值为16。一般第一次扩容时会扩容到64,之后好像是2倍。
总之,容量都是2的幂。
loadFactor
loadFactor译为装载因子。装载因子用来衡量HashMap满的程度。loadFactor的默认值为0.75f。
计算HashMap的实时装载因子的方法为:size/capacity

链表的长度大于8,转换为红黑树

一个桶的树化阈值 8
一个树的链表还原阈值 6
哈希表的最小树形化容量 64

HashMap在并发执行put操作时会引起死循环,是因为多线程会导致HashMap的Entry链表形成环,
一旦成环,Entry的next节点永远不为空,产生死循环.

currentHashMap:
锁分段技术可有效提升并发访问率
HashTable在竞争激烈的并发环境下表现出效率低下的原因是所有访问HashTable的线程都必须竞争同一把锁
假如容器里有多把锁,每一把锁用于锁容器其中一部分的数据,那么当多线程访问容器里不同数据段的数据时,
线程间就不会存在锁竞争,从而可以有效提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术

  • 首先将数据分成一段一段地存储
  • 然后给每一段数据配一把锁
  • 当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问

ConcurrentHashMap是由Segment数组和HashEntry数组组成.
Segment是一种可重入锁,在ConcurrentHashMap里扮演锁的角色;
HashEntry则用于存储键值对数据.
一个ConcurrentHashMap里包含一个Segment数组.
Segment的结构和HashMap类似,是一种数组和链表结构.
一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素,每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,
必须首先获得与它对应的Segment锁
Segment 继承于 ReentrantLock

二叉树

二叉树:所有结点都是充实的,没有空缺的结点。

完全二叉树:假设该二叉树为K层,则(K-1)层为满,且叶子结点,全部集中在左侧。

非完全二叉树:即普通二叉树

二叉树的遍历:
先序遍历(根左右)
中序遍历(左根右)
后序遍历(左右根)


平衡二叉树:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树

当最小不平衡树根结点的平衡因子BF是大于1时,就右旋,小于-1时就左旋

旋转的都是最小不平衡子树
如果有一边子树符号出现不统一,要先进行符号的统一,再进行旋转

红黑树

先了解一下二叉查找树
二叉查找树(BST)具备什么特性呢?
1.左子树上所有结点的值均小于或等于它的根结点的值。

2.右子树上所有结点的值均大于或等于它的根结点的值。

3.左、右子树也分别为二叉排序树。

二分查找的思想,查找所需要的次数等同于二叉查找树的高度

红黑树特点:
1.节点是红色或黑色。

2.根节点是黑色。

3.每个叶子节点都是黑色的空节点(NIL节点)。

4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)

5.从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点

红黑树的调整:
变色和旋转

单例模式

懒汉模式:
1.设计一个本类类型的静态私有属性
2.构造方法私有
3.设计一个静态方法,返回类型是本类类型

	  private static Lazysingle  instance;
	  private Lazysingle(){
	  }
	  public static Lazysingle getInstance(){
		return instance;
	  }

饿汉模式:
1.设计一个本类类型的静态私有属性并赋值
2.私有化构造方法
3.设计一个静态方法,返回类型是本类类型

	  private static HungrySingle instance = new HungrySingle();
	  private HungrySingle(){
	  }
	  public static HungrySingle getInstance(){
		return instance;
	  }

枚举单例:

	public enum A {
		INSTANCE
		public void show(){
		}
	}

双重锁单例:

	class Lazy{
		private volatile static Lazy ls;
		private Lazy(){
		}
		public static Lazy get(){
			if(ls == null)
			  synchronized(Lazy.class)
			  if(ls == null){
				ls = new Lazy();
			  }
			  return ls;
		}
	}

工厂模式:

public interface AnimalFactory{
	Animal getAnimal();
}
public class DogFactory implements AnimalFactory{
	@Override
	public Animal getAnimal(){
		return new Dog();
	}
}
public class CatFactory implements AnimalFactory{
	@Override
	public Animal getAnimal(){
		return new Cat();
	}
}
public static void main(String[] args){
	DogFactory dogFactory = new DogFactory();
	Dog dog = (Dog)dogFactory.getAnimal();
	dog.introduce();
	dog.eatFood();
	
	CatFactory catFactory = new CatFactory();
	Cat cat = (Cat)catFactory.getAnimal();
	cat.introduce();
	cat.eatFood();
}

排序算法

快速排序

快速排序(Quick Sort)是对冒泡排序的一种改进,基本思想是选取一个记录作为枢轴,
经过一趟排序,将整段序列分为两个部分,其中一部分的值都小于枢轴,另一部分都大于枢轴。
然后继续对这两部分继续进行排序,从而使整个序列达到有序。
挖坑法:

import java.util.Arrays;

public class QuickSort {
    public static void quickSort(int startIndex , int endIndex ,int [] arr){
        //递归结束条件
        if (startIndex >= endIndex){
            return;
        }
        //得到基准元素的位置
        int pivotIndex= partition(arr, startIndex, endIndex);
        //分治
        quickSort(startIndex,pivotIndex-1,arr);
        quickSort(pivotIndex+1,endIndex,arr);
    }

    public static int partition(int [] arr,int startIndex,int endIndex){
        //基准元素
        int povot = arr[startIndex];
        int left = startIndex;
        int right = endIndex;
        //基准元素下标
        int index = startIndex;
        //大循环在左右指针交互时结束
        while (right >= left){
            //right指针从右向左比较
            while (right >= left){
                if (arr[right] < povot){
                    arr[left]= arr[right];
                    index = right;
                    left++;
                    break;
                }
                right --;
            }
            //left指针从左向右比较
            while (right >= left){
                if (arr[left] > povot){
                    arr[right] = arr[left];
                    index = left;
                    right--;
                    break;
                }
                left++;
            }
        }
        arr[index] = povot;
        return index;
    }
    public static void main(String[] args) {
        int arr[] = new int[] {5,7,6,5,3,2,8,1};
        quickSort(0,arr.length-1,arr);
        System.out.println(Arrays.toString(arr));
    }
}

堆排序

插入元素:上浮(swim)操作会保持堆有序
每个叶子节点可以视为一个大小为一的堆,我们可以自底向上从非叶子节点开始每层从右至左给每个节点都调用下沉(sink)方法,
这样以当前节点为根节点的树就变为堆了

建立小根堆:

 public void buildHeap(int[] arr){
	for(int i=heapSize/2;i>=1;i--){
		sink(arr,i);
	}
  }

删除最小值代码,逻辑上删除heapSize–

private void deleteMin(int[] arr){
	swap(arr,1,heapSize);//交换堆顶元素和堆最后一个元素
	heapSize--;
	sink(arr,1);//调整使之堆有序
  }

降序排序:

public void heapSort(int[] arr){
	int N = arr.length-1;
	buildHeap(arr);
	for(int i = 0;i<=N-1;i++){
		deleteMin(arr);
	}
  }

只需要删除N-1次,剩下的那个自然最大

建立堆时间复杂度为O(n)
N-1次删除复杂度为O(nlog)
两者去较大的:所以堆排序时间复杂度为O(nlogn)

堆排序不稳定。

排序算法还有很多,例如一些归并排序,计数排序,鸡尾酒排序,桶排序==,很多 以后有时间了总结

JVM垃圾回收概念

垃圾回收指存在于内存中的、不会再次被使用的对象,而回收则是相当于将原来对象所占用的内存进行清除,
这样的话就能够有足够的空间进行运行,所以如果我们不对产生的对象进行清除的话那么就会出现内存全部占用,
造成内存溢出的情况,所以,垃圾回收也算一种常用的内存清理,

1.引用计数法
给对象加引用计数器
缺点:算法会失效,会耗性能
2.标记清除法
从根节点开始标记了存活的对象,清除阶段将不可达对象(垃圾对象)进行了清除,但是可以发现,
清楚后的空间并不是连续的,在对象的堆空间分配过程中尤其是大对象的内存分配,
不连续的内存的工作效率要低于连续的空间,因此这个是该算法的最大的缺陷。
3.复制算法
复制算法的核心是:将原有的内存空间分为两个部分,每次只使用其中的一个部分,
在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存中,
然后清除正在使用的内存中的所有的对象,交换两个内存的角色,完成垃圾回收的过程。
4.标记压缩法
和标记清除法差不多,就是压缩存活对象在内存另一端,然后清除占用的内存
既避免了碎片空间的产生,同时又不需要两块相同的内存空间,因此性价比较高
5.分代算法
新生代适用复制算法,老年代则使用标记压缩方法或者是标记清除法,提高垃圾的回收效率。
6.分区算法

java集合全部概念

集合是储存对象的,长度可变,可以封装不同的对象
迭代器: 其实就是取出元素的方式(只能判断,取出,移除,无法增加)
Iterator it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
Collection:

(1).List:元素是有序的,元素可以重复.因为改集合体系有索引.

!--ArrayList:底层的数据结构使用的是数组结构 ,特点:查询速度快,但是增删慢.线程不同步

!--LinkedList:底层使用的是;链表数据结构,特点:增删速度快,查寻速度慢.   

!--Vector:底层的数据结构使用的是数组结构 ,.线程同步,速度慢,被ArrayList替代l

list:特有方法,凡是可以操作角标的方法都是该体系特有的方法

增:add(index,element); addAll(index,Collection);

删:remove(index);

改:set(index,element);

查:get(index); subList(from,to); ListIterator();(重点)

注意:判断同一对象,equals方法

public boolean equals(Object obj){
if(!(obj instanceof Person))

return false;
Person p=new Person();

return this.name.equals(p.name)&&this.age==p.age;

}

Set:元素是无序的(元素存入和取出的顺序是不一定一致的),不可以重复

!--HashSet:底层数据结构是哈希表

    HashSet使如何保证元素唯一性的呢?

    是通过元素的两个方法,hashcode和equals来完成

    如果元素的HashCode相同才会判断equals是否为true,反の,不会调用

    对于判断元素是否存在,以及删除等操作,依赖的方法是元素的hashcode和equals方法

1.toArray()方法:是遍历用的(把集合转变为数组)
2.asList()方法,把数组转变为集合
3.list集合特有的遍历方法:就是size和get()的结合

for(int x=0; x<list.size();x++){
System.out.println(list.get(x));
}

4.HashTable和HashMap的区别
HashTable:线程安全,效率低,不允许null建和null值.
HashMap:线程不安全,效率高,允许null建和null值.
5.Collection和Collections的区别?
Collection:是单列集合的顶层接口,有子接口List和Set
Collections:是针对集合操作的工具类,有对集合进行排序和二分查找的方法

Map: HashMap 底层是数组加链表,1.8以后引入红黑树。

什么是链表?
链式存储的线性表,简称链表。链表由多个链表元素组成,这些元素称为节点。结点之间通过逻辑连接,形成链式存储结构。
存储结点的内存单元,可以是连续的也可以是不连续的。逻辑连接与物理存储次序没有关系。

链表分为两个域:
值域:用于存放结点的值
链域:用于存放下一个结点的地址或位置

从内存角度出发: 链表可分为 静态链表、动态链表。
从链表存储方式的角度出发:链表可分为 单链表、双链表、以及循环链表。

单链表:
单链表是一种顺序存储的结构。
有一个头结点,没有值域,只有连域,专门存放第一个结点的地址。
有一个尾结点,有值域,也有链域,链域值始终为NULL。
所以,在单链表中为找第i个结点或数据元素,必须先找到第i - 1 结点或数据元素,而且必须知道头结点,否者整个链表无法访问。

循环链表:
循环链表,类似于单链表,也是一种链式存储结构,循环链表由单链表演化过来。
单链表的最后一个结点的链域指向NULL,而循环链表的建立,不要专门的头结点,让最后一个结点的链域指向链表结点。
(循环链表与单链表的区别)
区别一、链表的建立。单链表需要创建一个头结点,专门存放第一个结点的地址。
单链表的链域指向NULL。而循环链表的建立,不要专门的头结点,让最后一个结点的链域指向链表的头结点。
区别二、链表表尾的判断。单链表判断结点是否为表尾结点,只需判断结点的链域值是否是NULL。
如果是,则为尾结点;否则不是。而循环链表盘判断是否为尾结点,则是判断该节点的链域是不是指向链表的头结点。

双链表:
双链表也是基于单链表的,单链表是单向的,有一个头结点,一个尾结点,要访问任何结点,
都必须知道头结点,不能逆着进行。而双链表则是添加了一个链域。通过两个链域,分别指向结点的前结点和后结点。
这样的话,可以通过双链表的任何结点,访问到它的前结点和后结点。但是双链表还是不够灵活,在实际编程中比较常用的是循环双链表。(但循环双链表使用较为麻烦。)


java实现链表相关:
1.头插法
2.尾插法
遍历
倒置
在有序的链表中插入一个数还是有序
在有序的链表中删除一个数还是有序
合并两个有序列表
Node文件代码如下:

public class Node {
    Object data;
    Node next;

    public Object getData() {
        return data;
    }

    public void setData(int data) {
        this.data = data;
    }

    public Node() {
        // TODO Auto-generated constructor stub
        this.data = null;
    }

    public Node(int data) {
        // TODO Auto-generated constructor stub
        this.data = data;
    }

    /**
     * 头插法建立链表
     * @param length 链表的长度,这里先设定初始长度
     * @return 链表的头指针
     */
    public Node createListF(int length) {
        Node head,s;
        head = new Node();
        head.next = null;

        for (int i=1; i<=length; i++) {
            s = new Node(i);
            s.next = head.next;
            head.next = s;
        }   

        return head;
    }

    /**
     * 尾插法建立链表
     * @param length 链表的长度,这里先设定初始长度
     * @return 链表的头指针
     */
    public Node createListR(int length) {
        Node head,rear,s;
        head = new Node();
        rear = head;

        for (int i=1; i<=length; i++) {
            s = new Node(i);
            rear.next = s;
            rear = s;
        }

        rear.next = null;

        return head;
    }

    public void traverseList(Node head) {
        Node p = head.next;

        while(p != null) {
            System.out.print(p.data + " ");
            p = p.next;
        }

        System.out.println();

    }

    /**
     * 链表反转
     * @param head 反转前链表的头指针
     * @return head 反转后链表的头指针
     */
    public Node reverseList(Node head) {
        Node p,q,temp;

        p = head.next;
        q = p.next;

        while (q != null) {
            temp = q.next;
            q.next = p;
            p = q;
            q = temp;
        }

        head.next.next = null;
        head.next = p;

        return head;
    }

java多线程,线程锁概念,线程池概念

线程的基本概念:

线程是指在程序执行过程中,能够执行程序代码的一个执行单位,每个程序至少都有一个线程,也就是程序本身,Java中的线程有四种状态运行,就绪,挂起,结束实现多线程有两种方式,一种是继承Thread类,一种是实现Runnable接口同步实现的方法:synchronized,wait和notify

实现线程的三种方式:

1.继承Thread类,复写run方法
2.实现Runnable接口,复写run方法
3.创建FutureTask对象,创建Callable子类对象,复写call

线程锁概念:

多线程在运行的时候可能会遇到这样的问题,多个线程要用到同一个资源,那么可能会出现错乱
就得给程序上锁。所以上了锁,就能保证线程有秩序的去运行了

JDK1.6到来了,synchronized在锁的获取和释放上有了重大改进,引入了偏向锁、轻量级锁和重量级锁
①偏向锁: 通常只有一个线程在临界区执行
②轻量级锁: 可以有多个线程交替进入临界区,在竞争不激烈的时候,稍微自旋等待一下就能获得锁。
③至于重量级锁,也是我最为期待的锁,那就是出现了激烈的竞争,只好让我们去阻塞休息了

synchronized:直接用关键字定义方法就可以了
Lock:获取锁和释放锁,直接调用 lock() 和 unlock() 等待可中断、可实现公平锁(时间顺序来依次获得锁)以及锁可以绑定多个条件等高级功能”

进程和线程的区别:

1.进程是某一个具有独立功能的程序的运行活动,它可以申请系统资源,是一个活动的实体。
2.线程的范围要比进程小,一个进程可以拥有多个线程。我们把进程作为分配资源的基本单位, 而把线程作为独立运行和独立调用的基本单位。

所谓线程池,就是将多个线程放在一个池子里面(所谓的池化技术),然后需要线程的时候,
不是创建一个线程,而是从线程池里面获取一个可用的线程,然后执行任务线程执行完任务后,
线程不会被销毁,而是会被重新放到池子里面,等待机会去执行任务


所有排序算法时间复杂度

排序算法 平均时间复杂度 最坏时间复杂度 空间复杂度 是否稳定
冒泡排序 O(n2) O(n2) O(1) 是
选择排序 O(n2) O(n2) O(1) 不是
直接插入排序 O(n2) O(n2) O(1) 是
归并排序 O(nlogn) O(nlogn) O(n) 是
快速排序 O(nlogn) O(n2) O(logn) 不是
堆排序 O(nlogn) O(nlogn) O(1) 不是
希尔排序 O(nlogn) O(ns) O(1) 不是
计数排序 O(n+k) O(n+k) O(n+k) 是
基数排序 O(N∗M) O(N∗M) O(M) 是

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值