小结:
1、链表是以结点的方式来存储,是链式存储。
2、每个结点包含 data 域, next 域:指向下一个结点。
3、链表的结点不一定是连续存储。
3、链表分 带头结点的链表 和 没有头结点的链表,根据实际的需求来确定。
头指针、头结点、首结点(元结点)的区别
头指针:指向链表中第一个结点(头结点或首结点)。
头结点:链表中首结点之前附加的一个结点,其数据域一般无意义,不存放有效数据。
首结点:链表中存储第一个元素的结点,是头结点后边第一个结点。
带头结点与不带头结点的区别:
带头结点时,不管是否为空表,头指针的值都不会发生变化,都指向头结点。
不带头结点需要根据不同情况来修改头指针的值,操作不统一,所以绝大多是都使用带头结点。
添加(创建):
1.先创建一个head头结点,表示单链表的头。
2.后面每添加一个结点,直接加到链表的最后。
遍历:
1.通过一个辅助变量,遍历整个链表。
单链表的反转思路:
1.先定义一个结点reverseHead = new HeroNode();
2.从头到尾遍历原来的链表,取出其中每一个结点,放在新链表的最前端;
3.原来链表的head.next = reverseHead.next。
从尾到头打印单链表思路:
方法1.将单链表进行反转操作,再遍历打印。问题:破坏原来的单链表的结构,不建议。
方法2.利用栈这个数据结构,将各个结点压入栈中,利用栈的先进先出的特点,实现逆序打印的结果。
import java.util.Stack;
/*
head表示头结点,
head.next == null 表示链表为空
HeroNode temp = head;
temp.next == null 表示temp是链表的最后一个结点
添加、删除结点:要把temp结点的next指向新的结点(temp.next = heroNode),可能会导致空指针异常,
HeroNode temp = head
修改、查看结点:不存在把temp结点的next指向新的结点,不会出现空指针异常。
HeroNode temp = head.next
*/
public class SingleLinkedListTest {
public static void main(String[] args) {
// 创建结点
HeroNode node1 = new HeroNode(1, "宋江", "及时雨");
HeroNode node2 = new HeroNode(6, "林冲", "豹子头");
HeroNode node3 = new HeroNode(3, "吴用", "智多星");
// 创建链表
SingleLinkedList singleLinkedList1 = new SingleLinkedList();
// 按照编号顺序添加结点
singleLinkedList1.addBy(node1);
singleLinkedList1.addBy(node2);
singleLinkedList1.addBy(node3);
HeroNode node4 = new HeroNode(7, "秦明", "霹雳火");
HeroNode node5 = new HeroNode(4, "公孙胜", "入云龙");
HeroNode node6 = new HeroNode(5, "关胜", "大刀");
HeroNode node7 = new HeroNode(2, "卢俊义", "玉麒麟");
SingleLinkedList singleLinkedList2 = new SingleLinkedList();
singleLinkedList2.addBy(node4);
singleLinkedList2.addBy(node5);
singleLinkedList2.addBy(node6);
singleLinkedList2.addBy(node7);
// 显示原链表
System.out.println("单向链表1的原的链表:");
singleLinkedList1.display();
System.out.println("单向链表2的原的链表:");
singleLinkedList2.display();
// 合并两条链表
// 方法里要用到myLinkedList、singleLinkedList2,以参数形式传进去
mergeLinkedList(singleLinkedList1, singleLinkedList2);
// 打印输出合并的两条链表
System.out.println("合并后的链表:");
int num1 = getLength(singleLinkedList1.getHead());
int num2 = getLength(singleLinkedList2.getHead());
if (num1 > num2) {
singleLinkedList1.display();
} else {
singleLinkedList2.display();
}
// 以下程序默认知道单向链条2的长度长
// 反转链表
System.out.println("反转后的链表:");
reverseList(singleLinkedList2.getHead());
singleLinkedList2.display();
// 逆序打印
System.out.println("反向打印链表:");
reversePrint(singleLinkedList2.getHead());
// 修改结点
HeroNode newNode = new HeroNode(1, "小宋", "及时雨");
singleLinkedList2.modify(newNode);
// 删除结点
singleLinkedList2.delete(1);
// 显示修改后的链表
System.out.println("修改后的链表情况:");
singleLinkedList2.display();
// 求单链表中有效结点的个数
System.out.println("单链表中有效结点的个数为:" + getLength(singleLinkedList2.getHead()));
// 查找单链表中的倒数第k个结点
System.out.println("倒数第3个结点是:" + findLastIndexNode(singleLinkedList2.getHead(), 3));
}
/**
* 返回单链表中有效结点的个数
* @param head 链表的头结点
* @return 有效结点的个数
*/
public static int getLength(HeroNode head) {
if (head.next == null) {
return 0;
}
int length = 0;
HeroNode temp = head;
while (temp.next != null) {
length++;
temp = temp.next;
}
return length;
}
/**
* 查找单链表中的倒数第k个结点
* @param head 链表的头结点
* @param index 倒数第k个结点
* @return 如果找到了,返回该结点;否则返回null
*/
public static HeroNode findLastIndexNode(HeroNode head, int index) {
if (head.next == null) {
return null;
}
// 第一次遍历,找到链表的有效结点个数
int size = getLength(head);
// 第二次遍历size-index+1次,就是倒数第index个结点的位置
if (index <= 0 || index > size) {
return null;
}
HeroNode temp = head;
for (int i = 0; i < size - index + 1; i++) {
temp = temp.next;
}
return temp;
}
/**
* 将单向链表反转后输出
* @param head 原链表的头结点
*/
public static void reverseList(HeroNode head) {
// 如果当前链表为空,或者只有一个结点,无需反转,直接返回
if (head.next == null || head.next.next == null) {
// return表示直接结束方法的执行,不再执行判断if之后的操作
return;
}
// 定义辅助结点,遍历原来的列表
HeroNode temp = head.next;
// 当前结点temp的下一个结点
HeroNode next = null;
// 新链表头结点
HeroNode reverseHead = new HeroNode(0, "", "");
// 遍历原来的链表,取出其中每一个结点,放在新链表的最前端
while (temp != null) {
// 保存当前结点的下一个结点为next
next = temp.next;
// 将reverseHead下一个结点的内存地址赋给temp的next(将reverseHead的下一个结点变成temp的下一个结点)
temp.next = reverseHead.next;
// 将temp连接到新的链表上
reverseHead.next = temp;
// temp后移
temp = next;
}
// 将reverseHead下一个结点的内存地址赋给head的next
head.next = reverseHead.next;
}
/**
* 将单向链表反向打印
* @param head 链表的头结点
*/
public static void reversePrint(HeroNode head) {
if (head.next == null) {
return;
}
// 创建一个栈
Stack<HeroNode> stack = new Stack<>();
HeroNode temp = head.next;
// 将链表的个结点压入栈中
while (temp != null) {
stack.push(temp);
temp = temp.next;
}
// 将栈中的结点打印
while (stack.size() > 0) {
System.out.println(stack.pop());
}
}
/**
* 将两个单项链表按序号合并成一个单向链表
* @param singleLinkedList1 第一个单向链表
* @param singleLinkedList2 第二个单向链条
*/
public static void mergeLinkedList(SingleLinkedList singleLinkedList1, SingleLinkedList singleLinkedList2) {
int num1 = getLength(singleLinkedList1.getHead());
int num2 = getLength(singleLinkedList2.getHead());
if (singleLinkedList1.getHead().next == null || singleLinkedList2.getHead().next == null) {
return;
}
// 当链表1比链表2长的时候,把链表2中的结点加入到链表1中
if (num1 >= num2) {
HeroNode temp = singleLinkedList2.getHead().next;
HeroNode next = null;
while (temp != null) {
next = temp.next;
singleLinkedList1.addBy(temp);
temp = next;
}
} else {
HeroNode temp = singleLinkedList1.getHead().next;
HeroNode next = null;
while (temp != null) {
next = temp.next;
singleLinkedList2.addBy(temp);
temp = next;
}
}
}
}
// 定义单链表
class SingleLinkedList {
// 初始化一个头结点,不存放具体的数据
private HeroNode head = new HeroNode(0, "", "");
public HeroNode getHead() {
return head;
}
// 添加结点到单向链表
public void add(HeroNode heroNode) {
// 需要一个辅助结点,头结点不能动
HeroNode temp = head;
// 遍历链表,当temp不是链表的最后结点
while (temp.next != null) {
temp = temp.next;
}
// 退出while循环时,temp指向了链表的最后结点
// 将最后这个结点的next指向新的结点
temp.next = heroNode;
}
// 根据编号添加新的结点,如果有这个编号,添加失败并提示
public void addBy(HeroNode heroNode) {
// 需要一个辅助结点,要位于添加的结点的前一个位置
HeroNode temp = head;
// 标识添加的结点编号是否存在
boolean flag = false;
// 当temp不是链表的最后结点
while (temp.next != null) {
if (temp.next.no > heroNode.no) {
break;
// 添加的结点编号已经存在
} else if (temp.next.no == heroNode.no) {
flag = true;
}
temp = temp.next;
}
if (flag) {
System.out.println("结点的编号" + heroNode.no + "已经存在,不能添加!");
} else {
heroNode.next = temp.next;
temp.next = heroNode;
}
}
// 修改结点,根据no来修改,no不一样相当于添加
public void modify(HeroNode heroNode) {
// 判断链表是否为空
if (head.next == null) {
System.out.println("链表为空!");
return;
}
// 需要一个辅助结点,头结点不能动
HeroNode temp = head.next;
// 遍历链表,根据no找到需要修改的结点
boolean flag = false;
// 当temp不是链表的最后结点
while (temp != null) {
if (temp.no == heroNode.no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.name = heroNode.name;
temp.nickName = heroNode.nickName;
} else {
System.out.println("没有找到编号为" + heroNode.no + "的结点!");
}
}
// 删除结点
public void delete(int no) {
// 判断链表是否为空
if (head.next == null) {
System.out.println("链表为空!");
return;
}
//
HeroNode temp = head;
boolean flag = false;
while (temp.next != null) {
if (temp.next.no == no) {
flag = true;
break;
}
temp = temp.next;
}
if (flag) {
temp.next = temp.next.next;
} else {
System.out.println("要删除的结点编号" + no + "不存在!");
}
}
// 显示链表(遍历)
public void display() {
// 判断链表是否为空
if (head.next == null) {
System.out.println("链表为空!");
return;
}
// 需要一个辅助结点,头结点不能动
HeroNode temp = head.next;
// 遍历链表,输出每一个结点
// 当temp不是链表的最后结点
while (temp != null) {
System.out.println(temp);
temp = temp.next;
}
}
}
// 定义HeroNode,每个HeroNode对象都是一个结点
class HeroNode {
public int no;
public String name;
public String nickName;
// 指向下一个结点
public HeroNode next;
public HeroNode(int no, String name, String nickName) {
this.no = no;
this.name = name;
this.nickName = nickName;
}
// 为了显示结点,重写toString方法(显示结点中的信息,不需要重写)
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name=" + name +
", nickName=" + nickName +
'}';
}
}