好多人都觉得为什么要自己写这样的数据结构,变成里面不是有吗?为什么要去写,有这个疑问,其实这个疑问这我的脑海中也存在了很长一段时间,本人是学习java编程的,直接看java的集合框架不行吗?这个时候如果你的水平到了还好。如果没有,你会发现你根本就理解不了编程语言里面数据结构,看了就忘掉了,也理解不了,学习了半个月编程里面的集合发现学不会,还要抱怨怎么可以这样,看了半个月都没有看懂,于是就放弃了。如果让我来分析缘由,那就是市面上已经发布的编程语言里面的数据结构(集合框架)都是王者水平(巅峰王者)的人写的,这样厉害的人写出的东西,你一个没有啥基础的编程人员(按照游戏段位排序:青铜),几天几个月就整的明明白白,如果这样都能会,只有一种可能,你是天才,不是普通人。
所以基于这样的分析,我们学习就是要先从没有进数据结构的大门到变得更够秀(进军白银段位以致更高的水平),所以简易版的练习过程是在必行,脚踏实地,方是走过漫漫长路的捷径之路,于此,便有了一些列的数据结构的简易版,今天分析链表。
public class LinkedListDemo {
/**
* 节点类
*/
private class Node {
public E e;
public Node next;
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
public Node(E e) {
this(e, null);
}
public Node() {
this(null, null);
}
@Override
public String toString() {
return e.toString();
}
}
private Node dummyHead;
private int size;
public LinkedListDemo() {
dummyHead = new Node(null, null);
size = 0;
}
public int getSize() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
}
泛型类设计理念创建类,这样有更好的类型高可拓展性,由于链表是引用存储方式,所以还需要设计一个内部类Node(用于存放值以及下一个Node节点的指针)。属性有size、dummyHead,先说一下size,是用来记录存放链表中的数据大小,对应size还有两个方法,getSize获取大小以及isEmpty是否为空;dummyHead属性是整个设计链表类的重头戏,在构造方法中 dummyHead = new Node(null, null);,dummy可以翻译为虚拟之意,目前的链表实现是以单向链表形式实现,单向必然需要一个方向管理,那么我这里就是通过头head进行,那又为什么要添加一个虚拟头(dummyHead)而不是null??这里和链表数据结构增删有一定关系,这样设计有利于增加和修改操作(当然还能有更好的设计方式,目前鉴于本人水平有限)。对此原因进行简要论述:
这样设计有利于1、当想要在第一个位置添加的时候能够迅速定位到。2、当想要在第一个位置删除的时候能够迅速定位到。后面将会使用源码进行进一步分析。
添加方法以及添加方法衍生出来的方法源码如下:
public void add(E e, int index) {
if (index < 0 && index > size)
throw new IllegalArgumentException(" index is not correct");
Node prev = this.dummyHead;
for (int i = 0; i < index; i++) {
prev = prev.next;
}
prev.next = new Node(e, prev.next);
size++;
}
public void addFirst(E e) {
add(e, 0);
}
public void addLast(E e) {
add(e, size);
}
添加方法参数有传入的值以及要存入的下标位置。当然在添加到相应位置前,需要对index进行校验,防止不在链表范围内;
prev就是链表的虚拟节点为起点。之后就是进行for循环,每次都找node=node.next,这里做了一个处理就是如果下个节点不是最终要的节点,那么就将下个节点引用赋值给当前节点,直到找到要插入坐标的前一个节点index=2的引用。
当index为2是,分析思路如上图所示(index只是一个常数,2或3其实一样,当然可以设置为k,代表常数),for循环如下:
for (int i = 0; i < 2; i++) {
prev = prev.next;
}
当找到了index下标的前一个位置node以后,将下一个prev.next(下标为2的位置引用)赋值给node.next(目前要插入的Node节点),这是时候,新插入的node已经和下面的node有了指向,然后就把自己在指向prev.next,也就是prev.next=node,0>>>>>1>>>>666>>>>2>>>>等等就算连接起来了,一下表形式放入成功,size进行加1(size++)。
代码赋值解读:①和②实现的赋值是一样的,不太明白的,去看一下上面的节点内部类,想一下就明白了,这里就不做过多的阐述了。
//①
Node a=new Node(e);
a.next=prev.next;
prev.next=a;
//②
prev.next = new Node(e, prev.next);
插入链表的思维图:
由add衍生出来的addFirst、add方法分别下标是0和size(其实一下标来论,目前只有size-1个,但是由于是添加到size-1个的后面,肯定是size,添加之前是没有的),即可完成方法封装。
手写源码如下:
public E reomve(int index){
if (index<0||index>size)
throw new IllegalArgumentException("");
Node prev = this.dummyHead;
for (int i = 0; i < index; i++) {
prev=prev.next;
}
Node retNode=prev.next;
prev.next=retNode.next;
retNode.next=null;
size--;
return retNode.e;
}
public E reomveFirst(){
return reomve(0);
}
public E reomveLast(){
return reomve(size-1);
}
remove方法:1、首先校验index是否在链表的大小范围内;2、以虚拟头结点为开始位置( Node prev = this.dummyHead),for循环index=0获取到的位置是链表的第一个位置引用,通过循环找到待删除的前一个节点(这里就是找到1下标引用的位置),找到以后将delNode(retNode)要删除节点的next引用赋值到1(prev)为节点的next,即
Node retNode=prev.next;
prev.next=retNode.next;
将链表重新连接起来,当然要删除的delNode(retNode)的next指向的3位置的引用需要断开连接,所以赋值为null(retNode.next=null),被删掉节点等待jvm垃圾回收;3、删除掉一个node后size减一(size–),返回删除的值retNode.e
删除方法衍生出来的删除第一个和最后一个的方法分别是下标为0和size-1,对于removejinx重用。
//获得链表的第index(0-based)的元素
//在链表中不是一个常用的操作
public E get(int index){
if (index<0||index>size)
throw new IllegalArgumentException("");
Node cur = dummyHead.next;
for (int i = 0; i < index; i++)
cur=cur.next;
return cur.e;
}
//获得链表的第一个元素
public E getFirst(){
return get(0);
}
//获得链表的第一个元素
public E getLast(){
return get(size-1);
}
public void set(int index,E e){
if (index<0||index>size)
throw new IllegalArgumentException("");
Node cur = dummyHead.next;
for (int i = 0; i < index; i++)
cur=cur.next;
cur.e=e;
}
//查找链表中是否存在元素e
public boolean contains(E e){
Node cur = dummyHead.next;
while (cur!=null){
if (cur.e.equals(e))
return true;
cur=cur.next;
}
return false;
}
@Override
public String toString() {
StringBuffer res = new StringBuffer();
for ( Node cur = dummyHead.next;cur!=null;cur=cur.next)
res.append(cur+"--->");
res.append("null");
return res.toString();
}
1、判断index合法性;2、节点就要从链表的第一个开始,也就是dummyHead.next虚拟节点的下一个。当下标传入的是0时,for不符合循环条件直接返回,不需要进行循环查找,直接返回cur.e;当下标为3时,for循环代码:
for (int i = 0; i < 3; i++)
cur=cur.next;
如图思维图所示
当然是实现get的衍生方法有getFirst和getLast分别是下标为0和size-1的元素。
set方法原理其实就是在get方法找到node的时候进行对node.e进行赋值,思维逻辑与get一直;
基本的思路就是将链表循环一遍,查看穿入值是否与链表中节点的值一致,当然这样比较存在一些问题的(待完善)
写博不易,关注博客了解更多最新内容