代码不全只是自己记录学习,借鉴:https://blog.csdn.net/javazejian/article/details/52953190;强烈推荐此大神博客。。。
package com.zejian.structures.LinkedList.MyLinked;
/**
* 单链表的进化版本
* @param <T>
*/
public class SingleLinkedList2<T> implements ILinkedList<T>{
//设定一个空的头结点
private Node<T> headnode;
//设定一个尾节点,代表链表的最后的一个数据
private Node<T> rear;
//构造方法初始化数据
public SingleLinkedList2(){
this.headnode=this.rear=new Node<T>(null);
}
//构造方法
public SingleLinkedList2(Node<T> head){
this();
//构造方法,将head对象赋值给头结点headNode的next上,将head的索引赋值rear,将最后的数据可以由 .rear获取
//这是的头结点就是尾节点,这时的headNode与rear是对等的,头结点的下一个节点为初始化的
this.headnode.next=this.rear.next=head;
//将尾部结构赋值给rear,也就是说rear就是head,只是通过一个公共的变量来控制尾部
rear=rear.next;
}
/**
* 传入数组,生成单链表
* @param arr
*/
public SingleLinkedList2(T[] arr){
this();
if (arr!=null&&arr.length>0){
//初始化头结点的下一个节点
this.headnode.next=new Node<T>(arr[0]);
//将数组的第一个数据赋值头结点的下一个数据
rear=this.headnode.next;
int i=1;
while (i<arr.length){
rear.next=new Node<T>(arr[i++]);
//始终将rear作为最后的值得引用
rear=rear.next;
}
}
}
/**
* 传入链表生成新的链表
* @param linkedList2
*/
public SingleLinkedList2(SingleLinkedList2<T> linkedList2){
//初始化链表
this();
if(linkedList2!=null&&linkedList2.headnode.next!=null){
//将头结点传入
this.headnode.next=new Node<T>(linkedList2.headnode.data);
//获取传入链表的头节点的下一个节点
Node<T> p = linkedList2.headnode.next;
//获取新链表的头节点的下一个节点
rear=this.headnode.next;
//赋值
while (p!=null){
//将p中的数据给尾节点数据赋值
rear.next= new Node<>(p.data);
//跟新末尾指针指向
rear=rear.next;
p = p.next;
}
}
}
/**
* 判断链表是否为空
* 需要判断链表是否为空的依据是头结点head是否为null,当head=null时链表即为空链表,因此我们只需判断头结点是
* @return
*/
@Override
public boolean isEmpty() {
return this.headnode==null;
}
/**
* 计算链表的长度
* 遍历链表需要从头结点HeadNode开始,为了不改变头结点的存储单元,
* 声明变量p指向当前头结点和局部变量length,然后p从头结点开始访问,
* 沿着next地址链到达后继结点,逐个访问,直到最后一个结点,每经过一个结点length就加一,
* 最后length的大小就是链表的大小
* @return
*/
@Override
public int length() {
int length=0;
Node<T> p=headnode;
while (p!=null){
length++;//这个节点不为null,就加1重新赋值
p=p.next;//指向下一个节点的位置
}
return length;
}
/**
* 在单链表中获取某个元素的值是一种比较费时间的操作,需要从头结点开始遍历直至传入值index指向的位置,
* 其中需要注意的是index是从0开始计算,也就是说传递的index=3时,查找的是链表中第4个位置的值
* @param index
* @return
*/
@Override
public T get(int index) {
Node<T> p=this.headnode;
int count=0;
while (p!=null&&count<index){
p=p.next;//指向下一个节点的位置,获取下一个节点的对象
count++;
}
if (p!=null){
return p.data;
}
return null;
}
/**
* 根据传递的index查找某个值并替换其值为data,
* 其实现过程的原理跟get(int index)是基本一样的,先找到对应值所在的位置然后删除即可
* @param index
* @param data
* @return
*/
@Override
public T set(int index, T data) {
if(this.headnode!=null&&data!=null&&index>=0){
Node<T> pre=this.headnode;//获取头结点
int count=0;
//查找需要替换的节点
while (pre!=null&&count<index){
count++;
pre=pre.next;
}
if (pre!=null){
T oldData=pre.data;
pre.data=data;//设置新值
return oldData;
}
}
return null;
}
/**
* 根据下标添加结点
* 1.头部插入
* 2.中间插入
* 3.末尾插入
* @param index 该值从0开始,如传4就代表插入在第5个位置
* @param data
* @return
*/
@Override
public boolean add(int index, T data) {
if (data==null){
throw new NullPointerException("data can\'t be empty!");
}
if(index<0)
throw new NullPointerException("index can\'t less than 0");
//无需区分位置操作,中间/头部/尾部插入
int j=0;
Node<T> pre=this.headnode;
while (pre.next!=null&&j<index){
pre=pre.next;
j++;
}
//将新插入的结点的后继指针指向pre.next
Node<T> q=new Node<T>(data,pre.next);
//更改指针指向
pre.next=q;
//如果是未指针
if (pre==this.rear)
this.rear=q;
return true;
}
/**
* 从代码和图示看来确实只要获取当前的尾部指向的结点rear并把新结点赋值给rear.next,最后更新rear结点的值即可,
* 完全不用遍历操作,但是如果是根据index来插入的还,遍历部分结点还是少不了的,
* 下面看看根据index插入的代码实现,由于有了头结点,头部、中间、尾部插入无需区分操作位都视为一种情况处理。
* @param data
* @return
*/
@Override
public boolean add(T data) {
//判断
if (data==null)
System.out.println("data is empty!");
this.rear.next=new Node<T>(data);
rear=rear.next;
return true;
}
/**
*
* @param index
* @return
*/
@Override
public T remove(int index) {
T old=null;
//通过headnode和rear删除
if (index>0){
Node<T> p = this.headnode;
int j=0;
while (p.next!=null&&j<index){
p= p.next;
j++;
}
//获取删除节点上一个节点
Node<T> move = p.next;
if (move!=null){
//获取旧值
old=move.data;
//判断是否是尾节点、
if (move==this.rear){//位置的比较
//将上一个节点赋值给尾节点
this.rear=p;
}
}
//删除节点
p.next=move.next;
}
return old;
}
/**
* 根据data移除所有数据相同的结点
* @param data
* @return
*/
@Override
public boolean removeAll(T data) {
if (data!=null){
//用于与记录要删除节点前的一个节点
Node<T> font = this.headnode;
//当前遍历的节点
Node<T> pre = font.next;
//查询所有节点相同的数据并删除
while (pre!=null){
// 判断data数据是否与链表数据相等,
if (pre.data.equals(data)){
//如果恰好是尾部结点,则更新rear的指向
if(data.equals(rear.data)){
this.rear=font;
}
//更改指针方法
font.next=pre.next;
//font.next重新指向pre,这时pre=pre.next
pre=font.next;
}else{
//如果不是相同的节点,就设置指针
font=pre;
pre=pre.next;
}
}
}
return true;
}
/**
* 删除所有节点
*/
@Override
public void clear() {
//将头结点的下一个节点赋值为空
this.headnode.next=null;
//将尾节点赋值为空
this.rear=this.headnode;
}
/**
* 判断是否包含某个值
* @param data
* @return
*/
@Override
public boolean contains(T data) {
Node<T> headnode = this.headnode;
while (headnode.next!=null){
if (data.equals(headnode.data)){
return true;
}
headnode=headnode.next;
}
return false;
}
/**
* 从末尾连接两个链表
* @param list
*/
public void concat(SingleLinkedList2<T> list) {
//如果本链表为空
if (this.headnode.next == null) {
//直接将外来链表赋值到本链表的头结点的后面一个节点
this.headnode.next = list.headnode.next;
} else {
Node<T> p = this.headnode.next;
while (p.next != null) {
p = p.next;
}
p.next = list.headnode.next;
//更新指针
this.rear = list.rear;
}
}
/**
*打印
*/
public String toString(){
String str="(";
Node<T> pre = this.headnode.next;
while (pre!=null)
{
str += pre.data;
pre = pre.next;
if (pre!=null)
str += ", ";
}
return str+")";
}
public static void main(String[] args) {
String[] letters={"A","B","C","D","E","F"};
SingleLinkedList2<String> list=new SingleLinkedList2<>(letters);
list.add("aaa");
list.removeAll("aaa");
for (int i = 0; i <list.length() ; i++) {
System.out.println(list.get(i));
}
System.out.println(list.contains("a"));
list.set(1,"vv");
System.out.println(list.toString());//(vv, B, C, D, E, F)
}
}