链表基本介绍
链表在内存中的实际存储结构
链表的逻辑结构
单链表应用实例
代码实现
// 英雄节点,存储了英雄的信息
class HeroNode {
public int id; // 英雄编号
public String name; // 英雄名字
public String nickName; // 英雄昵称
public HeroNode next; // 指针域,指向下一个节点
public HeroNode(int id, String name, String nickName) {
this.id = id;
this.name = name;
this.nickName = nickName;
this.next = null; // 初始指针指向 null
}
// 重写 toString() ,方便遍历链表的操作
public String toString() {
return "[" + id + " " + name + " " + nickName + "]";
}
}
// 单链表
class SingleLinkedList {
// 创建一个头结点并初始化,头结点用于指向一个链表,不存储数据
HeroNode head = new HeroNode(0, "", "");
// 向链表添加节点
// 直接把要插入的节点放在链表的末尾
public void add(HeroNode node) {
// 定义一个临时变量指向链表的头结点
HeroNode t = head;
// 找到链表的最后一个节点的位置
while (true) {
// 当节点 t 的 next 为 null ,表示 t 所在位置是最后一个节点
if (t.next == null) {
break;
}
// 否则还不是最后一个节点,继续后移指向下一个节点
t = t.next;
}
// 退出循环表示临时变量指向的是链表的最后一个节点
// 在链表的末尾插入新添加的元素
// 让最后一个节点的 next 指向新添加的节点,即可实现插入链表
t.next = node;
}
// 向链表添加节点
// 按照节点编号从小到大插入链表,即编号小的在链表前面,大的在链表后面
public void addById(HeroNode node) {
// 定义一个临时变量指向链表的头结点
HeroNode t = head;
// 找到比新节点ID值大的节点的位置
while (true) {
// 如果节点的 ID 值相等,表示已经存在节点,给出提示并返回
if (t.next != null && node.id == t.next.id) {
System.out.println("链表已经存在该节点" + node);
return;
}
// 当节点 t 的 next 为 null ,表示 t 所在位置是最后一个节点
// 找到了比新节点 ID 值大的节点或到达链表末尾
if (t.next == null || node.id < t.next.id) {
break;
}
// 没有找到合适位置的节点,继续后移指向下一个节点
t = t.next;
}
// 存储 t 节点的下一个节点
HeroNode p = t.next;
// 让 t 节点的 next 指向新节点
t.next = node;
// 让新节点的 next 指向 p 节点
node.next = p;
}
// 修改节点信息
// 以节点的 id 值为查找依据,所以 id 值不能修改
public void update(HeroNode newNode) {
HeroNode t = head.next;
// 首先判断列表是否为空
if (t == null) {
System.out.println("链表为空。。。");
return;
}
// 遍历链表查找 id 值和 newNode 的 id 值相同的节点
boolean flag = false;
while (true) {
if (t == null) {
break;
} else if (t.id == newNode.id) { // 找到了要修改的节点
flag = true;
break;
}
t = t.next;
}
if (flag) { // 修改对应的节点信息
t.name = newNode.name;
t.nickName = newNode.nickName;
} else {
System.out.println("链表没有节点" + newNode);
}
}
// 删除节点
public void deleteHeroNode(int id) {
HeroNode t = head;
boolean flag = false;
while (t.next != null) {
if (t.next.id == id) { // 找到要删除节点(t.next)的前一个节点 t
flag = true;
break;
}
t = t.next;
}
if (flag) {
// 让要删除节点的前一个节点 t 的 next 指向要删除节点的后一个节点
t.next = t.next.next;
} else {
System.out.println("链表中没有节点 id 为:" + id);
}
}
// 打印链表中的节点信息,即遍历链表
public void showList() {
// 从第一个数据节点开始
HeroNode t = head.next;
// 判断链表是否为空
if (t == null) {
System.out.println("链表为空...");
return;
}
while (t != null) {
if (t.next != null) {
System.out.print(t + " -> ");
} else {
System.out.println(t);
}
t = t.next;
}
}
}
// 单链表测试类
class SingleLinkedListTest {
public static void main(String[] args) {
// 创建英雄节点
HeroNode node1 = new HeroNode(1, "宋江", "及时雨");
HeroNode node2 = new HeroNode(2, "吴用", "及时雨");
HeroNode node3 = new HeroNode(3, "卢俊义", "玉麒麟");
HeroNode node4 = new HeroNode(4, "林冲", "豹子头");
// 创建一个链表,把上述几个节点加入链表
SingleLinkedList linkedList = new SingleLinkedList();
linkedList.add(node1);
linkedList.add(node2);
linkedList.add(node3);
linkedList.add(node4);
// 遍历链表
linkedList.showList();
// 创建一个链表,测试按编号顺序插入节点的方法
SingleLinkedList linkedList2 = new SingleLinkedList();
linkedList2.addById(node4);
linkedList2.addById(node1);
linkedList2.addById(node3);
linkedList2.addById(node2);
linkedList2.addById(node3);
linkedList2.showList();
// 测试修改节点的代码
HeroNode newNode = new HeroNode(3, "大红", "小红红");
linkedList2.update(newNode);
linkedList2.showList();
// 测试修改节点的代码
linkedList2.deleteHeroNode(6);
linkedList2.deleteHeroNode(1);
linkedList2.deleteHeroNode(2);
linkedList2.deleteHeroNode(3);
linkedList2.deleteHeroNode(4);
linkedList2.deleteHeroNode(5);
linkedList2.showList();
}
}
单链表面试题
问题一代码如下:
// 英雄节点,存储了英雄的信息
class HeroNode {
public int id; // 英雄编号
public String name; // 英雄名字
public String nickName; // 英雄昵称
public HeroNode next; // 指针域,指向下一个节点
public HeroNode(int id, String name, String nickName) {
this.id = id;
this.name = name;
this.nickName = nickName;
this.next = null; // 初始指针指向 null
}
// 重写 toString() ,方便遍历链表的操作
public String toString() {
return "[" + id + " " + name + " " + nickName + "]";
}
}
// 单链表
class SingleLinkedList {
// 创建一个头结点并初始化,头结点用于指向一个链表,不存储数据
HeroNode head = new HeroNode(0, "", "");
// 向链表添加节点
// 直接把要插入的节点放在链表的末尾
public void add(HeroNode node) {
// 定义一个临时变量指向链表的头结点
HeroNode t = head;
// 找到链表的最后一个节点的位置
while (true) {
// 当节点 t 的 next 为 null ,表示 t 所在位置是最后一个节点
if (t.next == null) {
break;
}
// 否则还不是最后一个节点,继续后移指向下一个节点
t = t.next;
}
// 退出循环表示临时变量指向的是链表的最后一个节点
// 在链表的末尾插入新添加的元素
// 让最后一个节点的 next 指向新添加的节点,即可实现插入链表
t.next = node;
}
// 打印链表中的节点信息,即遍历链表
public void showList() {
// 从第一个数据节点开始
HeroNode t = head.next;
// 判断链表是否为空
if (t == null) {
System.out.println("链表为空...");
return;
}
while (t != null) {
if (t.next != null) {
System.out.print(t + " -> ");
} else {
System.out.println(t);
}
t = t.next;
}
}
// 获取链表的有效节点数量,即如果有头结点,头结点不算有效节点数量
public int getLength() {
// 如果链表为空,返回 0
if (head.next == null) {
return 0;
}
int length = 1;
HeroNode t = head.next;
while (t.next != null) {
length++;
t = t.next;
}
return length;
}
// 获取链表倒数第 k 个节点
// 先调用 getLength() 方法得到链表总长度 len
// 然后从第一个有效数据节点开始遍历,遍历到第 (len - k) 个节点,就是所要求的那个
public HeroNode findLastIndexNode(int k) {
HeroNode t = head.next;
if (t == null) {
// 链表为空,则找不到节点,返回空
return null;
}
// 获取链表的长度(有效数据的个数)
int len = getLength();
int count = 1;
int flag = len - k;
if (flag > 0) {
while (count == flag) {
t = t.next;
count++;
}
// 使用 for 循环实现
// for (int i = 0;i < flag; i++) {
// t = t.next;
// }
} else {
// 超出了链表的范围
return null;
}
return t;
}
// 单链表的反转
public void reverseLinkedList() {
// 如果链表为空或只有一个节点,则不用反转
if (head.next == null || head.next.next == null) {
return;
}
// 创建一个新的头节点
HeroNode newHead = new HeroNode(0, "", "");
// 从头遍历链表,将链表中的每一个节点取出,依次将其插入新头节点的后面,其他数据节点的前面,即头插法
// 指向当前节点
HeroNode cur = head.next;
// 指向当前节点的下一个节点
HeroNode next = null;
while (cur != null) {
// 保存链表的下一个节点
next = cur.next;
// cur 是当前遍历链表取出来的节点
// 让当前节点指向新头结点指向的第一个节点
cur.next = newHead.next;
// 再让头结点指向当前节点
newHead.next = cur;
// 遍历链表的下一个节点
cur = next;
}
head = newHead;
}
// 反向遍历输出链表
// 方式一:先将链表反转再输出,但是这样会破坏链表原本的结构顺序
// 方式二:用栈先进后出的特点实现
public void reversePrint1() {
// 用数组模拟栈
// 获取链表的长度(有效数据的个数)
int len = getLength();
if (len == 0) {
System.out.println("链表为空...");
return;
}
HeroNode[] arr = new HeroNode[len];
HeroNode cur = head.next;
int i = 0;
while (cur != null) {
arr[i++] = cur;
cur = cur.next;
}
// 反向输出链表
for (i = len - 1; i >= 0; i--) {
System.out.println(arr[i]);
}
}
public void reversePrint2() {
// 用栈结构实现
Stack<HeroNode> stack = new Stack<HeroNode>();
HeroNode cur = head.next;
while (cur != null) {
stack.push(cur); // 入栈
cur = cur.next;
}
// 反向输出链表,即输出栈元素
while (stack.size() > 0) {
System.out.println(stack.pop());
}
}
}
// 单链表测试类
class SingleLinkedListTest {
public static void main(String[] args) {
// 创建英雄节点
HeroNode node1 = new HeroNode(1, "宋江", "及时雨");
HeroNode node2 = new HeroNode(2, "吴用", "及时雨");
HeroNode node3 = new HeroNode(3, "卢俊义", "玉麒麟");
HeroNode node4 = new HeroNode(4, "林冲", "豹子头");
// 创建一个链表,把上述几个节点加入链表
SingleLinkedList linkedList = new SingleLinkedList();
linkedList.add(node1);
linkedList.add(node2);
linkedList.add(node3);
linkedList.add(node4);
// 遍历链表
linkedList.showList();
// 测试获取链表有效数据个数代码
System.out.println("链表的长度为:" + linkedList.getLength());
// 测试链表倒数第 k 个节点代码
System.out.println("链表倒数第k个节点为:" + linkedList.findLastIndexNode(2));
linkedList.showList();
// 测试链表反转的代码
linkedList.reverseLinkedList();
linkedList.showList();
// 测试反向输出链表的代码
System.out.println("反向输出链表1:");
linkedList.reversePrint1();
System.out.println("反向输出链表2:");
linkedList.reversePrint2();
}
}