java链表

链表是有序的列表,但是它在内存中是存储

链表是以节点的方式来存储,是链式存储
每个节点包含 data 域, next 域:指向下一个节点.
链表的各个节点不一定是连续存储.
链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定
在这里插入图片描述
单向链表实例
在这里插入图片描述

添加(创建)

  1. 先创建一个head 头节点, 作用就是表示单链表的头
  2. 后面我们每添加一个节点,就直接加入到 链表的最后
    遍历:
  3. 通过一个辅助变量遍历,帮助遍历整个链表

需要按照编号的顺序添加

  1. 首先找到新添加的节点的位置, 是通过辅助变量(指针), 通过遍历来搞定
  2. 新的节点.next = temp.next
  3. 将temp.next = 新的节点

从单链表中删除一个节点的思路

  1. 我们先找到 需要删除的这个节点的前一个节点 temp
  2. temp.next = temp.next.next
  3. 被删除的节点,将不会有其它引用指向,会被垃圾回收机制回收

求单链表中有效节点的个数
查找单链表中的倒数第k个结点
单链表的反转
从尾到头打印单链表 【要求方式1:反向遍历 。 方式2:Stack栈】

/** 定义一个HeroNode节点,每个对象代表一个节点*/
 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 + '\'' +
                '}';
    }
}



/** 定义SingleLinkList 管理*/
class SingleLinkListDemo{

    /** 初始化一个头节点,对象就是一个头节点 */
    private HeroNode head=new HeroNode(0,"","");
    /**
     * 添加节点到单链表
     *
     *  1. 找到当前链表的最后节点
     *  2.将最后这个节点的next指向新的节点。
     * */

    /**
     * 第一种,插入到最后一条节点的下一个
     * @param heroNode
     */
    public void add(HeroNode heroNode){

        //因为head节点不能动,因此需要一个辅助节点
        HeroNode temp=head;
        while(true){
            //找到链表的最后一个元素,这时候退出循环
            if(temp.next==null){
                break;
            }
            //如果没有找到最后,向后移动
            temp=temp.next;
        }
        //把最后这个节点的下一个节点指向新节点。
        temp.next=heroNode;
    }

    /** 第二种添加,根据排名将英雄插入指定位置*/
    public void addByOrder(HeroNode heroNode){
		// 因为头结点不能动, 因此我们需要哟有一个临时结点,作为辅助
		// 注意,我们在找这个添加位置时,是将这个节点加入到temp的后面
		// 因此,在比较时,是将当前的heroNode 和 temp.next 比较
		HeroNode temp = head;
		boolean flag = false; // flag 是用于判断是否该英雄的编号已经存在, 默认为false
		while (true) {
			if (temp.next == null) { // 说明temp已经是链表最后
				break;
			}
			if (temp.next.no > heroNode.no) {
				// 位置找到,当前这个节点,应当加入到temp后
				break;
			} else if (temp.next.no == heroNode.no) {
				// 已经有这个节点
				flag = true;
				break;
			}
			temp = temp.next; // 注意
		}

		if (flag) { // 不可以加入
			System.out.printf("待插入的英雄编号 %d 已经有了,不能加入\n", heroNode.no);
		} else {
			// 加入,注意顺序
			heroNode.next = temp.next;
			temp.next = heroNode;
		}
    }

    // 修改节点的值, 根据no编号为前提修改, 即no不能改
	public void update(HeroNode newHeroNode) {
		if (head.next == null) {
			System.out.println("链表为空");
			return;
		}
		// 先找到节点
		HeroNode temp = head.next;
		boolean flag = false;
		while (true) {
			if (temp == null) {
				break;
			}
			if (temp.no == newHeroNode.no) {
				// 找到.
				flag = true;
				break;
			}
			temp = temp.next; //
		}

		// 判断是否找到
		if (flag) {
			temp.name = newHeroNode.name;
			temp.nickname = newHeroNode.nickname;
		} else {
			System.out.printf("没有找到 编号为%d 节点,不能修改\n", newHeroNode.no);
		}

	}

    /** 执行删除指定节点 */
    public void delNode(int no){

        HeroNode temp=head;
        //判断存在该节点
        boolean flag= false;
        while(true){

            if(temp.next==null){
                break;
            }

            if(temp.next.no < no){
                //把temp指向下一个节点
                temp=temp.next;
                continue;
            }else if(temp.next.no == no){
                flag=true;
                break;
            }
        }
        if(flag){
            temp.next=temp.next.next;
            System.out.println("成功删除节点");
        }else{
            System.out.println("该节点不存在");
        }
    }

    /** 遍历链表*/
    public void list(){

        //判断链表是否为null
        if(head.next == null){
            System.out.println("链表为空!");
            return ;
        }
        HeroNode temp=head.next;
        while(true){

            if(temp == null){
                break;
            }
            System.out.println(temp);
            temp=temp.next;
        }
    }
}




public class SingleLinkList {

public static void main(String[] args) {
    SingleLinkListDemo singleLinkListDemo = new SingleLinkListDemo();
    singleLinkListDemo.list();
//调试方法一添加
/* singleLinkListDemo.add(new HeroNode(1,"宋江","及时雨"));
singleLinkListDemo.add(new HeroNode(2,"卢俊义","玉麒麟"));
singleLinkListDemo.add(new HeroNode(3,"吴用","智多星"));
singleLinkListDemo.add(new HeroNode(4,"林冲","豹子头"));*/
//调试方法二添加
singleLinkListDemo.addByOrder(new HeroNode(1,"宋江","及时雨"));
singleLinkListDemo.addByOrder(new HeroNode(5,"卢俊义","玉麒麟"));
singleLinkListDemo.addByOrder(new HeroNode(4,"吴用","智多星"));
singleLinkListDemo.addByOrder(new HeroNode(2,"林冲","豹子头"));
singleLinkListDemo.addByOrder(new HeroNode(2,"林冲","豹子头"));
singleLinkListDemo.list();

    singleLinkListDemo.updateNode(new HeroNode(2,"林冲(修改版)","飞豹子头"));
    singleLinkListDemo.list();

    singleLinkListDemo.updateNode(new HeroNode(18,"林冲(修改版2)","飞豹子头2"));
    singleLinkListDemo.list();

    singleLinkListDemo.delNode(2);
    singleLinkListDemo.list();

    singleLinkListDemo.delNode(17);
    singleLinkListDemo.list();
    System.out.println("");
    singleLinkListDemo.addByOrder(new HeroNode(2,"林冲","豹子头"));
    singleLinkListDemo.list();


		 singleLinkedList.list();
		 
		 //1. 获取单链表的长度
		 System.out.println(getLength(singleLinkedList.getHead()));
		 //2. 找到倒数第index个节点, 没有的话,返回null
		 HeroNode findLastNode = findLastNode(singleLinkedList.getHead(), 2);
		 if(findLastNode == null) {
			 System.out.println("没有找到~");
		 }else {
			 System.out.println(findLastNode);
		 }

		 //4. 反向输出数据~, 注意没有真正改变链表节点顺序
		 System.out.println("反向输出数据~, 注意没有真正改变链表节点顺序");
		 reversePrint(singleLinkedList.getHead());
		 
		 //3. 单链表的反转
		 System.out.println(singleLinkedList.getHead().hashCode());
		 reverseList(singleLinkedList.getHead());
		 System.out.println("翻转后~~");
		 singleLinkedList.list();


//方法:获取单链表的长度 [注意:带头结点和不带头结点有区别,注意下即可]
	public static  int getLength(HeroNode head) {
		if (head.next == null) {
			return 0;
		}
		int length = 0;
		HeroNode current = head.next;
		while (current != null) {
			length++;
			current = current.next;
		}
		return length;
	}
	//查找单链表中的倒数第k个结点
	//找到倒数第index个节点, 没有的话,返回null
	//index代表的是倒数第index的那个结点
	//先将整个链表从头到尾遍历一次,计算出链表的长度size,得到链表的长度之后,就好办了,
	//直接输出第(size-k)个节点就可以了(注意链表为空,k为0,k为1,k大于链表中节点个数时的情况)。时间复杂度为O(n)
	public static HeroNode findLastNode(HeroNode head, int index) {

		if (head.next == null) {
			return null; //没有找到
		}
		// 第一次遍历,得到链表的长度size
		int size = getLength(head);
		if (index <= 0 || index > size ) {
			return null; //不满足条件
		}
		// 第二次遍历,输出倒数第index个结点的数据
		HeroNode current = head.next;
		for (int i = 0; i < size - index; i++) {
			current = current.next;
		}
		return current;
	}
	
	//单链表的反转
	//思路:从头到尾遍历原链表,每遍历一个结点,将其摘下放在新链表的最前端。注意链表为空和只有一个结点的情况。时间复杂度为O(n)
	//方法:链表的反转
    public static void reverseList(HeroNode head) {

        //如果链表为空或者只有一个节点,无需反转,直接返回原链表的头结点
        if (head.next == null || head.next.next == null) {
            return;
        }

        HeroNode current = head.next;
        HeroNode next = null; //定义当前结点的下一个结点
        HeroNode reverseHead  = new HeroNode(0, "", "");  //反转后新头节点

        while (current != null) {
        	
            next = current.next;  //暂时保存住当前结点的下一个结点,因为下一次要用

            current.next = reverseHead.next; //将current的下一个结点指向新链表的头结点
            reverseHead.next = current;  

            current = next;   // 操作结束后,current节点后移
        }
        //这句话很重要,不要写成了 head = reverseHead;
        head.next = reverseHead.next;
    }
    
    /**
    反向输出数据
     * 思路
     * 1. 对于这种颠倒顺序的问题,我们应该就会想到栈,后进先出。所以,使用栈完成
	   2. 不要想着先将单链表反转,然后遍历输出,这样会破坏链表的结构,不建议
     * @param head
     */
	public static void reversePrint(HeroNode head) {

		if (head.next == null) {
			return;
		}

		Stack<HeroNode> stack = new Stack<HeroNode>(); // 新建一个栈
		HeroNode current = head.next;

		// 将链表的所有结点压栈
		while (current != null) {
			stack.push(current); // 将当前结点压栈
			current = current.next;
		}

		// 将栈中的结点打印输出即可
		while (stack.size() > 0) {
			System.out.println(stack.pop()); // 出栈操作
		}

	}
}
}

双向链表
使用带head头的双向链表实现
管理单向链表的缺点分析:
单向链表,查找的方向只能是一个方向,而双向链表可以向前或者向后查找。
单向链表不能自我删除,需要靠辅助节点 ,而双向链表,则可以自我删除,所以前面我们单链表删除时节点,总是找到temp,temp是待删除节点的前一个节点(认真体会).

在这里插入图片描述
分析 双向链表的遍历,添加,修改,删除的操作思路===》代码实现

  1. 遍历 方和 单链表一样,只是可以向前,也可以向后查找
  2. 添加 (默认添加到双向链表的最后)
    (1) 先找到双向链表的最后这个节点
    (2) temp.next = newHeroNode
    (3) newHeroNode.pre = temp;
  3. 修改 思路和 原来的单向链表一样.
  4. 删除
    (1) 因为是双向链表,因此,我们可以实现自我删除某个节点
    (2) 直接找到要删除的这个节点,比如temp
    (3) temp.pre.next = temp.next
    (4) temp.next.pre = temp.pre;
// 先创建HeroNode
class HeroNode2 {
	public int no;
	public String name;
	public String nickname;
	public HeroNode2 pre; // pre 默认为null
	public HeroNode2 next; // next 默认为null

	public HeroNode2(int hNo, String hName, String hNickname) {
		no = hNo;
		name = hName;
		nickname = hNickname;
	}

}
class DoubleLinkedList {
	// 先初始化一个头结点, 头结点一般不会动
	HeroNode2 head = new HeroNode2(0, "", "");

	// 添加-遍历-修改-删除
	// 编写添加方法
	// 第一种方法在添加英雄时,直接添加到链表的尾部
	public void add(HeroNode2 heroNode) {
		// 因为头结点不能动, 因此我们需要哟有一个临时结点,作为辅助
		HeroNode2 temp = head;
		while (true) {
			if (temp.next == null) { // 说明temp已经是链表最后
				break;
			}
			// 如果没有到最后
			temp = temp.next;
		}

		// 当退出while循环后,temp就是链表的最后
		temp.next = heroNode;
		heroNode.pre = temp;

	}

	// 遍历方法一样
	public void list() {
		// 判断当前链表是否为空
		if (head.next == null) {
			System.out.println("链表为空!!");
			return;
		}
		// 因为头结点不能动, 因此我们需要哟有一个临时结点,作为辅助
		// 因为head 结点数据,我们不关心,因此这里 temp=head.next
		HeroNode2 temp = head.next;

		while (true) {
			// 判断是否到最后
			if (temp == null) {
				break;
			}
			System.out.printf("结点信息 no=%d name=%s nickname=%s\n", temp.no, temp.name, temp.nickname);
			temp = temp.next;
		}

	}

	// 修改节点的值, 根据no编号为前提修改, 即no不能改
	public void update(HeroNode2 newHeroNode) {
		if (head.next == null) {
			System.out.println("链表为空");

			return;
		}
		// 先找到节点
		HeroNode2 temp = head.next;
		boolean flag = false;

		while (true) {
			if (temp == null) {
				break;
			}
			if (temp.no == newHeroNode.no) {
				// 找到.
				flag = true;
				break;
			}
			temp = temp.next; //
		}

		// 判断是否找到
		if (flag) {
			temp.name = newHeroNode.name;
			temp.nickname = newHeroNode.nickname;
		} else {
			System.out.printf("没有找到 编号为%d 节点,不能修改\n", newHeroNode.no);
		}
	}

	// 删除
	// 思路,因为双向链表可以实现自我删除
	public void del(int no) {
		// 判断当前链表是否为空
		if (head.next == null) {
			System.out.println("链表空");
			return;
		}
		HeroNode2 temp = head.next;
		boolean flag = false; // 标志变量用于确定是否有要删除的节点
		while (true) {
			if (temp == null) {
				break;
			}
			if (temp.no == no) {
				// 找到了
				flag = true;
				break;
			}
			temp = temp.next; // temp后移
		}
		if (flag) {
			// 可以删除
			// temp.next = temp.next.next
			temp.pre.next = temp.next;
			// 思考
			if (temp.next != null) {
				temp.next.pre = temp.pre;
			}
		} else {
			System.out.printf("要删除的no=%d 不存在\n", no);
		}
	}

}

public class DoubleLinkedListDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		// 测试双向链表的添加和遍历
		HeroNode2 hero1 = new HeroNode2(1, "宋江", "及时雨");
		HeroNode2 hero2 = new HeroNode2(3, "宋江3", "及时雨3");
		HeroNode2 hero3 = new HeroNode2(4, "宋江4", "及时雨4");
		HeroNode2 hero4 = new HeroNode2(2, "宋江2", "及时雨2");
		// 创建一个单向链表
		DoubleLinkedList doubleLinkedList = new DoubleLinkedList();
		doubleLinkedList.add(hero1);
		doubleLinkedList.add(hero2);
		doubleLinkedList.add(hero3);
		doubleLinkedList.add(hero4);

		doubleLinkedList.list();

		HeroNode2 hero5 = new HeroNode2(2, "卢俊义", "玉麒麟");
		doubleLinkedList.update(hero5);
		System.out.println("--------------------------");
		doubleLinkedList.list();

		// 删除测试
		doubleLinkedList.del(2);
		doubleLinkedList.del(3);
		doubleLinkedList.del(4);
		System.out.println("删除后");
		doubleLinkedList.list();
		// 加入, 如果加入hero2 ,会多一个节点,为什么,同学们可以思考解决.
		HeroNode2 hero6 = new HeroNode2(6, "武松", "行者");
		doubleLinkedList.add(hero6);
		System.out.println("~~~~~~~~~~~~");
		doubleLinkedList.list();
	}

}

单向环形链表
在这里插入图片描述
单向环形链表应用场景
Josephu(约瑟夫、约瑟夫环) 问题
Josephu 问题为:设编号为1,2,… n的n个人围坐一圈,约定编号为k(1<=k<=n)的人从1开始报数,数到m 的那个人出列,它的下一位又从1开始报数,数到m的那个人又出列,依次类推,直到所有人出列为止,由此产生一个出队编号的序列。

提示:用一个不带头结点的循环链表来处理Josephu 问题:先构成一个有n个结点的单循环链表,然后由k结点起从1开始计数,计到m时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从1开始计数,直到最后一个结点从链表中删除算法结束。

public class Josephu {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//创建BoyGame
		BoyGame boyGame = new BoyGame();
	    boyGame.addBoy(5);
	    boyGame.showBoy();
	    //测试countBoy方法
	    boyGame.countBoy(1,2,5);
	}

}

// 定义Boy类
class Boy {
	private int no;
	private Boy next;// 默认next 为null
	public Boy(int bNo) {
		no = bNo;
	}
	public int getNo() {
		return no;
	}
	public void setNo(int no) {
		this.no = no;
	}
	public Boy getNext() {
		return next;
	}
	public void setNext(Boy next) {
		this.next = next;
	}
}

// 编写核心类BoyGame
class BoyGame {
	// 定义一个初始的头结点,
	private Boy first = new Boy(-1);
	// 添加小孩【形成一个单向环形的链表】
	// nums : 表示共有几个小孩
	public void addBoy(int nums) {
		if (nums < 1) {
			System.out.println("nums的值不正确");
			return;
		}
		// 在形成环形链表时,需要一个辅助指针
		Boy curBoy = null;
		for (int i = 1; i <= nums; i++) {
			// 更加编号创建小孩对象
			Boy boy = new Boy(i);
			// 如果是第一个小孩
			if (i == 1) {
				first = boy;
				first.setNext(first);// 形成一个环形的链表
				curBoy = first;
			} else {
				curBoy.setNext(boy);
				boy.setNext(first);
				curBoy = boy;
			}
		}
	}

	// 编写方法countBoy, 完成游戏
	// startNo 从第几个人开始数
	// countNum 数几下
	// nums: 一共多少人
	public void countBoy(int startNo, int countNum, int nums) {
		// 对参数进行判断
		if (first.getNext() == null || startNo < 1 || startNo > nums) {
			System.out.println("参数有误,重新输入!");
			return;
		}
		// 完成游戏的思路
		/*
		 * 完成游戏的思路分析->实现代码
		 * 
		 * 1) 在first 前面 设计一个辅助指针(helper) , 即将helper 指针定位到 first 前面 2) 将first
		 * 指针移动到 startNo 这个小孩(helper 对应移动) 3) 开始数 countNum 个数[first 和 helper
		 * 会对应的移动] 4) 删除first 指向的这个小孩节点 5) 思路
		 * 
		 */
		Boy helper = first;
		// 1)即将helper 指针定位到 first 前面
		while (true) {
			if (helper.getNext() == first) {
				break;
			}
			helper = helper.getNext();
		}
		// 2)将first 指针移动到 startNo 这个小孩(helper 对应移动)
		for (int j = 0; j < startNo - 1; j++) {
			first = first.getNext();
			helper = helper.getNext();
		}
		// 开始数数,按照给定的值,每数到一个小孩就出圈, 直到环形链表只有一个节点
		while (true) {
			if (helper == first) {
				// 只有一个人
				break;
			}
			// 3) 开始数 countNum 个数[first 和 helper 会对应的移动]
			for (int j = 0; j < countNum - 1; j++) {// 3
				first = first.getNext();
				helper = helper.getNext();
			}
			// 输出出圈的人的信息
			System.out.printf("小孩%d出圈\n", first.getNo());
			// 将first 指向的节点删除
			first = first.getNext();
			helper.setNext(first);

		}
		// 当while结束后, 只有一个人
		System.out.printf("最后留在圈的人是 小孩编号为 %d\n", first.getNo());

	}
	// 遍历单向的环形链表
	public void showBoy() {
		if (first.getNext() == null) {
			System.out.println("没有任何小孩~");
			return;
		}
		// 因为first不能动,还是借助一个辅助指针完成遍历
		Boy curBoy = first;
		while (true) {
			System.out.printf("小孩编号 %d\n", curBoy.getNo());
			if (curBoy.getNext() == first) {
				break;
			}
			curBoy = curBoy.getNext(); // curBoy后移
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值