链表的概念
链表是一种物理存储结构上非连续存储结构 ,数据元素的逻辑顺序是通过链表中的引用链接次序实现的。
不像数组,相邻的两个元素不仅逻辑上连续,物理上也连续
换句话说,由若干个链表节点构成的对象就称为链表
我们可以把链表类比成火车,每一列火车由若干个车厢组成,每个车厢就是一个Node结点,由多个Node结点组成的对象就是链表对象。火车的不同车厢之间都是通过挂钩连接的,当两个车厢之间脱钩之后,两个车厢就没有任何关系了。
那么怎么这一个一个的结点是怎样链接起来呢?在Java中他是通过引用所指向的地址来链接起来的。
假设咱们现在每节火车车厢只保存一个int值,这个值就是我们要存储的数据,每节车厢还要保存下一节车厢的地址
那么在JAVA中怎么定义结点呢?
class Node{
//存放结点的值
int val;
//存放下一结点地址
Node next;
public Node(int val) {
this.val = val;
}
public Node(int val, Node next) {
this.val = val;
this.next = next;
}
}
实际中链表链表的结构非常多样包括单向,双向,带头,不带头 ,接下来就让我们看看一个不带头的单链表如何实现?
单链表的创建
public class SingleLinedList {
//链表的头结点
private Node head=null;
//当前链表中Node节点的个数 = 有效数值的个数
private int size=0;
class Node{
//存放结点的值
int val;
//存放下一结点地址
Node next;
public Node(int val) {
this.val = val;
}
public Node(int val, Node next) {
this.val = val;
this.next = next;
}
}
}
如图所示:我们定义了一个单链表类,同时将结点类作为链表的内部类。这样就创建了一个基本的链表结构。
此外,我们还可以通过重写toString方法,遍历链表,实现链表的打印操作
那么如何遍历一个链表的每个节点?
从当前头节点出发。一次取出每个节点值,然后通过next引用走到下一个节点,直到走到链表的末尾(next = null)
public String toString(){
//遍历链表
String ret="";
Node x=head;
while (x!=null){
ret+=x.val;
ret+="->";
x=x.next;
}
ret+="Null";
return ret;
}
注意:
遍历过程中,不能直接使用head引用,因为直接使用head引用在一次遍历后链表就丢了,我们可以使用一个临时变量x暂存head的地址
单链表的增删改查
在创建好一个单链表后,我们就可以对单链表进行增删改查的操作了。
// 在在链表头插入元素
public void addFirst(int val) { }
//在链表的指定下标中插入元素
public void addIndex(int index, int val) { }
//删除指定下标的元素,返回删除前元素
public void remove(int index) { }
// 删除链表中第一个值为val的元素
public void removeValueOnce(int val) { }
//删除链表中所有值为val的元素
public void removeAllValue(int val) { }
//修改索引为index位置的结点值为newVal,返回修改前的节点值
public int set(int index,int newVal){ }
//查找第一个值为val的结点索引
public int getByValue(int val) { }
//查找索引为index位置的结点值
public int get(int index){ }
// 判断元素key是否在当前链表中
public boolean contains(int val) { }
接下来让我们看看这些操作是如何实现的吧
新增结点
- 头插
public void addFirst(int val){
Node newNode=new Node(val);
newNode.next = head; // 直接将该结点node变成新的头结点
head = newNode;
size++;
}
- 指定下标插入
想要在index位置插入新结点,因为单链表只能从前往后遍历,我们需要找到要插入结点的前驱结点。我们可以让prev引用从头结点开始走index-1步,这样就找到了待插入结点的前驱结点
public void addIndex(int index,int val) {
// 1.若index位置非法的
if(index<0 || index>size){
System.out.println("index is illegal");
return;
}
// 2.插入任意位置要找到前驱结点,但是链表中有一个结点没有前驱
// 头结点没有前驱!!!
if(index==0){
addFirst(val);
}else {
// 3.当前索引合法且不是在数组的头部插入,就要找到插入位置的前驱结点
Node prev=head;
Node newNode=new Node();
newNode.val=val;
for (int i = 0; i < index-1; i++) {
prev=prev.next;
}
// 此时prev一定指向了待插入节点的前驱
newNode.next=prev.next;
prev.next=newNode;
size++;
}
}
删除结点
在链表中,想要删除一个结点其实就是让该结点从链表中分离出来(没有其他任何的结点指向他 )
比如我们想删除1下标的结点,只需要找到1下标的前一个结点,也就是0下标。然后将0下标的next域里不再储存1下标的结点地址,而改成储存1下标的下一个结点->2下标的结点地址。这样就相等于1下标的结点被孤立下来了(就相等于是删除了)
- 指定下标删除,返回删除前的元素
public int remove(int index){
if (index>=0 && index<size){
//删除头结点
if(index==0){
Node x=head;
int oldVal=head.val;
head=head.next;
x.next=null;//可以省略
size--;
return oldVal;
}else {
Node prev=head;
for (int i = 0; i < index-1; i++) {
prev=prev.next;
}
Node x=prev.next;
int oldVal=x.val;
prev.next=x.next;
x.next=null;//可以省略
size--;
return oldVal;
}
}else {
System.out.println("index is illegal");
return -1;
}
}
- 删除链表中第一个值为val的元素
public void removeValueOnce(int val) {
//链表为空
if(head==null){
System.out.println("LinedList is empty");
return;
}
//要删除的是头结点
if(head.val==val){
Node x=head;
head=head.next;
x.next=null;
}else {
//找前驱结点
Node prev=head;
while (prev.next!=null){
//有后继结点
if(prev.next.val==val){
//此时后继结点就是要删除的结点
Node x=prev.next;
prev.next=x.next;
x.next=null;
return;
}
prev=prev.next;
}
}
size--;
}
- 删除链表中所有值为val的元素
public void removeAllValue(int val) {
while (head != null && head.val == val) {
// 头节点就是待删除节点
Node x = head;
head = head.next;
x.next = null;
size --;
}
// 头节点一定不是待删除的节点
// 判空
if (head == null) {
// 链表删完了
return;
}else {
Node prev = head;
while (prev.next != null) {
// 至少还有后继结点
if (prev.next.val == val) {
// 此时prev.next就是待删除的结点
Node node = prev.next;
prev.next = node.next;
node.next = null;
size --;
}else {
// 只有当prev.next.val != val才能移动prev指针!
prev = prev.next;
}
}
}
}
修改链表
- 修改索引为index位置的结点值为newVal,返回修改前的节点值
public int set(int index,int newVal){
if(index>=0 && index<size){
Node x=head;
for (int i = 0; i < index; i++) {
x=x.next;
}
int oldVal=x.val;
x.val=newVal;
return oldVal;
}
System.out.println("index is illegal");
return -1;
}
查找链表
- 查找第一个值为val的结点索引
public int getByValue(int val) {
int index=0;
Node pre=head;
while (pre.next!=null){
if(pre.next.val==val){
return index;
}
pre=pre.next;
index++;
}
// 说明当前链表中根本就没有值为val的结点,返回-1,表示不存在
return -1;
}
- 查找索引为index位置的结点值
public int get(int index) {
// 1.判断index的合法性
if(index>=0 && index<size){
Node x=head;
for (int i = 0; i < index; i++) {
x=x.next;
}
return x.val;
}
return -1;
}
- 查找值为val的结点是否在链表中
public boolean contains(int val) {
int index = getByValue(val);
return index != -1;
}
双向链表
单向链表默认只能从链表的头部遍历到链表的尾部,实际的应用较为少见,太局限。
双向链表:对于该链表的任意节点,既可以向后走,也可以向前走。双向链表在实际工程中应用非常广泛,是使用链表这个结构的首选。
为什么双向链表既能往后遍历也能往前遍历呢?让我们看看双向链表结点的定义
class DoubleNode{
//前驱结点
DoubleNode prev;
//结点值
int val;
//后继结点
DoubleNode next;
public DoubleNode() {
}
public DoubleNode(DoubleNode prev, int val, DoubleNode next) {
this.prev = prev;
this.val = val;
this.next = next;
}
public DoubleNode(int val) {
this.val = val;
}
}
每个结点既保存了下一个节点的地址,又保存了上一个节点的地址,这样双向链表就既能往后遍历也能往前遍历
public class DoubleLinkedList {
//有效结点个数
private int size;
//当前链表头结点
private DoubleNode head;
//当前链表尾结点
private DoubleNode tail;
}
双向链表的增删改查
新增结点
- 头插法
//头插法
public void addFirst(int val){
DoubleNode node=new DoubleNode(null,val,head);
//链表为空
if(tail==null){
tail=node;
}else {//链表不为空
head.prev=node;
}
// 对于头插来说,最终无论链表是否为空。head = node
head=node;
size++;
}
- 尾插法
public void addLast(int val){
DoubleNode node=new DoubleNode(tail,val,null);
if(head==null){
head=node;
}else {
tail.next=node;
}
//尾插最终tail都会等于node
tail=node;
size++;
}
- 在index位置插入
都是找前驱结点,此时是不是可以灵活一点?之前只能从head开始向后遍历,假设我们现在有100个结点,我想在97号结点插入新元素,从尾结点向前遍历就会快很多,我们可以创建一个私有方法来根据索引找到结点
private DoubleNode node(int index){
//根据索引找到对应结点
DoubleNode x=null;
if(index<=size/2){
x=head;
for (int i = 0; i < index; i++) {
x=x.next;
}
}else {
x=tail;
for (int i=size-1; i>index; i--){
x=x.prev;
}
}
return x;
}
public void add(int index,int val){
if(index < 0 || index > size){
System.out.println("index is illegal");
return;
}
if(index==0){
addFirst(val);
}else if (index==size){
addLast(val);
}else {
//中间位置插入
DoubleNode pre=node(index-1);
DoubleNode next=pre.next;
DoubleNode cur=new DoubleNode(pre,val,next);
next.prev=cur;
pre.next=cur;
size++;
}
}
注意:要把后继结点处理完才能移动前驱结点,操作1,2,3的顺序无所谓,4操作一定要最后执行
删除结点
这里运用到分治思想:我们可以先处理前驱节点的问题,完全不管后继,等前驱部分处理完毕再单独处理后继部分,由此我们创建一个私有方法,传入要删除的结点,将其从链表中删除
private void unlink(DoubleNode node){
//1.前空后不空
//2.前不空后空
//3.前后都为空
//4.前后都不空
DoubleNode pre=node.prev;
DoubleNode next=node.next;
//先处理前驱
if(pre==null){
//前驱为空,node为头结点
head=next;
}else {
pre.next=next;
node.prev=null;
}
//处理后继
if(next==null){
tail=pre;
}else {
next.prev=pre;
node.next=null;
}
size--;
}
- 指定下标删除,返回删除前的元素
public void removeIndex(int index){
if(index<0 || index>=size){
System.out.println("index is illegal");
return;
}
DoubleNode node=node(index);
unlink(node);
}
- 删除一次值为val的结点
public void removeOnce(int val){
DoubleNode x=head;
while (x!=null){
if(x.val==val){
unlink(x);
break;
}
x=x.next;
}
}
- 删除所有值为val的结点
public void removeAll(int val){
DoubleNode x=head;
while (x!=null){
if(x.val==val){
//暂存一下x后继结点的地址
DoubleNode next=x.next;
unlink(x);
x=next;
}else {
x=x.next;
}
}
}
修改链表
- 修改index处的值为newVal,返回index
public int set(int index,int newVal) {
if(index<0 || index>=size){
System.out.println("index is illegal");
return -1;
}
DoubleNode node=node(index);
int oldVal=node.val;
node.val=newVal;
return oldVal;
}
查询链表
- 查找索引为index位置的结点值
//获取index处结点的值
public int get(int index) {
if(index<0 || index>=size){
System.out.println("index is illegal");
return -1;
}
DoubleNode node=node(index);
int val=node.val;
return val;
}
完整代码
单链表:
package seq;
public class DoubleLinkedList {
//有效结点个数
private int size;
//当前链表头结点
private DoubleNode head;
//当前链表尾结点
private DoubleNode tail;
//头插法
public void addFirst(int val){
DoubleNode node=new DoubleNode(null,val,head);
//链表为空
if(tail==null){
tail=node;
}else {//链表不为空
head.prev=node;
}
// 对于头插来说,最终无论链表是否为空。head = node
head=node;
size++;
}
//尾插法
public void addLast(int val){
DoubleNode node=new DoubleNode(tail,val,null);
if(head==null){
head=node;
}else {
tail.next=node;
}
//尾插最终tail都会等于node
tail=node;
size++;
}
//在index位置插入元素val
public void add(int index,int val){
if(index < 0 || index > size){
System.out.println("index is illegal");
return;
}
if(index==0){
addFirst(val);
}else if (index==size){
addLast(val);
}else {
//中间位置插入
DoubleNode pre=node(index-1);
DoubleNode next=pre.next;
DoubleNode cur=new DoubleNode(pre,val,next);
next.prev=cur;
pre.next=cur;
size++;
}
}
//根据索引找到对应结点
private DoubleNode node(int index){
DoubleNode x=null;
if(index<=size/2){
x=head;
for (int i = 0; i < index; i++) {
x=x.next;
}
}else {
x=tail;
for (int i=size-1; i>index; i--){
x=x.prev;
}
}
return x;
}
//根据索引删除元素
public void removeIndex(int index){
if(index<0 || index>=size){
System.out.println("index is illegal");
return;
}
DoubleNode node=node(index);
unlink(node);
}
//头删
public void removeFirst(){
removeIndex(0);
}
//尾删
public void removeLast(){
removeIndex(size-1);
}
//传入一个结点,将其从链表中删除
private void unlink(DoubleNode node){
//1.前空后不空
//2.前不空后空
//3.前后都为空
//4.前后都不空
DoubleNode pre=node.prev;
DoubleNode next=node.next;
//先处理前驱
if(pre==null){
//前驱为空,node为头结点
head=next;
}else {
pre.next=next;
node.prev=null;
}
//处理后继
if(next==null){
tail=pre;
}else {
next.prev=pre;
node.next=null;
}
size--;
}
//删除一次值为val的结点
public void removeOnce(int val){
DoubleNode x=head;
while (x!=null){
if(x.val==val){
unlink(x);
break;
}
x=x.next;
}
}
//删除所有值为val的结点
public void removeAll(int val){
DoubleNode x=head;
while (x!=null){
if(x.val==val){
//暂存一下x后继结点的地址
DoubleNode next=x.next;
unlink(x);
x=next;
}else {
x=x.next;
}
}
}
//获取index处结点的值
public int get(int index) {
if(index<0 || index>=size){
System.out.println("index is illegal");
return -1;
}
DoubleNode node=node(index);
int val=node.val;
return val;
}
//修改index处的值为newVal,返回index
public int set(int index,int newVal) {
if(index<0 || index>=size){
System.out.println("index is illegal");
return -1;
}
DoubleNode node=node(index);
int oldVal=node.val;
node.val=newVal;
return oldVal;
}
public String toString(){
String ret="";
for (DoubleNode x=head;x!=null;x=x.next){
ret+=x.val;
ret+="->";
}
ret+="Null";
return ret;
}
}
class DoubleNode{
//前驱结点
DoubleNode prev;
//结点值
int val;
//后继结点
DoubleNode next;
public DoubleNode() {
}
public DoubleNode(DoubleNode prev, int val, DoubleNode next) {
this.prev = prev;
this.val = val;
this.next = next;
}
public DoubleNode(int val) {
this.val = val;
}
}
双向链表:
package seq;
public class DoubleLinkedList {
//有效结点个数
private int size;
//当前链表头结点
private DoubleNode head;
//当前链表尾结点
private DoubleNode tail;
//头插法
public void addFirst(int val){
DoubleNode node=new DoubleNode(null,val,head);
//链表为空
if(tail==null){
tail=node;
}else {//链表不为空
head.prev=node;
}
// 对于头插来说,最终无论链表是否为空。head = node
head=node;
size++;
}
//尾插法
public void addLast(int val){
DoubleNode node=new DoubleNode(tail,val,null);
if(head==null){
head=node;
}else {
tail.next=node;
}
//尾插最终tail都会等于node
tail=node;
size++;
}
//在index位置插入元素val
public void add(int index,int val){
if(index < 0 || index > size){
System.out.println("index is illegal");
return;
}
if(index==0){
addFirst(val);
}else if (index==size){
addLast(val);
}else {
//中间位置插入
DoubleNode pre=node(index-1);
DoubleNode next=pre.next;
DoubleNode cur=new DoubleNode(pre,val,next);
next.prev=cur;
pre.next=cur;
size++;
}
}
//根据索引找到对应结点
private DoubleNode node(int index){
DoubleNode x=null;
if(index<=size/2){
x=head;
for (int i = 0; i < index; i++) {
x=x.next;
}
}else {
x=tail;
for (int i=size-1; i>index; i--){
x=x.prev;
}
}
return x;
}
//根据索引删除元素
public void removeIndex(int index){
if(index<0 || index>=size){
System.out.println("index is illegal");
return;
}
DoubleNode node=node(index);
unlink(node);
}
//头删
public void removeFirst(){
removeIndex(0);
}
//尾删
public void removeLast(){
removeIndex(size-1);
}
//传入一个结点,将其从链表中删除
private void unlink(DoubleNode node){
//1.前空后不空
//2.前不空后空
//3.前后都为空
//4.前后都不空
DoubleNode pre=node.prev;
DoubleNode next=node.next;
//先处理前驱
if(pre==null){
//前驱为空,node为头结点
head=next;
}else {
pre.next=next;
node.prev=null;
}
//处理后继
if(next==null){
tail=pre;
}else {
next.prev=pre;
node.next=null;
}
size--;
}
//删除一次值为val的结点
public void removeOnce(int val){
DoubleNode x=head;
while (x!=null){
if(x.val==val){
unlink(x);
break;
}
x=x.next;
}
}
//删除所有值为val的结点
public void removeAll(int val){
DoubleNode x=head;
while (x!=null){
if(x.val==val){
//暂存一下x后继结点的地址
DoubleNode next=x.next;
unlink(x);
x=next;
}else {
x=x.next;
}
}
}
//获取index处结点的值
public int get(int index) {
if(index<0 || index>=size){
System.out.println("index is illegal");
return -1;
}
DoubleNode node=node(index);
int val=node.val;
return val;
}
//修改index处的值为newVal,返回index
public int set(int index,int newVal) {
if(index<0 || index>=size){
System.out.println("index is illegal");
return -1;
}
DoubleNode node=node(index);
int oldVal=node.val;
node.val=newVal;
return oldVal;
}
public String toString(){
String ret="";
for (DoubleNode x=head;x!=null;x=x.next){
ret+=x.val;
ret+="->";
}
ret+="Null";
return ret;
}
}
class DoubleNode{
//前驱结点
DoubleNode prev;
//结点值
int val;
//后继结点
DoubleNode next;
public DoubleNode() {
}
public DoubleNode(DoubleNode prev, int val, DoubleNode next) {
this.prev = prev;
this.val = val;
this.next = next;
}
public DoubleNode(int val) {
this.val = val;
}
}