赞
踩
文章目录
引言
链表是一种重要的数据结构。它的存储空间是不连续的,单向链表是最简单的一种链表。本文主要介绍单向链表,及其“增删改查”等功能的实现。我们希望设计出来的链表不仅仅只能存储一种类型的数据。而是可以像ArrayList集合那样,存储任意类型的数据,因此设计的时候要结合泛型。
实现思路
链表是由一个个的节点链接起来的,这里介绍的是单向链表,每个节点都要有一个数据域“value”用来存储数据,和指向下一个节点的引用“next”,它就像是一根链子,把每一个节点链接起来。除了最后一个节点之外,其余的每个节点的“next”都指向它的下一个节点,这样就形成了一个链表。
为了方便之后进行“增删改查”操作,链表要有一个头结点“head”,尾结点“tail”及链表的长度“size”。
首先要有一个私有的静态内部类,这个类是“节点类”,它是为整个链表服务的,我们要存入链表中的数据就要被封装成一个个节点,然后链接在链表中。显然,每个节点都是根据这个节点类创建出来的。
private static class ListNode<T> {// 定义私有节点内部类 T value;// 用来存储数据(数据域) ListNode<T> next;// 用来存储下一节点的引用(地址域) public ListNode(T val) {// 构造方法, this.value = val; }添加元素
添加元素时,我们只需要把要添加的元素封装成一个节点,然后链接在链表的尾部即可。如果链表为空,那么链表的头结点和尾结点都是这个节点。否则就把这个节点直接链接到“尾结点”后面。同时它就是新的“尾结点”
public void append(T val) {// 添加元素,直接添加在链表的末尾 ListNode<T> newNode = new ListNode<T>(val); // 如果尾结点为空,那么这个链表一个节点都没有,即第一次添加节点 if (tail == null) { head = tail = newNode; } else {// 否则直接把节点链接在链表尾部 tail.next = newNode; tail = newNode; } size++; }插入元素
插入元素时,如果是在链表头部插入新节点newNode,那么直接让newNode的next指向头结点,然后newNode就是新的头结点。如果是在链表尾部插入,则直接添加元素方法append()即可。如果是在中间某个位置插入新节点newNode,那么通过for循环,从头结点开始,找到要插入位置的前一个节点prev,和prev的下一个节点after;然后让prev的next指向newNode,newNode的next指向after,链表长度加1即可。如图:
public boolean insert(int position, T val) {// 插入节点,可以在链表的任意位置插入节点 if (position > size || position < 0) {//如果插入的位置不合法,做出提示 System.out.println("请输入正确的下标:0-" + (size - 1)); return false; } ListNode<T> newNode = new ListNode<T>(val); if (position == 0) {// 在链表头结点中插入节点 newNode.next = head; head = newNode; if (tail == null) { tail = newNode;// 如果第一次添加元素,此时尾结点也是此节点 } size++; return true; } else if (position == size) {// 在链表尾结点中插入节点 this.append(val); return true; } else { ListNode<T> prev = head; //找到要插入位置的前一个节点 for (int i = 0; i < position - 1; i++) { prev = prev.next; } //此时prev的下一个节点就就是要插入位置的后一个节点 ListNode<T> after = prev.next; //更改指向,即完成了添加元素 prev.next = newNode; newNode.next = after; size++; return true; } }删除元素
删除元素时,如果删除的节点是“头结点”,那么新的“头结点”就是原头结点的下一个节点,然后再判断链表的长度是否为0,如果为0,那么尾结点为null,删除的节点是其他的节点的话,就要从“头结点”开始遍历链表,找到要删除的节点“cur”和它前一个节点“prev”。然后让“prev”的next指向 “cur”的“next”即可。
public boolean delete(T val) { if (head != null && head.value.equals(val)) { head = head.next; size--; // 删除后如果长度为0,头结点和尾结点都为空 if (size == 0) { tail = null; } return true; } else { ListNode<T> prev = head;//标记要删除的节点的前一个节点 ListNode<T> cur = head;//标记要删除的节点 //从头结点开始遍历链表,找到了要删除的节点 while (prev != null && cur != null) { if (cur.value.equals(val)) {//此条件如果成立,则找到了要删除的节点 if (cur == tail) {//如果它是尾结点,那么新的尾结点就是前一个节点 tail = prev; } prev.next = cur.next;//让前一个节点指向要删除节点的下一个节点 size--; return true; } prev = cur; cur = cur.next; } } return false; }查找元素
查找元素,通过使用for循环,从头结点开始查找。找到目标节点后,将index返回。如果没有找到 ,就返回-1。
public int search(T val) {// 查找元素,返回该元素在链表中的下标,如果没有则返回-1 ListNode<T> cur = head; //从头结点遍历链表,寻找目标节点 for (int index = 0; cur != null; index++) { //如果某个节点的value内容和val一样,即找到了目标节点 if (cur.value.equals(val)) { return index; } cur = cur.next; } //遍历链表没找到目标节点,返回-1 return -1; }更新元素
更新元素时,通过while循环查找要更新的节点。找到目标节点之后,把它的“value”修改成要修改的数据“newVal”即可。
public boolean update(T oldVal, T newVal) {// 更新链表中的某个节点中的值 ListNode<T> cur = head; // 从头结点开始遍历链表,寻找要更新的节点 while (cur != null) { //找到目标节点后,更新它的值 if (cur.value.equals(oldVal)) { cur.value = newVal; return true; } cur = cur.next; } return false; }显示链表
显示链表可以先创建一个ArrayList集合对象“NodeList”。通过while循环,把链表的每个节点都添加进去,然后把“”NodeList打印输出即可。也可以创建一个StringJoiner对象,通过循环把每个节点的value 添加进去,然后打印输出。
public void display() {// 用于显示链表 ArrayList<ListNode<T>> NodeList = new ArrayList<LinkedList.ListNode<T>>(); StringJoiner sj = new StringJoiner(",", "[", "]"); ListNode<T> cur = head; // 通过while循环遍历链表所有节点 while (cur != null) { // 把链表节点添加到ArrayList集合中 NodeList.add(cur); cur = cur.next; } System.out.println(NodeList);// 打印 // while (cur != null) { // sj.add(cur.value.toString()); // cur = cur.next; // } // System.out.println(sj.toString()); }
实现代码(完整)
LinkedList.java
import java.util.ArrayList; import java.util.StringJoiner; public class LinkedList<T> { ListNode<T> head;// 链表的头结点 ListNode<T> tail;// 链表的尾结点 int size;// 链表的长度 private static class ListNode<T> {// 定义私有节点内部类 T value;// 用来存储数据(数据域) ListNode<T> next;// 用来存储下一节点的引用(地址域) public ListNode(T val) {// 构造方法, this.value = val; } @Override // 重写toString()方法,便于查看链表内容,如果链表中放入自己定义的类的对象,那么在该类中一定要重写toString()方法 public String toString() { // TODO Auto-generated method stub return this.value.toString(); } } public LinkedList() {// 链表的构造方法, head = null; tail = null; size = 0; } public void append(T val) {// 添加元素,直接添加在链表的末尾 ListNode<T> newNode = new ListNode<T>(val); // 如果尾结点为空,那么这个链表一个节点都没有,即第一次添加节点 if (tail == null) { head = tail = newNode; } else {// 否则直接把节点链接在链表尾部 tail.next = newNode; tail = newNode; } size++; } public boolean insert(int position, T val) {// 插入节点,可以在链表的任意位置插入节点 if (position > size || position < 0) { System.out.println("请输入正确的下标:0-" + (size - 1)); return false; } ListNode<T> newNode = new ListNode<T>(val); if (position == 0) {// 在链表头结点中插入节点 newNode.next = head; head = newNode; if (tail == null) { tail = newNode;// 如果第一次添加元素,此时尾结点也是此节点 } size++; return true; } else if (position == size) {// 在链表尾结点中插入节点 this.append(val); return true; } else { ListNode<T> prev = head; // 找到要插入位置的前一个节点 for (int i = 0; i < position - 1; i++) { prev = prev.next; } // 此时prev的下一个节点就就是要插入位置的后一个节点 ListNode<T> after = prev.next; // 更改指向,即完成了添加元素 prev.next = newNode; newNode.next = after; size++; return true; } } public void display() {// 用于显示链表 ArrayList<ListNode<T>> NodeList = new ArrayList<LinkedList.ListNode<T>>(); StringJoiner sj = new StringJoiner(",", "[", "]"); ListNode<T> cur = head; // 通过while循环遍历链表所有节点 while (cur != null) { // 把链表节点添加到ArrayList集合中 NodeList.add(cur); cur = cur.next; } System.out.println(NodeList);// 打印 // while (cur != null) { // sj.add(cur.value.toString()); // cur = cur.next; // } // System.out.println(sj.toString()); } // 用于删除链表的元素 public boolean delete(T val) { if (head != null && head.value.equals(val)) { head = head.next; size--; // 删除后如果长度为0,头结点和尾结点都为空 if (size == 0) { tail = null; } return true; } else { ListNode<T> prev = head;// 标记要删除的节点的前一个节点 ListNode<T> cur = head;// 标记要删除的节点 // 从头结点开始遍历链表,找到了要删除的节点 while (prev != null && cur != null) { if (cur.value.equals(val)) {// 此条件如果成立,则找到了要删除的节点 if (cur == tail) {// 如果它是尾结点,那么新的尾结点就是前一个节点 tail = prev; } prev.next = cur.next;// 让前一个节点指向要删除节点的下一个节点 size--; return true; } prev = cur; cur = cur.next; } } return false; } public int search(T val) {// 查找元素,返回该元素在链表中的下标,如果没有则返回-1 ListNode<T> cur = head; // 从头结点遍历链表,寻找目标节点 for (int index = 0; cur != null; index++) { // 如果某个节点的value内容和val一样,即找到了目标节点 if (cur.value.equals(val)) { return index; } cur = cur.next; } // 遍历链表没找到目标节点,返回-1 return -1; } public boolean update(T oldVal, T newVal) {// 更新链表中的某个节点中的值 ListNode<T> cur = head; // 从头结点开始遍历链表,寻找要更新的节点 while (cur != null) { //找到目标节点后,更新它的值 if (cur.value.equals(oldVal)) { cur.value = newVal; return true; } cur = cur.next; } return false; } }Main.java
public class Main { public static void main(String[] args) { LinkedList<String> list = new LinkedList<String>(); list.append("关羽"); list.append("张飞"); list.append("赵云"); list.append("马超"); list.append("黄忠"); list.display(); System.out.println("---------------------------------"); int index1 = list.search("赵云"); System.out.println("赵云的位置:"+index1); int index2 = list.search("刘备"); System.out.println("刘备的位置:"+index2); System.out.println("---------------------------------"); list.insert(2, "刘备"); list.delete("马超"); list.update("张飞", "孙权"); list.display(); } }测试结果 :
总结
单向链表只有指向下一个节点的引用“next”,因此,遍历只能从头结点开始从前往后遍历,直到尾结点。
要实现链表的“插入”,“删除”,“更新”等操作,关键是要通过对链表的遍历找到“目标节点”,然后更改其引用“next”的指向即可。
由于水平有限,一些细节可能没有考虑到位,还请理解!本文仅供参考使用,如有帮助,不甚荣幸!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。