单链表
特点
以节点的形式存储,链式存储
每个节点包含data(数据),next(下一个节点的地址)
链表不一定是连续存储
链表分带头结点的链表和没有头结点的链表
内存结构
地址 | data | next |
---|---|---|
110 | a2 | 180 |
120 | ||
130 | a4 | 170 |
140 | a6 | NULL |
150 | a1 | 110 |
160 | ||
170 | a5 | 140 |
180 | a3 | 130 |
特点就是
我们的a1的next指向我们a2的地址,依次排列
但是我们的内存顺序不一定是连续的!!
也就是说逻辑结构看起来是连续的,但实际上内存不是连续的
单链表的实现
先创建一个head头结点,作用表示单链表的头部
每添加一个节点,就直接加入到链表的最后
遍历:
通过辅助变量,帮助遍历整个链表
代码
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 singleLinkedList = new SingleLinkedList();
singleLinkedList.add(hero1);
singleLinkedList.add(hero4);
singleLinkedList.add(hero2);
singleLinkedList.add(hero3);
singleLinkedList.findAllList();
}
}
//定义我们的链表 管理我们的英雄数据
class SingleLinkedList{
//初始化头结点
private HeroNode head = new HeroNode(0,"","");
//添加节点到链表,当不考虑链表的顺序时
//1.找到当前链表的最后节点
//2.将最后这个节点的next指向新的节点
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 findAllList(){
//先判断链表是否为空
if (head.next == null)
{
System.out.println("链表为空");
return;
}
//因为头结点不能动,因此需要一个辅助变量来遍历
HeroNode temp = head.next;
while (true){
//是否为最后
if (temp.next == null)
{
System.out.println(temp);
break;
}
//输出节点信息
System.out.println(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;
}
//重写我们的toString方法
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname + '\'' +
'}';
}
}
还没完成的时,我们发现我们存储的顺序,跟我们谁先add有关,而不是跟排名有关
改进(根据排名的顺序插入)
首先通过遍历先找到新添加节点的位置
让新的节点的next=temp.next
让temp.next = 新的节点
//根据我们的排名来存储
public void addByOrder(HeroNode heroNode){
//因为头指针不能动,所以还要一个辅助指针来帮助找到添加的位置
//因为单链表,以为我们找到的temp是位于添加位置的前一个节点,否则插入不了
HeroNode temp = head;
boolean flag = false;//标志添加的编号是否存在,默认为false
while (true)
{
if (temp.next == null){
break;
}
if (temp.next.no>heroNode.no){
//位置找到,就在heroNode后面
break;
}else if (temp.next.no == heroNode.no){
//已经存在过了
flag = true;
break;
}
//往下移动,继续遍历
temp = temp.next;
}
if (flag == true)
{
//不能添加,已经存在过了
System.out.println("准备插入的英雄编号"+heroNode.no+"已经存在了");
}else {
//插入到对应的位置
heroNode.next = temp.next;
temp.next = heroNode;
}
}
根据编号修改节点信息
我们先要找到这个需要修改的英雄(遍历)
//修改链表
public void update(HeroNode heroNode){
//判断是否为空
if (head.next == null)
{
System.out.println("链表为空");
return;
}
//找到需要修改的编号
//用一个辅助节点
HeroNode temp = head.next;
boolean flag = false;
while (true)
{
if (temp == null){
break;//遍历结束
}
if (temp.no == heroNode.no){
//找到我们的修改位置
flag = true;
break;
}
temp = temp.next;
}
//根据flag判断是否找到节点
if (flag)
{
temp.name = heroNode.name;
temp.nickname = heroNode.nickname;
}else {
System.out.println("没有找到"+heroNode.no+"的英雄");
}
}
删除节点
我们先找到需要删除的节点的前一个节点
temp.next = temp.next.next (把中间那个忽略掉了)
别删除的节点将不会有其他引用指向,会被垃圾回收机制回收
//删除节点
public void delete(HeroNode heroNode){
HeroNode temp = head;
boolean flag = false;
while (true)
{
if (temp.next ==null)
{
break;
}
if (temp.next.no == heroNode.no){
//找到了待删除的节点
flag = true;
break;
}
temp = temp.next;
}
//判断flag
if (flag){
//可以删除
temp.next = temp.next.next;
}else {
System.out.println("要删除的"+heroNode.no+"不存在");
}
}
单链表面试题
1)求单链表中有效节点的个数
2)查找单链表中的倒数第k个结点[新浪面试题]
3)单链表的反转[腾讯面试题]
4)从尾到头打印单链表[百度,要求方式1:反向遍历。方式2: Stack栈 ]
5)合并两个有序的单链表,合并之后的链表依然有序[课后练习.]
1.获取单链表的节点个数
带头节点的链表,最后结果是不能算头结点的,没有存储数据
public static int getLength(HeroNode head){
if (head.next == null){
return 0;
}
int length=0;
//定义一个辅助变量
HeroNode cur = head.next;
while (cur!=null){
length++;
cur = cur.next;
}
return length;
}
public HeroNode getHead(){
return head;
}
2.查找单链表中的倒数第k个结点[ 新浪面试题
//编写一个方法,接收head节点,同时接收一个index
//index表示是倒数第index个节点
//先把列表从头到尾遍历,得到链表的总长度
//得到size后,从链表的第一个开始遍历(size-index)个,就可以得到了
public static HeroNode findLastIndexNode(HeroNode head,int index){
if (head.next == null){
return null;
}
//得到链表长度
int size = getLength(head);
//第二次遍历,size-index位置,就是我们倒数第K个节点
//先做index数据校验
if (index<=0||index>size){
return null;
}
//定义辅助变量
HeroNode cur = head.next;
for (int i = 0;i<size-index;i++)
{
cur = cur.next;
}
return cur;
}
单链表反转 腾讯面试题
先定义一个节点recerseHead = new HeroNode();
从头到尾遍历原来的单链表,每遍历一个节点,就将其取出,并放在新的链表的最前端
原来链表的head.next = recerseHead.next
//将单链表反转
public static void reversetList(HeroNode heroNode)
{
//如果当前链表为空,则不需要进行反转
if (heroNode.next == null || heroNode.next.next == null)
{
return;
}
//先定义一个辅助指针,帮我们遍历原来的链表
HeroNode cur = heroNode.next;
HeroNode next = null;//表示当前的节点[cur]的下一个节点,因为我们这个节点要被拿掉放到反转链表中,不把下一个节点存起来,这个链表就断掉了
HeroNode reverseHead = new HeroNode(0,"","");
//遍历原来的链表,每遍历一个节点,我们就把这个节点放到新的resveseHead节点中
while (cur != null)
{
next = cur.next;//暂时保存我们当前节点的下一个节点,因为后面会使用
cur.next = reverseHead.next;//将cur的下一个节点指向新链表的最前端
reverseHead.next = cur;//recerseHead 指向我们的新链表
cur = next;//让cur指向下一个节点
}
//将原来的head.next(头部)指向我们的新链表的头部
heroNode.next = reverseHead.next;
}
从尾到头打印单链表
**方式1:**先将单链表进行反转操作,然后再遍历即可,这样的做的问题是会破坏原来的单链表的结构,不建议
**方式2:**可以一用栈这个数据结构,将各个节点压入栈中,然后利用栈的先进后出的特点
//栈的基本操作
public class TestStack {
public static void main(String[] args) {
Stack<String> stack = new Stack<>();
//入栈
stack.add("jack");
stack.add("tom");
stack.add("smith");
//出栈
while (stack.size()>0)
{
System.out.println(stack.pop());
}
}
}
//结果
smith
tom
jack
//利用栈逆序打印我们的单链表
public static void recerserPrint(HeroNode node){
if (node.next == null){
return;
}
//创建一个栈,将各个节点压入栈中
Stack<HeroNode> stack = new Stack<>();
HeroNode cur = node.next;//辅助存储我们下一个节点
//将链表的所有节点压入栈中
while (cur != null)
{
stack.push(cur);
cur = cur.next;//将我们的cur指向下一个节点
}
//将栈中的节点打印
while (stack.size()>0)
{
System.out.println(stack.pop());
}
}