链表定义
通俗点来说,链表是由一系列结点组成的,而结点是由数据域和指针域组成的,数据域主要存储数据元素信息,而指针域中存储其直接后继的地址
头结点
真实头结点:其第一个结点用于存储数据
虚拟头结点:其第一个结点不许存储数据,即数据域为空
头指针:一个引用变量,存储头结点地址的指针而已
链表的实现
对链表的某些操作功能也是基于线性表实现的
package Ds02.动态链表;
import DS01.动态数组.List;
import java.util.Iterator;
/*步骤:
1.先定义一个Node结点内部类,用于描述和定义结点
2.定义head和rear指针,但实质就是虚拟结点
3.实现各种操作
* */
//虚拟头结点实现的
//用动态链表实现的线性表 链表
public class LinkList<E> implements List<E> {
private Node head;//链表头指针(虚头结点)
private Node rear;//链表尾指针(最后一个结点)
private int size;//链表的元素个数(结点个数)不包括头节点,头节点为虚结点
public LinkList(){//不需要最大容量
head=new Node();//head是一个对象的名称,创建一个结点对象 虚拟头结点
rear=head;//当前两个指向同一个
}
private class Node{//内部类(结点的属性与行为)
E data;//数据域
Node next;//指针域,因为存下一个节点,节点类型为Node
Node(){
this(null,null);
}
Node(E data,Node next){
this.data=data;
this.next=next;
}
@Override
public String toString(){
return data.toString();
}
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size==0&&head==rear;
}
@Override
public void add(int index, E e) {
if(index<0||index>size){//在尾部添加元素
throw new IllegalArgumentException("角标越界");
}
Node n=new Node();
n.data=e;//用结点把e存储起来(存到数据域中),然后把结点插入
if(isEmpty()){//空表状态 特殊处理
head.next=n;
rear=n;
}else if(index==0){//头插法
n.next=head.next;//把当前头结点存储的下一跳赋给当前要插入的下一跳
head.next=n;
}else if(index==size){//尾插法
rear.next=n;//之前的rear的下一跳指到现在要插入的结点
rear=n;//当前的尾结点为n;
}else{//中间插
Node p=head;
for(int i=0;i<index;i++){
p=p.next;//移到要插入位置的前一个
}
n.next=p.next;//把当前的next赋给现在要插入的节点的next
p.next=n;//把要插入的结点赋给当前的next
}
size++;
}
@Override
public void addFirst(E e) {
add(0,e);
}
@Override
public void addLast(E e) {
add(size,e);
}
@Override
public E get(int index) {
if(isEmpty()){
throw new IllegalArgumentException("链表为空");
}
if(index<0||index>=size){
throw new IllegalArgumentException("角标越界");
}
if(index==0){
return head.next.data;
}else if(index==size-1){
return rear.data;
}else{
Node p=head;
for(int i=0;i<=index;i++){
p=p.next;
}
return p.data;
}
}
@Override
public E getFirst() {
return get(0);
}
@Override
public E getLast() {
return get(size-1);
}
@Override
public void set(int index, E e) {
if(isEmpty()){
throw new IllegalArgumentException("空表");
}
if(index<0||index>=size){
throw new IllegalArgumentException("角标越界");
}
Node p=head;
for(int i=0;i<=index;i++){//也是从虚拟头结点开始,但是虚拟头结点不算进去
p=p.next;
}
p.data=e;
}
@Override
public boolean contains(E e) {
return find(e)!=-1;
}
@Override
public int find(E e) {
if(isEmpty()){
throw new IllegalArgumentException("空表");
}
Node p=head;
for(int i=0;i<size;i++){
p=p.next;
if(p.data.equals(e)){
return i;
}
}
return -1;
}
@Override
public E remove(int index) {
if(isEmpty()){
throw new IllegalArgumentException("空表");
}
if(index<0||index>=size){
throw new IllegalArgumentException("角标越界");
}
E ret=null;
if(size==1){//只有一个元素,无论删头删尾都一样
ret=rear.data;//需要返回要删除位置的元素,需要先保存一下,不然就会修改
head.next=null;
rear=head;
}else if(index==0){//删头
Node del=head.next;//第一个结点
head.next=del.next;
del.next=null;//垃圾回收
del=null;//让删除的那个任何都找不到
}else if(index==size-1){//删尾
Node p=head;
while (true){
if(p.next!=rear){
p=p.next;
}else{
break;
}
}
ret=p.next.data;//最后一个存的数据(倒数第二个结点的下一跳中所存的数据)
p.next=null;
rear=p;
}else{
Node p=head;
for(int i=0;i<index;i++){
p=p.next;//移到删除位置的前一个
}
Node del=p.next;
ret=del.data;
p.next=del.next;
del.next=null;//释放空间
}
size--;
return ret;
}
@Override
public E removeFirst() {
return remove(0);
}
@Override
public E removeLast() {
return remove(size-1);
}
@Override
public void removeElement(E e) {
int index=find(e);
if(index!=-1){
remove(index);
}else{
throw new IllegalArgumentException("未找到");
}
}
@Override
public void clear() {
head.next=null;
rear=head;
size=0;
}
@Override
public int getCapacity() {
return 0;
}
public String toString(){
StringBuilder sb=new StringBuilder();
sb.append("LinkList: "+size+"\n");
sb.append('[');
if(isEmpty()){
sb.append(']');
}else{
Node p=head;
while (true){
if(p.next!=rear){
p=p.next;
sb.append(p.data);
sb.append(",");
}else{
p=p.next;
sb.append(rear.data);
sb.append("]");
break;
}
}
}
return sb.toString();
}
@Override
public Iterator<E> iterator() {
return new LinkListIterator();
}
public class LinkListIterator implements Iterator<E>{
Node p=head;
@Override
public boolean hasNext() {
return p.next!=null;
}
@Override
public E next() {
p=p.next;
return p.data;
}
}
}
链表的头插法
功能分析:
利用头插法添加结点,简单来说,就是将头结点的后继赋给当前要插入结点的后继,然后将此时头结点的后继定义为此时要插入的结点.
if(index==0){//头插法
n.next=head.next;//把当前头结点存储的下一跳赋给当前要插入的下一跳
head.next=n;
}
链表的尾插法
功能分析:
将结点插入此链表的最后,就是将要插入的结点赋给当前尾结点rear的后继,然后把rear尾结点后移。
if(index==size){//尾插法
rear.next=n;//之前的rear的下一跳指到现在要插入的结点
rear=n;//当前的尾结点为n;
}
链表的一般插入
功能分析:
找到要插入位置的前一个,将当前位置的后继赋给要插入结点的后继,然后此时位置的后继为当前要插入的结点
else{//中间插
Node p=head;
for(int i=0;i<index;i++){
p=p.next;//移到要插入位置的前一个
}
n.next=p.next;//把当前的next赋给现在要插入的节点的next
p.next=n;//把要插入的结点赋给当前的next
}
链表的删除(只有一个元素)
功能分析:
只剩一个元素时删头删尾都一样,先将要删除的结点存储一下,避免最后返回时出错,然后将头结点的后继置空,尾结点等于头结点
if(size==1){//只有一个元素,无论删头删尾都一样
ret=rear.data;//需要返回要删除位置的元素,需要先保存一下,不然就会修改
head.next=null;
rear=head;
}
注意:在删除和添加元素是,需从虚拟头结点开始,到要删除或要添加位置的前一个,所以走过的次数是要进行操作的下标次,即index.