java链表算法_双链表算法原理【Java实现】

前言

前面两节内容我们详细介绍了ArrayList,一是手写实现ArrayList数据结构,而是通过分析ArrayList源码看看内置实现,关于集合内容一如既往,本节课我们继续学习集合LinkedList,我们首先入门LinkedList数据结构,然后再去看看LinkedList源码是如何实现的,我们开始吧。

LinkedList入门

LinkedList内置是通过双链表数据结构来存储数据,和ArrayList不同的是,ArrayList属于真正意义物理意义上的线性结构,而LinkedList也属于线性链表,只不过需要通过我们手动来关联前后节点数据,同时呢,双链表和单链表只是在结构上有所不同而已,只是双链表多了一个前驱节点,其他无差异,那么到底何为双链表呢?在我们日常生活中到处都是这样的例子,比如我们音乐播放器应该算比较形象了,如下:

97d789011ef39a5436e65d8a3ed48fca.png

单链表自定义实现

接下来我们来实现单链表,然后对单链表进行改造成双链表,我们看到如上播放器,单链表只是少了前驱节点,但是有后继节点(如上写错了),所以我们需要定义一个节点,然后在此节点上有连接下一节点的引用(在C或C++中为指针),和当前节点所存储的数据,所以我们定义如下泛型节点类:

public class Node{//当前节点值

publicT data;//后继节点

publicNode next;publicNode(T data) {this.data =data;

}

}

接下来则是定义链表来操作上述节点类并存储数据了, 这里我们稍微做的简单点,在链表中会存在头节点和尾节点,这里呢我们通过来头节点来操作,等我们升级到双链表时再来定义尾节点,所以在单链表中有头节点和链表长度两个变量,如下:

public class MyLinkedList{//头节点

privateNode head;//链表元素长度

private intlength;

}

温馨提示:这里我就不给大家画图演示了,自行脑补,实在感觉绕的话自己在画板或纸上画一下就明白了,我也是在纸上画了一番才动手写代码的。首先我们需要考虑头节点和尾节点即播放器中第一首歌和最后一首歌,然后针对指定位置添加歌曲通过next串联就形成了歌曲列表,更为形象的例子当属我们吃过的串串了。那么接下来我们完成往播放器列表中添加第一个首歌,此时我们应该想,头节点是否添加了第一首歌,若不存在则直接实例化头节点即可,若已存在第一首歌,我们则将重新实例化一首歌,然后将其已添加的第一首歌的引用赋值给新添加的歌曲的next,所以就有了如下方法:

//添加至头结点

public voidaddToHead(T data) {if (head == null) {

head= newNode(data);

}else{

Node temp=head;

head= newNode(data);

head.next=temp;

}

length++;

}

好了,将新添加的歌曲放在第一首我们已经完全搞定了,然后我们再来往歌曲列表中最后添加一首歌曲,这个时候我们肿么知道是最后一首呢,只要next为空,说明就是最后一首歌曲,这就是判断依据,这点就不用我再过多解释了,那么就有了如下方法:

//添加至尾节点

public voidaddToTail(T data) {

Node temp=head;while (temp.next != null) {

temp=temp.next;

}

temp.next= newNode(data);

length++;

}

单链表的确定就在这里,我们只能循环遍历才能找到最后一首,然后添加对应歌曲,所以当数据量足够大时,可想其性能。接下来则是最重要的一块了,我们想要在指定歌曲下添加歌曲,这个时候就涉及到找到对应歌曲索引然后添加数据,

//添加到指定索引元素

public void add(intindex, T data) {if (index < 0) {throw new RuntimeException("非法索引");

}if (index >length) {throw new RuntimeException("超出索引边界");

}if (head == null || index == 0) {

addToHead(data);return;

}//头节点

Node temp =head;//指定索引下一节点

Node holder;for (int i = 0; i < index - 1 && temp.next != null; i++) {

temp=temp.next;

}//未插入节点时指定索引下一节点

holder =temp.next;//指定索引节点下一节点即待插入的节点

temp.next = newNode(data);//将列表中指定索引节点下一节点引用指向指定待插入节点(此时指定索引下节点即为待插入节点,然后再下一节点即为待插入节点)

temp.next.next =holder;

length++;

}

接下来则是根据指定索引查找元素,我就不解释了,直接上代码,如下

//根据索引查找元素

public T find(intindex) {if (index < 0) {throw new RuntimeException("非法索引");

}if (length == 0 || index >length) {throw new RuntimeException("超出索引边界");

}

Node temp=head;for (int i = 0; i < index; i++) {

temp=temp.next;

}return(T) temp.data;

}

最后老规矩重写toString方法,打印链表数据,如下:

//链表元素大小

public intsize() {returnlength;

}

@OverridepublicString toString() {

StringBuilder sb= newStringBuilder();

Node temp=head;while (temp != null) {

sb.append(temp.data);

sb.append(",");

temp=temp.next;

}if (sb.charAt(sb.length() - 1) == ',') {

sb.delete(sb.length()- 1, sb.length());

}returnsb.toString();

}

最后我们来往播放器列表中添加歌曲做个测试吧,走你,如下:

public classMain {public static voidmain(String[] args) {

MyLinkedList list = new MyLinkedList<>();//添加元素11到头节点

list.addToHead(11);

System.out.println(list);//添加元素15到尾节点

list.addToTail(15);

System.out.println(list);//添加元素12到头节点

list.addToHead(12);

System.out.println(list);//添加元素13到头节点

list.addToHead(13);

System.out.println(list);//添加元素8到尾节点

list.addToTail(8);//添加元素7到尾节点

list.addToTail(7);

list.add(2, 9);

System.out.println(list);//在索引2位置添加元素9

list.add(2, 9);

System.out.println(list);//删除索引为4的元素

list.delete(4);

System.out.println(list);

}

}

df61914d91a30ac7fa56c8d8179f838f.png

双链表自定义实现

有了如上单链表的铺垫,接下来我们再来实现双链表则是轻而易举了,只不过添加了前驱节点和链表中的尾结点而已,走你,我们往节点类中添加前驱节点,如下:

public class Node{//当前节点值

publicT data;//前驱节点

publicNode previous;//后继节点

publicNode next;publicNode(T data) {this.data =data;

}

}

同理,我们在链表类中添加尾节点字段,如下:

public class MyLinkedList{//头节点

privateNode head;//尾节点

privateNode tail;//链表元素长度

private intlength;

}

同样,当添加歌曲至首位时,此时我们也需初始化头节点,只不过这时多了个尾节点,没关系,这个时候头节点就是尾节点,我们封装一个初始化头节点和尾节点的方法,如下:

//初始化头接点和尾节点

voidinitHead(T data) {//初始化头节点

head = newNode(data);//此时尾节点即头节点

tail =head;

}

然后添加歌曲至头节点时,只不过多了个前驱节点,也就相应多了一行代码而已,就是将已添加首位歌曲的前驱节点赋给待添加的首位歌曲,如下:

//添加元素至头结点

public voidaddToHead(T data) {if (head == null) {

initHead(data);

}else{

Node temp=head;

head= newNode(data);

head.next=temp;

temp.previous=head;

}

length++;

}

而添加歌曲至末位时就和上述单链表就有些不同了,单链表中是直接循环遍历,这里我们定义了尾节点,所以直接操作尾节点即可,如下:

//添加至尾节点

public voidaddToTail(T data) {if (size() == 0) {

initHead(data);

}else{

Node temp=tail;

tail= newNode(data);

temp.next=tail;

tail.previous=temp;

}

length++;

}

接下来又是添加指定索引元素的核心方法了,其实也非常简单,我都将注释给你写好了,还是看不懂,建议到纸上画画哈。

//添加指定索引元素

public void add(intindex, T data) {if (index < 0) {throw new RuntimeException("非法索引");

}if (index >length) {throw new RuntimeException("超出索引边界");

}if (head == null || index == 0) {

initHead(data);return;

}//头节点

Node temp =head;//定义获取指定索引节点下一节点

Node holder;for (int i = 0; i < index - 1 && temp.next != null; i++) {

temp=temp.next;

}//当前节点的下一节点

holder =temp.next;//要添加的下一节点

temp.next = newNode(data);//插入节点的后继节点为当前节点下一节点

temp.next.next =holder;//当前节点下一前驱节点为插入节点

temp.next.next.previous =temp.next;

length++;

}

无论是添加还是删除最重要的是我们需要想清楚,添加时和删除后前驱节点和后继节点分别指向谁,把这个问题想明白了,那也就没什么了,走你,删除方法:

//删除指定索引元素

public void delete(intindex) {if (index < 0) {throw new RuntimeException("非法索引");

}if (length == 0 || index >length) {throw new RuntimeException("超出索引边界");

}

Node temp=head;for (int i = 0; i < index - 1 && temp.next != null; i++) {

temp=temp.next;

}

temp.next.next.previous=temp;

temp.next=temp.next.next;

length--;

}

为了验证我们所写代码,我们打印出对应节点的前驱和后继节点,如下:

public intsize() {returnlength;

}

@OverridepublicString toString() {

StringBuilder sb= newStringBuilder();

Node temp=head;while (temp != null) {

sb.append(temp.data);

sb.append(",");if (temp.previous != null && temp.next != null) {

System.out.println(temp.previous.data+ "" +temp.next.data);

}

temp=temp.next;

}if (sb.charAt(sb.length() - 1) == ',') {

sb.delete(sb.length()- 1, sb.length());

}returnsb.toString();

}

控制台测试数据和单链表中一样,结果数据如下(当然我们可以分开打印对应节点前驱和后继节点去验证也是阔以的,这里我也验证过来,么有任何问题):

dcb6cdb3f4677428818dff3fd58bf354.png

总结

本节我们通过手写代码实现了单链表和双链表,还是非常简单,下一节我们详细分析LinkedList源码,感谢您的阅读,我们下节见

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
/* * 基于链表实现树结构 */ package dsa; public class TreeLinkedList implements Tree { private Object element;//树根节点 private TreeLinkedList parent, firstChild, nextSibling;//父亲、长子及最大的弟弟 //(单节点树)构造方法 public TreeLinkedList() { this(null, null, null, null); } //构造方法 public TreeLinkedList(Object e, TreeLinkedList p, TreeLinkedList c, TreeLinkedList s) { element = e; parent = p; firstChild = c; nextSibling = s; } /*---------- Tree接口中各方法的实现 ----------*/ //返回当前节点中存放的对象 public Object getElem() { return element; } //将对象obj存入当前节点,并返回此前的内容 public Object setElem(Object obj) { Object bak = element; element = obj; return bak; } //返回当前节点的父节点;对于根节点,返回null public TreeLinkedList getParent() { return parent; } //返回当前节点的长子;若没有孩子,则返回null public TreeLinkedList getFirstChild() { return firstChild; } //返回当前节点的最大弟弟;若没有弟弟,则返回null public TreeLinkedList getNextSibling() { return nextSibling; } //返回当前节点后代元素的数目,即以当前节点为根的子树的规模 public int getSize() { int size = 1;//当前节点也是自己的后代 TreeLinkedList subtree = firstChild;//从长子开始 while (null != subtree) {//依次 size += subtree.getSize();//累加 subtree = subtree.getNextSibling();//所有孩子的后代数目 } return size;//即可得到当前节点的后代总数 } //返回当前节点的高度 public int getHeight() { int height = -1; TreeLinkedList subtree = firstChild;//从长子开始 while (null != subtree) {//依次 height = Math.max(height, subtree.getHeight());//在所有孩子中取最大高度 subtree = subtree.getNextSibling(); } return height+1;//即可得到当前节点的高度 } //返回当前节点的深度 public int getDepth() { int depth = 0; TreeLinkedList p = parent;//从父亲开始 while (null != p) {//依次 depth++; p = p.getParent();//访问各个真祖先 } return depth;//真祖先的数目,即为当前节点的深度 } }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值