Java模拟单链表

本篇博文通过Java模拟一个单向链表

本篇博文通过Java模拟一个能够实现:头插、尾插、指定插入、修改、删除、打印、反转、反转打印、链表合并等功能的带头节点的单向链表;

创建数据域内的存储类User

创建用来存储在单向链表的data域内的类型User,代码如下:

package edu.hebeu;

/**
 * @author 13651
 *
 */
public class User {
	
	private int id;
	private String name;
	private Character sex;
	
	public User(int id, String name, Character sex) {
		super();
		this.id = id;
		this.name = name;
		this.sex = sex;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Character getSex() {
		return sex;
	}

	public void setSex(Character sex) {
		this.sex = sex;
	}

	@Override
	public String toString() {
		return "User [id=" + id + ", name=" + name + ", sex=" + sex + "]";
	}
	
}

创建用于构成单向链表的节点类Node

该类有两种类型(用来作为data域和next域),data域是主要用来存储上面的User类,next域是Node类型的,用来指向下一个Node,代码如下:

package edu.hebeu;

/**
 * 节点类
 * @author 13651
 *
 */
public class Node {
	
	/**
	 * 数据域
	 */
	public User data;
	
	/**
	 * next域
	 */
	public Node next;
	
	public Node(User data) {
		this.data = data;
	}
	
}

单向链表的构建和功能完善

须知:

  • 链表是以节点的方式来存储,是链式存储;
  • 每个节点包含 data 域, next 域:指向下一个节点.;
  • 如图:发现链表的各个节点不一定是连续存储.;
  • 链表分带头节点的链表和没有头节点的链表,根据实际的需求来确定;

链表在内存中的存储图解如下所示:
在这里插入图片描述
这里我先把代码考出来,在下面会对各个方法进行讲解,SingleLinkedList类的代码如下:

package edu.hebeu.single;

import java.util.Stack;

import edu.hebeu.Node;

/**
 * 单向链表
 * @author 13651
 *
 */
public class SingleLinkedList {
	private Node head;
	
	public SingleLinkedList() {
		head = new Node(null); // 头节点不存放数据
	}
	
	/**
	 * 这个方法用来获取链表的头节点
	 * @return
	 */
	public Node getHead() {
		return head;
	}
	
	/**
	 * 这个方法用来通过头插法添加节点,思路分析:
	 *  1、将“待插入”节点的next域 指向 头节点的next域(头结点的下一个结点);
	 *  2、将头节点的next域 指向 “待插入”节点
	 * @param node “待插入”结点
	 */
	public void headInsert(Node node) {
		node.next = head.next; // 将"待插入"节点的next域 指向 头节点的next域(头节点的下一个节点);
		head.next = node; // 将头节点的next域 指向"待插入"节点
	}
	
	/**
	 * 这个方法表示通过尾插法添加节点,思路分析:
	 *  1、先找到链表的最后一个节点;
	 *  2、让最后一个节点的next域 指向 待插入的结点;
	 *  3、将待插入的节点的next域 变成null;
	 * @param node “待插入”结点
	 */
	public void tailInsert(Node node) {
		Node lastNode = head; // 该变量用来保存插入之前链表的最后一个节点,初始值为头节点
		while(true) {
			if(lastNode.next == null) { // 如果当前节点的next域 为空,表示当前节点就是最后一个节点
				lastNode.next = node; // 让当前链表的最后一个节点的next域 指向 "待插入"节点
				node.next = null; // 将"待插入"节点的next域 设置为null,表示其为最后一个节点;(这步其实是多余的,Java中引用类型默认为null)
				break;
			}
			lastNode = lastNode.next; // 将节点后移
		}
	}
	
	/**
	 * 这个方法表示通过data域的id属性 升序的插入节点
	 *  1、找到“被插入”节点的前一个节点;
	 *  2、将“待插入”节点的next域 指向 “被插入”节点的前一个节点的next域
	 *  3、将 “被插入”节点的前一个结点的next域 指向 “待插入”节点
	 * @param node “待插入”节点
	 */
	public void byIdInsert(Node node) {
		Node preNode = head; // 这个变量用来保存“被插入”节点的 前一个节点,初始值为头节点
		while(true) {
			if(preNode.next == null) { // 如果“被插入”节点的 前一个结点的next域 为null,则表示 “被插入”节点是最后一个节点
				node.next = preNode.next;
				preNode.next = node;
				// 以上表示将“待插入”节点 插入 “被插入”节点 之前
				break;
			} else if(preNode.next.data.getId() > node.data.getId()) { // 如果“被插入”节点的 前一个节点的id 大于 “待插入”节点的id
				node.next = preNode.next;
				preNode.next = node;
				// 以上表示将“待插入”节点 插入 “被插入”节点 之前
				break;
			} else if(preNode.next.data.getId() == node.data.getId()) { // 如果“被插入”节点的 前一个节点的id 等于 “待插入”节点的id
				System.err.println("id:" + node.data.getId() + "存在,不能加入!");
				break;
			}
			preNode = preNode.next; // 将节点后移
		}
	}
	
	/**
	 * 该方法用来实现删除节点
	 *  1、找到“被删除”节点的前一个节点
	 *  2、将“被删除”节点的前一个节点的next域 指向 “被删除”节点的next域的next域
	 *  3、释放掉“被删除”节点的内存资源(会自动被Java的垃圾回收器回收掉)
	 * @param node “待删除”节点
	 */
	public void delete(Node node) {
		if(head.next == null) { // 如果头结点的next域 为null,即头结点为最后一个节点(链表为null)
			System.err.println("链表为空!");
			return;
		}
		Node preNode = head; // 这个变量用来保存“被删除”节点的 前一个节点,初始值为头节点
		while(true) {
			if(preNode.next == null) { // 如果“被删除”节点的 前一个节点的next域 为null,则表示 “被删除”节点是最后一个节点
				break;
			} else if(preNode.next.data.getId() == node.data.getId()) { // 如果“被删除”节点的 前的节点的id 等于 “待插入”节点的id
				preNode.next = preNode.next.next;
				// 以上代码表示将“被删除”节点删除
				break;
			}
			preNode = preNode.next; // 节点后移
		}
	}
	
	/**
	 * 该方法用来实现链表节点的修改,思路分析:
	 *  1、根据指定条件查询节点
	 *  2、将查询到的节点数据修改
	 * @param node “待修改”节点
	 */
	public void update(Node node) {
		if(head.next == null) { // 如果头节点的next域 为null,即头节点为最后一个节点(链表为null)
			System.err.println("链表为空!");
			return;
		}
		Node currentNode = head.next; // 因为头节点不存储数据,所以将头节点的下一个结点设置为当前的节点
		while(true) {
			if (currentNode == null) { // 如果当前节点为null,即链表已经遍历完了
				break;
			} else if(currentNode.data.getId() == node.data.getId()) { // 如果当前结点的data域 的id 等于 “待修改”结点的id,即找到了“要修改”结点
				currentNode.data.setName(node.data.getName());
				currentNode.data.setSex(node.data.getSex());
				// 以上代码表示进行修改
				break;
			}
			currentNode = currentNode.next; // 将节点后移
		}
	}
	
	/**
	 * 这个方法用来获取链表的倒数第index个节点(新浪面试题),思路分析:
	 *  1、先获取链表的长度 记为length;
	 *  2、在判断 倒数第lastIndex 个节点是否合法(lastIndex要大于0,且小于等于链表长度length);
	 *  3、思路转换:获取倒数的 lastIndex 个节点 就是 获取 第(length - lastIndex)个节点;
	 * @param lastIndex 倒数第几个
	 * @return
	 */
	public Node findNodeLastIndex(int lastIndex) {
		if(head.next == null) {
			System.err.println("链表为空!");
			return null;
		}
		int length = getLength(); // 获取当前链表的长度
		if(lastIndex > length || lastIndex <= 0) { // 如果要查询的倒数的lastIndex是大于链表的长度的 或者 是小于等于0的
			System.err.println("未找到倒数第" + lastIndex + "个节点");
			return null;
		}
		Node currentNode = head.next; // 将当前节点初始化为头结点的后一个节点
		for(int i = 0; i < length - lastIndex; i++) { // 遍历至倒数第lastIndex个节点
			currentNode = currentNode.next; // 将节点后移
		}
		return currentNode;
	}
	
	/**
	 * 这个方法用来计算链表中有效节点的个数
	 * @return
	 */
	public int getLength() {
		if(head.next == null) {
			System.err.println("链表为空!");
			return 0;
		}
		int length = 0; // 改变量用来保存链表的长度
		Node currentNode = head.next; // 将当前节点初始化为头结点的后一个节点
		while(true) {
			if(currentNode == null) {
				break;
			}
			length++;
			currentNode = currentNode.next; // 将节点后移
		}
		return length;
	}
	
	/**
	 * 这个方法用来实现链表的反转(腾讯面试题),思路分析:
	 *  1、先定义一个反转后链表的头结点 reversetHead;
	 *  2、从头到尾遍历原来的链表,每遍历一个节点,就将其取出,并放到反转后链表的最前端(即反转后链表头结点之后);
	 *  3、将原链表的头结点的next域 指向 反转后链表的next域;
	 */
	public void reverset() {
		if(head.next == null || head.next.next == null) { // 如果链表为null 或者 链表只有一个节点
			return; // 直接结束该方法,因为此时链表反转和原先的顺序是一样的
		}
		Node currentNode = head.next; // 这个变量用来保存当前节点,初始值为头结点的后一个节点
		Node nextNode = null; // 这个变量用来保存当前节点的下一个节点
		Node reversetHead = new Node(null); // 这个变量用来保存反转后链表的头结点
		while(currentNode != null) { // 如果当前节点不为空,即链表还没有遍历完
			nextNode = currentNode.next; // 保存当前节点在原链表中的下一个节点
			currentNode.next = reversetHead.next; // 将当前节点的next域 指向 反转后链表的头结点 即将当前节点添加到最前面
			reversetHead.next = currentNode; // 反转后链表的头结点的next域 指向 当前节点
			currentNode = nextNode; // 将节点后移
		}
		head.next = reversetHead.next; // 将原链表的头结点的下一个节点 指向 反转后链表的头结点的下一个节点
	}
	
	/**
	 * 这个方法用来实现有序链表的有序合并(将有序链表source合并至本有序链表对象,合并之后的链表仍是有序的)
	 * @param source 被合并的有序链表
	 */
	public void merge(SingleLinkedList source) {
		Node sourceHead = source.getHead(); // 获取被合并链表的头节点
		if(sourceHead.next == null) { // 如果被合并链表的头节点的next域为null,即表示被合并链表无数据
			return;
		}
		if(head.next == null) { // 如果本链表的头节点的next域为null,即表示本链表无数据
			head.next = sourceHead.next; // 让本链表的头节点的next域 指向 被合并链表的next域
			return;
		}
		
		Node preNode = head; // 用来保存本链表的当前节点的前一个节点
		Node sourceCurrentNode = sourceHead.next; // 用来保存被合并链表的当前节点
		Node sourceNextNode = null; // 用来保存被合并链表的当前节点的后一个节点
		
		while(sourceCurrentNode != null) { // 如果被合并链表的当前节点不为空,即被合并链表没有遍历完
			sourceNextNode = sourceCurrentNode.next; // // 保存被合并链表的当前节点在其原链表中的下一个节点
			while(true) { // 如果当前节点不为空,即当前链表还没有遍历完
				if(preNode.next == null) { // 如果本链表的当前节点的前一个节点的next域 为null,则表示是本链表的最后一个节点
					sourceCurrentNode.next = preNode.next; // 被合并链表的当前节点的next域 指向 本链表的当前节点的前一个节点的next域
					preNode.next = sourceCurrentNode; // 本链表的当前节点的前一个节点的next域 指向 被合并链表的当前节点
					break;
				} else if(preNode.next.data.getId() > sourceCurrentNode.data.getId()) { // 如果本链表的当前节点的前一个节点的id 大于 被合并链表的当前节点的id
					sourceCurrentNode.next = preNode.next;
					preNode.next = sourceCurrentNode;
					// 以上表示将“待插入”节点 插入 “被插入”节点 之前
					break;
				}
				preNode = preNode.next; // 将节点后移
			}
			sourceCurrentNode = sourceNextNode; // 将被合并链表的节点后移
		}
		
	}
	
	/**
	 * 该方法用来打印链表
	 */
	public void show() {
		if(head.next == null) { // 如果头节点的next域 为null,即头结点为最后一个节点(链表为空)
			System.err.println("链表为空!");
			return;
		}
		Node currentNode = head.next; // 因为头节点不存储数据,所以将头节点的下一个节点设置为当前的节点
		while(true) {
			if(currentNode == null) { // 如果当前节点为null,表示链表已经遍历完了
				break;
			}
			System.out.println(currentNode.data + "->->->"); // 输出当前节点的数据信息
			currentNode = currentNode.next; // 将节点后移
		}
	}
	
	/**
	 * 从尾到头(倒置)打印单链表(百度面试题),思路分析:
	 *  1、可以将单链表反转,然后在遍历,但是这样做会破坏原先链表的结构(不推荐);
	 *  2、可以通过栈的性质(先进后出),将链表的各个节点压入栈,然后再进行弹栈
	 */
	public void reversetShow() {
		if(head.next == null) { // 如果头节点的next域 为null,即头结点为最后一个节点(链表为空)
			System.err.println("链表为空!");
			return;
		}
		Stack<Node> nodeStack = new Stack<>(); // 创建Node泛型的栈结构
		Node currentNode = head.next; // 因为头节点不存储数据,所以将头节点的下一个节点设置为当前的节点
		while(currentNode != null) { // 如果当前节点不为空,即链表还没有遍历完
			nodeStack.push(currentNode); // 将当前的节点入栈
			currentNode = currentNode.next; // 将节点后移
		}
		while(nodeStack.size() > 0) { // 如果栈中还有元素
			Node node = nodeStack.pop(); // 将栈顶元素出栈
			System.out.println(node.data + "->->->"); // 输出当前节点的数据信息
		}
	}
}

头插法

上述代码的headInsert()方法就是头插法,头插法的图解如下所示:
在这里插入图片描述

尾插法

上述代码的tailInsert()方法就是尾插法,尾插法的图解如下所示:
在这里插入图片描述

在某一位置插入

上述代码的byIdInsert()方法就是通过链表中的id大小进行升序的插入,在链表的某一位置插入节点图解如下所示:
在这里插入图片描述

删除节点

上述代码的delete()方法就是删除节点,删除链表中的节点图解如下所示:
在这里插入图片描述

修改节点

上述代码的update()方法就是修改某个节点的数据信息,修改链表中的节点图解如下所示:
在这里插入图片描述

获取倒数第n个节点

上述代码的findNodeLastIndex()方法就是获取倒数第n个节点,如下图解:
在这里插入图片描述

链表反转

上述代码的reverset()方法就是单链表的反转,如下图解:
在这里插入图片描述

链表的合并

上述代码中的merge()方法就是有序单链表的有序合并,图解如下所示:
在这里插入图片描述

测试类Test的编写

该类主要用来测试上述的链表,Test类的代码如下:

package edu.hebeu.single;

import java.util.Random;
import java.util.Scanner;

import edu.hebeu.Node;
import edu.hebeu.User;

public class Test {
	
	private static Scanner SCANNER = new Scanner(System.in);
	private static int CHOICE;
	
	private static final SingleLinkedList SINGLE_LINKED_LIST = new SingleLinkedList();

	public static void main(String[] args) {
		while(true) {
			menu();
			option();
			
			if(CHOICE == 0) {
				break;
			}
		}
		System.out.println("bye");
	}
	
	public static void menu() {
		System.out.println("尾插法添加5条数据,t(tail)");
		System.out.println("尾头插法添加5条数据,h(head)");
		System.out.println("有序的插入5条数据,o(order)");
		System.out.println("修改数据,u(update)");
		System.out.println("删除数据,d(delete)");
		System.out.println("链表长度,l(length)");
		System.out.println("查找链表倒数第n个节点,7(lastIndex)");
		System.out.println("反转链表,r(reverset)");
		System.out.println("显示链表全部数据,s(show)");
		System.out.println("反向显示链表全部数据,1(reversetShow)");
		System.out.println("链表合并,2(MergeLinkedList)");
		System.out.println("输入其他退出");
		char keyword= SCANNER.next().charAt(0);
		switch(keyword) {
			case 't':
				CHOICE = 1;
				break;
			case 'h':
				CHOICE = 2;
				break;
			case 'o':
				CHOICE = 3;
				break;
			case 'u':
				CHOICE = 4;
				break;
			case 'd':
				CHOICE = 5;
				break;
			case 'l':
				CHOICE = 6;
				break;
			case '7':
				CHOICE = 7;
				break;
			case 'r':
				CHOICE = 8;
				break;
			case 's':
				CHOICE = 9;
				break;
			case '1':
				CHOICE = 10;
				break;
			case '2':
				CHOICE = 11;
				break;
			default:
				CHOICE = 0;
				break;
		}
	}
	
	private static void option() {
		if(CHOICE == 1) {
			Random random = new Random();
			for(int i = 0; i < 5; i++) {
				User user = new User(random.nextInt(100), "user00" + random.nextInt(10) + i, i / 2 == 0 ? '女' : '男');
				Node node = new Node(user);
				SINGLE_LINKED_LIST.tailInsert(node);
			}
			System.out.println("尾插5条数据完毕!");
		} else if(CHOICE == 2) {
			Random random = new Random();
			for(int i = 0; i < 5; i++) {
				User user = new User(random.nextInt(100), "user00" + random.nextInt(10) + i, i / 2 == 0 ? '女' : '男');
				Node node = new Node(user);
				SINGLE_LINKED_LIST.headInsert(node);
			}
			System.out.println("头插5条数据完毕!");
		} else if(CHOICE == 3) {
			Random random = new Random();
			for(int i = 0; i < 5; i++) {
				User user = new User(random.nextInt(100), "user00" + random.nextInt(10) + i, i / 2 == 0 ? '女' : '男');
				Node node = new Node(user);
				SINGLE_LINKED_LIST.byIdInsert(node);
			}
			System.out.println("按照id升序插入5条数据完毕!");
		} else if(CHOICE == 4) {
			System.out.print("请输入要修改的用户的ID:"); int id = SCANNER.nextInt();
			System.out.print("请输入要修改的用户的姓名:"); String name = SCANNER.next();
			System.out.print("请输入要修改的用户的性别:"); char sex = SCANNER.next().charAt(0);
			SINGLE_LINKED_LIST.update(new Node(new User(id, name, sex)));
			System.out.println("按照id《" + id + "》修改数据完毕!");
		} else if(CHOICE == 5) {
			System.out.print("请输入要删除的用户的ID:"); int id = SCANNER.nextInt();
			SINGLE_LINKED_LIST.delete(new Node(new User(id, null, null)));
			System.out.println("按照id《" + id + "》删除数据完毕!");
		} else if(CHOICE == 6) {
			int length = SINGLE_LINKED_LIST.getLength();
			System.out.println("链表的长度:" + length);
		} else if(CHOICE == 7) {
			System.out.print("请输入查询倒数第几个节点:");int lastIndex = SCANNER.nextInt();
			Node node = SINGLE_LINKED_LIST.findNodeLastIndex(lastIndex); // 获取倒数第lastIndex个节点
			System.out.println("倒数第" + lastIndex + "节点的data域:" + (node == null ? "null" : node.data));
		} else if(CHOICE == 8) {
			SINGLE_LINKED_LIST.reverset();
			System.out.println("反转链表成功");
		} else if(CHOICE == 9) {
			SINGLE_LINKED_LIST.show();
		} else if(CHOICE == 10) {
			SINGLE_LINKED_LIST.reversetShow();
		} else if(CHOICE == 11) {
			SingleLinkedList linkedList1 = new SingleLinkedList();
			SingleLinkedList linkedList2 = new SingleLinkedList();
			while(true) {
				System.out.println("链表1有序的添加5条数据,1(1add)");
				System.out.println("链表2有序的添加5条数据,2(2add)");
				System.out.println("链表1显示数据,3(1show)");
				System.out.println("链表2显示数据,4(2show)");
				System.out.println("链表2有序的合并至链表1,5(2MergeTo1)");
				System.out.print("请输入您的业务:"); int keyword = SCANNER.nextInt();
				if(keyword == 1) {
					Random random = new Random();
					for(int i = 0; i < 5; i++) {
						User user = new User(random.nextInt(800), "user00" + random.nextInt(10) + i, i / 2 == 0 ? '女' : '男');
						Node node = new Node(user);
						linkedList1.byIdInsert(node); // 有序的插入节点
					}
					System.out.println("linked1有序的添加5条数据完毕");
				} else if(keyword == 2) {
					Random random = new Random();
					for(int i = 0; i < 5; i++) {
						User user = new User(random.nextInt(800), "user00" + random.nextInt(10) + i, i / 2 == 0 ? '女' : '男');
						Node node = new Node(user);
						linkedList2.byIdInsert(node); // 有序的插入节点
					}
					System.out.println("linked2有序的添加5条数据完毕");
				} else if(keyword == 3) {
					linkedList1.show();
				} else if(keyword == 4) {
					linkedList2.show();
				} else if(keyword == 5) {
					linkedList1.merge(linkedList2);
					linkedList2 = new SingleLinkedList(); // 清空链表2中的数据
					System.out.println("合并成功!");
				} else {
					linkedList1 = null;
					linkedList2 = null;
					break;
				}
			}
		}
	}
}

运行结果展示

尾插法测试

在这里插入图片描述

头插法测试

在这里插入图片描述

按照id升序插入(指定位置插入)测试

在这里插入图片描述

修改节点数据测试

在这里插入图片描述

删除节点测试

在这里插入图片描述

查询倒数第n个节点测试

在这里插入图片描述

链表反转测试

在这里插入图片描述

从后到前遍历链表

在这里插入图片描述

有序链表的合并测试

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值