数据结构之链表
1.链表介绍
链表是有序的列表,但是它在内存中是存储如下:
小结上图:
- 链表是以节点的方式来存储,是链式存储
- 每个节点包含data域,next域:指向下一个节点.
- 如图:发现链表的各个节点不一定是连续存储.
- 链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定
2.单链表的应用实例
使用带head头的单向链表实现–水浒英雄排行榜管理完成对英雄人物的增删改查操作
1)第一种方法在添加英雄时,直接添加到链表的尾部
2)第二种方式在添加英雄时,根据排名将英雄插入到指定位置(如果有这个排名,则添加失败,并给出提示)
3)修改节点功能
思路:(1)先找到该节点,通过遍历,(2) temp.name = newHeroNode.name ; temp.nickname=newHeroNode.nickname.
4)删除节点
package com.yt.link;
public class SingleLinkedListDemo {
public static void main(String[] args) {
//测试单链表的添加方法
//创建英雄节点
HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
HeroNode hero4 = new HeroNode(4, "林冲", "豹子头");
SingleLinkedList list = new SingleLinkedList();
list.add(hero1);
list.add(hero2);
list.add(hero3);
list.add(hero4);
list.list();
//测试删除方法
System.out.println("测试删除方法");
list.delete(3);
list.list();
//测试更新方法
System.out.println("测试更新方法");
HeroNode node = new HeroNode(5, "小卢", "玉麒麟");
list.update(node);
list.list();
//测试按序号插入方法
System.out.println("测试按序号插入序号");
HeroNode heroNode3 = new HeroNode(3, "武松", "行者");
list.addByOrder(heroNode3);
list.list();
}
}
//2.创建单链表,便于进行管理
class SingleLinkedList{
//先初始化一个头结点,头结点不动,也不存放具体的数据
private HeroNode head = new HeroNode(0,"","");
//添加节点到单链表
//思路,当不考虑编号顺序时
//1.先找到当前链表的最后节点
//2.将新的节点增加到链表的最后
public void add(HeroNode heroNode){
//因为head节点不能动,所以需要一个临时辅助变量temp
HeroNode temp = head;
//循环查找
while (true){
if (temp.next == null){
break;
}
//不是最后一个节点,就一直遍历
temp = temp.next;
}
//遍历到最后一个节点,将最后一个节点指向新加入的节点
temp.next = heroNode;
}
//第二种方式添加英雄,根据排名将英雄插入到指定的位置
//如果有这个排名,则添加失败,给出提示信息
public void addByOrder(HeroNode heroNode){
//创建临时变量,遍历到要插入位置的前一个节点
HeroNode temp = head;
boolean flag = false;
while (true){
if (temp.next == null){
//已经遍历到链表的最后
break;
}
if (temp.next.no > heroNode.no){//位置已经找到
break;
} else if (temp.next.no == heroNode.no){
//说明希望添加的节点已经存在
flag = true;
break;
}
temp = temp.next;//往后遍历
}
if (flag){
System.out.printf("准备插入的英雄编号 %d 已经存在了",heroNode.no);
} else {
//插到链表中,temp的后面
heroNode.next = temp.next;
temp.next = heroNode;
}
}
//修改节点的信息,根据no编号来修改,即no编号不能改
public void update(HeroNode heroNode){
//判断是否为空
if (head.next == null){
System.out.println("链表为空");
return;
}
//根据no节点,找到需要修改的节点
//定义辅助变量temp
HeroNode temp = head.next;
boolean flag = false;//表示是否找到该节点,默认找不到
while (true){
if (temp == null){//遍历到最后都没有匹配
break;//退出循环
}
if (temp.no == heroNode.no){
//找到符合要求的节点
flag = true;
break;
}
temp = temp.next;//往下一个节点遍历
}
if (flag){
temp.name = heroNode.name;
temp.nickName = heroNode.nickName;
} else {
System.out.printf("没有找到 %d 的节点,不能修改\n",heroNode.no);
}
}
//删除节点的方法
//1.创建辅助变量temp来帮助遍历
//2.说明:比较到序号no相同就认为是两个相同的英雄,直接删除
public void delete(int no){
HeroNode temp = head;
//标识表示是否找到待删除的节点
boolean flag = false;//默认表示没有删除的节点
while (true){
if (temp.next == null){
//链表已经到达最后,说明没有找到要删除的节点
break;
}
//找到要删除的节点
if (temp.next.no == no){
flag = true;//修改标识位
break;
}
temp = temp.next;
}
if (flag){
temp.next = temp.next.next;
} else {
System.out.printf("要删除的%d节点不存在\n",no);
}
}
//遍历单链表的方法
public void list(){
//判断链表是否为空
if (head.next == null){
System.out.println("链表为空");
return;
}
// HeroNode temp = head;//这样会遍历出头结点,但是头结点没有数据,不合逻辑
HeroNode temp = head.next;
while (true){
//判断是否到链表的最后
if (temp == null){//注意:这里不是temp.next
break;
}
//当前节点不为null,输出当前节点
System.out.println(temp);
//往后移一个节点
temp = temp.next;
}
}
}
//1.创建节点
class HeroNode{
public int no;
public String name;
public String nickName;//英雄的绰号
public HeroNode next;//指向下一个节点的指针,默认为null
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 + '\'' +
'}';
}
}
3.习题1-单链表个数
求单链表的个数
//计算链表中有几个节点
public int getLength(HeroNode head){
int count = 0;
HeroNode temp = head;
if (temp.next == null){
return 0;
}
while (temp.next != null){
count++;
temp = temp.next;
}
return count;
}
4.习题2-倒数第k个位置
查找单链表中倒数第k个结点
思路:
- 先计算出总共有多少个结点,即结点个数count
- 倒数第一个就是count - 0的位置
- 倒数第二个结点就是count-1的位置
- 倒数第count个结点就是第一个结点,也就是count-(count-1)的位置
//计算链表中有几个节点
public int getLength(HeroNode head){
int count = 0;
HeroNode temp = head;
if (temp.next == null){
return 0;
}
while (temp.next != null){
count++;
temp = temp.next;
}
return count;
}
//计算倒数第k个结点的信息
//head为要传入的单链表的头结点,k表示要查找的位置
public HeroNode findLastIndexNode(HeroNode head, int k){
//先计算出单链表的总长度
int size = getLength(head);
//处理特殊情况
if (k <= 0 || k > size){
return null;
}
//设置临时变量
HeroNode res = head.next;
for (int i = 0; i < size - k; i++) {
res = res.next;
}
return res;
}
5.单链表的反转
注意看代码注释
public static void reverseList(HeroNode headNode){
//只有头结点或者只有一个结点时不要反转
if (headNode.next==null || headNode.next.next==null){
return;
}
HeroNode cur = headNode.next;//用于遍历原链表
HeroNode temp = null;//用于保存原链表节点的下一个节点temp=temp.next
HeroNode newHead = new HeroNode(0,"","");//用于指向新链表的第一个结点,临时的
//遍历原链表
while (cur != null){
temp = cur.next;//将原链表的下一个结点先保存
cur.next = newHead.next;//注意,将要插入新链表的结点先指向新链表的最前面,(总是这一句写错)
newHead.next = cur;//将原链表中截取下来的结点连接到新链表
cur = temp;//将cur再指向原链表
}
headNode.next = newHead.next;//将原配的头结点还原
}
6.从尾到头打印单链表
方式一:将单链表反转,但是这会破坏原来链表的结构,不推荐
方式二:使用栈Stack方式
先测试栈Stack的使用
package com.yt.link;
import java.util.Stack;
public class TestStack {
public static void main(String[] args) {
Stack<String> stack = new Stack<>();
//入栈
stack.push("jack");
stack.push("tom");
stack.push("smith");
//出栈
// System.out.println(stack.pop());
while (stack.size() > 0) {
System.out.println(stack.pop());
}
}
}
public static void reversePrint(HeroNode headNode){
if (headNode.next == null){
return;
}
Stack<HeroNode> heroNodesStack = new Stack<>();
HeroNode temp = headNode.next;//用于遍历链表
while (temp != null){
heroNodesStack.push(temp);
temp = temp.next;
}
while (heroNodesStack.size() > 0){
System.out.println(heroNodesStack.pop());
}
}