线性结构的链式存储是用若干地址分散的存储单元存储数据元素,逻辑上相邻的两个数据元素在物理位置上并不一定相邻,必须采用附加信息来表示数据元素之间的顺序关系。因此存储一个数据元素的数据单元至少包含两部分------数据域和地址域
上述的结构通常称为结点
一个节点表示一个数据元素,通常节点当中的地址会把数据结点连接起来,节点当中的连接关系体现了线性表当中数据元素间的顺序关系,采用这种关系的称为线性链表。
从上图当中,head是线性链表当中的第一个节点,但是这个节点在数据域当中并没有存储数据,这里之所以写这个的目的是我们能够通过头指针去遍历我们整个链表。
最后一个节点的地址域为空,表示其后不再有节点。每个结点只有一个地址域的链表称为单链表
单链表:该地址域通常指向候机节点; 每个节点有两个地址域的线性表称为双链表
双链表:两个地址域分别指向前驱节点和后继节点。
1.定义单链表的节点
public class ListNode {
int val; //定义当前节点的值
ListNode next; //下一个节点的地址
//定义一个构造函数
public ListNode(int value) {
this.val = value;
}
}
2.建立单链表
public class Test {
public static void main(String[] args) {
//定义一个节点,数据域为 1 ,地址域为 null;
ListNode node1 = new ListNode(1);
//定义一个节点,数据域为2 ,地址域为 null;
ListNode node2 = new ListNode(2);
//定义一个节点,数据域为 2 ,地址域为 null;
ListNode node3 = new ListNode(3);
node1.next = node2; //node1的地址域为 node2的地址
node2.next = node3; //node2的地址域为 node3的地址
}
}
示意图
3.链表的创建和遍历(头插法和尾插法)
public class LinkList {
//定义链表的头结点
ListNode head = null;
/**
* 尾插法添加元素生成链表
* @param val 值
*/
public void EndInsert(int val) {
//每次执行这个方法的时候,都要新建路一个节点用来存储数据、
ListNode listNode = new ListNode(val);
//判断头特点是否为空,如果是那么将头结点指向新的节点,然后结束
if (head == null) {
head = listNode;
return;
}
//定义一个临时结点由他的充当指针,指针最开始指向头节点
ListNode indexNode = listNode;
//从头结点可是遍历,到最尾部插入
while (indexNode.next != null) {
indexNode = indexNode.next;
}
indexNode.next = listNode;
}
/**
* 头插法添加元素生成链表
* @param val
*/
public void HeadInsert(int val) {
//每次执行这个方法的时候,都要新建路一个节点用来存储数据、
ListNode listNode = new ListNode(val);
//判断头特点是否为空,如果是那么将头结点指向新的节点,然后结束
if (head == null) {
head = listNode;
return;
}
listNode.next = head;
head= listNode;
}
/**
* 链表的输出
*/
public void printLink() {
//定义一个临时结点充当指针,从头结点喀什不断想向后移动
ListNode tempListNode = head;
//将临时结点不断的往后边移动,直到临时结点指向了 null;
while (tempListNode !=null) {
System.out.print(tempListNode.val);
tempListNode = tempListNode.next;
}
System.out.println();
}
/**
* @return 链表的长度
*/
public int GetListLenght() {
//定义一个节点指向该链表
ListNode tempListNode = head;
int length = 0;
while (tempListNode != null) {
length ++;
tempListNode = tempListNode.next;
}
return length;
}
}
4.查找某个元素是否在链表的结点上
/**
* 查找某个元素是否在链表的结点上
* @param val
* @return
*/
public boolean contains(int val) {
//定义一个节点指向该链表、
ListNode tempListNode = head;
int length = 0;
while (tempListNode != null) {
if (tempListNode.val == val) {
return true;
}
tempListNode = tempListNode.next;
}
return false;
}
5.指定位置插入结点
实现思路:先判断插入位置为头尾两端的情况,即index == 0插入到头部,index == size()插入到尾部;如果插入位置不是头尾两端,则先找出当前index位置的结点cur以及前一个结点 pre,然后cur成为新结点的下一个结点,新结点成为pre的后一个结点,这样就成功插入到index位置。
/**
* 结点插入至指定位置
* @param val 要插入的值
* @param index 要插入的位置
*/
public void addNodeAtIndex(int val,int index) {
//首先判断index的合法性
if (index<0 || index > GetListLenght()) {
throw new IndexOutOfBoundsException("index 不合法");
}
//如果index = 0,那么就是用头插法
//如果index = size,那么就是使用尾插法
if (index == 0) {
HeadInsert(val);
}else if (index == GetListLenght()) {
EndInsert(val);
}else {
//插到某个中间位置
//每次执行这个方法的时候,都要新建路一个节点用来存储数据、
ListNode listNode = new ListNode(val);
//定义一个节点指向该链表、
ListNode tempListNode = head;
ListNode pre = null; //记录前置结点
//找到相关的位置
int position =0;
while(tempListNode != null) {
//如果位置正确那么就定下来.进行交换
if (position == index) {
listNode.next = tempListNode;
pre.next = listNode;
return;
}
pre = tempListNode;
tempListNode = tempListNode.next;
position ++ ;
}
}
}
6.删除指定位置结点
实现思路:找出当前index位置的结点cur以及前一个结点 pre,pre.next直接指向cur.next即可删除cur结点。
/**
* 删除指定位置结点
* @param index
*/
public void deleteNodeAtIndex(int index) {
//首先判断index的合法性
if (index<0 || index > GetListLenght()) {
throw new IndexOutOfBoundsException("index 不合法");
}
//如何删除的是头部,那么栈内存直接指向一下个就好了
if(index == 0) {
head = head.next;
return;
}
//定义一个节点指向该链表、
ListNode tempListNode = head;
ListNode pre = null; //记录前置结点
//找到相关的位置
int position =0;
while(tempListNode != null) {
//如果位置正确那么就定下来.进行交换
if (position == index) {
pre.next = tempListNode.next;
tempListNode.next = null;
return;
}
pre = tempListNode;
tempListNode = tempListNode.next;
position ++ ;
}
}