1.了解单链表
不同于数组结构,数组的存储单元是连续的,因此在查找时可以采用下标的方式,但单链表中的数据是以结点来表示,每个结点的构成都是由元素和指针,元素就是数据的存储单元,指针就是链接每个结点的地址数据,单链表的存放可以是连续的也可以是不连续的,因此查找不方便,没有下标的概念。
2.手写单链表各部分
1.首先是定义节点类,方便后续在单链表类中调用。
public class Node<E> {//<E>为任意类型,内部不需要强制转换类型
E item;//数据
Node<E> next;//下一个
Node<E> prev;//上一个,单链表没有,双链表才有,用于回溯
public Node(E item, Node<E> next) {//单链表
this.item = item;
this.next = next;
}
}
2.单链表的在头部、尾部添加数据
把添加的数据放在头部,首先要进行判断头指针指向的开始结点是否为null,如果为空则直接把数据放在头指针下的开始结点,若是有则需要将要添加的这个数据的下一位指向头指针下的开始结点一开始指向的next,将开始结点的next指向添加的数据。
把数据添加到尾部,首先要判断头指针指向的开始结点是否为null,如果为空则直接把数据放在头指针下的开始结点,若是有则需要进行循环找到下一位指向为空的最后一位,让它的next指向要添加的这个数据。
为了后续查看方便,要进行toString重写。
public class SingleLinkList<E>{
private Node head;//定义头部
private Node<E> last;
private static int size;
public SingleLinkList() {
head=new Node<>(null, null);
}
public void addFirst(E e){//在头部添加
Node<E> newHead=head;
Node<E> newNode=new Node<>(e, null);
if(newHead.next==null){//判断头结点指向为空,为空时
newHead.next=newNode;
}else{//不为空时
newNode.next=head.next;//新数据的下一位是头结点的next指向
newHead.next=newNode;//头结点的next指向新数据
}
size++;//为了之后方便获取某一位置下的数据
}
public void addLast(E e){//在尾部添加
Node<E> newNode=new Node<>(e, null);
Node<E> newHead=head;
if(newHead.next==null){//判断头结点指向为空,为空时
newHead.next=newNode;
size++;
return;
}
while(newHead.next!=null){//不为空时,进行循环找next指向空的最后一位
newHead=newHead.next;
}
newHead.next=newNode;//最后一位的next指向要添加的新数据
size++;
}
@Override
public String toString() {
Node<E> newHead=head;
StringBuilder str=new StringBuilder("[");
while(newHead.next!=null){
newHead=newHead.next;
str.append("'"+newHead.item+"'");
if(newHead.next!=null){
str.append(",");
}
}
str.append("]");
return str.toString();
}
3.在任意位置添加数据、在尾部添加集合数据、在任意位置添加集合数据
在任意位置添加数据时,首先要进行添加的下标位置是否正确的判断,若是不符合进行抛出错误处理,还是进行判断头指针指向的开始结点是否为null,如果为空则直接把数据放在头指针下的开始结点,若是不为空进行循环查找到指定位置处,让指定位置的next指向这个数据,这个数据的next指向一开始头部结点所指向的next。
在尾部添加集合数据时,首先要判断添加的这个集合是否为空,如果为空,则进行抛出错误处理,若不为空则循环找出最后一位的next,让其指向集合数据,集合数据进行for each进行遍历,依次添加到头结点的next下。
在任意位置添加集合数据时,首先要进行判断头指针指向的开始结点是否为null,如果为空则直接把集合数据放在头指针下的开始结点,然后返回,若是不为空进行循环查找到指定位置处,让指定位置的next指向这个集合数据,这个集合数据的最后一位next指向一开始头部结点所指向的next。
public void add(int index,E e){
checkIndexRange(index);//检查下标范围是否正确
Node<E> newHead=head;
Node<E> newNode=new Node<>(e, null);
if(newHead.next==null){
newHead.next=newNode;
}else{
for(int i=0;i<index;i++){
newHead=newHead.next;
}
newNode.next=newHead.next;
newHead.next=newNode;
}
size++;
}
public void addAll(Collection<? extends E> c){
Node<E> newHead=head;
if(c.size()==0){
throw new RuntimeException(c.getClass()+" is null");
}
while(newHead.next!=null){
newHead=newHead.next;
}
for(E e:c){
newHead.next=new Node<>(e, null);
newHead=newHead.next;
size++;
}
}
public void addAll(Collection<? extends E> c,int index){
checkIndexRange(index);
Node<E> newHead=head;
if(newHead.next==null){
addAll(c);
return ;
}
for(int i=0;i<index;i++){
newHead=newHead.next;
}
Node<E> save=newHead.next;
for(E e:c){
Node<E> newNode=new Node<>(e, null);
newHead.next=newNode;
newHead=newNode;
size++;
}
newHead.next=save;
}
public static void checkIndexRange(int index){//检查下标范围是否正确
if(index<0 || index>=size){
throw new IndexOutOfBoundsException("下标越界!");
}
}
4.删除头部结点、尾部结点、指定位置结点
删除头部结点时,首先要进行判断头指针指向的开始结点是否为null,如果为空则抛出异常没有办法进行删除处理,还有就是判断长度是否为1,若为1进行存储这个数据,然后把长度变为0,若不是则进行存储要删除数据的存储,然后将头结点指向的next指向头结点指向的next下的next,进行直接跳过要删除的那一项。
删除尾部结点时,首先要进行判断头指针指向的开始结点是否为null,如果为空则抛出异常没有办法进行删除处理,找到倒数第二位的next指向令其为空,记录此项数据。
删除指定位置的结点时,首先要进行添加的下标位置是否正确的判断,若是不符合进行抛出错误处理,还要进行判断头指针指向的开始结点是否为null,如果为空则抛出异常没有办法进行删除处理,还有就是判断长度是否为1,若为1进行存储这个数据,然后把长度变为0,若上述都不是则要进行for循环找到要删除那一项的上一个位置,保存要删除的数据,然后把要删除的上一位的next指向要删除的数据所指向的next。
public Object removeFirst(){
Object o;
if(head.next==null){
throw new NullPointerException("空指针异常");
}else if(size==1){
o=head.next.item;
head.next=null;
size--;
}else{
o=head.next.item;
head.next=head.next.next;
size--;
}
return o;
}
public Object removeLast(){
Node<E> newHead=head;
if(newHead.next==null){
throw new NullPointerException("空指针异常");
}
for(int i=0;i<size-1;i++){
newHead=newHead.next;
}
Object o=newHead.next.item;
newHead.next=null;
size--;
return o;
}
public Object remove(int index){
checkIndexRange(index);
Node<E> newHead=head;
Object o;
if(head.next==null){
throw new NullPointerException("空指针异常");
}else if(size==1){
o=newHead.next.item;
newHead.next=null;
size--;
return o;
}
for(int i=0;i<index;i++){
newHead=newHead.next;
}
o=newHead.next.item;
newHead.next=newHead.next.next;
size--;
return o;
}
5.获取指定内容的位置、获取指定位置的内容
获取指定内容的位置时,首先要进行判断头指针指向的开始结点是否为null,如果为空则返回-1,若不为空则进行遍历查找,若找到相同内容直接返回此位置,若没有找到,则输出-1。
获取指定位置的内容时,首先要进行添加的下标位置是否正确的判断,若是不符合进行抛出错误处理,然后进行循环找到指定位置处,进行返回此处的数据内容。
public int indexOf(E e){
Node<E> newHead=head;
int index=0;
if(newHead.next==null){
return -1;
}else{
while(newHead.next!=null){
newHead=newHead.next;
if(newHead.item.equals(e)){
return index;
}
index++;
}
}
return -1;
}
public E get(int index){
checkIndexRange(index);
Node<E> newHead=head;
for(int i=0;i<=index;i++){
newHead=newHead.next;
}
return newHead.item;
}
6.清空处理
直接将头结点的下一位为空,数据长度为0.
public void clear(){
head.next=null;
size=0;
}