一、链表介绍
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的
链表分为带头节点的链表和没有头节点的链表,根据实际的需求确定
二、链表的应用示例
使用带head头的单向链表实现输入学生信息,查询时根据学生id顺序显示
- 添加节点:根据学生id将学生信息插入到指定位置(若id存在则提醒添加失败)
添加(创建)
1.创建一个head头节点,作为单链表的头部
2.找到新添加节点的位置,可以通过辅助变量(指针)完成
3.新的节点.next = temp.next
4.temp.next = 新的节点.
注意:第三步与第四步顺序不能反,若先将当前节点的next指向新节点,则原链表截断后的元素则丢失。
- 修改节点
1.找到将要修改的节点
2.将信息给原节点
- 删除节点
1.先找到需要删除的节点的前一个节点
2.temp.next = temp.next.next
- 查询所有节点
通过遍历进行查询
三、代码实现
public class SingleLinkedListDemo {
//进行测试
public static void main(String[] args) {
//创建节点
//顺序添加
// StudentNode node1 = new StudentNode(1,"唐僧");
// StudentNode node2 = new StudentNode(2,"孙悟空");
// StudentNode node3 = new StudentNode(3,"猪八戒");
// StudentNode node4 = new StudentNode(4,"沙僧");
//乱序添加
StudentNode node1 = new StudentNode(1,"唐僧");
StudentNode node3 = new StudentNode(3,"猪八戒");
StudentNode node2 = new StudentNode(2,"孙悟空");
StudentNode node4 = new StudentNode(4,"沙僧");
//创建链表
SingleLinkedList singleLinkedList = new SingleLinkedList();
System.out.println("创建后查询:");
singleLinkedList.show();
//将节点添加到链表中
singleLinkedList.add(node1);
singleLinkedList.add(node3);
singleLinkedList.add(node2);
singleLinkedList.add(node4);
System.out.println("添加后查询:");
singleLinkedList.show();
//修改节点
StudentNode node5 = new StudentNode(4,"沙和尚");
singleLinkedList.update(node5);
System.out.println("修改后查询:");
singleLinkedList.show();
//删除节点
singleLinkedList.delete(4);
System.out.println("删除后查询:");
singleLinkedList.show();
}
}
/**
* 链表
*/
class SingleLinkedList{
//初始化一个头节点,头节点不存放具体数据
private StudentNode head = new StudentNode(0,"");
/**
* 添加节点到单向链表
* 思路:
* 1.找到新添加节点的位置
* 2.将新节点的下一个节点指向到新节点的next中
* 3.将当前节点的next指向到新节点
* 注意:第二步与第三步顺序不能反,若先将当前节点的next指向新节点,则原链表截断后的节点则丢失。
* @param studentNode
*/
public void add(StudentNode studentNode){
//因为头节点不能动,所有我们需要一个辅助指针(变量)来协助找到添加的位置
//单链表结构,我们找到的temp是位于 添加位置 的前一个节点。
StudentNode temp = head;
//标志着添加编号是否存在,默认为false;
boolean flag = false;
while(true){
//说明temp已经是链表的最后一个
if(temp.next == null){
break;
}
//找到要添加的位置,在temp后边添加节点
if(temp.next.id > studentNode.id){
break;
}
//说明该id已存在,更改验证存在标识为true
else if(temp.next.id == studentNode.id){
flag = true;
break;
}
//指针后移,遍历当前链表
temp = temp.next;
}
//判断是否进行添加操作
if(flag){
System.out.printf("待添加的学生id %d 已被占用,不可添加",studentNode.id);
}else{
//将新节点的下一个节点指向到新节点的next中
studentNode.next = temp.next;
//将当前节点的next指向到新节点
temp.next = studentNode;
System.out.println("添加成功!");
}
}
/**
* 修改节点,根据id进行修改,因此id不可更改
* 思路:
* 1.找到将要修改的节点
* 2.将信息给原节点
* @param studentNode
*/
public void update(StudentNode studentNode){
//判断当前链表是否为空
if(head.next == null){
System.out.println("链表为空 ~~~~~ ");
return;
}
//找到将要修改的节点
StudentNode temp = head.next;
//标识是否找到该节点
boolean flag = false;
while(true){
//说明temp已经是链表的最后一个
if(temp == null){
break;
}
//将节点进行比较
if(temp.id == studentNode.id){
flag = true;
break;
}
temp = temp.next;
}
//判断是否可以进行修改操作
if(flag){
temp.name = studentNode.name;
}else {
System.out.printf("没有找到 id为 %d 的节点,不可修改\n",studentNode.id);
}
}
/**
* 删除节点 ,根据id删除节点
* 思路:
* 1.如果指针指向了要删除的节点则无法删除,所有需要找到要删除的节点的上一个节点
* 2.temp.next = temp.next.next
* @param id
*/
public void delete(int id){
//因为头节点不能动,所有我们需要一个辅助指针(变量)来协助找到添加的位置
//单链表结构,我们找到的temp是位于 添加位置 的前一个节点。
StudentNode temp = head;
//标识是否找到待删除的节点
boolean flag = false;
while (true){
//说明temp已经是链表的最后一个
if(temp == null){
break;
}
//将节点进行比较
if(temp.next.id == id){
flag = true;
break;
}
temp = temp.next;
}
//判断是否可以进行删除操作
if(flag){
temp.next = temp.next.next;
}else {
System.out.printf("没有找到 id为 %d 的节点,无法删除\n",id);
}
}
/**
* 显示链表中所有节点
*/
public void show(){
//判断当前链表是否为空
if(head.next == null){
System.out.println("链表为空 ~~~~~ ");
return;
}
System.out.println("-----------------显示链表开始-------------------");
//因为头节点不能动,所有我们需要一个辅助指针(变量)来协助找到添加的位置
//单链表结构,我们找到的temp是位于 添加位置 的前一个节点。
StudentNode temp = head;
while (true){
//说明temp已经是链表的最后一个
if(temp == null){
break;
}
//将节点进行打印
System.out.println(temp);
temp = temp.next;
}
System.out.println("-----------------显示链表结束-------------------");
}
}
/**
* 定义一个StudentNode, 每个StudentNode对象就是一个链表中的节点
*/
class StudentNode{
public int id;
public String name;
public StudentNode next;
public StudentNode(int id,String name){
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "StudentNode{id=" + id +", name='" + name + "'}";
}
}
四、拓展功能
- 统计当前链表有效节点数
//测试代码
System.out.println("当前链表有效节点数为:"+singleLinkedList.size());
//实现代码
/**
* 获取当前链表有效节点数
* 思路:遍历链表中的所有节点
* @return
*/
public int size(){
//空链表
if(head.next == null){
return 0;
}
int size = 0;
//定义一个辅助指针(变量)
StudentNode temp = head.next;
while (temp != null){
size++;
//遍历当前链表下的节点
temp = temp.next;
}
return size;
}
- 找到单链表中倒数第index个节点
//测试代码
//查找倒数第n个节点
System.out.println("倒数第1个节点为:"+singleLinkedList.findLastIndexNode(1));
//实现代码
/**
* 找到单链表中倒数第index个节点
* 思路:
* 1.判断节点是否存在
* 2.从第一个节点开始遍历,遍历(size - index)个节点
* @param index
* @return
*/
public StudentNode findLastIndexNode(int index){
//空链表
if(head.next == null){
return null;
}
//判断节点数是否存在
if(index<=0 || index > size()){
return null;
}
//遍历第size - index的位置,该位置就是我们要找的节点
StudentNode temp = head.next;
for (int i = 0;i < size() - index;i++){
temp = temp.next;
}
return temp;
}
- 反转链表
//测试代码
//反转当前链表
System.out.println("反转后查询:");
singleLinkedList.reverseList();
singleLinkedList.show();
//实现代码
/**
* 反转当前链表
*/
public void reverseList(){
//空链表或只有一个节点直接返回
if(head.next == null || head.next.next == null){
return;
}
//定义一个辅助指针(变量)
StudentNode temp = head.next;
//指向当前节点的下一个节点,避免节点丢失
StudentNode next = null;
//创建临时链表头节点
StudentNode reverseHead = new StudentNode(0,"");
//遍历原链表中的每一个节点将其取出放在临时链表reverseHead的最前端
while (temp != null){
//暂时保存当前节点后的节点
next = temp.next;
//将temp的下一个链表指向新链表的最前端
temp.next = reverseHead.next;
//将temp连接到新链表上
reverseHead.next = temp;
//遍历当前链表下的节点(指针后移)
temp = next;
}
head.next = reverseHead.next;
}
- 反转打印链表
//测试代码
//反转打印当前链表
System.out.println("反转打印:");
singleLinkedList.reverseShow();
//实现代码
/**
* 反向打印单链表
* 使用Stack栈,利用Stack栈先进后出原则进行反向打印,反向打印不影响源数据结构
*/
public void reverseShow(){
//空链表
if(head.next == null){
return;
}
//创建一个栈,将各个节点压入栈
Stack<StudentNode> stack = new Stack<StudentNode>();
StudentNode temp = head.next;
while(temp != null){
stack.add(temp);
//指针后移
temp = temp.next;
}
while (stack.size()>0){
//打印节点信息
System.out.println(stack.pop());
}
}
- 合并链表,并保持有序
//测试代码
//合并链表
//创建第二个链表
SingleLinkedList singleLinkedList2 = new SingleLinkedList();
StudentNode node11 = new StudentNode(6,"白骨精");
StudentNode node12 = new StudentNode(7,"牛魔王");
StudentNode node13 = new StudentNode(2,"孙悟空");
singleLinkedList2.add(node11);
singleLinkedList2.add(node12);
singleLinkedList2.add(node13);
//进行合并
singleLinkedList.addAll(singleLinkedList2);
System.out.println("合并后打印:");
singleLinkedList.show();
//实现代码
/**
* 合并/添加一个有序的单链表,合并后依然有序
*/
public void addAll(SingleLinkedList singleLinkedList){
//判断待添加链表有效节点数的数量
if(singleLinkedList.size()==0)
{
return;
}
//将待添加链表加入当前链表中
while (singleLinkedList.size()>0){
StudentNode temp = singleLinkedList.findLastIndexNode(1);
singleLinkedList.delete(temp.id);
this.add(temp);
}
}
附完整代码
import java.util.Stack;
/**
* @author 张家宝
* @date 2020/4/7
*/
public class SingleLinkedListDemo {
//进行测试
public static void main(String[] args) {
//创建节点
//顺序添加
// StudentNode node1 = new StudentNode(1,"唐僧");
// StudentNode node2 = new StudentNode(2,"孙悟空");
// StudentNode node3 = new StudentNode(3,"猪八戒");
// StudentNode node4 = new StudentNode(4,"沙僧");
//乱序添加
StudentNode node1 = new StudentNode(1,"唐僧");
StudentNode node3 = new StudentNode(3,"猪八戒");
StudentNode node2 = new StudentNode(2,"孙悟空");
StudentNode node4 = new StudentNode(4,"沙僧");
//创建链表
SingleLinkedList singleLinkedList = new SingleLinkedList();
System.out.println("创建后查询:");
singleLinkedList.show();
//将节点添加到链表中
singleLinkedList.add(node1);
singleLinkedList.add(node3);
singleLinkedList.add(node2);
singleLinkedList.add(node4);
System.out.println("添加后查询:");
singleLinkedList.show();
//修改节点
StudentNode node5 = new StudentNode(4,"沙和尚");
singleLinkedList.update(node5);
System.out.println("修改后查询:");
singleLinkedList.show();
//删除节点
singleLinkedList.delete(2);
System.out.println("删除后查询:");
singleLinkedList.show();
//统计当前链表有效节点数
System.out.println("当前链表有效节点数为:"+singleLinkedList.size());
//查找倒数第n个节点
System.out.println("倒数第1个节点为:"+singleLinkedList.findLastIndexNode(1));
//反转当前链表
System.out.println("反转后查询:");
singleLinkedList.reverseList();
singleLinkedList.show();
// //反转打印当前链表
System.out.println("反转打印:");
singleLinkedList.reverseShow();
//合并链表
//创建第二个链表
SingleLinkedList singleLinkedList2 = new SingleLinkedList();
StudentNode node11 = new StudentNode(6,"白骨精");
StudentNode node12 = new StudentNode(7,"牛魔王");
StudentNode node13 = new StudentNode(2,"孙悟空");
singleLinkedList2.add(node11);
singleLinkedList2.add(node12);
singleLinkedList2.add(node13);
//进行合并
singleLinkedList.addAll(singleLinkedList2);
System.out.println("合并后打印:");
singleLinkedList.show();
}
}
/**
* 链表
*/
class SingleLinkedList{
//初始化一个头节点,头节点不存放具体数据
private StudentNode head = new StudentNode(0,"");
/**
* 添加节点到单向链表
* 思路:
* 1.找到新添加节点的位置
* 2.将新节点的下一个节点指向到新节点的next中
* 3.将当前节点的next指向到新节点
* 注意:第二步与第三步顺序不能反,若先将当前节点的next指向新节点,则原链表截断后的节点则丢失。
* @param studentNode
*/
public void add(StudentNode studentNode){
//因为头节点不能动,所有我们需要一个辅助指针(变量)来协助找到添加的位置
//单链表结构,我们找到的temp是位于 添加位置 的前一个节点。
StudentNode temp = head;
//标志着添加编号是否存在,默认为false;
boolean flag = false;
while(true){
//说明temp已经是链表的最后一个
if(temp.next == null){
break;
}
//找到要添加的位置,在temp后边添加节点
if(temp.next.id > studentNode.id){
break;
}
//说明该id已存在,更改验证存在标识为true
else if(temp.next.id == studentNode.id){
flag = true;
break;
}
//指针后移,遍历当前链表
temp = temp.next;
}
//判断是否进行添加操作
if(flag){
System.out.printf("待添加的学生id %d 已被占用,不可添加\n",studentNode.id);
}else{
//将新节点的下一个节点指向到新节点的next中
studentNode.next = temp.next;
//将当前节点的next指向到新节点
temp.next = studentNode;
// System.out.println("添加成功!");
}
}
/**
* 修改节点,根据id进行修改,因此id不可更改
* 思路:
* 1.找到将要修改的节点
* 2.将信息给原节点
* @param studentNode
*/
public void update(StudentNode studentNode){
//判断当前链表是否为空
if(head.next == null){
System.out.println("链表为空 ~~~~~ ");
return;
}
//找到将要修改的节点
StudentNode temp = head.next;
//标识是否找到该节点
boolean flag = false;
while(true){
//说明temp已经是链表的最后一个
if(temp == null){
break;
}
//将节点进行比较
if(temp.id == studentNode.id){
flag = true;
break;
}
temp = temp.next;
}
//判断是否可以进行修改操作
if(flag){
temp.name = studentNode.name;
}else {
System.out.printf("没有找到 id为 %d 的节点,不可修改\n",studentNode.id);
}
}
/**
* 删除节点 ,根据id删除节点
* 思路:
* 1.如果指针指向了要删除的节点则无法删除,所有需要找到要删除的节点的上一个节点
* 2.temp.next = temp.next.next
* @param id
*/
public void delete(int id){
//因为头节点不能动,所有我们需要一个辅助指针(变量)来协助找到添加的位置
//单链表结构,我们找到的temp是位于 添加位置 的前一个节点。
StudentNode temp = head;
//标识是否找到待删除的节点
boolean flag = false;
while (true){
//说明temp已经是链表的最后一个
if(temp == null){
break;
}
//将节点进行比较
if(temp.next.id == id){
flag = true;
break;
}
temp = temp.next;
}
//判断是否可以进行删除操作
if(flag){
temp.next = temp.next.next;
}else {
System.out.printf("没有找到 id为 %d 的节点,无法删除\n",id);
}
}
/**
* 显示链表中所有节点
*/
public void show(){
//判断当前链表是否为空
if(head.next == null){
System.out.println("链表为空 ~~~~~ ");
return;
}
System.out.println("-----------------显示链表开始-------------------");
//因为头节点不能动,所有我们需要一个辅助指针(变量)来协助找到添加的位置
//单链表结构,我们找到的temp是位于 添加位置 的前一个节点。
StudentNode temp = head;
while (true){
//说明temp已经是链表的最后一个
if(temp == null){
break;
}
//将节点进行打印
System.out.println(temp);
temp = temp.next;
}
System.out.println("-----------------显示链表结束-------------------");
}
/*----------- 拓展 ------------*/
/**
* 获取当前链表有效节点数
* 思路:遍历链表中的所有节点
* @return
*/
public int size(){
//空链表
if(head.next == null){
return 0;
}
int size = 0;
//定义一个辅助指针(变量)
StudentNode temp = head.next;
while (temp != null){
size++;
//遍历当前链表下的节点
temp = temp.next;
}
return size;
}
/**
* 找到单链表中倒数第index个节点
* 思路:
* 1.判断节点是否存在
* 2.从第一个节点开始遍历,遍历(size - index)个节点
* @param index
* @return
*/
public StudentNode findLastIndexNode(int index){
//空链表
if(head.next == null){
return null;
}
//判断节点数是否存在
if(index<=0 || index > size()){
return null;
}
//遍历第size - index的位置,该位置就是我们要找的节点
StudentNode temp = head.next;
for (int i = 0;i < size() - index;i++){
temp = temp.next;
}
return temp;
}
/**
* 反转当前链表
*/
public void reverseList(){
//空链表或只有一个节点直接返回
if(head.next == null || head.next.next == null){
return;
}
//定义一个辅助指针(变量)
StudentNode temp = head.next;
//指向当前节点的下一个节点,避免节点丢失
StudentNode next = null;
//创建临时链表头节点
StudentNode reverseHead = new StudentNode(0,"");
//遍历原链表中的每一个节点将其取出放在临时链表reverseHead的最前端
while (temp != null){
//暂时保存当前节点后的节点
next = temp.next;
//将temp的下一个链表指向新链表的最前端
temp.next = reverseHead.next;
//将temp连接到新链表上
reverseHead.next = temp;
//遍历当前链表下的节点(指针后移)
temp = next;
}
head.next = reverseHead.next;
}
/**
* 反向打印单链表
* 使用Stack栈,利用Stack栈先进后出原则进行反向打印,反向打印不影响源数据结构
*/
public void reverseShow(){
//空链表
if(head.next == null){
return;
}
//创建一个栈,将各个节点压入栈
Stack<StudentNode> stack = new Stack<StudentNode>();
StudentNode temp = head.next;
while(temp != null){
stack.add(temp);
//指针后移
temp = temp.next;
}
while (stack.size()>0){
//打印节点信息
System.out.println(stack.pop());
}
}
/**
* 合并/添加一个有序的单链表,合并后依然有序
*/
public void addAll(SingleLinkedList singleLinkedList){
//判断待添加链表有效节点数的数量
if(singleLinkedList.size()==0)
{
return;
}
//将待添加链表加入当前链表中
while (singleLinkedList.size()>0){
StudentNode temp = singleLinkedList.findLastIndexNode(1);
singleLinkedList.delete(temp.id);
this.add(temp);
}
}
}
/**
* 定义一个StudentNode, 每个StudentNode对象就是一个链表中的节点
*/
class StudentNode{
public int id;
public String name;
public StudentNode next;
public StudentNode(int id,String name){
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "StudentNode{id=" + id +", name='" + name + "'}";
}
}