Java数据结构(三):单链表和双链表

版权声明:转载请注明原文地址。 https://blog.csdn.net/qq_39240270/article/details/86766945

什么是链表

       链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。
       使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体或磁盘上顺序,数据的存取往往要在不同的排列顺序中转换。链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。链表有很多种不同的类型:单向链表,双向链表以及循环链表。链表可以在多种编程语言中实现。像Lisp和Scheme这样的语言的内建数据类型中就包含了链表的存取和操作。程序语言或面向对象语言,如C,C++和Java依靠易变工具来生成链表。
数据结构中的链表是这样的:
在这里插入图片描述

单链表的实现

       单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。

public class LinkedList {
	private Node first;//定义头节点
	private int nItems;//定义单链表中实际的数据的数目
	
	//初始化
	public LinkedList() {
		this.first = null;
		this.nItems = 0;
	}
	
	//添加头节点
	public void addFirst(int data) {
		//新建节点
		Node newNode = new Node(data);
		//将新节点的下一个节点指向旧的头节点
		newNode.next = first;
		//将新节点设为头节点
		first = newNode;
		
		nItems ++;
	}
	
	//删除头结点
	public boolean deleteFirst() {
		//判断链表是否为空
		if(isEmpty()) {
			System.out.println("链表为空!");
			return false;
		}
		first = first.next;
		nItems --;
		return true;
	}
	
	//有序链表的插入,这样简单排序就可以用链表来实现,复杂度为O(N)
	public void add(int data) {
		//创建新节点
		Node newNode = new Node(data);
		//创建要插入节点之前的节点
		Node previous = null;
		//创建要插入节点的位置上原来的节点
		Node current = first;
		//按从小到大的顺序排序
		while(current != null && data > current.data) {
			previous = current;
			current = current.next;
		}
		if(previous == null) {
			first = newNode; 
		}else {
			previous.next = newNode;
		}
		newNode.next = current;
		nItems ++;
	}
	
	//查询某个特定值的节点
	public Node findNode(int data) {
		//定义一个新节点用于查询
		Node current = first;
		while(current != null && current.data != data) {
			if(current.next == null) {
				System.out.println("该节点不存在");
				return null;
			}
			current = current.next;
		}
		return current;
	}
	
	//删除某个特定值的节点,并返回该节点
	public Node deleteNode(int data) {
		//定义被删除节点之前的节点
		Node previous = null;
		//定义被删除的节点
		Node current = first;
		while(current != null && current.data != data) {
			if(current.next == null) {
				System.out.println("该节点不存在");
				return null;
			}
			previous = current;
			current = current.next;
		}
		if(previous == null) {
			first = first.next;
		}else {
			previous.next = current.next;
		}
		nItems --;
		return current;
	}
	
	//遍历链表
	public void traverseList() {
		//定义一个节点用于遍历
		Node current = first;
		//判断链表是否为空
		if(current == null) {
			System.out.println("链表为空!");
			return;
		}
		while(current != null) {
			System.out.println(current.data);
			current = current.next;
		}
	}
	
	//链表的长度
	public int size() {
		return nItems;
	}
	
	//判断链表是否为空
	public boolean isEmpty() {
		return first == null;
	}
	
}
//定义节点
class Node{
	//声明为public,方便存取
	//指向下一个节点
	public Node next;
	//数据域
	public int data;
	
	public Node(int data) {
		this.data = data;
	}
}

几个简单有趣的单链表算法问题(均为上面链表中的普通方法)

  1. 找到链表中倒数第k个元素
           解决办法就是创建两个节点,使两个节点的距离为k(包括这两个节点,这两个节点自身的距离为2),让第一个节点追第二个节点,当第二个节点为尾节点时,此时第一个节点为倒数第k个节点。
	//找到链表中倒数第k个节点
	public Node findNodeK(int k) {
		//定义两个节点,让两个节点之间的距离为k,距离包括这两个节点
		Node previous = first;
		Node current = first;
		if(k < 1 || k > nItems) {
			System.out.println("k值必须在1到" + nItems + "之间");
			return null;
		}
		//这一步使两个节点的位置为k
		for(int i = 0 ; i < k - 1;i++) {
			current = current.next;
		}
		//当current节点为尾节点时,previous为倒数第k个节点
		while(current.next != null) {
			previous = current;
			current = current.next;
		}
		return previous;
	}
  1. 对链表进行排序,我在上面插入已经使用了简单的方法进行插入时排序,这里使用冒泡排序进行排序。
	//冒泡排序
	public void sortedList() {
		//定义临时节点,从头结点开始
		Node previous = first;
		Node current = first;
		//从小到大排序
		while(current.next != null) {
			while(previous.next != null) {
				//当前一个节点的值大于后一个节点时,交换值
				if(previous.data > previous.next.data) {
					int i;
					i = previous.data;
					previous.data = previous.next.data;
					previous.next.data = i;
				}
				//遍历下一个节点
				previous = previous.next;
			}
			//下一个遍历
			current = current.next;
		}
	}
  1. 删除链表中重复的元素,链表为有序链表,跟冒泡排序很类似,当相邻的两个节点的值相等时,删除下一个节点。
	//删除链表中重复的元素
	public void deleteRepetition() {
		//定义临时节点,从首节点开始
		Node previous = first;
		Node current = first;
		
		//遍历到最后一个节点跳出
		while(previous.next != null) {
			while(current.next != null ) {
				if(current.data == current.next.data) {
					//当前节点的值与下一个节点的值相等,删除下一个节点
					current.next = current.next.next;
				}else {
					//不相等继续往后遍历
					current = current.next;
				}
			}
			//下一轮比较
			previous = previous.next;
		}
	}
  1. 查找链表中间的节点,这个算法可以这样解决:设计两个节点,第一个节点每次走一步,第二个节点每次走两步,当第二个节点遍历完之后,第一个节点就是中间节点,可以自己画图理解一下。
	//找到链表中间的元素
	public Node findMiddle() {
		//判断链表是否为空
		if(isEmpty()) {
			return null;
		}
		Node middle = first;
		Node last = first;
		while(last.next != null && last.next.next != null) {
			middle = middle.next;
			last = last.next.next;
		}
		return middle;
	}
  1. 使用递归逆序输出单链表的数据,逆序可以理解为先进后出,这是典型的栈的特性,可以用递归实现,每当访问到一个结点的时候,我们先递归输出它后面的结点,直到递归到最后一个结点时,再返回来输出结点自身。
//通过递归从头到尾输出链表
	public void reverseList(Node head) {
		if(head != null) {
			reverseList(head.next);
			System.out.println(head.data);
		}
	}
  1. 反转链表
    在这里插入图片描述
  • 递归法
           递归法会逐层确定该节点有没有next节点,若为空,则认定递归到最深层元素。同时将该层节点的next指向父节点,在递归栈中以此类推。
	//通过递归反转链表
	public Node reverseList(Node head) {
		if(head == null || head.next == null) {
			return head;
		}else {
			Node newNode = reverseList(head.next);
			head.next.next = head;
			head.next = null;
			return newNode;
		}
	}
  • 遍历(迭代)法
    将当前节点current的下一个节点 current.next缓存到temp后,然后更改当前节点指针指向上一结点previous。也就是说在反转当前结点指针指向前,先把当前结点的指针域用temp临时保存,以便下一次使用,其过程可表示如下:
    1. previous:上一个节点
    2. current:当前节点
    3. temp:临时节点,用于保存当前结点的指针域(即下一结点)
	//通过遍历(迭代)反转链表
	public Node reverseListIte(Node head) {
		Node previous = head;
		Node current = head.next;
		Node temp = null;
		if(head == null) {
			return head;
		}
		while(current != null) {
			//储存当前节点的下一个节点
			temp = current.next;
			//将当前节点的指针(下一个节点)指向前一个节点
			current.next = previous;
			
			//继续遍历
			previous = current;
			current = temp;
		}
		
		//将原链表的头结点的指针域置空
		head.next = null;
		
		//返回新链表的头结点
		return previous;
	}

双链表的实现

       双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。

public class DoubleLinkedList {
	//声明为public,方便存取
	//头指针
	public Node first;
	//尾指针
	public Node last;
	//链表中实际存储的数据的数目
	public int size;
	
	//初始化
	public DoubleLinkedList() {
		this.first = null;
		this.last = null;
		this.size = 0;
	}
	
	//得到链表容量
	public int size() {
		return size;
	}
	
	//判断链表是否为空
	public boolean isEmpty() {
		return size == 0;
	}
	
	//添加头节点
	public void addFirst(int data) {
		//创建新节点
		Node newNode = new Node(data);
		//判断链表是否为空
		if(isEmpty()) {
			last = newNode;
		}else {
			first.previous = newNode;
			newNode.next = first;
		}
		first = newNode;
		size ++;
	}
	
	//添加尾节点
	public void addLast(int data) {
		//创建新节点
		Node newNode = new Node(data);
		//判断链表是否为空
		if(isEmpty()) {
			first = newNode;
		}else {
			last.next = newNode;
			newNode.previous = last;
		}
		last = newNode;
		size ++;
	}
	
	//删除头结点,并返回头结点
	public Node deleteFirst() {
		if(isEmpty()) {
			System.out.println("链表为空!");
			return null;
		}
		Node temp = first;
		if(size == 1) {
			//如果链表中只有一个元素
			last = null;
		}else {
			first.next.previous = null;
		}
		first = first.next;
		size --;
		return temp;
	}
	
	//删除尾节点,并返回尾节点
	public Node deleteLast() {
		if(isEmpty()) {
			System.out.println("链表为空!");
			return null;
		}
		Node temp = last;
		if(size == 1) {
			//如果只有一个元素
			first = null;
		}else {
			last.previous.next = null;
		}
		last = last.previous;
		size --;
		return temp;
	}
	
	//将节点插入到指定值为key的节点后面
	public void insert(int key,int value) {
		//创建要插入的新节点
		Node newNode = new Node(value);
		//创建要插入节点位置上原来的节点
		Node current = first;
		if(isEmpty()) {
			System.out.println("没有值为" + key + "的值!");
			return;
		}
		while(current.data != key) {
			if(current == null) {
				System.out.println("没有值为" + key + "的值!");
				return;
			}
			//往下遍历
			current = current.next;
		}
		current.next.previous = newNode;
		newNode.next = current.next;
		current.next = newNode;
		newNode.previous = current;
		size ++;
	}
	
	//删除特定的节点,并返回该节点
	public Node deleteNode(int value) {
		if(isEmpty()) {
			System.out.println("没有值为" + value + "的值!");
		}
		//创建要删除节点
		Node current = first;
		while(current.data != value) {
			if(current == null) {
				System.out.println("没有值为" + value + "的值!");
			}
			//继续向下遍历
			current = current.next;
		}
		//如果要删除的节点为首节点
		if(current == first) {
			deleteFirst();
		}else if(current == last) {
			//如果要删除的节点为尾节点
			deleteLast();
		}else {
			current.previous.next = current.next;
			current.next.previous = current.previous;
		}
		size --;
		return current;
	}
	
	
	//正向遍历链表
	public void traverseForward() {
		Node current = first;
		while(current != null) {
			System.out.println(current.data);
			current = current.next;
		}
	}
	//反向遍历链表
	public void traverseBackwrad() {
		Node current = last;
		while(current != null) {
			System.out.println(current.data);
			current = current.previous;
		}
	}
}
class Node{
	//声明为public,方便存取
	//指向前一个节点
	public Node previous;
	//指向后一个节点
	public Node next;
	//数据域
	public int data;
	
	public Node(int data) {
		this.data = data;
	}
}

猜你喜欢

转载自blog.csdn.net/qq_39240270/article/details/86766945