链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的.
一个链表如下图所示:
单向链表 双向链表
带头链表 不带头链表
循环的 非循环的
排列组合后我们可以的到八种链表,但是在链表的面试中我们常考的经常是不带头单向非循环链表和Java的集合框架中LinkedList底层实现的不带头双向非循环链表.
链表的组成是由一个个节点组成的,节点又是由一个数据域(val)和一个指针域(next)组成的.数据域用来存放你想要存放的数据(根据数据类型),指针域用来存放下一个节点的地址.这样一个个的节点就连接起来了,构成我们所说的链表.
节点的图示如下:
首先我们来看一个五个节点的链表:
第一个节点,地址是0x673 数据域是1,指针域是0x996 指向了第二个节点的地址
第二个节点,地址是0x996 数据域是4,指针域是0x532 指向了第三个节点的地址
第三个节点,地址是0x532 数据域是5,指针域是0x885 指向了第四个节点的地址
第四个节点,地址是0x885 数据域是7,指针域是0x345 指向了第五个节点的地址
第五个节点,地址是0x345 数据域是6,指针域是null 说明它是最后一个节点,不指向其他的节点
最后还需要定义一个head头节点 地址为0x673
节点由数据域(val)和指针域(next)组成,数据域可以根据所需要的的类型定义,但是指针域存放的是下一个节点的地址,故指针域的类型是引用类型(public ListNode next).然后需要提供一个构造方法,对val值进行初始化.
class ListNode {
public int val;//数据域
public ListNode next;//指针域:存放下一个节点的域
public ListNode(int val){
this.val=val;
}
方法一:使用枚举法(内容简单,但过于繁琐不建议使用)
这种方法首先是创建五个节点出来(以五个为例),然后让第一个节点next指向第二个节点,第二个节点next域指向第三个节点,以此类推把一个个节点串起来构成一个链表.
最后一个节点可以不赋值,因为next域是引用类型,默认值是null.
public void create(){
//先创建节点
ListNode listNode=new ListNode(0);
ListNode listNode1=new ListNode(1);
ListNode listNode2=new ListNode(2);
ListNode listNode3=new ListNode(3);
ListNode listNode4=new ListNode(4);
//把节点的next域连接起来
listNode.next=listNode1;
listNode1.next=listNode2;
listNode2.next=listNode3;
listNode3.next=listNode4;
}
方法二:头插法
头插法是在链表的头节点之间插入一个节点,首先我们要定义一个要插入的节点(node),然后对node的next域赋值,如果要让node与链表产生联系,它的next域就应该等于头结点(head),最后更新head的位置.
//头插法
public void addFirst(int data){
//首先要创建一个节点,要头插的节点
ListNode listNode =new ListNode(data);
//让节点的next等于head,然后把head的位置改变一下
listNode.next=head;
head= listNode;
}
方法三:尾插法
尾插法是在链表的尾节点插入一个节点,首先还是定义一个要插入的节点(node),在尾巴节点插入,这时我们需要找尾巴节点,为了不改变头结点的位置,我们要定义一个代跑的变量(cur)来遍历链表,cur赋值为head头结点,然后如果cur的next不为空我们就一直遍历,如果cur.next域为空了那么我们就找到了最后一个节点,让cur.next=node;
我们还要注意一点就是,head如果为空,那么我们直接让head=node,以免造成空指针异常;
//尾插法
public void addLast(int data){
//尾插也是先要创建一个节点
ListNode listNode =new ListNode(data);
ListNode cur=head;
//判断head为空的情况
if(head==null){
head= listNode;
return;
}
while(cur.next!=null){
cur=cur.next;
}
cur.next= listNode;
}
通过链表的结构我们可以知道每个节点之间通过next产生联系的,而且我们也知道头结点,所以我们可以根据头结点的移动来遍历这个链表并打印.但是为了保证head头结点的位置不发生改变,我们需要定义一个cur代跑的变量ListNode cur=head;通过cur=cur.next来移动
//打印链表
public void display(){
//代跑变量cur
ListNode cur=head;
while(cur!=null){
System.out.print(cur.val+" ");
cur=cur.next;
}
System.out.println();
}
还是一样我们需要遍历链表,同样定义cur代跑,对比cur.val与key是否相同,如果相同返回true,否则让cur往后走.但是当cur走完还没有返回true,那就说明没有这个key.
如果head为空,就说明是空链表没有任何节点,直接返回true.
//查找是否包含关键字key是否在单链表当中
public boolean contains(int key){
ListNode cur=head;
if(head==null){
return false;
}
while(cur!=null){
if(cur.val==key){
return true;
}
cur=cur.next;
}
return false;
}
首先我们可以定义一个计数器count,然后还是遍历链表,代跑cur继续上线.当cur不为空我们就count++,cur往后走一步.
//得到单链表的长度
public int size(){
ListNode cur=head;
int count=0;
while(cur!=null){
count++;
cur=cur.next;
}
return count;
}
比如我们需要把一个节点插入2号位置.
根据图解我们可以分析得出插入到2号位置,我们需要把head移动到2号位置的前一个位置.滴滴滴,代跑上线,这时我们还是需要借助cur来移动.找到一个节点之后,定义要插入的节点node.让node.next=cur.next,cur.next=node.
//找到前一个节点的位置
private ListNode findIndex(int index){
ListNode cur=head;
if(head==null){
return null;
}
for (int i = 0; i
首先我们还是要找到要删除的节点的前一个节点,然后使前一个节点的next指向要删除的节点的next域,简而言之,就是使指向跳过要删除的节点.这里还要注意head如果为空的情况,还有head.val正好四是key的情况
如图所示:
//找到前一个节点
private ListNode searchPrev(int key) {
ListNode cur = head;
while (cur.next != null) {
if(cur.next.val == key) {
return cur;
}
cur = cur.next;
}
return null;//没有你要删除的节点
}
//删除第一次出现关键字为key的节点 O(N)
public void remove(int key){
if(head == null) {
return ;//一个节点都没有
}
if(head.val == key) {
head = head.next;
return;
}
ListNode cur = searchPrev(key);
if(cur == null) {
return;
}
ListNode del = cur.next;//要删除的节点
cur.next = del.next;
}
很多同学可能会想到把删除第一个出现key节点的函数多次调用就可以实现,但是如果上升到面试的高度,只能遍历一次链表,那该如何解决呢?
我们可以首先定义一个前驱节点(prev)让它等于head,然后定义一个cur等于head的下一个节点(ListNode cur=head.next),当cur!=null的时候我们进入循环 判断cur.val是否等于key,如果等于的话,就让前驱节点的next等于cur.next;如果不等于的话就让prev移动到cur.不管相不相等cur都要往后面遍历.
注意:head节点没有判断,最后还要判断一下head.val是否等于key,等于的话还要把head往后走一步(head=head.next).
//删除所有值为key的节点
public void removeAllKey(int key){
if(head==null){
System.out.println("链表为空");
return;
}
ListNode prev=head;
ListNode cur=head.next;
while(cur!=null){
if(cur.val==key){
prev.next=cur.next;
}else{
prev=cur;
}
cur=cur.next;
}
//判断head节点的val是否等于key
if(head.val==key){
head=head.next;
}
}
暴力求解:直接让head为null.
温柔求解:一个节点一个节点的赋值为null
//清空链表
public void clear(){
while(head!=null){
ListNode curNext=head.next;
head.next=null;
head=curNext;
}
}
以上是对链表的基础操作,大部分都是通过遍历链表实现的,学习链表一定要搞清楚next域的指向,不要乱就可以写出来,学习数据结构要多画图,很多不会的题一画图就非常明了.
以上是我对链表基础操作的了解,如有错误,请大家指出.