链表
包括单向链表[数据域]+[指针域],双向链表[指针域]+[数据域]+[指针域],循环链表
每一个存储单元叫节点
单向链表
节点
package cn.edu.nefu.LineTable;
//这里的E是泛型的用法,相当于指代数据类型,到时候<>中可以任意填数据类型
//节点类
public class NodeForMe<E> {
//数据域
private E data;
//指针域
private NodeForMe<E> next;
public E getData() {
return this.data;
}
public void setData(E data) {
this.data = data;
}
public NodeForMe<E> getNext() {
return this.next;
}
public void setNext(NodeForMe<E> next) {
this.next = next;
}
//重载:列表(类型不同,数量不同,顺序不同)
// (但注意比如:String a,int b和int a,String b是不同的但int a,int b和int b,int a是一样的)
//构造方法和getter和setter的区别在于gs需要调用方法来给私有的成员变量赋值,而构造方法是在new的时候已经赋值好了
//比如NodeForMe nfe=new NodeForMe(val);,这时候this.data就被直接赋值为val了
public NodeForMe(){
}
public NodeForMe(E data){
this.data=data;
}
public NodeForMe(E data,NodeForMe<E> next){
this.data=data;
this.next=next;
}
}
链表
package cn.edu.nefu.LineTable;
//链表类
public class LinkedListForMe<E> {
//这个意思就是,一开始这个链表是空的,head就是空的,这时候把node赋给head,
// 等下一次插的时候head里面就有东西了,并不是每次都重来,这时候再找head继续往下插就行了
//创建一个链表头节点
private NodeForMe<E> head;
//创建一个链表尾节点
private NodeForMe<E> tail;
public static void main(String[] args) {
LinkedListForMe<Integer> link=new LinkedListForMe<>();
//NodeForMe<Integer> node=new NodeForMe<>();
link.insertByHead(1);
link.insertByHead(2);
}
//往链表中添加值
//方法一:头插法,时间复杂度为O(1),因为需要if打断所以需要返回值,其实不写也可以,但要加一个else
public boolean insertByHead(E value){
//把node的数据域赋值为value
NodeForMe<E> node = new NodeForMe<E>(value);
//如果head为空也就是链表是空的
if(head==null){
//把node的数据域赋值给head,因为head是空的,也就是这条链表就只有head这一个值,node的指针域就不用管了
head=node;
return true;
}
//如果head不为空也就是链表不是空的,就把head的指针域赋给node
node.setNext(head);
//把node 的数据域和指针域都赋给head实质上是把node设置为头结点,他的指针域指向原来的头结点
//这样一来node就被插在了链表的最前边
head=node;
return true;
}
//方法二:尾插法
//得到尾节点有三种方法, 单指针递归和循环和双指针,双指针实际上是一个头指针,一个尾指针指到最后然后停在最后
//之后这个尾指针就停在尾节点,每次加的时候只需要在尾指针后面加然后更新尾指针就行
//单指针的时间复杂度都是O(n),双指针的时间复杂度是第一次找到尾指针的O(1)加上每一次把尾指针向后挪一个的复杂度
//注意头指针不能动,一旦头指针懂了,你就没法找到这个链表头指针之前的东西了,比如12345,现在头指针在1位置,
//整个链表无论想怎么操作都是可以找到所有数据的,这时候如果头指针改到2,1你就找不到了
//单指针尾插法,传进来一个要加在后面的值
public void addByTailOne(E value){
NodeForMe<E> node = new NodeForMe<>(value);
NodeForMe<E> temp=new NodeForMe<>();
//这里以循环查找尾节点为例
NodeForMe<E> myTail=getTailRepeat(temp);
//如果这个尾节点是空的,说明整个链表都是空的
if(myTail==null){
head=node;
}else {
//尾节点不为空就把node赋值给当前尾节点的下一个
myTail.setNext(node);
}
}
//2.1 递归得到尾节点,定义当下的一个NodeForMe的对象,每次递归最终得到最后一个值
public NodeForMe<E> getTailRecursion(NodeForMe<E> current){
if(head==null){
//如果头结点为空,说明链表是空的,这时候head就直接是最后一个了
return head;
} else if (current.getNext()==null) {
//如果现在的这个节点的下一个节点是空的就说明现在的节点就是最后一个节点直接返回
return current;
}
return getTailRecursion(current.getNext());
}
//2.2 循环得到尾节点
public NodeForMe<E> getTailRepeat(NodeForMe<E> temp){
if(head==null){
temp=head;
return temp;
}
//每次循环指针指向下一个直到下一个为空输入当前指针位置就是最后一个的位置
while(temp.getNext()!=null){
temp=temp.getNext();
}
return temp;
}
//双指针尾插法:实际上就是单指针尾插法中的myTail变成成员变量保存下来,每次往后更新一个就行
public void getTailDouble(E value){
NodeForMe<E> node=new NodeForMe<>(value);
//如果头指针为空就说明整个链表为空,那么node既是头节点也是尾节点
if(head==null){
head=node;
tail=node;
}else {
//尾指针下一个指向node
tail.setNext(node);
//把尾指针下一个也就是node赋给尾指针,也就是现在尾指针就指向node也就是尾节点现在为node
tail=tail.getNext();
//也就是相当于先是让尾节点指向node把node添加在尾节点后面,然后更新尾指针
}
}
}
跳跃表
左右各一个指针,时间复杂度就变成n/2,中间再插一个指针就变成n/4,以此类推,空间复杂度换时间复杂度
快慢指针
比如,前一个指针一次走一步,后一个指针一次走两步
双向链表
节点
//由于head是在LinkedListForMeTwo类当中定义的,所以如果外部类LinkedListForMeTwo没有加泛型,即便是内部类NodeTwo加了
//泛型head也不能以NodeTwo<E>的形式用,必须传入包装类
//创建一个内部类
private class NodeTwo<E>{
NodeTwo<E> prev;
E data;
NodeTwo<E> next;
public NodeTwo(){}
public NodeTwo(E data){
this.data=data;
}
public NodeTwo(NodeTwo<E> prev, E data, NodeTwo<E> next){
this.prev=prev;
this.data=data;
this.next=next;
}
}
private int size=0;
private NodeTwo<E> head;
private NodeTwo<E> middle;
private NodeTwo<E> tail;
链表
//双向链表是指比如12345,在1左边放一个指针,右边放一个指针,向右检索的时候右边指针向右移
//左边指针也向右移
public void add(E value){
//每次加进去的时候size++达到用size存储链表的大小的目的
size++;
//如果头指针为空就把新指针同时赋值给头指针尾指针和中间指针
if(head==null){
head=new NodeTwo<>(value);
tail=head;
middle=tail;
//void由于输出是空的一般可以省略,这里用return是为了省略else
return ;
}
//把新节点赋值给tail.next也就是把新节点接在tail的下一个,prev用tail是为了让tail下一个节点的前指针域指向tail
tail.next=new NodeTwo<>(tail,value,null);
//把tail下一个赋给tail也就是把node设置成尾节点也就是更新尾节点
tail=tail.next;
//size每前进两步也就是tail每前进两步的时候middle指针前进一步,这样middle就会在链表的中间节点的位置
//设置middle指针的原因是,比如12345现在middle在3,要找2,
// 我们可以先比较2和3的大小,然后只遍历所在的那一半,时间复杂度降低
//同理多加几个指针时间复杂度就更小
middle=(size%2)==0?middle.next:middle;
}
public NodeTwo<E> getNodeByIndex(int index){
//先把特殊情况全部列出来,这样可以降低时间复杂度
if(index==0){
return head;
} else if (index==size-1) {
return tail;
} else if (index<0||index>=size) {
return null;
}
//找到中间位置的索引,把整个链表分为两部分看,降低时间复杂度
int middleNum =size/2;
NodeTwo<E> node;
if(index== middleNum){
return middle;
//先看index和在中间位置左边还是右边
} else if (index>middleNum) {
//再判断更靠近中间位置还是结尾位置分情况搜索
if (size + middleNum - 2 * index >= 0) {
//离middle更近所以从middle这边开始索引
// 先把middle赋给node所以此时node在middle位置,向后的指针指向middle位置的下一个位置
node = middle;
while (true) {
//循环的每一次把node后面的位置赋给node,于是node一次一次往后走
node = node.next;
//每次索引middleNum加一直到其值跟index一直时候的node就是要找的node
middleNum++;
if (middleNum == index) {
return node;
}
}
}
//把索引放在尾节点的位置,把尾节点赋值给node
middleNum = size - 1;
node = tail;
while (true) {
//把前索引值赋给node也就是把node前一个赋给node
node = node.prev;
middleNum--;
if (middleNum == index) {
return node;
}
}
}
if(middleNum-2*index>=0){
middleNum=0;
node=head;
while (true){
node=node.next;
middleNum++;
if (middleNum==index){
return node;
}
}
}
middleNum=size/2;
node=middle;
while (true){
node=node.prev;
middleNum--;
if (middleNum==index){
return node;
}
}
}
public static void main(String[] args) {
LinkedListForMeTwo<Integer> link=new LinkedListForMeTwo<>();
link.add(1);
link.add(2);
System.out.println(link.getNodeByIndex(1).data);
}