数据结构之链表

链表(Liked List)

一、链表的概念

  1. 链表是有序的列表,但是在内存中链表的各个节点不一定是连续的。
  2. 下图是链表在内存中的存储结构示意图
    在这里插入图片描述
    总结:
    a.链表是以节点的方式存储,链式存储
    b.每个节点包含了data域(存值)、next域(指向下一个节点)
    c.链表的各个节点不一定是连续存储的
    d.链表分带头节点的链表和没有带头节点的链表,根据实际的需求来确定

二、单链表

  1. 单链表(带头结点)逻辑结构示意图,实际并不连续
    . 在这里插入图片描述

  2. 单链表的创建,不按编号添加,头节点只写一次,思路分析如下
    在这里插入图片描述

  3. 上述解释了如何创建链表,需要一个头节点(不能动),还有数据,还有next域。这时我们创建一个类,来代表单链表,然后再里面写上增删改查的方法,来实现一个完整的链表已经操控这个单链表

  4. 添加节点到单链表,不考虑编号

//添加节点到单向链表 当不考虑编号的顺序时
	//1、找到当前链表的最后节点
	//2、将最后这个节点的next,指向新的节点
	public void add(HerNode herNode){
	//因为head节点不能动,因此需要一个辅助变量temp
		HerNode temp = head;
		//遍历链表,找到最后
		while (true){
			//判断当前节点是否是最后一个
			if (temp.next==null){
				break;
			}
			//如果没有找到最后,将temp后移,查找后一个
			temp=temp.next;
		}
		//当前退出while循环时,temp就指向链表最后
		temp.next=herNode;
	}
  1. 考虑编号时,向单链表中插入节点,思路分析
    在这里插入图片描述
    代码实现:
//考虑编号顺序时
	public void addOrder(HerNode herNode){
		//因为头节点不能动,我们需要通过一个辅助变量来帮助找到添加的位置
		//因为是单链表,我们要找的temp,是位于添加位置的前一个节点,否则插入不了
		HerNode temp =head;
		boolean flag= false;//用来标志添加的编号是否已经存在,默认为false
		while (true){
			if (temp.next==null){//说明temp已经在链表最后
				break;
			}
			if (temp.next.no>herNode.no){//找到位置,就在temp的后面插入
				break;
			}else if (temp.next.no==herNode.no){//说明希望添加的heroNode的编号已然存在
				flag=true;
				break;
			}
			temp=temp.next;//后移,遍历当前链表
		}
		//判断flag的值
		if (flag){
			System.out.printf("您的英雄%d已存在\n",herNode.no);
		}else {
			//插入到链表中,temp后面
			herNode.next=temp.next;//当前插入的节点的指向temp的后一位
			temp.next=herNode;//temp指向新的节点,这样就插到中间了。
		}
	}
  1. 修改节点,直接上代码
//修改节点
	public void update(HerNode newNode){
		//判断是否为空
		if (head.next==null){
			System.out.println("链表为空");
			return;
		}
		//找到修改的节点通过遍历
		HerNode temp=head.next;
		boolean flag =false;//表示是否找到该节点
		while (true){
			if (temp==null){
				break;//已经遍历完成
			}
			if (temp.no== newNode.no){
				flag=true;
				break;
			}
			temp=temp.next;
		}
		if (flag){
			temp.name=newNode.name;
			temp.nickName=newNode.nickName;
		}else {
			System.out.printf("没有找到编号%d 的节点",newNode.no);
		}


	}
  1. 删除节点
    a.思路:
    在这里插入图片描述
    b.代码实现
//删除节点
	//思路:head节点不能动,需要一个辅助变量帮助我们找到待删除的节点的前一个节点
	//		在比较时是,temp.next.no和需要删除的节点no比较
	public void delete(int no){
		HerNode temp = head;
		boolean flag = false;//表示是否找到待删除节点
		while (true){
			if (temp.next==null){//已经到链表最后
				break;
			}
			if (temp.next.no==no){//找到待删除节点的前一个节点temp
				flag=true;
				break;
			}
			temp=temp.next;
		}
		if (flag){
			//删除
			temp.next=temp.next.next;
		}else {
			System.out.println("要删除的节点不存在。。。");
		}

	}

8.查询链表,根据id或者no查询链表中的节点数据,
a.思路:使用while遍历,知道后一个为止,切记遍历完一个必须指向下一个,接着遍历。
b.代码实现

//显示链表
	public void list(){
		//判断链表是否为空
		if(head.next==null){
			System.out.println("链表为空!");
			return;
		}
		//如果不为空至少有一个节点,所以从第二个节点开始遍历,
		// 头节点不能动,因此,需要一个辅助变量来遍历
		HerNode temp = head.next;
		while (true){
			//是否到链表最后
			if (temp==null){
				System.out.println("遍历到最后跳出");
				break;
			}
			//输出当前节点的信息
			System.out.println(temp);
			//将temp后移,判断下一个,输出
			temp=temp.next;
		}
	}

c.根据no查询节点

//根据编码查询链表
	public void getByNo(int no){
		//判断链表是否为空
		if(head.next==null){
			System.out.println("链表为空!");
			return;
		}
		//如果不为空至少有一个节点,所以从第二个节点开始遍历,
		// 头节点不能动,因此,需要一个辅助变量来遍历
		HerNode temp = head.next;
		boolean flag=false;//判断是否找到
		while (true){
			//是否到链表最后
			if (temp==null){
				System.out.println("遍历到最后跳出");
				break;
			}
			if (temp.no==no){
				flag=true;//存在
				break;
			}
			//将temp后移,判断下一个,输出
			temp=temp.next;
		}
		if (flag){
			System.out.println(temp);
		}else {
			System.out.println("您输入的编号不存在");
		}

	}

三、测试代码

package linkedlist;

import java.util.Stack;
public class SingleLinkedListDemo {
	public static void main(String[] args) {
		//测试
		HerNode hero1 = new HerNode(1, "宋江", "及时雨");
		HerNode hero2 = new HerNode(2, "卢俊义", "玉麒麟");
		HerNode hero3 = new HerNode(3, "吴用", "智多星");
		HerNode hero4 = new HerNode(4, "林冲", "豹子头");
		HerNode hero5 = new HerNode(4, "林冲2", "豹子头2");

		//创建一个链表
		SingleLinkedList singleLinkedList = new SingleLinkedList();
		SingleLinkedList singleLinkedList2 = new SingleLinkedList();
		SingleLinkedList singleLinkedList3 = new SingleLinkedList();

		//不按顺序加入
		singleLinkedList.add(hero1);
		singleLinkedList.add(hero3);


		singleLinkedList2.add(hero2);
		singleLinkedList2.add(hero4);



		//合并为新的链表
		HerNode merge = merge(singleLinkedList.getHead(), singleLinkedList2.getHead());
		show(merge);

		/*System.out.println("原来的链表");
		singleLinkedList.list();

		System.out.println("逆序打印后的链表");
		reversePrint(singleLinkedList.getHead());

		System.out.println("反转后的链表");
		reversetList(singleLinkedList.getHead());
		singleLinkedList.list();*/

		/*//按顺序加入
		singleLinkedList.addOrder(hero1);
		singleLinkedList.addOrder(hero3);
		singleLinkedList.addOrder(hero4);
		singleLinkedList.addOrder(hero2);


		System.out.println("修改前");
		singleLinkedList.list();

		singleLinkedList.update(hero5);

		System.out.println("修改后");
		singleLinkedList.list();

		System.out.println("删除后");
		singleLinkedList.delete(4);
		singleLinkedList.list();

		System.out.println("根据编号查询后的记录");
		singleLinkedList.getByNo(1);

		System.out.println("单链表的有效个数为:"+ getLength(singleLinkedList.getHead()));

		System.out.println("查找单链表中的倒数第k个结点:"+getNode(singleLinkedList.getHead(),1));*/
	}
	//通过一个头节点,来查询链表
	public static void show(HerNode head) {
		HerNode cur = head.next;
		if (cur == null) {
			System.out.println("链表为空~");
		}
		while (cur != null) {
			System.out.println(cur);
			cur = cur.next;
		}
	}
	}
}
//定义一个SIngleLinkedList管理我们的英雄
class SingleLinkedList{
	//初始化一个头节点,头节点不要动,不存放具体的数据
	private HerNode head = new HerNode(0,"","");

	//返回头节点
	public HerNode  getHead(){
		return head;
	}

	//添加节点到单向链表 当不考虑编号的顺序时
	//1、找到当前链表的最后节点
	//2、将最后这个节点的next,指向新的节点
	public void add(HerNode herNode){
	//因为head节点不能动,因此需要一个辅助变量temp
		HerNode temp = head;
		//遍历链表,找到最后
		while (true){
			//判断当前节点是否是最后一个
			if (temp.next==null){
				break;
			}
			//如果没有找到最后,将temp后移,查找后一个
			temp=temp.next;
		}
		//当前退出while循环时,temp就指向链表最后
		temp.next=herNode;
	}
	//考虑编号顺序时
	public void addOrder(HerNode herNode){
		//因为头节点不能动,我们需要通过一个辅助变量来帮助找到添加的位置
		//因为是单链表,我们要找的temp,是位于添加位置的前一个节点,否则插入不了
		HerNode temp =head;
		boolean flag= false;//用来标志添加的编号是否已经存在,默认为false
		while (true){
			if (temp.next==null){//说明temp已经在链表最后
				break;
			}
			if (temp.next.no>herNode.no){//找到位置,就在temp的后面插入
				break;
			}else if (temp.next.no==herNode.no){//说明希望添加的heroNode的编号已然存在
				flag=true;
				break;
			}
			temp=temp.next;//后移,遍历当前链表
		}
		//判断flag的值
		if (flag){
			System.out.printf("您的英雄%d已存在\n",herNode.no);
		}else {
			//插入到链表中,temp后面
			herNode.next=temp.next;//当前插入的节点的指向temp的后一位
			temp.next=herNode;//temp指向新的节点,这样就插到中间了。
		}
	}


	//修改节点
	public void update(HerNode newNode){
		//判断是否为空
		if (head.next==null){
			System.out.println("链表为空");
			return;
		}
		//找到修改的节点通过遍历
		HerNode temp=head.next;
		boolean flag =false;//表示是否找到该节点
		while (true){
			if (temp==null){
				break;//已经遍历完成
			}
			if (temp.no== newNode.no){
				flag=true;
				break;
			}
			temp=temp.next;
		}
		if (flag){
			temp.name=newNode.name;
			temp.nickName=newNode.nickName;
		}else {
			System.out.printf("没有找到编号%d 的节点",newNode.no);
		}


	}

	//删除节点
	//思路:head节点不能动,需要一个辅助变量帮助我们找到待删除的节点的前一个节点
	//		在比较时是,temp.next.no和需要删除的节点no比较
	public void delete(int no){
		HerNode temp = head;
		boolean flag = false;//表示是否找到待删除节点
		while (true){
			if (temp.next==null){//已经到链表最后
				break;
			}
			if (temp.next.no==no){//找到待删除节点的前一个节点temp
				flag=true;
				break;
			}
			temp=temp.next;
		}
		if (flag){
			//删除
			temp.next=temp.next.next;
		}else {
			System.out.println("要删除的节点不存在。。。");
		}

	}
	//显示链表
	public void list(){
		//判断链表是否为空
		if(head.next==null){
			System.out.println("链表为空!");
			return;
		}
		//如果不为空至少有一个节点,所以从第二个节点开始遍历,
		// 头节点不能动,因此,需要一个辅助变量来遍历
		HerNode temp = head.next;
		while (true){
			//是否到链表最后
			if (temp==null){
				System.out.println("遍历到最后跳出");
				break;
			}
			//输出当前节点的信息
			System.out.println(temp);
			//将temp后移,判断下一个,输出
			temp=temp.next;
		}
	}

	//根据编码查询链表
	public void getByNo(int no){
		//判断链表是否为空
		if(head.next==null){
			System.out.println("链表为空!");
			return;
		}
		//如果不为空至少有一个节点,所以从第二个节点开始遍历,
		// 头节点不能动,因此,需要一个辅助变量来遍历
		HerNode temp = head.next;
		boolean flag=false;//判断是否找到
		while (true){
			//是否到链表最后
			if (temp==null){
				System.out.println("遍历到最后跳出");
				break;
			}
			if (temp.no==no){
				flag=true;//存在
				break;
			}
			//将temp后移,判断下一个,输出
			temp=temp.next;
		}
		if (flag){
			System.out.println(temp);
		}else {
			System.out.println("您输入的编号不存在");
		}

	}

}

//定义一个heroNode,每个node对象就是一个节点
class HerNode{
	public int no;
	public String name;
	public String nickName;
	public HerNode next;//指向下一个节点
	//构造器
	public HerNode(int no,String name,String nickName){
		this.no=no;
		this.name=name;
		this.nickName=nickName;
	}
	public HerNode(){};

	@Override
	public String toString() {
		return "HerNode{" +
				"no=" + no +
				", name='" + name + '\'' +
				", nickName='" + nickName + '\'' +
				'}';
	}
}

四、面试题

一、求单链表中有效节点的个数(如果时带头链表,不加入统计)

/**
	 * 求单链表中有效节点的个数(如果时带头链表,不加入统计)
	 * @param head 链表的头节点
	 * @return
	 */
	public static int getLength(HerNode head){
		if (head.next==null){
			return 0;
		}
		//定义一个辅助变量,没有统计头节点
		HerNode temp = head.next;
		int count=0;
		while (temp!=null){
			count++;
			temp=temp.next;//遍历
		}
		return count;

	}

二、查找单链表中的倒数第k个结点 【新浪面试题】
,里面的getLength长度,也就是上面的有效节点个数拉。

/**
	 * 查找单链表中的倒数第k个结点 【新浪面试题】
	 * @param head 头节点
	 * @param num  输入的值
	 * @return
	 */
	public static HerNode getNode(HerNode head, int num){
		//判断是否为空
		if (head.next==null){
			return null;//没有找到
		}
		//第一次遍历得到的长度
		int size = getLength(head);

		//size-num 的位置就是我们倒数第K个节点
		if (num<=0 || num>size){
			return null;
		}
		//定义一个辅助变量,帮助我们遍历

		HerNode cur =head.next;
		for (int i = 0; i < size-num; i++) {
			cur=cur.next;
		}
		return cur;
	}

三、单链表的反转【腾讯面试题,有点难度】

/**
	 * 单链表的反转【腾讯面试题,有点难度】
	 * @param head
	 */
	public static void reversetList(HerNode head) {
		//① 如果当前链表为空,或者只有一个节点,我们无需反转,直接返回
		if (head.next==null || head.next.next==null){
			return;
		}
		// ② 定义一个辅助变量,帮助我们遍历原来的链表
		 HerNode temp =head.next;
		 HerNode next = null;//指向当前节点【temp】的下一个节点
		 HerNode reverseHead= new HerNode(0,"","");

	 	// ③遍历原来的链表,每遍历一个节点,就将其取出,并放在新的链表reverseHead的最前端
		while (temp!=null){
			next=temp.next;//暂时保存当前节点的下一个节点,后面需要使用
			temp.next=reverseHead.next; //将temp的下一个节点,指向新的链表的最前端
			reverseHead.next=temp; //将取出来的节点,接到新的链表上
			temp=next;//让temp后移
		}
		//将head.next指向reverseHead.next,实现反转
		head.next=reverseHead.next;
	}

四、从尾到头打印单链表 【百度,要求方式1:反向遍历 (会破坏原来的单链表结构)。 方式2:Stack栈】

/**
	 * 从尾到头打印单链表 【百度,要求方式1:反向遍历 。 方式2:Stack栈】
	 * 使用方式二来实现方式二,stack先进后出
	 * @param head
	 */
	public static  void  reversePrint(HerNode head){
		if (head.next==null){//空链表无法打印
			return;
		}
		//创建一个栈,将节点压入栈中,再取出,就可以实现
		Stack<HerNode> stack = new Stack<>();
		HerNode cur = head.next;
		//压入栈中
		while (cur!=null){
			stack.push(cur);
			//后移压下一个
			cur=cur.next;
		}
		//将栈中的节点进行打印,出栈
		while (stack.size()>0){
			System.out.println(stack.pop());
		}

	}

五、合并两个有序的单链表,合并之后的链表依然有序

/**
 * 合并两个有序的单链表,合并之后的链表依然有序
 */
public static HerNode merge(HerNode herNode1, HerNode herNode2) {
		if (herNode1.next == null) {
			return herNode2;
		} else if (herNode2.next == null) {
			return herNode1;
		}

		HerNode newNode = new HerNode();
		HerNode n1 = newNode;
		HerNode l1 = herNode1.next;
		HerNode l2 = herNode2.next;

		while (l1 != null && l2 != null) {
			if (l1.no < l2.no) {
				n1.next = l1;
				l1 = l1.next;
				n1 = n1.next;
			} else {
				n1.next = l2;
				l2 = l2.next;
				n1 = n1.next;
			}
		}
		if (l1 == null) {
			n1.next = l2;
		}
		if (l2 == null) {
			n1.next = l1;
		}
		return  newNode;
	}

五、单链表的缺点:
1、查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
2、不能自我删除,需要靠辅助节点,而双向链表,则可以实现自我删除,删除单向链表节点时,需要通过辅助节点找到待删除节点的前一个,让前一个指向待删除节点的后一个,完成剔除。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值