Java数据结构之带头结点的单链表操作

今天介绍一下单链表的增删查改,外加三个小操作:反转单链表,逆序打印单链表结点以及合并两个有序链表,我这里是用单链表去实现存储水浒英雄人物的小案例,但其实单链表的核心操作就这几种:

首先创建一个结点类,存储英雄编号,姓名,昵称,里面包括了构造方法以及重写了toString方法

class HeroNode{
	int no;
	String name;
	String nickname;
	HeroNode next;//next默认为null,指向下一个结点
	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 + "]";
	}
	
}

创建一个头结点,不存放具体数据:

	//先初始化一个头结点,头结点不动且不存放具体数据
	private HeroNode head = new HeroNode(0, "", "");

添加结点:
这里给了两种方式,法一是直接添加至链表尾部,法二是根据编号来插入结点,都用到了辅助指针进行遍历,其中法二需利用辅助指针找到添加位置的前一个结点再进行插入

	//添加结点,方法1,找到最后节点将next指向新节点
	public void add(HeroNode heroNode) {
		//头节点不能动,所以用辅助指针
		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;//标识添加的编号是否存在,默认false
		while(true) {
			if(temp.next==null) {//已在链表最后
				break;
			}
			if(temp.next.no>heroNode.no) {//位置找到就在temp后面插入
				break;
			}
			else if(temp.next.no==heroNode.no) {
				flag=true;
				break;
			}
			temp = temp.next;
		}
		//判断flag
		if(flag) {
			System.out.printf("待插入的英雄编号%d已存在,不能插入\n",heroNode.no);
		}
		else {
			heroNode.next=temp.next;
			temp.next=heroNode;
		}
	}

删除结点:
利用辅助指针找到要删除的结点的前一个结点,直接越过待删除结点,让它成为垃圾

	//删除节点,找到待删除结点的前一个结点
	public void del(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);
		}
	}

查找结点:
分为根据编号直接查找和查找倒数第index个结点,其中查找倒数第index个结点需要用for循环遍历到第length-index个结点,length又得另外写一个方法获取

	//获取单链表的结点个数
	public int getLength() {
		HeroNode cur = head.next;
		int length = 0;
		while(cur!=null) {
			length++;
			cur=cur.next;
		}
		return length;
	}
	//根据编号直接查找
	public HeroNode findIndexNode(int index) {

		HeroNode temp = head;
		boolean flag=false;//标识添加的编号是否被找到,默认false
		while(true) {
			if(temp.next==null) {//已在链表最后
				break;
			}
			else if(temp.next.no==index) {
				flag=true;
				break;
			}
			temp = temp.next;
		}
		//判断flag
		if(flag) {
			return temp.next;
		}
		else {
			System.out.printf("英雄编号%d不存在\n",index);
		}
		return null;
	}
	//查找倒数第index个结点
	//先得到总长度,然后从第一个遍历length-index个
	public HeroNode findLastIndexNode(int index) {

		HeroNode cur = head.next;
		if(cur==null) {
			return null;
		}
		int length = getLength();
		//先数据校验一下
		if(index<=0||index>length) {
			return null;
		}
		//for循环定位到index
		for(int i=0;i<length-index;i++) {
			cur=cur.next;
		}
		return cur;
	}

修改结点信息:
传入新结点,类似于直接查找结点,找到就用新结点信息覆盖原来的结点

	//修改节点信息,根据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 reverse() {
		HeroNode cur=head.next;
		//链表为空或只有一个节点无需反转
		if(cur==null||cur.next==null) {
			return;
		}
		HeroNode next = null;//用于存cur的下一个节点
		//新链表
		HeroNode reverseHead = new HeroNode(0, "", "");
		//遍历原来的链表,每遍历一个节点就将其取出放在新链表的最前端
		while(cur!=null) {
			next=cur.next;
			cur.next=reverseHead.next;//cur的下一个结点指向新链表
			reverseHead.next=cur;
			cur=next;//cur后移
		}
		//将head.next指向reverseHead.next实现反转
		head.next=reverseHead.next;
		
	}

合并两个有序链表:
也是定义一个新的头结点,循环比较两条链表的编号大小,依次插入新的头结点,最后形成一条新链表,这里要注意遍历完一条链表后要将另一条继续遍历加进新链表

	//合并两个有序链表
	public SingleLinkedList mergeTwoLists(SingleLinkedList l1,SingleLinkedList l2)
	{
		SingleLinkedList s = new SingleLinkedList();
		HeroNode h = s.head;
		HeroNode h1 = l1.head.next;
		HeroNode h2 = l2.head.next;
		while(h1!=null&&h2!=null) {
			if(h1.no<=h2.no) {
				h.next=h1;
				h1=h1.next;
				h=h.next;
			}else {
				h.next=h2;
				h2=h2.next;
				h=h.next;
			}
		}
		while(h1!=null) {
			h.next=h1;
			h1=h1.next;
			h=h.next;
		}
		
		while(h2!=null) {
			h.next=h2;
			h2=h2.next;
			h=h.next;
		}
		return s;
	}

打印单链表结点:
这里分顺序打印和逆序打印,其中逆序打印利用了栈的先进后出来实现,可以去了解一下栈的一些push()和pop()方法

	//显示链表
	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;
		}
	}
	//逆序打印单链表
	//利用栈将各个节点压入到栈,然后利用栈的先进后出特点实现逆序打印
	public void reversePrint() {
		HeroNode cur = head.next;
		if(cur==null) {
			System.out.println("链表为空");
			return;
		}
		//创建一个栈
		Stack<HeroNode> stack = new Stack<>();
		//将链表所有结点压入栈
		while(cur!=null) {
			stack.push(cur);
			cur=cur.next;
		}
		//将栈中结点进行打印
		while(stack.size()>0) {
			System.out.println(stack.pop());
		}
	}

以上便是带头结点的单链表的一些基本操作,关于测试可以自行实现

你可能感兴趣的:(Java数据结构之带头结点的单链表操作)