#学习记录
##数据结构
##链表
链表的引入:
对于顺序表而言,其存在许多缺陷,比如删除某个数据,他的时间复杂度可能会很高达到O(N),比如删除第一个数据,扩容也可能造成空间的浪费。这时候就体现出多个数据结构的好处,可以在特定业务下调用合适的数据结构,为了弥补顺序表的缺陷,引入链表
链表的概念:
对于下图中一个个物理意义上非连接的节点共同就组成了一个链表(大致)
链表的分类:
1带头与不带头
2循环与非循环
3单向与双向
那么链表共有8种类型(排列组合 2*2*2=8),只需要特别掌握划红线的即可,其他的都可以类推。
什么是带头,什么是头呢?
什么是循环?
什么是双向?
像这种就是单向:
像这种就是双向:
单向不带头非循环链表的实现
public class MySingleList implements IList {//无头单向非循环链表
static class ListNode{//节点类--
int val;
ListNode next;
public ListNode(int val) {//由于一个节点的后一个节点是变动的,不确定的,所以在这里next先不写进构造方法
this.val = val;//但是要存储的值是一定的
}
}
public ListNode head;//需要一个标志头】head默认为null
@Override
public void addFirst(int data) {//头插
ListNode listNode=new ListNode(data);
if (head==null){
this.head=listNode;
}else {
listNode.next=head;
head=listNode;}
}
@Override
public void addLast(int data) {
ListNode node=new ListNode(data);
if (head==null){
this.head=node;
}
ListNode cur=head;
while (cur.next !=null){//while的循环条件特别巧妙 也是关键
cur=cur.next;
}
cur.next=node;
}
@Override
public void addIndex(int index, int data) {
ListNode node=new ListNode(data);
if (head==null||index<0||index>size()){
return;
}
//注意到首尾位置处要特别注意要调用头插和尾差来实现特定位置插入
if (index==size()){
addLast(data);
return;
}
if (index==0){
addFirst(data);
return;
}
ListNode cur=head;
int count=index-1;
while (count!=0){
cur=cur.next;
count--;
}
node.next=cur.next;
cur.next=node;
}
@Override
public boolean contains(int key) {
ListNode cur=head;
while (cur != null){
if(cur.val==key){
return true;
}
cur=cur.next;//移动到下一个节点
}
return false;
}
@Override
public void remove(int key) {
if (head==null){
return;
}
if (head.val==key){
head=head.next;
return;
}
ListNode cur=findNodeOfKey(key);
ListNode del=cur.next;
cur.next=del.next;
}
//我们可以写一个方法来找要删除的内容所在的位置的前一个位置
public ListNode findNodeOfKey(int key){
ListNode cur=head;
while (cur!=null){
if (cur.next.val==key){
return cur;
}
cur=cur.next;
}
return null;
}
@Override
public void removeAllKey(int key) {
if (head==null){
return;
}
ListNode cur=head.next;
ListNode pre=head;
while (cur!=null){
if (cur.val==key){
pre.next=cur.next;
//此时不用移动pre
}else{
pre=pre.next;
//如果该节点不是要删除的节点,那么就需要移动pre
}
cur=cur.next;
}
//回头来看首位置是否也存在要删的对象,因为在上面代码的方法无法删除欲删的首节点
if (head.val==key){
head=head.next;
return;
}
}
@Override
public int size() {//节点个数
ListNode cur=head;
int count=0;
while (cur!=null){
cur=cur.next;
count++;
}
return count;
}
@Override
public void clear() {
ListNode cur=head;
while (cur!=null){
ListNode code=cur.next;//做一份缓存
cur.next=null;
cur=code;}
//head=nll;
}
@Override
public void display() {
ListNode cur=head;
while (cur!=null){
System.out.print(cur.val+" ");
cur=cur.next;
}
}
@Override
public void creatHoop(ListNode head) {
//这个是生成回文链表
}
@Override
public boolean hasCycle(ListNode head) {
//判断是否有环,这个两个在此不实现
return false;
}
public void CreatList(){//在该方法里面会帮助生成许多的节点
//实例节点出来
ListNode listNode0=new ListNode(11);
ListNode listNode1=new ListNode(12);
ListNode listNode2=new ListNode(13);
ListNode listNode3=new ListNode(14);
ListNode listNode4=new ListNode(15);
//将每个实例出来的节点连接起来
listNode0.next=listNode1;//next存放下一个节点的地址就可以将每个单独节点连接起来
listNode1.next=listNode2;
listNode2.next=listNode3;
listNode3.next=listNode4;
this.head=listNode0;
}
}
认识LinkedList
我们前文讲述的单非循环不带头列表其实不是ieadl提供的链表,其只是链表当中的一种情况,真正的常用链表LinkedList是不带头非循环双向链表,双向链表能够很好的体现出它的优点,不需要像单向链表那样去找尾巴,可以很好的降低时间复杂度,而且也具备单链表的功能,。更能体现出对于不同的业务,采取高效的数据结构这一特征。
注意:
1 LinkedList实现了List接口,重写了List当中许多的方法;
2 LinkedList底层使用了双向链表;
3 LinkedList没有实现RandomAccess接口,因此不支持随机访问;
4 LinkedList的任意位置插入和删除元素时效率比较高,时间复杂度为O(1)
5 LinkedList比较适合任意位置插入的场景
模拟实现LinkedLIst
我们有了单链表模拟实现的基础,那么来实现LinkedList也就容易了.LinkedList也实现了List接口。
public class MyLinkedList implements IList {
class ListNode{
public int val;
public ListNode pre;
public ListNode next;
public ListNode(int val) {
this.val = val;
}
}
//定义头指针和指针两个引用变量;
public ListNode head;
public ListNode last;
@Override
//头插
public void addFirst(int data) {
ListNode node=new ListNode(data);
if(head==null){
last=head=node;
return;
}
head.pre=node;
node.next=head;
head=node;
}
@Override
//尾差
public void addLast(int data) {
ListNode node=new ListNode(data);
if (head==null){
head=last=node;
}
last.next=node;
node.pre=last;
last=node;
}
@Override
//指定位置插入
public void addIndex(int index, int data) {
int len=size();
if (head==null||index<0||index>len){
return;
}
//注意到首尾位置处要特别注意要调用头插和尾差来实现特定位置插入
if (index==len){
addLast(data);
return;
}
if (index==0){
addFirst(data);
return;
}
ListNode cur=findIndex(index);
ListNode node=new ListNode(data);
cur.pre.next=node;
node.pre=cur.pre;
cur.pre=node;
node.next=cur;
}
//查找对应下标的节点的方法,这个不是重写的,这个方法是在该类中使用的,为该类的模拟实现过程提供便利;
private ListNode findIndex(int index){
ListNode cur=head;
while (index!=0){
cur=cur.next;
index--;
}
return cur;
}
@Override
//判断是否含有某个数据key
public boolean contains(int key) {
ListNode cur=head;
while (cur!=null){
if (cur.val==key){
return true;
}
cur=cur.next;
}
return false;
}
@Override
public void remove(int key) {
ListNode cur = head;
while (cur != null) {
//开始删除
if (cur.val == key) {
if (cur == head) {
head = head.next;
head.pre = null;
} else {
cur.pre.next = cur.next;
if (cur.next == null) {
last = last.pre;
return;
}
cur.next.pre=cur.pre;
return;
}
}
cur = cur.next;
}
}
@Override
public void removeAllKey(int key) {
if (head==null){
return;
}
ListNode cur=head;
while (cur!=null){
//开始删除
if (cur.val==key){
if (cur!=head&&cur!=last){
cur.pre.next=cur.next;
cur.next.pre=cur.pre;
}else if (cur==head){
head=head.next;
head.pre=null;
}else {
last=last.pre;
last.next=null;
}
}
cur=cur.next;
}
}
@Override
public int size() {
int count=0;
ListNode cur=head;
while (cur!=null){
count++;
cur=cur.next;
}
return count;
}
@Override
public void clear() {
ListNode cur=head;
while (cur!=null){
//这里要注意备份一份下节点,因为我们已经将本节点的next赋值为null了,我们无法通过cur.next来移动到下一个节点了
ListNode curN=cur.next;
cur.next=null;
cur.pre=null;
cur=curN;
}
head=last=null;
}
@Override
public void display() {
ListNode cur=head;
while (cur!=null){
System.out.print(cur.val+" ");
cur=cur.next;
}
System.out.println();
}
//生成一个回文链表
public MyLinkedList creatCycycle(){
MyLinkedList myLinkedList1=new MyLinkedList();
myLinkedList1.addFirst(1);
myLinkedList1.addFirst(2);
myLinkedList1.addFirst(3);
myLinkedList1.addFirst(4);
myLinkedList1.addFirst(5);
myLinkedList1.addFirst(6);
int count=2;
ListNode cur=myLinkedList1.head;
while (count!=0){
cur=cur.next;
count--;
}
myLinkedList1.last.next=cur;
return myLinkedList1;
}
}
LinkedList的构造方法
有两种
一种一不带参数的,另一种是带参数,构造链表的时候实参部分可以写一个集合容器比如list
然后就会将list这个集合容器内的数据全部插入到此时我们新构造的这个链表内
LinkedList链表当中的方法
和顺序表单链表一样,有许多的方便的方法可以使用
ArrayList和LinkedList的区别
注意:
1 ArrayList底层是数组,数组是连起来的,它在存储空间上是连续的,物理意义上也连续,但是LinkedList的底层是一个个节点,我们每new一个节点的时候分配给这个节点对象的地址是不一样的,也不一定是连续的,是随机的,只有当地址是连续的,物理意义上才是连续的,每个节点之间是通过引用来联系起来的,通过next当中的地址访问到另外的节点,所以逻辑意义上是连续的,但是每个节点地址不一样,是分隔的,物理意义上不连续。
2 顺序表底层是数组,数组我们可以通过下标随意的去访问该容器内的数据,但是对于链表而言就不是,你可能会说链表当中不是有get(int x)方法吗?能够返回链表中指定位置的数据吗?但是我们看源码就知道了,其实要移动到指定的位置是需要while循环来实现的,有while循环那么时间复杂度就O(N)了
3合理的使用顺序表和链表,不同的情况使用不同的数据结构