2.1 Java实现单链表

链表

  1. 链表以节点的方式存储数据,特点是:线性结构、链式存储。
  2. 每个节点包含 data域 和 next域,data域保存数据,next域用来指向下一节点。
  3. 链表的各个节点在内存中不一定是连续存储。
  4. 链表分带头节点的链表和不带头节点的链表,头节点指向链表中第一个数据节点。

单链表类中实现方法

  1. void add(Node n):将节点n添加到链表尾。在遍历过程中,可以对temp.next进行判断。
  2. void addByOrder(Node n):假设链表中 节点 按照 id 从小到大的顺序进行排列,将节点n按照id大小插入到链表的对应位置。在遍历过程中,必须对temp.next进行判断,因为需要使用对应位置的前一个位置上的节点。
  3. void update(Node n):将链表中与节点n相同id的节点 的数据域中的信息变为节点n数据域中的信息。在遍历过程中,可以对temp进行判断。最好首先判断链表是否为空。
  4. void delete(int nodeId):删除链表中id为参数nodeId的的节点。在遍历过程中,必须对temp.next进行判断,因为需要使用对应位置的前一个位置上的节点。
  5. void printList():打印链表。
  6. int length():返回链表长度。最好首先判断链表是否为空。
  7. Node getNodeFromBack(int k):获取链表中倒数第k个节点。思路是:获取链表长度Len,倒数第k个节点就是正数第(Len-k+1)个节点,然后遍历找到第(Len-k+1)个节点。最好首先判断链表是否为空。
  8. Node getNodeFromBackTwo(int k):通过遍历一次链表获取链表中倒数第k个节点。思路:使用两个辅助节点,头一个辅助节点先向后遍历k个节点后,第二个辅助节点开始向后遍历链表,这样当第一个辅助节点为空时,第二个辅助节点所在的位置就是倒数第k个节点所在的位置。
  9. void reverse():反转单链表。创建一个新链表,从头开始提取每一个节点,每次都将节点添加到新链表的头部,然后让旧链表的头节点指向此新链表的第一个节点。
  10. void reversePrint():逆序打印链表,不改变链表结构。使用了栈。

merge函数

  1. static SingleLinkedList merge(SingleLinkedList listOne, SingleLinkedList listTwo):合并两个有序链表,假设listOne和listTwo都是有序链表,且顺序为id从小到大。

代码

代码

import java.util.Stack;

/**
 * 实现单链表,测试其使用
 * @author dxt
 *
 */
public class SingleLinkedListDemo {
	/**
	 * 合并两个有序链表,假设listOne和listTwo都是有序链表,且顺序为id从小到大
	 * @param listOne
	 * @param listTwo
	 * @return
	 */
	public static SingleLinkedList merge(SingleLinkedList listOne, SingleLinkedList listTwo) {
		//1.判断上述两个链表是否为空。如果全为空,那么返回的是null
		if(listOne.getHeadNode().next == null) {
			return listTwo;
		}
		if(listTwo.getHeadNode().next == null) {
			return listOne;
		}
		//2. 合并
		SingleLinkedList result = new SingleLinkedList();	//最终合并后的结果
		Node tempNodeOne = listOne.getHeadNode().next;	//初始为链表1的第一个节点
		Node tempNodeTwo = listTwo.getHeadNode().next;	//初始为链表2的第一个节点
		Node resultNode = result.getHeadNode();
		while(true) {
			//第一个链表已遍历完,将第二个链表剩余节点链接到结果链表后即可
			if(tempNodeOne == null) {	
				resultNode.next = tempNodeTwo;
				break;
			}
			//第二个链表已遍历完,将第一个链表剩余节点链接到结果链表即可
			if(tempNodeTwo == null) {
				resultNode.next = tempNodeOne;
				break;
			}
			//当两个链表都有节点时,选择当前id小的节点加入到链表中(没有对id相同情况作特殊处理)
			if(tempNodeOne != null && tempNodeTwo != null) {
				if(tempNodeOne.id < tempNodeTwo.id) {
					resultNode.next = tempNodeOne;
					tempNodeOne = tempNodeOne.next;
				}else {
					resultNode.next = tempNodeTwo;
					tempNodeTwo = tempNodeTwo.next;
				}
				resultNode = resultNode.next;	//结果链表后移
			}
		}
		return result;
	}
	
	public static void main(String[] args) {
		//测试merge()方法是否正确
		SingleLinkedList sll1 = new SingleLinkedList();
		SingleLinkedList sll2 = new SingleLinkedList();
		Node n1 = new Node(1, "张三");
		Node n2 = new Node(2, "李四");
		Node n3 = new Node(3, "王五");
		Node n4 = new Node(4, "喜羊羊");
		Node n5 = new Node(5, "灰大狼");
		Node n6 = new Node(6, "懒洋洋");
		
		sll1.add(n1);
		sll1.add(n2);
		sll1.add(n5);
		sll2.add(n3);
		sll2.add(n4);
		sll2.add(n6);
		SingleLinkedList sll3 = merge(sll1, sll2);
		sll3.printList();
		System.out.println("##########美丽分界线###########");
		
		//测试 SingleLinkedList类中方法是否正确
		//1. 声明一个链表对象
		SingleLinkedList sll = new SingleLinkedList();
		//2. 准备测试节点
		Node nn1 = new Node(1, "张三");
		Node nn2 = new Node(2, "李四");
		Node nn3 = new Node(3, "王五");
		//3. 基础测试
		sll.printList();
		sll.add(nn1);
		sll.add(nn3);
		sll.addByOrder(nn2);
		sll.printList();
		sll.reverse();
		sll.reversePrint();
		sll.printList();
		Node n = new Node(1, "灰太狼");
		sll.update(n);
		sll.printList();
		System.out.println(sll.length());
		System.out.println(sll.getNodeFromBack(1));
		System.out.println(sll.getNodeFromBackTwo(1));
		System.out.println(sll.getNodeFromBack(2));
		System.out.println(sll.getNodeFromBackTwo(2));
		System.out.println(sll.getNodeFromBack(3));
		System.out.println(sll.getNodeFromBackTwo(3));
		
	}
}
/**
 * 节点类,每个节点包含 数据域 和 next域,数据域中的id属性来唯一标识一个节点
 * @author dxt
 *
 */
class Node{
	public int id;
	public String name;	//节点数据内容
	public Node next;	//指向下一个节点
	//构造器
	public Node(int id, String name) {
		this.id = id;
		this.name = name;
	}
	//重写toString()方法
	@Override
	public String toString() {
		return "Node [id=" +this.id+ ", name=" +this.name+ "]";	//拼接一个字符串
	} 
}
/**
 * 单链表实现类
 * @author dxt
 *
 */
class SingleLinkedList{
	//初始化一个头节点,头节点不存放任何数据,
	private Node headNode = new Node(0, "");
	
	/**
	 * 获取链表头节点
	 * @return
	 */
	public Node getHeadNode() {
		return headNode;
	}
	
	/**
	 * 添加节点到链表中
	 * @param node
	 */
	public void add(Node node) {
		//1. 定位到链表最后,需要使用一个辅助节点,且要对temp.next进行判断
		Node temp = headNode;
		while(true) {
			if(temp.next == null) {
				break;
			}
			temp = temp.next;
		}
		//2. 将此节点添加到链表尾
		temp.next = node;
	}
	
	/**
	 * 假设链表中 节点 按照 id从小到大的顺序进行排列,将node节点按照id插入到链表的对应位置
	 * @param node
	 */
	public void addByOrder(Node node) {
		//1. 定位到插入位置的前一个节点,需要一个辅助节点,需要对temp.next进行判断
		Node temp = headNode;
		boolean flag = false;	//判断是否会出现 新加入节点与链表中某一结点存在相同id问题
		while(true) {
			//已到链表尾,说明node节点的id是最大的 或 链表为空
			if(temp.next == null) {
				break;
			}
			//temp下一个节点的id大于node节点的id,则temp就是对应位置的前一个节点
			if(temp.next.id > node.id) {
				break;
			}else if(temp.next.id == node.id) {	//出现问题,因为我们定义id是唯一的
				flag = true;	//存在新加入节点 与 链表中节点为统一id的问题
				break;
			}
			temp = temp.next;	//后移
		}
		if(flag) {
			System.out.println("链表中已存在对应节点。");
		}else {	//将node插入到链表中
			node.next = temp.next;	//注意两条语句的顺序
			temp.next = node;
		}
	}
	
	/**
	 * 更新节点。将链表中与node节点相同id的节点 的数据域中的信息变为node节点数据域中的信息
	 * @param node
	 */
	public void update(Node node) {
		//1. 判断链表是否为空
		if(headNode.next == null) {
			System.out.println("链表为空, 更新失败。");
			return;
		}
		//2. 找到对应节点
		Node temp = headNode.next;	//已确定链表至少有一个节点,注意是对temp进行判断
		boolean flag = false;	//表示是否找到该节点,默认没找到
		while(true) {
			if(temp == null) {	//遍历完整个链表,没有找到
				break;	
			}
			if(temp.id == node.id) {	//找到
				flag = true;
				break;
			}
			temp = temp.next;
		}
		//3. 更改对应数据
		if(flag) {	//找到
			temp.name = node.name;
		}else {
			System.out.println("未找到对应节点,更新失败。");
		}
	}
	/**
	 * 删除链表中id为参数nodeId的的节点
	 * @param no
	 */
	public void delete(int nodeId) {
		Node temp = headNode;
		boolean flag = false;	//标志是否找到要删除的节点
		while(true) {
			if(temp.next == null) {	//遍历完链表,没有找到
				break;
			}
			if(temp.next.id == nodeId) {	//定位到要删除节点的前一个节点
				flag = true;
				break;
			}
			temp = temp.next;	//后移
		}
		if(flag) {	//找到
			temp.next = temp.next.next;
		}else {
			System.out.println("未找到对应节点,删除失败。");
		}
	}
	
	/**
	 * 打印链表
	 */
	public void printList() {
		//判断链表是否为空
		if(headNode.next == null) {
			System.out.println("链表为空");
			return;
		}
		//开始遍历
		Node temp = headNode.next;	//链表不为空,至少有一个节点
		while(temp != null) {	//我一定要写一下这种遍历方式
			System.out.println(temp);
			temp = temp.next;	//后移
		}
	}
	/**
	 * 返回链表中有效节点的个数(不包含头节点)
	 * @return
	 */
	public int length() {
		//如果为空,直接返回0
		if(headNode.next == null) {
			return 0;
		}
		Node temp = headNode.next;
		int len = 0;
		while(temp != null) {
			len++;
			temp = temp.next;
		}
		return len;
	}
	/**
	 * 获取链表中倒数第k个节点
	 * 思路:获取链表长度Len,倒数第k个节点就是正数第(Len-k+1)个节点,然后遍历找到第(Len-k+1)个节点
	 * @param k
	 * @return
	 */
	public Node getNodeFromBack(int k) {
		//1. 判断链表是否为空
		if(headNode.next == null) {
			return null;
		}
		//2.1 获取链表总长度,(此过程会进行一次遍历)
		int len = this.length();
		//2.2 校验一下参数,看是否合法
		if(k <= 0 || k > len) {
			throw new RuntimeException("参数越界,发生错误");
		}
		
		//3. 定位到倒数第k个元素
		int index = len - k + 1;
		//4. 进行第2次遍历查找
		Node temp = headNode.next;
		for(int i=0; i<index-1; i++) {	//要找到正数第index个元素,只需移动 index-1 次
			temp = temp.next;
		}
		return temp;
	}
	/**
	 * 通过遍历一次链表获取链表中倒数第k个节点,效率更高
	 * 思路:使用两个辅助节点,头一个辅助节点先走k个节点的位置,然后第二个节点在开始向后遍历链表,这样当第一个节点
	 * 为空时,第二个节点所在的位置就是倒数第k个节点所在的位置。
	 * @param k
	 */
	public Node getNodeFromBackTwo(int k) {
		//1. 判断链表是否为空
		if(headNode.next == null) {
			return null;
		}
		//2. 数据校验
		if(k <= 0) {
			throw new RuntimeException("参数越界,发生错误");
		}
		//3.先让第一个节点向前移动k个节点的位置
		Node temp_fast = headNode.next;	//走在前面的节点
		Node temp_slow = headNode.next;	//走在后面的节点
		int i=0;
		for(i=0; i<k && temp_fast!=null; i++) {	//i从1开始
			temp_fast = temp_fast.next;
		}
		//4. 判断参数k是否合理,防止链表中不足k个节点
		if(i < k) {	//链表中节点个数小于k个
			throw new RuntimeException("参数越界,发生错误。");
		}
		//5. 然后两个节点在各自的位置以相同的速度向后遍历
		while(temp_fast != null) {
			temp_fast = temp_fast.next;
			temp_slow = temp_slow.next;
		}
		return temp_slow;
	}
	
	/**
	 * 单链表的反转
	 */
	public void reverse() {
		//1. 链表为空 或 链表中只有一个元素 则直接返回
		if(headNode.next==null || headNode.next.next==null) {
			return;
		}
		//2. 获得一个新的反转链表
		Node cur = headNode.next;	//辅助指针,用于遍历整个链表
		Node next = headNode.next;	//指向cur节点的下一个节点,否则cur无法遍历
		Node newHead = new Node(0,"");	//一个新的链表的头节点,cur每取到一个节点,都插入到此链表的头部
		while(cur != null) {
			next = cur.next;	//保存当前节点的下一个节点
			cur.next = newHead.next;	//将当前节点作为新链表的第一个节点
			newHead.next = cur;		//新链表的头节点指向第一个节点
			cur = next;	//遍历旧链表的下一个节点
		}
		//3. 更改指向
		headNode.next = newHead.next;	//让headNode.next指向新的反转链表
	}
	/**
	 * 逆序打印单链表,使用了栈结构。不改变链表结构
	 */
	public void reversePrint() {
		//1. 检查链表是否为空
		if(headNode.next == null) {
			return;
		}
		//2. 创建栈,并将链表节点从头到尾压入栈中
		Stack<Node> stack = new Stack<Node>();
		Node temp = headNode.next;
		while(temp != null) {
			stack.push(temp);
			temp = temp.next;
		}
		//3. 打印
		while(stack.size() > 0) {
			System.out.println(stack.pop());
		}
	}
}

结果:
result

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值