当前位置: 首页 > ai >正文

数据结构:链表

 链表的概念及结构:

链表的概念:

链表是一种物理储存结构上非连续的储存结构,数据元素的逻辑顺序是通过引用链接次序实现的

那物理存储结构连续是什么意思?

之前我们讲过顺序表,顺序表的底层是数组,如下:

因为数组开辟的空间,是连续的,这里所储存的数据,它们在物理储存结构上是连续的

链表的结构:

链表是一个节点链接另一个节点的,一个节点都是一个对象。

如果我用链表储存11,22,33,44,55这几个数据,首先我们有五个节点,因为节点是对象,所以会分配地址。

并把这些数据储存到对应的节点里面,然后再将他们连接起来。

 这个链表叫做单向不带头非循环链表

 这是单向带头非循环链表,两个的区别是,带头链表多了一个头(也叫哨兵位),头插一个元素时,带头链表会将新的元素放在头(哨兵位)的后面。带头链表的值没有意义,所以也可以不用放数据。但是不带头链表,头插的时候,头插在第一个元素的前面即可。

链表分类:

 这就是循环链表,最后一个节点又指向第一个节点,形成一个圈。

单向不带头非循环链表的实现:

我先创建一个内部类(节点):

static class ListNode(){int val;Listnode next;
//构造方法public ListNode(int val){this.val=val;}
}
//还要有一个头结点
ListNode head=null;

在ListNode类中,val用来存储数据,而next用来指向下一个节点,再写一个构造方法用来初始化val值。

定义一个头节点可以利于遍历链表。

实现链表dispaly(打印链表):

如何打印上面链表?我们要打印出每个节点的val值,并利用每个节点的next找到下一个节点。

一直这样循环,一直到最后一个节点的next值为null停止。

public void dispaly(){ListNode cur=head;while(next!=null){System.out.print(cur.val+" ");cur=cur.next;}
}

这里要注意一点:不要直接拿头结点head去遍历链表,会导致最后head指向链表的尾部,后续操作就不方便了。所以重新定义一个cur节点等于头结点。用cur来遍历数组。

实现链表addFirst(头插):

 在实现链表头插时要分情况:

1.如果链表为空:

这种情况,只用定义新的节点,让头结点指向新节点

public void addFirst(int data){
//链表为空的情况if(head==null){ListNode node=new ListNode(data);head=node;}
}

2、如果链表不为空:

 这种情况怎么头插呢?

首先:我们还是要第一个新的节点,让这个新的节点的next值指向head头节点,最后将head头节点指向新的头结点。

public void addFirst(int data){
//链表为空的情况if(head==null){ListNode node=new ListNode(data);head=node;
//链表不为空的情况}else{ListNode node=new ListNode(data);node.next=head;head=node;}
}

最后发现,这两种情况可以合并为:

public void addFirst(int data){ListNode node=new ListNode(data);node.next=head;head=node;}

实现链表addLast(尾插):

实现尾插时也要分情况

1.如果链表不为空:

 如果对这个链表进行尾插,应该怎么做呢?

首先我们要遍历链表到这个链表的尾部,然后将要插入的节点与尾节点连接,完成尾插。

public void addLast(int data){
//创建一个新的节点ListNode node=new ListNode(data);ListNode cur=head;
//遍历到链表尾部while(cur.next!=null){cur=cur.next;}
//插入尾部cur.next=node;
}

 2.如果链表为空:

如果用上面1的方法,会出现空指针异常。

所以如果为空链表,我们就直接将head头节点指向需要插入的节点,加一个if语句进行判断。

public void addLast(int data){ListNode node=new ListNode(data);
//为空链表的情况if(head==null){head=node;}
//不为空链表的情况ListNode cur=head;while(cur.next!=null){cur=cur.next;}cur.next=node;
}

实现链表size(求链表长度):

既然要求链表的长度,并定义一个计数器,遍历链表时,只要当前节点不为null,计数器就加1

注意:这里遍历链表时,我们不用head,重新定义一个节点指向head,然后往后遍历。

public int size(){
int count=0;//计数器
ListNode node=head;
//开始遍历数组while(node!=null){count++;node=node.next;}return count;
}

实现链表addIndex(指定位置下插入数据):

既然要在指定位置下插入数据,我们首先就要考虑所提供的下标是否越界:

所以Index(下标)不能小于0,也不能大于链表的长度。

然后如果Index==0,则直接调用头插的方法,如果index==链表的长度,就直接调用尾插的方法。

这里有一个新节点ListNode node =new ListNode(25),要把新节点25插入到22和33之间,应该怎么做呢?

这里我们重新定义一个cur=head;

将cur遍历到22节点的位置

 因为我们要将25这个节点插入2下标的位置,所以要将cur从head移动2-1个位置到22这个位置(这样才方便插入)

node.next=cur.next;

cur.next=node;

public void addIndex(int Index,int data){
int len=size();//求链表的长度 
//判断Index下标是否异常 if(Index<0||Index>len){System.out.print("下标异常");return ;}
//特殊处理Index==0或者Index==len
if(Index==0){addFirst(data);}
if(Index==len){addLast(data);}
//插入到链表的中间下标
ListNode cur=head;
ListNode node=new ListNode(data);
//找到插入下标的前一个位置
while(Index-1!=0){cur=cur.next;
Index--;}
//开始插入
node.next=cur.next;
cur.next=node;
}

`实现链表remove(删除指定节点):

给一个链表,我们如果要删除33这个节点,应该怎么删除呢?

首先,如果这个节点为空,我们直接提前返回。

我们如果要删除33这个节点,那我们要重新定义一个节点,让这个节点指向被删除的节点的前一个节点, 也就是22这个节点。怎么找呢?

先有一个大循环,遍历链表,直到链表为空,然后嵌套一个if,找到被删除节点的前一个节点。

//这个函数用来删除节点
public void remove(int key){if(head==null){return ;}
//如果第一个节点是要删除的节点if(head.val==key){head=head.next;}
//第三种情况
ListNode node=findKey(key);//这时node节点就是要删除的节点的前一个节点
node.next=node.next.next;//删除key节点
}
//这个函数用来找到被删除节点的前一个节点public ListNode findKey(int key){ListNode cur=head;
//遍历链表
while(cur!=null){if(cur.next.val==key){return cur;}cur=cur.next;}
return null;
}

实现链表clear(清空链表):

清空链表有两种方法:

1.最简单的方法:将链表的头节点置为空。

public void clear(ListNode head){head=null;
}

2.第二种方法:遍历链表,将链表的所有的节点全部置为空。

public void clear(ListNode head){
ListNode cur=head;while(cur!=null){ListNode curN=cur.next;//curN指向后一个节点cur.next=null;//将当前的cur节点的next域置为nullcur=curN;//将cur移到curN的位置}
}
http://www.xdnf.cn/news/1211.html

相关文章:

  • 近几年字节测开部分面试题整理
  • 明远智睿2351开发板四核1.4G Linux处理器:驱动创新的引擎
  • Protues8.11安装只需5步骤即可。
  • 如何创建Vue3工程
  • 状态管理最佳实践:Riverpod响应式编程
  • 理解 C++ 中的隐式构造及其危害
  • STM32 中断系统深度剖析
  • element-ui cascader 组件源码分享
  • Ray是什么,它解决了什么问题
  • nodejs的包管理工具介绍,npm的介绍和安装,npm的初始化包 ,搜索包,下载安装包
  • TypeError: ‘weights_only‘ is an invalid keyword argument for Unpickler()解决
  • 【刷题Day23】线程和进程(浅)
  • elasticsearch 查询检索
  • 1.1 AI大模型与Agent的兴起及其对企业数字化转型的推动作用
  • 变更管理 Change Management
  • opencv 读取3G大图失败,又不想重新编译opencv ,可以如下操作
  • AI催生DLP新战场 | 天空卫士连续6年入选Gartner 全球数据防泄漏(DLP)市场指南
  • 工程投标k值分析系统(需求和功能说明)
  • 【项目】基于MCP+Tabelstore架构实现知识库答疑系统
  • move闯关(更新啦)1
  • 力扣刷题Day 25:反转链表(206)
  • 输入框仅支持英文、特殊符号、全角自动转半角 vue3
  • C# foreach 循环中获取索引的完整方案
  • PCIe体系结构学习入门——PCI总线概述(一)PCI 总线的基础知识
  • [预备知识]4. 概率基础
  • 关于ubuntu密码正确但是无法登录的情况
  • Android-KeyStore安全的存储系统
  • P3909 异或之积 解题报告
  • QML FontDialog:使用FontDialog实现字体选择功能
  • 【重走C++学习之路】16、AVL树