概述:众所周知,数据对于数据的存储时连续的,也就是说在计算机的内存中是一个整体的、连续的、不间断的ADT数据结构。伴随的问题也会随之出现,这样其实对于内存的动态分配是不灵活的。而链表具备这个优点。因此链表对于数据的插入和删除是方便的,但是对于数据的查询是麻烦的。因为需要遍历链表,而对于链表的遍历确实极度的麻烦。

1 单向链表的定义

链表主要用来存储引用类型的数据。其结构可以由下图清楚的表示: 

链表结点的定义

class Node{
// 链表中保存的数据
public Object obj;
// 下一个结点的应用
public Node next;
// 该结点中保存的数据
public Node(Object obj){
this.obj=obj;
}
public void setNext(Node next){
this.next=next;
}
public Node getNext(){
return this.next;
}
public Object getObj(){
return this.obj;
}
}

链表的设置和取出

/**
* 对链表设置数据和取出数据
*/
public class LinkDemo{
public void static main(String[] args){
//设置数据
Node root= new Node("链表头结点");
Node n1=new Node("链表1");
Node n2=new Node("链表2");
Node n3=new Node("链表3");
// 维护链表的关系
root.setNext(n1);
n1.setNext(n2);
n2.setNext(n3);
// 取出链表的数据
Node currentNode=root;// 创建当前结点对象,将根结点赋值给当前结点
// 判断当前结点是不是为null,如果当前结点为null,则终止循环,否则继续输出
while(currentNode != null){
// 打印出当前结点的数据,然后修改当前结点的引用
System.out.println(currentNode.getObj());
// 将下一个结点设置为当前结点
currentNode=currentNode.getNext();
}
}
}

结果测试如下: 
 
使用递归的方式优化输出:

   /**
*对链表设置数据和取出数据
*/
public class LinkDemo{
public void static main(String[] args){
//设置数据
Node root= new Node("链表头结点");
Node n1=new Node("链表1");
Node n2=new Node("链表2");
Node n3=new Node("链表3");
// 维护链表的关系
root.setNext(n1);
n1.setNext(n2);
n2.setNext(n3);
// 取出链表的数据
Node currentNode=root;// 创建当前结点对象,将根结点赋值给当前结点
// 判断当前结点是不是为null,如果当前结点为null,则终止循环,否则继续输出
print(root);
}
public static print(Node node){
//递归的结束条件
if(node == null){
return;
}else{
System.out.println(node.getObj());
print(node.getNext());
}
}
}

在实际的开发中,我们希望对于数据的保存和输出应该是由如下的形式出现的:

public class LinkDemo3{
public static void main(){
Link link=new Link();
link.add("A");
link.add("B");
link.add("C");
link.add("D");
// 展示所有的数据
link.print();
}
}

此时我们希望让Node结点类来负责对结点的操作,而Link类类负责对数据的操作。

// 节点类对象
class Node{
// 结点中的数据
public Object obj;
// 结点的下一个引用
public Node next;
// 结点中保存数据
public Node(Object obj){
return this.obj=obj;
}
public Node getNext(){
return this.next;
}
public void setNext(Node next){
this.next=next;
}
public Object getObj(){
return this.obj;
}
// 添加结点
public void addNode(Node node){
// 对于添加结点来说,首先判断头节点是否存在下一个的引用,如果为null,此时就可以添加
// 第一次调用该方法,this表示的就是Link.root对象
if(this.next==null){
//为空,那么将该结点挂到头结点的下一个引用上
this.next=node;
}else{
//头节点的下一个引用不为空,那么应该是将当前结点的下一个引用为此node结点
//递归循环
this.next.addNode(node);
}
}
// 展示所有的结点
public void printNode(){
System.out.println(this.obj);
if(this.next!=null){
// 如果当前结点对于下一个结点的引用不为空,那么当前结点对象递归调用打印方法。
// 当前结点的下一个引用this.next
this.next.printNode();
}
}
}
// 对链表的操作
class Link{
// 初始化结点对象,即头结点
public Node root;
// 添加数据
public void add(Object obj){
//创建当前结点对象,然后保存数据
Node currentNode=new Node(obj);
// 如果要添加数据,就要首先判断根结点是否为空
if(root==null){
//头节点为空,则将创建的当前结点赋值给根结点
root=currentNode;
}else{
//如果头结点不为空,此时应该由结点自己来判断
//头节点不为空值,要保存的数据应该在之后的结点上,调用添加结点的方法
root.addNode(currentNode);
}
}
// 展示所有的数据
public void print(){
if(root!=null){
root.printNode();
}
}
}

当我们创建结点对象的时候,对于保存数据和输出输出,我们每次都会判断根节点是不是空值。这个很重要。当前结点不是根节点这个应该注意到。进一步的优化,注意到我们不希望调用者直接操作结点对象,单纯的Node类会被直接操作,这样不符合Java的封装思想,我们可以使用内部类,并且直接对Node内部类私有化

// 对链表的操作
class Link{
// 初始化结点对象,即头结点
public Node root;
// 添加数据
public void add(Object obj){
//创建当前结点对象,然后保存数据
Node currentNode=new Node(obj);
// 如果要添加数据,就要首先判断根结点是否为空
if(root==null){
//头节点为空,则将创建的当前结点赋值给根结点
root=currentNode;
}else{
//如果头结点不为空,此时应该由结点自己来判断
//头节点不为空值,要保存的数据应该在之后的结点上,调用添加结点的方法
root.addNode(currentNode);
}
}
// 展示所有的数据
public void print(){
if(root!=null){
root.printNode();
}
}
// 节点类对象
private class Node{
// 结点中的数据
public Object obj;
// 结点的下一个引用
public Node next;
// 结点中保存数据
public Node(Object obj){
return this.obj=obj;
}
// 添加结点
public void addNode(Node node){
// 对于添加结点来说,首先判断头节点是否存在下一个的引用,如果为null,此时就可以添加
// 第一次调用该方法,this表示的就是Link.root对象
if(this.next==null){
//为空,那么将该结点挂到头结点的下一个引用上
this.next=node;
}else{
//头节点的下一个引用不为空,那么应该是将当前结点的下一个引用为此node结点
//递归循环
this.next.addNode(node);
}
}
// 展示所有的结点
public void printNode(){
System.out.println(this.obj);
if(this.next!=null){
// 如果当前结点对于下一个结点的引用不为空,那么当前结点对象递归调用打印方法。
// 当前结点的下一个引用this.next
this.next.printNode();
}
}
}
}

确定链表的数据结构

class Link {
// 需要结点对象
private Node root;//根节点对象
// 结点对象
//***************内部类*******************
private class Node {
private Object obj;//结点中保存的数据
private Node next;//下一个结点的引用 // 结点中的数据
public Node(Object obj) {
this.obj = obj;
}
}
//***************内部类*******************
}

添加数据 public void add(Object obj);

class Link {
// 需要结点对象
private Node root;//根节点对象
// 结点对象
//***************内部类*******************
private class Node {
private Object obj;//结点中保存的数据
private Node next;//下一个结点的引用 // 结点中的数据
public Node(Object obj) {
this.obj = obj;
}
//结点的添加
public void addNode(Node node){
if(this.next==null){
//把当前结点赋值给this.next
this.next=node;
}else{
//添加一个结点
this.next.addNode(node);
}
}
}
//***************内部类*******************
public void add(Object obj){
//对于数据为null,是可以保存的,在这里我假设数据为null是不可以保存的
if(obj==null){
return;
}
Node node=new Node(obj);
if(this.root==null){
root=node;
}else{
//注意:Link类负责根节点的维护和结点的创建,对于结点的具体操作,应该是Node类操作
this.root.addNode(node);
}
}
}

获取链表的长度 public int size();

每一次的数据保存都需要长度加1.因此我们在Link类中添加组成员变量size.保存时让它自增运算;

class Link {
// 需要结点对象
private Node root;//根节点对象
private int size;//链表的长度
// 结点对象
//***************内部类*******************
private class Node {
private Object obj;//结点中保存的数据
private Node next;//下一个结点的引用 // 结点中的数据
public Node(Object obj) {
this.obj = obj;
}
//结点的添加
public void addNode(Node node){
if(this.next==null){
//把当前结点赋值给this.next
this.next=node;
}else{
//添加一个结点
this.next.addNode(node);
}
}
}
//***************内部类*******************
/**数据的添加*/
public void add(Object obj){
//对于数据为null,是可以保存的,在这里我假设数据为null是不可以保存的
if(obj==null){
return;
}
Node node=new Node(obj);
if(this.root==null){
root=node;
}else{
//注意:Link类负责根节点的维护和结点的创建,对于结点的具体操作,应该是Node类操作
this.root.addNode(node);
}
this.size++;//长度自增运算
}
/**
*链表的长度
*/
public int size(){
return this.size;
} }

判断链表是否为null,public boolean isEmpty();

  1. 原理1:如果root为null,则链表的长度为null

  2. 原理2:如果链表的size==0.则链表的长度为空

class Link {
// 需要结点对象
private Node root;//根节点对象
private int size;//链表的长度
// 结点对象
//***************内部类*******************
private class Node {
private Object obj;//结点中保存的数据
private Node next;//下一个结点的引用 // 结点中的数据
public Node(Object obj) {
this.obj = obj;
}
//结点的添加
public void addNode(Node node){
if(this.next==null){
//把当前结点赋值给this.next
this.next=node;
}else{
//添加一个结点
this.next.addNode(node);
}
}
}
//***************内部类*******************
/**数据的添加*/
public void add(Object obj){
//对于数据为null,是可以保存的,在这里我假设数据为null是不可以保存的
if(obj==null){
return;
}
Node node=new Node(obj);
if(this.root==null){
root=node;
}else{
//注意:Link类负责根节点的维护和结点的创建,对于结点的具体操作,应该是Node类操作
this.root.addNode(node);
}
this.size++;//长度自增运算
}
/**
*链表的长度
*/
public int size(){
return this.size;
}
/**
*判读链表是否为空
*/
public boolean isEmpty(){
return this.size==0?true:flase;
}
}

判断链表中是否存在某个元素 public boolean contains(Object obj);

对象的匹配可以使用equals()方法,但是对于自定义对象而言,需要写compare()方法来自定义匹配结果。 

class Link {
// 需要结点对象
private Node root;//根节点对象
private int size;//链表的长度 public void add(Object obj) {
// 创建结点对象,并且保存数据
Node newNode = new Node(obj);
if (root == null) {
root = newNode;
} else {
//此时根节点不为空,需要添加结点
root.addNode(newNode);
}
this.size++;//每次保存数据,链表自增
} /**
* 链表的长度
* @return
*/
public int size(){
return this.size;
} /**
* 判断链表是否为空
* @return
*/
public boolean isEmpty(){
return this.size==0?true:false;
}
public boolean contains(Object obj){
if(obj==null || this.root==null){
return false;
}else {
// 此时的判断应该交给Node结点完成
return root.containsNode(obj);
}
} public void print() {
if (root != null) {
root.printNode();
}
}
private class Node {
private Object obj;//结点中保存的数据
private Node next;//下一个结点的引用 // 结点中的数据
public Node(Object obj) {
this.obj = obj;
}
// 结点的保存
public void addNode(Node node) {
if (this.next == null) {//当前结点的下一个结点为空,此时就可以添加结点
this.next = node;
} else {
//否则,此时应该循环递归添加结点
// 当前结点对象应该添加新的结点
this.next.addNode(node);
}
} // 输出一个结点
public void printNode() {
//打印数据
System.out.println(this.obj);
if (this.next != null) {
this.next.printNode();//递归调用输出
}
} /**
* 判断链表中受否包含某个元素
* @param obj
* @return
*/
public boolean containsNode(Object obj) {
if(this.next==null){
return false;
}else {
// 链表结点不为空。此时要判断元素是否匹配
if(this.obj.equals(obj)){
return true;
}else {
return this.next.containsNode(obj);
}
}
}
}
}

根据索引查询元素

然后定义get方法;

  1. 查询有多次,但是每一次的查询都要将foot属性设置为0;
  2. 如果当前查询的索引大于Link类的编号size.此时查询不到
public Object get(int index){
//如果当前要查询的索引大于链表的额size.那么直接返回null
if(index>this.size){
return null;
}
// 每一的查询都要将foot从0开始
this.foot=0;
// 此后交给Node结点来判断
return this.root.getNode(index);
}

getNode(index)的实现

public Object getNode(int index) {
//外部类Link调用内部对象this.foot,外部类直接对内部类的访问
// 如果当前结点的编号自增==当前索引
if(Link.this.foot++==index){
// 返回数据
return this.obj;
}else {
return this.next.getNode(index);
}
}

修改链表元素,和上述的查询实现基本一致 public void set(int index,Object obj);

public void  set(int index,Object obj){
if(index>this.size){
return;
}
this.foot=0;
this.root.setNode(index,obj);
}
public void setNode(int index, Object obj) {
if(Link.this.foot++==index){
this.obj=obj;//数据的设置
}else {
this.next.setNode(index,obj);
}
}

总结

NO 方法名称 类型 备注
1 public void add(Object obj) 普通方法 向链表之中添加数
2 public int size() 普通方法 取得链表的长度
3 public boolean isEmpty() 普通方法 判断链表是否为空
4 public boolean contains(Object obj) 普通方法 判断链表是否存在某个元素
3 public Object get(int index) 普通方法 根据链表索引查询某个元素
3 public void set(int index,Object obj) 普通方法 修改某个元素

java对单向单向链表的操作的更多相关文章

  1. Java数据结构之单向环形链表(解决Josephu约瑟夫环问题)

    1.Josephu(约瑟夫.约瑟夫环)问题: 设编号为1,2,… n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m ...

  2. 3,java数据结构和算法:约瑟夫环出队顺序, 单向环形链表的应用

    什么是约瑟夫环? 就是数小孩游戏: 直接上代码: 要实现这个,只需要理清思路就好了 孩子节点: class Boy{ int no;//当前孩子的编码 Boy next; // 下一节点 public ...

  3. 数据结构与算法——链表 Linked List(单链表、双向链表、单向环形链表-Josephu 问题)

    链表是有序的列表,但是在内存中存储图下图所示 链表是以 节点 的方式来存储,是 链式存储 每个节点包含 data 域.next 域,指向下一个节点 链表的各个节点 不一定是连续存储,如上图所示 链表还 ...

  4. java实现单链表常见操作

    一.概述: 本文主要总结单链表常见操作的实现,包括链表结点添加.删除:链表正向遍历和反向遍历.链表排序.判断链表是否有环.是否相交.获取某一结点等. 二.概念: 链表: 一种重要的数据结构,HashM ...

  5. 单向环形链表解决约瑟夫环(Josephus)问题

    一.约瑟夫环问题 Josephu 问题为:设编号为1,2,- n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那 ...

  6. 数据结构Java实现04----循环链表、仿真链表

    单向循环链表 双向循环链表 仿真链表 一.单向循环链表: 1.概念: 单向循环链表是单链表的另一种形式,其结构特点是链表中最后一个结点的指针不再是结束标记,而是指向整个链表的第一个结点,从而使单链表形 ...

  7. 数据结构Java实现03----单向链表的插入和删除

    文本主要内容: 链表结构 单链表代码实现 单链表的效率分析 一.链表结构: (物理存储结构上不连续,逻辑上连续:大小不固定)            概念: 链式存储结构是基于指针实现的.我们把一个数据 ...

  8. 数据结构Java实现02----单向链表的插入和删除

    文本主要内容: 链表结构 单链表代码实现 单链表的效率分析 一.链表结构: (物理存储结构上不连续,逻辑上连续:大小不固定)            概念: 链式存储结构是基于指针实现的.我们把一个数据 ...

  9. Java描述数据结构之链表的增删改查

    链表是一种常见的基础数据结构,它是一种线性表,但在内存中它并不是顺序存储的,它是以链式进行存储的,每一个节点里存放的是下一个节点的"指针".在Java中的数据分为引用数据类型和基础 ...

随机推荐

  1. UML建模:学习笔记(1)

    UML:学习笔记(1) 事物 结构事物 类: 接口: 协作:(定义元素之间的相互作用) 用例:(在系统外部和系统交互的人) 组件:(描述物理系统的一部分) 节点:(一个节点可以被定义为运行时存在的物理 ...

  2. iOS 在视图控制器里面判断 应用程序的前台 后台切换 UIViewController

    1.时机  用户点击home 键  应用退到后台 再次点击进入前台  在UIViewController里面 控制器如何获取相关的事件? 2.需求 (1)NSTimer   在应用程序进入后台 10秒 ...

  3. LRC歌词文件读取代码

    /**************************************************/ /*******************-main文件-******************* ...

  4. eclipse新建Maven项目

    1.在eclipse中安装maven插件 2.点击File->new->maven project,出现弹窗后点击next. 接着在弹窗Select an Archetype中,filte ...

  5. 【转载】xtrabackup原理及实施

    转载于:http://www.baidu-ops.com/2013/05/26/xtrabackup/ xtrabackup是基于InnoDB存储引擎灾难恢复的.它复制InnoDB的数据文件,尽管数据 ...

  6. vue双向绑定补充说明方法

    本文总结自: https://segmentfault.com/a/1190000006599500,将每一个流程提炼出来做一个简单的说明,以免自己被繁杂的逻辑弄昏头脑~ observer: 遍历数据 ...

  7. 高通8X16电池BMS算法(一)【转】

    本文转载自:http://www.voidcn.com/blog/yanleizhouqing/article/p-6037399.html 最近一直在搞电源管理相关内容,之前是8610的bms,现在 ...

  8. Spring Cloud之搭建动态Zuul网关路由转发

    传统方式将路由规则配置在配置文件中,如果路由规则发生了改变,需要重启服务器.这时候我们结合上节课内容整合SpringCloud Config分布式配置中心,实现动态路由规则. 将yml的内容粘贴到码云 ...

  9. Spring Cloud2.0之整合Consul作为注册中心

    使用Consul来替换Eureka Consul简介 Consul 是一套开源的分布式服务发现和配置管理系统,由 HashiCorp 公司用 Go 语言开发. 它具有很多优点.包括: 基于 raft ...

  10. JavaWeb -- Session实例 -- 自动登录 和 防止表单重复提交(令牌产生器) MD5码

    1. 自动登录 http://blog.csdn.net/xj626852095/article/details/16825659 2. 防止表单重复提交 表单Servlet //负责产生表单 pub ...