【数据结构篇】手写双向链表、单向链表(超详细)

这篇具有很好参考价值的文章主要介绍了【数据结构篇】手写双向链表、单向链表(超详细)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

链表

1、基本介绍

  • 什么是链表

    链表(Linked List)是用链式存储结构实现的线性表。链表示意图:

    手写双向链表,算法与数据结构,数据结构,链表

  • 链表的组成数据域+引用域(数据域和引用域合称结点或元素)

    • 数据域存放数据元素自身的数据
    • 引用域存放相邻结点的地址
  • 链表的特点

    • 链表中元素的联系依靠引用域

    • 具有线性结构的特点,链表所使用的逻辑结构是线性结构

    • 具有链式存储结构的特点,所使用的物理存储结构是链式存储

  • 链表的分类

    • 单向链表:单链表是一种最简的链表,只有一个引用域1next

      特点:通过next可以访问到后继结点,终端结点的引用域指向null

    • 双向链表:具有两个引用域prevnext,prev用来保存前驱结点的地址,next用来保存后继结点的地址

      特点:通过next可以访问后继结点,终端结点的next指向null;通过prev可以访问到前驱节点,起始结点的prev指向null

    • 循环链表:循环链表本质是一种特殊的单向链表,只是它的终端结点指向了开始结点(也就是next存放了开始结点的地址)

      特点:所有结点都能具有前驱节点和后继结点

  • 链表的使用场景:对查找速度要求不高,但对插入和删除速度要求高时,可以使用链表。常见的比如:

2、单向链表

单向链表(简称单链表)有带头结点的单链表,也有不带头链表的单链表。

手写双向链表,算法与数据结构,数据结构,链表

手写双向链表,算法与数据结构,数据结构,链表

  • 单链表的基本操作

    • 非空判断:判断链表中是否含有元素
    • 求表长度:获取链表中所有元素的个数
    • 插入结点:在单链表中添加一个新的结点
    • 删除结点:删除单链表中的结点
    • 取表元素:更具所给索引,确定该索引所在链表的结点
    • 定位元素:根据所给值,确定该元素所在链表的索引号
    • 修改元素:根据所给索引,修改对应的结点
    • 清空链表:清空链表中所有的元素

2.1 带头节点的单向链表

带头结点就是先固定一个头节点,用来标识链表的初始位置,它的data域不存任何东西,它的next域用来第一个结点的地址,每次遍历链表或定位结点都需要借助一个辅助变量temp来实现。

  • 插入结点示意图:

    手写双向链表,算法与数据结构,数据结构,链表

  • 删除结点示意图:

    手写双向链表,算法与数据结构,数据结构,链表

  • 修改结点示意图:

    手写双向链表,算法与数据结构,数据结构,链表

遍历经验总结当我们想要进行的操作的结点依赖于前一个结点时,比如插入删除修改等操作操作,就必须从head结点开始遍历,否则会出现空指针异常;当我们想要进行的操作不依赖前一个结点时,就无须从head结点开始遍历,比如根据id获取结点非空判断获取链表长度展示链表等操作。

测试类
package com.hhxy.linkedlist;

import java.util.Scanner;

import com.hhxy.queue.ArrayQueue2;
/**
 * 单向链表测试类
 * @author ghp
 * 测试数据:
 * 1 宋江 及时雨
 * 2 林冲 豹子头
 * 3 鲁智深 花和尚
 * 4 吴用 智多星
 */
public class SingleLinkedListTest {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		SingleLinkedListDemo1 sll = new SingleLinkedListDemo1();//创建链表
		OUT:
		while(true) {
			System.out.println("-------------------单向链表操作界面-----------------");
			System.out.println("请输入操作指令:");
			System.out.println("0 : 退出程序");
			System.out.println("1 : 在链尾添加结点");
			System.out.println("2 : 按id从小到大的顺序添加结点");
			System.out.println("3 : 根据id获取结点");
			System.out.println("4 : 根据id删除结点");
			System.out.println("5 : 获取链表中元素的个数");
			System.out.println("6 : 展示链表中所有的元素");
			System.out.println("7 : 根据id修改结点");
			System.out.println("8 : 清空链表");
			//用于接收用户输入
			int id;
			String name="";
			String alias="";
			Student student = null;
			switch(sc.next()) {
			case "0": //退出程序
				System.out.println("正在退出程序~~~");
				break OUT;
			case "1": //在链尾添加结点
				System.out.println("请按照 id name alias 的格式输入要添加的元素:");
				id = sc.nextInt();
				name = sc.next();
				alias = sc.next();
				student = new Student(id,name,alias);
				if(sll.add(student)) System.out.println("结点:"+student+"添加成功!");
				break;
			case "2"://按id从小到大的顺序添加结点
				System.out.println("请按照 id name alias 的格式输入要添加的元素:");
				id = sc.nextInt();
				name = sc.next();
				alias = sc.next();
				student = new Student(id,name,alias);
				if(sll.addById(student)) System.out.println("结点:"+student+"添加成功!");
				break;
			case "3"://根据id获取结点
				System.out.println("请输入要获取结点的id号:");
				id = sc.nextInt();
				try {
					student  = sll.get(id);
					System.out.println(id+"号结点为:"+student);
				}catch(Exception e){
					System.out.println(e.getMessage());
				}
				break;
			case "4"://根据id删除结点
				System.out.println("请输入要删除结点的id号:");
				id = sc.nextInt();
				try {
					if(sll.remove(id)) System.out.println("结点删除成功!");
				}catch(Exception e) {
					System.out.println(e.getMessage());
				}
				break;
			case "5"://获取链表中结点的个数(不包括头节点)
				System.out.println("链表中的结点个数为:"+sll.size());
				break;
			case "6"://展示链表中所有的结点(不包括头节点)
				sll.show();
				break;
			case "7"://根据id修改结点
				System.out.println("请按照 id name alias 的格式输入要修改的元素:");
				student = new Student(sc.nextInt(),sc.next(),sc.next());
				try {
					if(sll.update(student)) System.out.println("修改成功");
				}catch(Exception e) {
					System.out.println(e.getMessage());
				}
				break;
			case "8"://清空链表
				if(sll.clear()) System.out.println("链表已成功清空!");
				break;
			default:
				System.out.println("请输入有效指令!");
				break;
			}
		}
		System.out.println("程序已退出");
	}
}

手写双向链表,算法与数据结构,数据结构,链表

链表实现类:
package com.hhxy.linkedlist;

//结点类
class Student{
	//数据域(将成员变量设置为public方便外部访问)
	public int id;
	public String name;
	public String alias;
	//引用域
	public Student next;
	
	public Student(int id, String name, String alias) {
		this.id = id;
		this.name = name;
		this.alias = alias;
	}

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

//链表类
public class SingleLinkedListDemo1 {
	//初始化头结点
	Student head = new Student(-99,"","");
	/**
	 * 判断链表是否为空
	 * @return true表示链表为空
	 */
	public boolean isEmpty() {
		//因为头节点是链表位置的标识,不能动,所以使用一个辅助引用来遍历链表
		Student temp = head.next;
		if(temp!=null) {
			//head后面存在至少一个元素,所以链表不为空
			return false;
		}
		return true;
	}
	/**
	 * 在链尾添加结点
	 * @param student 待添加的结点
	 * @return true表示添加成功
	 */
	public boolean add(Student student) {
		//同理,因为链表头节点不能动。
		//注意:需要是从头节点开始遍历,因为链表可能为空,如果从头节点后一个遍历,当链表为空时会报空指针异常
		Student temp = head;
		//遍历寻找尾结点。因为temp=head,所以是从头节点开始遍历
		while(temp.next != null) {
			temp = temp.next;
		}
		//已找到链表尾结点,进行指向
		temp.next = student;
		return true;
	}
	/**
	 * 按照id从小到大的顺序添加结点
	 * @param student 待添加的结点
	 * @return true表示添加成功
	 */
	public boolean addById(Student student) {
		Student temp = head;
		boolean flag = true;//用于判断链表是加在尾结点,还是加在结点之间
		while(temp.next != null) {
			if(student.id < temp.next.id) {
				//说明是添加在结点之间
				flag = false;
				break;
			}
			temp = temp.next;
		}
		if(flag) {
			//如果添加的结点是在尾结点
			temp.next = student;
		}else {
			//添加的结点是在结点之间
			student.next = temp.next;//切记:先改变后一个指向,再改变前一个指向
			temp.next = student;
		}
		return true;
	}
	/**
	 * 根据id获取结点
	 * @param id 
	 * @return 返回对应id的结点
	 */
	public Student get(int id) {
		if(isEmpty()) {
			throw new RuntimeException("该链表为空!");
		}
		Student temp = head.next;
		//从head结点后面开始遍历
		boolean flag = false;//判断链表中是否存在待获取的结点
		while(temp != null) {
			if(temp.id == id) {
				//找到id对应的结点
				flag = true;
				break;
			}
			temp = temp.next;
		}
		if(flag) {
			//如果找到id对应结点
			return temp;
		}else {
			//如果没有找到id对应的结点
			throw new RuntimeException("待获取的结点不存在!");
		}
	}
	/**
	 * 根据id删除结点
	 * @param id 待删除结点的id
	 * @return true表示删除成功
	 */
	public boolean remove(int id) {
		if(isEmpty()) {
			throw new RuntimeException("链表为空!");
		}
		Student temp = head;//删除结点需要依赖前一个结点,所以从头节点开始遍历
		boolean flag = false;//判断链表中是否存在待删除的结点
		while(temp.next != null) {
			if(temp.next.id == id) {
				//找到该结点
				flag = true;
				break;
			}
			temp = temp.next;
		}
		if(flag) {
			//如果找到了要删除的结点
			temp.next = temp.next.next;
		}else {
			//如果没有找到id对应的结点
			throw new RuntimeException("待删除的结点不存在!");
		}
		return true;
	}
	/**
	 * 获取链表中结点的个数(不包括头节点)
	 */
	public int size() {
		Student temp = head;
		int count = 0;
        //这里虽然遍历了头节点,但是没有遍历尾结点
		while(temp.next != null) {
			count++;
			temp = temp.next;
		}
		return count;
	}
	/**
	 * 展示链表中所有的结点(不包括头节点)
	 */
	public void show() {
		if(isEmpty()) {
			System.out.println("链表为空!");
			return;
		}
		//注意:不需要展示头节点!
		Student temp = head;
		while(temp.next != null) {
			System.out.println(temp.next);
			temp = temp.next;
		}
	}
	/**
	 * 根据id修改结点
	 * @param student 待修改的结点
	 * @return true表示修改成功
	 */
	public boolean update(Student student) {
		if(isEmpty()) {
			throw new RuntimeException("链表为空!");
		}
		Student temp = head;
		boolean flag = false;//判断链表是否修改成功
		while(temp.next != null) {
			if(temp.next.id == student.id) {
				//找到要修改的链表
				flag = true;
				student.next = temp.next.next;
				temp.next = student;
				break;
			}
			temp = temp.next;
		}
		if(flag) {
			//如果修改成功
			return true;
		}else {
			//如果链表中没有找到待删除的结点
			throw new RuntimeException("链表中不存在该结点");
		}
	}
	/**
	 * 清空链表
	 * @return true表示清空成功
	 */
	public boolean clear() {
		/*方式一:直接将头节点指向空(节约时间,但占内存)
		 * head.next = null;
		 * 除头节点以外其它结点存在引用,JVM不会回收结点内存,仍然会占据内存
		 * 这种清楚方法很费内存! 
		 */
		//方式二:将所有结点占据的内存都进行释放(耗时,但不占内存)
		Student2 temp = head;//这里需要从后往前遍历,逐步去掉所有结点的引用,否则无法遍历下取
		while(head.next != null) {
			for (int i = size(); i > 1; i--) {
				temp = temp.next;
			}
			temp.next = null;
		}
		return true;
	}
}

2.2 不带头节点的单向链表

略……逻辑思路都差不多,只是将头节点换成一个头指针

不带头节点和带头结点的主要区别:带头结点遍历的时候、不能将头节点进行计数;而不带结点能够直接进行遍历

本质上两者并没有什么太大区别,带头节点的链表没有指针,头结点就相当于头指针,而不带头节点的链表是由头指针的

注意:这里所谓的指针和结点其实都是结点对象,只是指针初始值为null,结点要进行初始化

2.3 练习

  • 反转链表示意图

手写双向链表,算法与数据结构,数据结构,链表

手写双向链表,算法与数据结构,数据结构,链表

  • 合并链表示意图

手写双向链表,算法与数据结构,数据结构,链表

手写双向链表,算法与数据结构,数据结构,链表

手写双向链表,算法与数据结构,数据结构,链表

测试类:
/**
 * 练习题1:获取链表倒数第k个结点
 * 练习题2:将链表反转
 * 练习题3:从尾到头打印链表的结点
 * 练习题4:合并两个有序链表,合并后仍然有序
 */
//测试类:
public class SingleLinkedListDemo3{
	public static void main(String[] args) {
		Node node1 = new Node(1);
		Node node2 = new Node(2);
		Node node3 = new Node(3);
		SingleLinkedList sll = new SingleLinkedList();
		sll.add(node1);
		sll.add(node2);
		sll.add(node3);
		System.out.println("链表原始状态:");
		sll.show();
		
		System.out.println("------------------------");
		//测试1:测试获取链表倒数第k个结点
		Node t = sll.findLastIndexNode(sll, 1);
		System.out.println(sll.findLastIndexNode(sll,1));
		System.out.println(sll.findLastIndexNode(sll,2));
		System.out.println(sll.findLastIndexNode(sll,3));
		
		System.out.println("-------------------------");
		//测试2:测试将链表反转
		sll.reverset(sll);
		System.out.println("反转后的链表:");
		sll.show();
		
		System.out.println("-------------------------");
		//测试3:从头到位打印链表
		System.out.println("反向打印链表:");
		sll.reversetPrint(sll);
		
		System.out.println("-------------------------");
		//测试4:将两个有序链表合并,合并后仍然有序
		SingleLinkedList list1 = new SingleLinkedList();
		SingleLinkedList list2 = new SingleLinkedList();
		Node node4 = new Node(4);
		Node node5 = new Node(5);
		Node node6 = new Node(6);
		Node node7 = new Node(7);
		Node node8 = new Node(8);
		Node node9 = new Node(9);
		Node node10 = new Node(10);
		Node node11 = new Node(11);
		
		list1.add(node4);
		list1.add(node7);
		list1.add(node8);
		list1.add(node10);
		list1.add(node11);
		System.out.println("链表1:");
		list1.show();
		
		System.out.println("链表2:");
		list2.add(node5);
		list2.add(node6);
		list2.add(node9);
		list2.show();
		
		SingleLinkedList list = new SingleLinkedList();
		list = list.combine(list1, list2);
		
		System.out.println("合并后的链表:");
		list.show();
	}
}

手写双向链表,算法与数据结构,数据结构,链表

手写双向链表,算法与数据结构,数据结构,链表

链表实现类:
package com.hhxy.linkedlist;

import java.util.Stack;

//结点类
class Node {
	int n;
	Node next;
	public Node(int n) {
		this.n = n;
	}
	@Override
	public String toString() {
		return "[n=" + n + "]";
	}
}
//链表类:
public class SingleLinkedList {
	//初始化头节点
	public Node head = new Node(-99);
	/**
	 * 添加结点
	 */
	public void add(Node node) {
		Node current = head;
		while(current.next != null) {
			current = current.next;
		}
		current.next = node;
	}
	/**
	 * 获取链表的长度
	 * @return
	 */
	public int size() {
		Node current = head.next;
		int count = 0;
		while(current != null) {
			count++;
			current = current.next;
		}
		return count;
	}
	/**
	 * 展示
	 */
	public void show() {
		Node current = head.next;
		while(current != null) {
			System.out.println(current);
			current = current.next;
		}	
	}
	/*--------------------核心方法-------------------------*/
	/**
	 * 寻找链表倒数第k个结点
	 * @param index
	 * @return
	 */
	public Node findLastIndexNode(SingleLinkedList sll,int index) {
		Node head = sll.head;
		if(index <0 || index>sll.size() || head.next == null) {
			return null;
		}
		Node current = head.next;
		//将指针从第二个结点开始往后移动index位
		for (int i = 0; i < size()-index; i++) {
			current = current.next;
		}
		return current;
	}
	/**
	 * 将链表反转
	 * @param sll 待反转的链表
	 */
	public void reverset(SingleLinkedList sll) {
		Node head = sll.head;
		if(head.next == null || head.next.next == null) {
			//当前链表为空,或者只有一个结点,直接返回
			return;
		}
		SingleLinkedList sllTemp = new SingleLinkedList();//创建一个新链表
		Node headTemp = sllTemp.head;
		Node temp = null;//用来存旧链表的引用,方便遍历旧链表
		Node current = head.next;//辅助遍历旧链表
		while(current != null) {
			temp = current.next;//不保存,新链表就会断开,就无法进行遍历了
			current.next = headTemp.next;//指向新创建的头结点的后面的结点
			headTemp.next = current;//新创建的头结点,指向插入的结点
			current = temp;//指针往后移
		}
		head.next = headTemp.next;
	}
	/**
	 * 反向打印链表
	 * @param sll
	 */
	public void reversetPrint(SingleLinkedList sll) {
		Node head = sll.head;
		if(head.next == null) {
			return;
		}
	/*
	  //方式一:使用findLastIndexNode方法(要先实现findLastIndexNode方法,不值得推荐)
		for(int i=1;i<=sll.size();i++) {
			System.out.println(sll.findLastIndexNode(sll, i));
		}
	  //方式二:使用reverset方法(要先实现reverset方法,并且改变了链表的结构,不值得推荐)
	    reverset(sll);
	    sll.show();
	*/
		//方式三:使用栈(强烈推荐)
		Stack<Node> stack = new Stack<>();
		Node current = head.next;
		while(current != null) {
			stack.push(current);
			current = current.next;
		}
		while(stack.size()>0) {
			System.out.println(stack.pop());
		}
	}
	/**
	 * 合并两个有序链表,合并后仍然有序(这里我是默认按从小到大排序的)
	 */
	public SingleLinkedList combine(SingleLinkedList sll1,SingleLinkedList sll2) {
		Node head1 = sll1.head.next;//用于遍历sll1链表
		Node head2 = sll2.head.next;
		if(head1 == null || head2  == null) {
        	//只要有一个链表为空就直接返回
        	return head1 != null ? sll1 : sll2;
        }
		SingleLinkedList sll = new SingleLinkedList();//合并后的链表
        Node temp=sll.head;//用来给sll链表添加结点的
        while (head1 != null && head2 != null){
            if (head1.n < head2.n){
            	//链表1的结点是当前最小结点
                temp.next = head1;//新链表连接最小结点
                temp = temp.next;//每新增一个结点,temp就往后移一位,保证他在尾结点方便连接新结点
                head1 = head1.next;//链表1的指针也往后移一位
            }else{
            	//链表2的结点是当前最小结点
                temp.next = head2;
                temp = temp.next;
                head2 = head2.next;
            }
        }
        if (head1 != null && head2 == null){
        	//经过一一段时间的合并后,sll2的链表为空了,直接就将sll1链表后面的结点拼接上去
            temp.next = head1;
        }
        if (head1 == null && head2 != null){
            temp.next = head2;
        }
        return sll;
    }
	/*------------------------------------------------*/
}

3、双向链表

双向链表相对单向链表就较为简单了,因为每个结点既能往后遍历,又能往前遍历 ,对于插入、删除、修改都无需像单链表一样依靠前一个结点。

与单链表的主要区别

  1. 遍历不仅可以往前遍历,还可以往后遍历

  2. 插入、删除、修改不需要依赖前一个结点(在链尾插入需要依赖尾结点)

  3. 添加的时候,需要进行双向绑定!

  • 双向链表插入示意图

    手写双向链表,算法与数据结构,数据结构,链表

    手写双向链表,算法与数据结构,数据结构,链表

  • 双向链表删除示意图

    手写双向链表,算法与数据结构,数据结构,链表

    手写双向链表,算法与数据结构,数据结构,链表

测试类:

和单向链表的测试方法相同

示意图:

手写双向链表,算法与数据结构,数据结构,链表

双向链表实现类:

其实只要理解了单向链表,再来看双向链表就会觉得so easy😄单向链表的方法双向链表都能使用,只是添加和修改的时候,需要多修改下prev的的指向。

package com.hhxy.linkedlist.doubles;
//结点类
class Student2{
	public int id;
	public String name;
	public String alias;
	public Student2 prev;//指向前一个结点
	public Student2 next;//指向后一个结点
	
	public Student2(int id, String name, String alias) {
		super();
		this.id = id;
		this.name = name;
		this.alias = alias;
	}
	@Override
	public String toString() {
		return "Student [n=" + id + ", name=" + name + ", alias=" + alias + "]";
	}
}
//链表类
public class DoubleLinkedListDemo1 {
	//初始化头节点
	public Student2 head = new Student2(-99,"","");
	/**
	 * 判断链表是否为空
	 * @return true表示链表为空
	 */
	public boolean isEmpty() {
		//因为头节点是链表位置的标识,不能动,所以使用一个辅助引用来遍历链表
		 Student2 temp = head.next;
		if(temp!=null) {
			//head后面存在至少一个元素,所以链表不为空
			return false;
		}
		return true;
	}
	/**
	 * 在链尾添加结点
	 * @param student2 待添加的结点
	 * @return true表示添加成功
	 */
	public boolean add(Student2 student2) {
		//同理,因为链表头节点不能动。
		//注意:需要是从头节点开始遍历,因为链表可能为空,如果从头节点后一个遍历,当链表为空时会报空指针异常
		Student2 temp = head;
		//遍历寻找尾结点。因为temp=head,所以是从头节点开始遍历
		while(temp.next != null) {
			temp = temp.next;
		}
		//形成双向链表
		temp.next = student2;
		student2.prev = temp;
		return true;
	}
	/**
	 * 按照id从小到大的顺序添加结点
	 * @param student2 待添加的结点
	 * @return true表示添加成功
	 */
	public boolean addById(Student2 student2) {
		Student2 temp = head;
		boolean flag = true;//用于判断链表是加在尾结点,还是加在结点之间
		while(temp.next != null) {
			if(student2.id < temp.next.id) {
				//说明是添加在结点之间
				flag = false;
				break;
			}
			temp = temp.next;
		}
		if(flag) {
			//如果添加的结点是在尾结点
			//形成双向链表
			temp.next = student2;
			student2.prev = temp;
		}else {
			//添加的结点是在结点之间,注意要形成双向指向
			student2.next = temp.next;//切记:先改变后一个指向,再改变前一个指向
			temp.next.prev = student2;
			//前面一根线
			temp.next = student2;
			student2.prev = temp;
		}
		return true;
	}
	/**
	 * 根据id获取结点
	 * @param id 
	 * @return 返回对应id的结点
	 */
	public Student2 get(int id) {
		if(isEmpty()) {
			throw new RuntimeException("该链表为空!");
		}
		Student2 temp = head.next;
		//从head结点后面开始遍历
		boolean flag = false;//判断链表中是否存在待获取的结点
		while(temp != null) {
			if(temp.id == id) {
				//找到id对应的结点
				flag = true;
				break;
			}
			temp = temp.next;
		}
		if(flag) {
			//如果找到id对应结点
			return temp;
		}else {
			//如果没有找到id对应的结点
			throw new RuntimeException("待获取的结点不存在!");
		}
	}
	/**
	 * 根据id删除结点
	 * @param id 待删除结点的id
	 * @return true表示删除成功
	 */
	public boolean remove(int id) {
		if(isEmpty()) {
			throw new RuntimeException("链表为空!");
		}
		//使用双向链表可以进行自我删除
		Student2 temp = head.next;//删除结点需要依赖前一个结点,所以从头节点开始遍历
		boolean flag = false;//判断链表中是否存在待删除的结点
		while(temp != null) {
			if(temp.id == id) {
				//找到该结点
				flag = true;
				break;
			}
			temp = temp.next;
		}
		if(flag) {
			//如果找到了要删除的结点
			temp.prev.next = temp.next;//前一根线
			if(temp.next != null) {//要排除最后一个结点的可能,否则会出现空指针异常
				temp.next.prev = temp.prev;//后一根线
			}
		}else {
			//如果没有找到id对应的结点
			throw new RuntimeException("待删除的结点不存在!");
		}
		return true;
	}
	/**
	 * 获取链表中结点的个数
	 * @return
	 */
	public int size() {
		Student2 temp = head;
		int count = 0;
		while(temp.next != null) {
			count++;
			temp = temp.next;
		}
		return count;
	}
	/**
	 * 展示链表中所有的结点
	 */
	public void show() {
		if(isEmpty()) {
			System.out.println("链表为空!");
			return;
		}
		Student2 temp = head;
		while(temp.next != null) {
			System.out.println(temp.next);
			temp = temp.next;
		}
	}
	/**
	 * 根据id修改结点
	 * @param student2 待修改的结点
	 * @return true表示修改成功
	 */
	public boolean update(Student2 student2) {
		if(isEmpty()) {
			throw new RuntimeException("链表为空!");
		}
		Student2 temp = head.next;
		boolean flag = false;//判断链表是否修改成功
		while(temp != null) {
			if(temp.id == student2.id) {
				//找到要修改的链表
				flag = true;
				//后一根线
				student2.next = temp.next;
				if(temp.next!=null) {
					//排除最后一个节点
					temp.next.prev = student2;
				}
				//前一根线
				temp.prev.next = student2;
				student2.prev = temp.prev;
			
				
				break;
			}
			temp = temp.next;
		}
		if(flag) {
			//如果修改成功
			return true;
		}else {
			//如果链表中没有找到待删除的结点
			throw new RuntimeException("链表中不存在该结点");
		}
	}
	/**
	 * 清空链表
	 * @return
	 */
	public boolean clear() {
		/*方式一:直接将头节点指向空(节约时间,但占内存)
		 * head.next = null;
		 * 除头节点以外其它结点存在引用,JVM不会回收结点内存,仍然会占据内存
		 * 这种清楚方法很费内存! 
		 * return true;
		 */
		//方式二:将所有结点占据的内存都进行释放(耗时,但不占内存)
		Student2 temp = head;//这里需要从后往前遍历,逐步去掉所有结点的引用,否则无法遍历下取
		Student2 current = head;
		while(current.next != null) {
			current = temp;
			current.next = null;
			current.prev = null;
			temp = temp.next;
		}
		return true;
	}
}

4、单向环形链表

基本和单向链表类似,也可以分为带头节点,和不带头结点点,这里演示的是不带头结点的单向环形链表,单向环形链表和单向链表唯一的区别:尾结点的next不指向空,而是指向开始节点

主要思想还是在单链表那一节😄只要掌握单向链表,这些双向链表还有单向循环链表就是弟弟(′д` )…彡…彡直接套用第一节的接口,实现所有的方法

手写双向链表,算法与数据结构,数据结构,链表

测试类

和2.1一样,换个对象就行了(这个测试类真渣😆)

手写双向链表,算法与数据结构,数据结构,链表

手写双向链表,算法与数据结构,数据结构,链表

手写双向链表,算法与数据结构,数据结构,链表

手写双向链表,算法与数据结构,数据结构,链表

实现类

这里主要记录以下按顺序插入结点的思路,怕以后忘记了。其实主要思想还是和单向链表的addById方法的逻辑是一致的,主要是要考虑循环!思路主要如下:

  1. 先将链表添加分为两大类,首结点的添加非首结点的添加,因为首结点的添加需要自动成环
  2. 再将非首结点的添加又分为在 添加在首结点之前之后,之前需要移动first指针,之后不需要移动

示意图:

手写双向链表,算法与数据结构,数据结构,链表

删除结点:

  1. 先将删除分为两大类,删除头结点删除普通结点
  2. 删除头结点又可以分为两类,链表只有一个头结点除了头结点还有其它结点
  3. 删除普通结点时需要注意链表是单向的,删除操作需要依赖待删除结点的前一个结点

修改结点的逻辑思路和删除类似,不在赘述,示意图:

手写双向链表,算法与数据结构,数据结构,链表

  • 清空链表:链表的清空有两种方法,一种是直接让first=null,这种清空简单省事,但是是假清空,链表仍然存在内存中!

    第二种方法是让每个结点的next指向空,然后将first=null,这种费脑子但是省空间

package com.hhxy.linkedlist.circular;


//结点类
class Student{
	public int id;
	public String name;
	public String alias;
	public Student next;
	
	public Student(int id, String name, String alias) {
		super();
		this.id = id;
		this.name = name;
		this.alias = alias;
	}

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

//链表类
public class CircularLinkedListDemo1 {
	private Student first = null;//注意这是头指针,不是头结点!
	
	/**
	 * 非空判断
	 * @return true表示为空
	 */
	public boolean isEmpty() {
		//因为是没有头结点,所以直接判断first
		if(first != null) {
			return false;
		}else {
			return true;
		}
	}
	/**
	 * 在尾结点添加结点
	 * @param student
	 * @return true表示结点添加成功
	 */
	public boolean add(Student student) {
		if(first == null) {
			//对第一个结点进行单独考虑
			first =  student;
			first.next = first;//构成环状
			return true;
		}else {
			Student current = first;
			//找到最后一个结点
			while(current.next != first) {
				current = current.next;
			}
			//找到后将节点加入环形链表中
			current.next = student;
			student.next = first;
			return true;
		}
	}
	/**
	 * 根据id从小到大的顺序添加结点
	 * @return true表示添加成功
	 */
	public boolean addById(Student student) {
		if(first == null) {
			//第一个结点单独成环
			first = student;
			first.next = first;
		}else {
			Student current = first;
			if(student.id < current.id && current == first) {
				//说明这个结点比头结点还要小,要将头指针移动位置
				current.next = student;
				student.next = current;
				first = student;//将first移动到student上
				return true;
			}
			while(current.next != first) {
				//寻早结点添加的位置
				if(student.id < current.next.id) {
					//已找到添加的位置
					break;
				}
				current = current.next;
			}
			student.next = current.next;//切记:一定要先改变后一根线,不然链表会断裂!
			current.next = student;
		}
		return true;
	}
	/**
	 * 根据id获取结点
	 */
	public Student get(int id) {
		if(isEmpty()) {
			throw new RuntimeException("链表为空!");
		}
		Student current = first;
		boolean flag = false;
		while(true) {
			if(current.id == id) {
				//找到就直接结束遍历
				flag = true;
				break;
			}
			if(current.next == first) {
				//如果是最后一个结点,就表明还没有找到,直接结束遍历
				break;
			}
			current = current.next;//辅助指针后移,遍历链表
		}
		if(flag) {
			//找到就返回这个结点
			return current;
		}else {
			//没有找到,打印提示信息
			throw new RuntimeException("链表中不存在该结点!");
		}
	}
	/**
	 * 根据id删除结点
	 */
	public boolean remove(int id) {
		if(isEmpty()) {
			throw new RuntimeException("链表为空!");
		}
		if(first.id == id) {
			//当头结点就是要删除的结点时,分类讨论
			if(first.next == first) {
				//如果链表只有头结点时
				first = null;
				return true;
			}else {
				//如果链表除了头结点还有其它结点时,需要移动first指针
				Student current = first;
				//找到尾结点
				while(current.next != first) {
					current = current.next;
				}
				current.next = current.next.next;
				first = current.next;//移动头指针
				return true;
			}
		}
		//删除普通结点
		Student current = first;
		boolean flag = false;//判断链表中是否存在该结点
		while(true) {
			if(current.next.id == id) {//这里使用current.next判断是为了使用前一个结点
				//找到结点直接退出
				flag = true;
				break;
			}
			if(current.next == first) {
				//遍历完成,直接退出
				break;
			}
			current = current.next;
		}
		if(flag) {
			//找到待删除的结点,利用前一个结点将其删除(思路和单项链表是一样的)
			current.next = current.next.next;
			return true;
		}else {
			throw new RuntimeException("该结点不存在!");
		}
	}
	/**
	 * 获取链表长度
	 */
	public int size() {
		Student current = first;
		if(isEmpty()) {
			return 0;//这里要不要都无所谓,只是习惯了~~~
		}
		int count = 0;
		while(true) {
			count++;
			if(current.next == first) {
				break;
			}
			current = current.next;
		}
		return count;
	}
	/**
	 * 展示链表
	 */
	public void show() {
		Student current = first;
		if(first == null) {
			//排除空链表
			throw new RuntimeException("链表为空!");
		}
		while(true) {
			System.out.println(current);
			if(current.next == first) {
				//当发现结点是最后一个结点直接退出打印
				break;
			}
			current = current.next;
		}
	}
	/**
	 * 根据id修改结点
	 */
	public boolean update(Student student) {
		if(isEmpty()) {
			throw new RuntimeException("链表为空!");
		}
		if(student.id == first.id) {
			//修改的结点是头节点
			if(first.next == first) {
				//只有一个头节点
				first = student;
				first.next = student;
				return true;
			}else {
				//除了头节点还有其它结点
				Student current = first;
				//找到尾结点
				while(current.next != first) {
					current = current.next;
				}
				student.next = current.next.next;//修改后一根线
				current.next = student;//修改前一根线
				first = student;//修改头指针
				return true;
			}
		}
		//修改的结点是普通结点
		Student current = first;
		boolean flag = false;//判断链表中是否存在该结点
		while(current.next != first) {
			if(current.next.id == student.id) {
				//找到结点,直接退出
				flag = true;
				break;
			}
			current = current.next;
		}
		if(flag) {
			student.next = current.next.next;//修改后一根线,防止链表断裂
			current.next = student;
			return true;
		}else {
			throw new RuntimeException("不存在该结点!");
		}
	}
	/**
	 * 	清空链表
	 */
	public boolean clear() {
		/*方式一:直接将头节点指向空(节约时间,但占内存)
		 * first.next = null;
		 * 除头节点以外其它结点存在引用,JVM不会回收结点内存,仍然会占据内存
		 * 这种清楚方法很费内存! 
		 * return true;
		 */
		//方式二:将所有结点占据的内存都进行释放(耗时,但不占内存)
		if(isEmpty()) {
			return true;
		}
		if(first == first.next) {
			//只有一个结点
			first = null;
			return true;
		}
		Student temp = first;//临时存储current的引用,辅助遍历
		Student current = first;
		//将链表每个结点的引用都断开,这样每个结点都没有被引用,就能被JVM给回收
		while(current.next != first) {
			temp = temp.next;
			current.next = null;
			current = temp;
		}
		//将最后一个结点的引用 和头指针 设为空
		current.next = null;//不断开最后一个接待你的引用,头节点就不会被回收
		first = null;
		return true;
	}
}

  1. 引用域:是链表结点中一片很小的空间,用来存放后继结点的地址 ↩︎文章来源地址https://www.toymoban.com/news/detail-677490.html

到了这里,关于【数据结构篇】手写双向链表、单向链表(超详细)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 数据结构 —— 双向链表(超详细图解 & 接口函数实现)

    数据结构 —— 顺序表 数据结构 —— 单链表 数据结构 —— 双向链表 数据结构 —— 队列 数据结构 —— 栈 数据结构 —— 堆 数据结构 —— 二叉树 数据结构 —— 八大排序 数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元

    2024年02月11日
    浏览(42)
  • 数据结构-双向链表(c++)超全超详细

    单链表结点中只有一个指向其后继的指针,使得单链表只能从头结点依次顺序地向后遍历。要访问某个结点的前驱结点(插入,删除操作时),只能从头开始遍历,访问后继结点的时间复杂度为O(1),访问前驱结点的时间复杂度为O(n)。 提示:以下是本篇文章正文内容,下面案

    2023年04月08日
    浏览(45)
  • 【数据结构】—C语言实现双向链表(超详细!)

                                          食用指南:本文在有C基础的情况下食用更佳                                       🔥 这就不得不推荐此专栏了:C语言                                     🍀 双向链表 前 置知识 :单链表      

    2024年02月13日
    浏览(49)
  • 【数据结构与算法】 - 双向链表 - 详细实现思路及代码

    前几篇文章介绍了怎样去实现单链表、单循环链表, 这篇文章主要介绍 双向链表 以及实现双向链表的步骤,最后提供我自己根据理解实现双向链表的C语言代码 。跟着后面实现思路看下去,应该可以看懂代码,看懂代码后,就对双向链表有了比较抽象的理解了,最后自己再

    2024年02月01日
    浏览(39)
  • 【数据结构】单向链表

    哈喽,大家好,今天我们学习的是数据结构里的链表,这里主要讲的是不带哨兵卫头节点的单向链表,下篇将会继续带大家学习双向链表。 目录 1.链表的概念 2.单向链表接口的实现 2.1动态申请一个节点 2.2单链表打印 2.3单链表尾插 2.4单链表头插 2.5单链表尾删 2.6单链表头删

    2024年02月11日
    浏览(52)
  • 数据结构——实现单向链表

    单链表是一种常见的数据结构,用于存储一系列的数据元素,每个节点包含数据和指向下一个节点的指针。 单链表通常用于实现某些算法或数据结构,如链式前向星、哈希表、链式栈、队列等等。 单链表在程序设计中的作用不可忽略,是很多基础算法的核心数据结构之一。

    2024年02月07日
    浏览(56)
  • 数据结构与算法(三):单向链表

    链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑是通过链表种的指针链接次序实现的。链表由一系列节点组成,每个节点包括两部分:一个是存储数据元素的数据域,一个是存储下一个节点地址的指针域。单向链表从头节点(也可以没有头节点)开始

    2024年02月15日
    浏览(53)
  • 【数据结构】动图详解单向链表

    目录 1.什么是链表         1.问题引入         2. 链表的概念及结构         3. 问题解决 2.单向链表接口的实现         1.接口1,2---头插,尾插         2. 接口3,4---头删,尾删         3. 接口5---查找          4. 接口6,7---插入,删除         5. 接口

    2024年01月18日
    浏览(56)
  • 【数据结构】双向链表 超详细 (含:何时用一级指针或二级指针;指针域的指针是否要释放)

    目录 一、简介 二. 双链表的实现 1.准备工作及其注意事项 1.1 先创建三个文件 1.2 注意事项:帮助高效记忆 1.3   关于什么时候 用 一级指针接收,什么时候用 二级指针接收? 1.4 释放节点时,要将节点地址 置为NULL,难道 节点内部的 指针域的指针 就不用置为 NULL吗?  2.双链

    2024年02月20日
    浏览(87)
  • 数据结构——单向链表(C语言版)

    在数据结构和算法中,链表是一种常见的数据结构,它由一系列节点组成,每个节点包含数据和指向下一个节点的指针。在C语言中,我们可以使用指针来实现单向链表。下面将详细介绍如何用C语言实现单向链表。 目录 1. 定义节点结构体 2. 初始化链表 3. 插入节点 4. 删除节点

    2024年03月24日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包