数据结构 之链表 单向链表-java实现
1.链表之所叫链表,是因为每个对象都存储了下对象的引用地址看起像链条一样连在一起所以称之为链表;如下图:
2.但是如果用容器的思想去看链表,他或许又像一个嵌套的java对象容器层层的像似千层饼一样如图:
1.知道链表概念以后就可以动手把他抽象成一个类,这个类肯定是有两种属性,第一种属性是,通过自己去找到下一个对象,第二个他还可以携带自身的数据;知道这些特征就可上代码了如下
public class linkedTable<T> {
//头节点
private Node<T> head;
//用内部类定义一个数据节点
class Node<T> {
//存储数据对象
T data;
//下一个节点
Node<T> next;
public Node() {
}
//创建一个构造方法
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
}
}
2.我们知道他的接口以后在思考一下怎么存储数据呢,怎么设计他的添加元素的方法呢?
3.第一种头插法,如下图:
基于这个思想我们上代码:
//定义个add方法
//1.用头插法向链表添加一个数据节点
public boolean firstAdd(T data){
//创建一个新的节点,把 Data元素放进去
if(data==null){
//不让插入空的数据
return false;
}
Node<T> newNode = new Node<>(data, null);
//判断头节点是否为空
if(head==null){
//如果头节点为空,那直接把新节点变成头节点
head=newNode;
}else {
//如果头节点不为空
//1.先让新节点指向头节点
newNode.next=head;
//在让新节点变头节点
head=newNode;
}
return true;
}
5.我们在写一个获取全部元素的方法让他去打印数据,让他来验证我们这种插入方法是否正确
//查询全部节点的方法
public String selectAll(){
String str="[";
//定义个迭代变量指向头节点
Node tamp=head;
while (tamp!=null){
//掉用Data的toString方法
str+=","+tamp.data.toString();
//让迭代变量指向下一个节点
tamp=tamp.next;
}
//把第一个逗号去除掉
return str.replaceFirst(",","")+"]";
}
6.写一个demo类去测试一下我们的头插法是否有效:
可见我们的头插法是有效的
第二中尾插法如图:
上代码:
public boolean lastAdd(T data){
//创建一个新的节点,把 Data元素放进去
if(data==null){
//不让插入空的数据
return false;
}
Node<T> newNode = new Node<>(data, null);
//如果头节点是空
if (head==null){
//新节点变成头节点
head=newNode;
}else {
//找到他最后一个节点
Node last=head;
while (last.next!=null){
last=last.next;
}
//把最后一个节点的next指向新节点
last.next=newNode;
}
return true;
}
用demo测试一下尾插法是否有效:
可以见到除了顺序不一样,数据还是正确的打印出来了
7.我们现在可以往链表添加数据了,那怎么获取链表的数据呢,我们可以考虑加入索引,和链表长度,把之前的代码在改一下:
package com.data.structure;
public class linkedTable<T> {
//头节点
private Node<T> head;
private int size;
//用内部类定义一个数据节点
class Node<T> {
//存储数据对象
T data;
//下一个节点
Node<T> next;
int index;
public Node() {
}
//创建一个构造方法
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
index=size;
}
}
//定义个add方法
//1.用头插法向链表添加一个数据节点
public boolean firstAdd(T data){
//创建一个新的节点,把 Data元素放进去
if(data==null){
//不让插入空的数据
return false;
}
Node<T> newNode = new Node<>(data, null);
//判断头节点是否为空
if(head==null){
//如果头节点为空,那直接把新节点变成头节点
head=newNode;
}else {
//如果头节点不为空
//1.先让新节点指向头节点
newNode.next=head;
//在让新节点变头节点
head=newNode;
}
size++;
return true;
}
public boolean lastAdd(T data){
//创建一个新的节点,把 Data元素放进去
if(data==null){
//不让插入空的数据
return false;
}
Node<T> newNode = new Node<>(data, null);
//如果头节点是空
if (head==null){
//新节点变成头节点
head=newNode;
}else {
//找到他最后一个节点
Node last=head;
while (last.next!=null){
last=last.next;
}
//把最后一个节点的next指向新节点
last.next=newNode;
}
size++;
return true;
}
//查询全部节点的方法
public String selectAll(){
String str="[";
//定义个迭代变量指向头节点
Node tamp=head;
while (tamp!=null){
//掉用Data的toString方法
str+=","+tamp.data.toString();
//让迭代变量指向下一个节点
tamp=tamp.next;
}
//把第一个逗号去除掉
return str.replaceFirst(",","")+"]";
}
}
那我们来写一下get方法通过索引的方式去实现上代码:
//通过索引获取元素的方法
public T get(int index){
Node tamp=head;
while (tamp!=null){
if (tamp.index==index){
return (T)tamp.data;
}
tamp= tamp.next;
}
return null;
}
//返回链表长度
public int getSize(){
return size;
}
这个就比较简单了,不赘述了,直接测试:
8.修改的方法比较简单我就不实现了,现在我们来研究一下他怎么在链表上移除一个元素,那现在就分了三种情况
第一种,他移除的元素就是头节点
第二种,他移除的是尾节点
这两种比较简单
主要是第三种他移除的数据在中间节点
我直接上代码去实现:
public T remove(int index) throws Exception {
if (index<0||index>=size){
throw new Exception("索引越界");
}
//因为逻辑索引位置不带表实际的物理位置,索引需要拿节点的索引去比较,你可以把节点的索引理解为当前元素的id或者是一个key
//如果移除的元素是头节点
if ( head.next.index!=index){
//直接把第二元素变成新的头节点
head=head.next;
return (T)head;
}
//不是头节点
//先获取当前节点的之前一个节点
Node beforeNode=null;
Node tame=head;
while (tame!=null){
if (tame.next.index==index){
beforeNode=tame;
break;
}
}
if (beforeNode==null){
//没有找到这个元素无法删除
return null;
}
//要删除的节点
Node delNode=beforeNode.next;
//要删除的节点是不是尾节点
if (delNode.next==null){
//如果是尾节点,那直接让前一个节点的next,这样前一个节点就成了新的尾节点
beforeNode.next=null;
return (T)delNode;
}
//上面情况都不满足,那他只能是中间节点
//让他前一个节点指向他后一个节点,这样中间节点就删除了
beforeNode.next=delNode.next;
return (T)delNode;
}
我们上代码去测试测试:
我们让链表移除指定元素生效。
单向链表学会了吗?,评论区见!