单链表相关操作
- 单链表的创建、添加、修改、删除
- 常见面试题:
1)求单链表中有效结点个数
2)查找单链表中的倒数第k个结点
3)单链表的反转
4)从尾到头打印单链表
5)合并两个有序链表
package com.atguigu.demomptest.linkedlist;
import java.util.Date;
import java.util.Stack;
import static com.atguigu.demomptest.linkedlist.SingleLinkedList.*;
public class SingleLinkedListDemo {
public static void main(String[] args) {
//进行测试
//1.先创建结点
HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
HeroNode hero5 = new HeroNode(5, "a", "aa");
HeroNode hero6 = new HeroNode(6, "b", "bb");
HeroNode hero7 = new HeroNode(7, "c", "cc");
HeroNode hero8 = new HeroNode(8, "d", "dd");
/* //创建链表,加入英雄
SingleLinkedList singleLinkedList = new SingleLinkedList();
singleLinkedList.add(hero1);
singleLinkedList.add(hero4);
singleLinkedList.add(hero2);
singleLinkedList.add(hero3);
// singleLinkedList.addByOrder(hero1);
// singleLinkedList.addByOrder(hero4);
// singleLinkedList.addByOrder(hero2);
// singleLinkedList.addByOrder(hero3);
// singleLinkedList.addByOrder(hero3);
*//* // 修改前
singleLinkedList.list();
// 测试修改
HeroNode heroNode = new HeroNode(2, "小卢", "玉麒麟~~");
singleLinkedList.update(heroNode);
System.out.println("修改后的链表情况~");
//显示
singleLinkedList.list();*//*
//测试删除
// System.out.println("删除前的链表~~~");
// singleLinkedList.list();
// singleLinkedList.delete(1);
// singleLinkedList.delete(4);
// System.out.println("删除后的链表~~~");
// singleLinkedList.list();
// 单链表的有效结点个数
System.out.println("有效结点个数=" + getLength(singleLinkedList.getHead()));
//测试是否得到了倒数第k个结点
HeroNode res = findLastIndexNode(singleLinkedList.getHead(), 1);
System.out.println("res=" + res);
//测试单链表反转
System.out.println("反转前的链表==============");
singleLinkedList.list();
reverseList(singleLinkedList.getHead());
System.out.println("反转后链表===================");
singleLinkedList.list();
//测试逆序打印单链表
System.out.println("==========逆序打印单链表,链表结构不变==============");
reversePrint(singleLinkedList.getHead());*/
//测试有序链表合并
SingleLinkedList s1 = new SingleLinkedList();
s1.addByOrder(hero1);
s1.addByOrder(hero3);
s1.addByOrder(hero5);
s1.addByOrder(hero8);
SingleLinkedList s2 = new SingleLinkedList();
System.out.println("链表的头是否相等"+(s1.getHead()==s2.getHead()));
s2.addByOrder(hero2);
s2.addByOrder(new HeroNode(5,"aaa","aaaaa"));
s2.addByOrder(hero7);
// s2.add(hero8);
System.out.println("h1:");
s1.list();
System.out.println("h2:");
s2.list();
//方法一:
// mergeList(s1.getHead(), s2.getHead());
//方法二:递归
mergeList1(s1.getHead(),s2.getHead());
System.out.println("合并后的链表:::");
s1.list();
}
}
//定义单链表管理英雄结点
class SingleLinkedList {
//创建头结点
private HeroNode head = new HeroNode(0, "", "");
public HeroNode getHead() {
return head;
}
//向链表中插入数据思路:
//1、找到链表的末尾,
//2、将最后结点的next 指向新的结点
public void add(HeroNode heroNode) {
//因为head头结点不能东,因此需要定义一个辅助变量
HeroNode temp = head;
while (true) {
if (temp.next == null) {//说明找到链表的最后
break;
}
//还没找到链表最后,temp继续往后找
temp = temp.next;
}
//跳出循环,说明找到链表最后,将数据插入
temp.next = heroNode;
}
//第二方式添加英雄,根据排名将英雄插入到指定位置(如果有这个排名,则添加失败,并给出提示)
//1、首先找到新添加的结点的位置,是通过辅助变量(指针)
//2、新的结点.next=temp.next
//3、temp.next=新的结点
public void addByOrder(HeroNode heroNode) {
//因为head头结点不能动,因此需要定义一个辅助变量来帮助找到添加的位置
//因为单链表,因此我们找的temp是位于 添加位置的前一个结点
HeroNode temp = head;
//标志:添加的编号是否存在,默认为false
boolean flag = false;
while (true) {
if (temp.next == null) {//说明找到链表的最后
break;
}
if (temp.next.no > heroNode.no) {//因为是从头结点开始比较,找到第一个比heroNode.no大的no,然后把heroNode插入前面。
// 不能和temp.no比较,因为temp没有前置指针,没办法将heroNode插入temp的前面
//说明找到了
break;
} else if (temp.next.no == heroNode.no) {// 说明希望添加的heroNode的编号已存在
flag = true; //说明编号已存在
break;
}
//还没找到插入的位置,temp继续往后找
temp = temp.next;
}
//跳出循环,判断flag的值
if (flag) {
System.out.printf("准备插入的英雄的编号 %d 已经存在,不能加入\n", heroNode.no);
} else {
//插入到链表中,temp的后面
heroNode.next = temp.next;
temp.next = heroNode;
}
temp.next = heroNode;
}
//修改链表,no不能修改
public void update(HeroNode newHeroNode) {
// 判断链表是否为空
if (head.next == null) {
System.out.println("链表为空");
return;
}
//定义辅助变量
HeroNode temp = head;
boolean flag = false;//是否找到要修改的英雄
while (true) {
if (temp.next == null) {
System.out.println("遍历完链表还没找到");
break;
}
if (temp.next.no == newHeroNode.no) {//找到了要修改的英雄
flag = true;
break;
}
// temp后移,遍历链表
temp = temp.next;
}
if (flag) {//找到了
temp.next.name = newHeroNode.name;
temp.next.nickname = newHeroNode.nickname;
} else {
System.out.printf("不存在编号为 %d 的英雄,不能修改", newHeroNode.no);
}
}
//删除,因为是单向链表,因此要找到要删除结点的前一个结点,temp.next=temp.next.next
public void delete(int no) {
HeroNode temp = head;
boolean flag = false;//是否找到要删除的英雄
//找到要删除结点的前一个结点
while (true) {
if (temp.next == null) {
System.out.println("链表遍历结束");
break;
}
if (temp.next.no == no) { //找到了
flag = true;
break;
}
//还没找到,temp后移
temp = temp.next;
}
if (flag) {
temp.next = temp.next.next;
} else {
System.out.printf("没有找到要编号为 %d 的英雄,删除失败", no);
}
}
//求单链表中有效结点的个数
/**
* @param head 单链表的头结点
* @return 有效结点个数
*/
public static int getLength(HeroNode head) {
//定义辅助变量
HeroNode curr = head.next;
int length = 0;
while (curr != null) {
length++;
curr = curr.next; //遍历
}
return length;
}
//查找单链表中的倒数第k个结点
//思路
//1.编写一个方法,接收head结点,同时接收一个index
//2.index 表示倒数第index个结点
//3.先把链表从头到尾遍历,得到链表的总长度getLength
//4.得到size后,我们从链表的第一个开始遍历,遍历到size-index,就可以得到。
//5.如果找到了,则返回该结点,否则返回null
public static HeroNode findLastIndexNode(HeroNode head, int index) {
//判断链表为空,返回null
if (head.next == null) {
return null;
}
//第一次遍历得到链表的长度(结点个数)
int size = getLength(head);
//第二次遍历,size-index位置,就是我们倒数第k个结点
//先做一个index校验
if (index <= 0 || index > size) {
return null;
}
//定义辅助变量,for循环定位到倒数的index
HeroNode temp = head.next;
for (int i = 0; i < size - index; i++) { //目的是让指针后移
temp = temp.next;
}
return temp;
}
//单链表反转
//思路:头插法
//1、先定义一个结点reverseHead=new HeroNode()
//2、从头到尾遍历原来的链表,每遍历一个结点,就将其取出,并放在新的链表的最前端
//3、原来的链表head.next = reverseHead.next
public static void reverseList(HeroNode head) {
//如果当前链表为空,或者只有一个结点,无需反转,直接返回
if (head.next == null || head.next.next == null) {
return;
}
HeroNode reverseHead = new HeroNode(0, "", "");
//遍历原来的链表
//定义辅助变量cur
HeroNode cur = head.next; // 从第一个有效结点开始
HeroNode next = null; //指向当前结点的下一个结点
while (cur != null) {
next = cur.next; //每次都要先保存当前结点的下一个结点,否则原来的链表会断链,无法继续遍历下去
// 将当前结点取出,并放在新链表的最前面
cur.next = reverseHead.next;
reverseHead.next = cur;
cur = next;// 指针后移
}
//将head.next 指向reverseHead.next,实现链表反转
head.next = reverseHead.next;
}
//从尾到头打印单链表
//思路
//方式一:先将单链表反转,然后再遍历。会破坏原来的单链表结构,不建议
//方式二:可以利用栈。将各个结点压入栈中,然后利用栈后进先出的特点,实现逆序打印的效果
public static void reversePrint(HeroNode head) {
if (head.next == null) {
return;
}
//创建一个栈,将各个结点压入栈
Stack<HeroNode> stack = new Stack<>();
//定义辅助变量
HeroNode cur = head.next;
while (cur != null) {
stack.push(cur); //入栈
cur = cur.next;//指针后移,压入下一个结点
}
// 将栈中的结点pop
while (stack.size()>0){
System.out.println(stack.pop());
}
}
//合并两个有序链表:思路和单链表反转类似
public static void mergeList(HeroNode h1,HeroNode h2){
System.out.println("进入了merge方法。h1.next="+h1.next);
HeroNode cur1 = h1.next;
HeroNode cur2 = h2.next;
HeroNode next1=null;
HeroNode next2=null;
HeroNode mergeHead = new HeroNode(0,"","");
HeroNode temp = mergeHead;
System.out.println("cur1==null:::"+cur1==null);
System.out.println("cur2==null:::"+cur2==null);
while(true){
if (cur1==null){
temp.next=cur2;
System.out.println("第一个单链表遍历完了,cur2:"+cur2+",cur2.next:"+cur2.next);
break;
}
if (cur2==null){
temp.next=cur1;
System.out.println("第二个单链表遍历完了");
break;
}
if (cur1.no<cur2.no){
next1 = cur1.next;
cur1.next=null;
temp.next=cur1;
temp = temp.next;
cur1=next1;
System.out.println("h1::"+mergeHead.next);
}else{
next2 = cur2.next;
cur2.next=null;
temp.next=cur2;
temp = temp.next;
cur2=next2;
System.out.println("h2:::"+mergeHead.next);
}
}
h1.next=mergeHead.next;
}
//合并两个有序链表:递归
public static HeroNode mergeList1(HeroNode h1,HeroNode h2){
if (h1==null){
return h2;
}else if (h2==null){
return h1;
}else if (h1.no<h2.no){
h1.next=mergeList1(h1.next,h2);
return h1;
}else {
h2.next = mergeList1(h1,h2.next);
return h2;
}
}
//显示链表【遍历】
public void list() {
//判断链表是否为空
if (head.next == null) {
System.out.println("链表为空");
return;
}
//因为头节点不能动,需要一个辅助变量来遍历
HeroNode temp = head.next; //避免将头结点也打印出来
//链表不为空,至少有一个
while (true) {
//判断是否到达链表最后
if (temp == null) {
break;
}
//还没到最后,则输出结点信息
System.out.println(temp);
//将temp后移
temp = temp.next;
}
}
}
//定义英雄节点对象
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;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname + '\'' +
'}';
}
}