数据结构与算法(3)

动态链表存储结构

这个版块主要学习以下的内容,本次讲解的主要是线性表的链式单向存储结构

链表的定义

为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了储存本省的信息之外,还需要存储一个指示其直接后继的信息(即直接后继的存储位置)。我们把存储数元素信息的域称为数据域,把存储直接后继位置的域称为指针域,指针域中存储的信息称做指针或链。这两部分信息组成数据元素ai的存储映像,称为结点(Node)
n个结点(ai的存储映像)链接成一个链表,即为线性表(a1,a2…an)的链式存储结构,因为此链表的每个结点中只包含一个指针域,所以叫做单链表。

结点组成:数据域data,指针域next.

例如这个图称为单链表
在这里插入图片描述

头结点与头指针

头结点是指链表中的第一结点,有真实头结点和虚拟头结点之分
那么问题来了,何为真实头结点和虚拟头结点

真实头结点:其第一个结点用于存储数据
虚拟头结点:其第一个结点不允许存储数据(这里说明一下,本篇就采用虚拟头结点讲述单链表)

再用两个图来说明以下
在这里插入图片描述
此图为真实头结点,根据概念就可以看出来,该链表的头结点有数据;而虚拟头结点没有数据,如下图所示
在这里插入图片描述
通过两张图我们显而易见,可以直观的分清楚真实头结点和虚拟头结点。
头指针:仅仅是一个引用变量,存储头结点地址的指针而已,这里解释以下,当你创建一个结点时,相当于在堆内存中,创建一个链表对象,然后你在给这个对象赋值,也就是存储的数据元素。
尾指针:同头指针,不过尾指针是链表中最后一个结点的指针而已。
在这里插入图片描述
这里的head指头指针,而rear指尾指针。

线性表的链式存储结构LinkedList的定义

在这里插入图片描述

说明一点,LinkedList和Node里面的属性蓝色杠为私有属性。
LinkedList宏观讲为链表,但是它是由一堆结点链接起来的链表,存在结点说法,所以有一个Node类,那我们可以将Node当作一个内部类来写。

插入元素—头插法

头插法分两种情况:
在这里插入图片描述

第一种当此时链表为空,那么插入元素A,操作是将把头结点的下一跳给新结点下一跳,再将新元素的地址给头结点的下一跳,再将尾指针指向A
第二种此时链表不为空,那么插入元素B,操作是将头结点的下一跳给新结点的下一跳,再将B的地址给头结点的下一跳,此时尾指针不需要移动
区别在于:链表为空需要移动尾指针,而不为空咋不需要移动尾指针

代码实现

/**
	 * 因为我们一般插入中,是不是当index=0时,就相当于头插
	 * 那么我们可以直接调用add方法,让它的index赋值0
	 * */
	@Override
	public void addFirst(E e) {
		add(0,e);
	}

插入元素—尾插法

在这里插入图片描述

尾插法操作,此时链表为空,先将新元素的地址给尾结点结点的下一跳,尾结点后移。再插入一个元素, 将此元素的地址给上一元素的下一跳,尾指针后移。

代码实现

/**
	 * 同样,当我们尾插时是不是当index=size时,就相当于尾插
	 * 那么我们可以直接调用add方法,让它的index赋值size
	 * */
	@Override
	public void addLast(E e) {
		add(size,e);
	}

插入元素—头插尾插结合

头插:将头结点的下一跳给新元素下一跳,将新元素的地址指向头结点,此时链表不为空,若为空,尾结点需要后移。
尾插:将新元素的地址给尾指针下一跳,尾指针后移,此时链表不为空。

插入元素—一般插入

在这里插入图片描述

看图让我们把D元素插入到AB之间,操作将A节点的下一跳给D,再将D的地址给A下一跳,其实此时的操作类似于头插法,这里我们将A看作头结点,就完全是头插法。

代码实现

/**
	 * 链表的一般插入
	 * 这里的index我们可以当作是一种链表的编号,链表不存在角标。
	 * */
	@Override
	public void add(int index, E e) {
		if(index < 0||index > size) {
			throw new IllegalArgumentException("插入角标非法");
		}
		Node n = new Node(e,null);
		size++;
		if(index == 0) { //相当于头插法
			n.next = head.next;
			head.next = n;
			if(size == 0) {
				rear = n;
			}
		} else if(index == size) {//相当于尾插法
			rear.next = n;
			rear = rear.next;
		} else {
			Node p = head;
			for(int i=0;i<index;i++) {
				p=p.next;
			}
			n.next = p.next;
			p.next = n.next;
		}
	}

删除元素—删头

在这里插入图片描述
这里也分为两种情况:

第一种链表元素大于1,操作将A头删除,将A结点的下一跳给头节点的下一跳,当然这里A和B之间还存在关系,这里要清除AB最后的关系。
第二种链表元素只有A,操作A的下一跳指向头结点的下一跳,将尾指针前移。

代码实现

/**
	 * 删除链表头元素
	 * */
	@Override
	public E removeFirst() {
		return remove(0);
	}

删除元素—删尾

在这里插入图片描述

这里删除尾,先找到尾结点前一个结点,将尾结点前移,再将尾结点的下一跳制空。

代码实现

/**
	 * 删除链表尾元素
	 * */
	@Override
	public E removeLast() {
		return remove(size-1);
	}

删除元素—一般删除

在这里插入图片描述

删除B元素,先找到B元素的前一结点,然后将B结点的下一跳给A的下一跳,再将B的下一跳制空。

代码实现

/**
	 * 链表元素的一般删除
	 * */
	@Override
	public E remove(int index) {
		if(index < 0||index >= size) {
			throw new IllegalArgumentException("删除角标非法");
		}
		E res = null; 
		if(index == 0) { //相当于头删除
			Node p = head;
			res = p.data;
			head.next = p.next;
			p.next = null;
			p = null;
			if(size == 1) {
				rear = head;
			}
			size--;
			return res;
		} else if(index == size-1) { //相当于尾删除
			Node p = head;
			res = rear.data;
			while(p.next != rear) {
				p = p.next;
			}
			p.next = null;
			rear = p;
			size--;
			return res;
		} else {			
			Node p = head;
			for(int i=0;i<index;i++) {
				p = p.next;
			}
			Node del = p.next;
			res = del.data;
			p.next = del.next;
			del.next = null;
			del = null;
		}
		size--;
		return res;
	}

以上内容为详解思路,下面为具体代码实现,里面还存在清空链表,toString方法重写等。
实现代码

package com.study.动态链表;

import com.study.shuzu.List;

public class LinkedList<E> implements List<E> {
	/**
	 * 单向链表的结点类
	 * */
	private class Node{
		E data;//数据域
		Node next;//指针域
		public Node() {
			this(null,null);
		}
		public Node(E data,Node next) {
			this.data = data;
			this.next = next;
		}
		@Override
		public String toString() {
			return data.toString();
		}
	}
	
	private Node head; //指向头结点的头指针
	private Node rear; //指向尾结点的尾指针
	private int size; //记录元素的个数
	
	public  LinkedList() {
		head = new Node();
		rear = head;
		size = 0;
	}
	public LinkedList(E[] arr) {
		head = new Node();
		rear = head;
		size = 0;
	}
	
	@Override
	public int getSize() {
		return size;
	}

	@Override
	public boolean isEmpty() {
		return head.next == null&&size == 0;
	}
	/**
	 * 链表的一般插入
	 * 这里的index我们可以当作是一种链表的编号,链表不存在角标。
	 * */
	@Override
	public void add(int index, E e) {
		if(index < 0||index > size) {
			throw new IllegalArgumentException("插入角标非法");
		}
		Node n = new Node(e,null);
		size++;
		if(index == 0) { //相当于头插法
			n.next = head.next;
			head.next = n;
			if(size == 0) {
				rear = n;
			}
		} else if(index == size) {//相当于尾插法
			rear.next = n;
			rear = rear.next;
		} else {
			Node p = head;
			for(int i=0;i<index;i++) {
				p=p.next;
			}
			n.next = p.next;
			p.next = n.next;
		}
	}
	/**
	 * 因为我们一般插入中,是不是当index=0时,就相当于头插
	 * 那么我们可以直接调用add方法,让它的index赋值0
	 * */
	@Override
	public void addFirst(E e) {
		add(0,e);
	}
	/**
	 * 同样,当我们尾插时是不是当index=size时,就相当于尾插
	 * 那么我们可以直接调用add方法,让它的index赋值size
	 * */
	@Override
	public void addLast(E e) {
		add(size,e);
	}
	/**
	 * 获取指定位置的元素
	 * */
	@Override
	public E get(int index) {
		if(index < 0||index >= size) {
			throw new IllegalArgumentException("查找角标非法");
		}
		if(index == 0) {
			return head.next.data;
		} else if(index == size-1) {
			return rear.data;
		}
		Node p = head;
		for(int i=0;i<index;i++) {
			p = p.next;
		}
		return p.data;
	}
	/**
	 * 获取链表首元素
	 * */
	@Override
	public E getFirst() {
		return get(0);
	}
	/**
	 * 获取链表尾元素
	 * */
	@Override
	public E getLast() {
		return get(size-1);
	}
	/**
	 * 修改链表指定位置的元素
	 * */
	@Override
	public void set(int index, E e) {
		if(index < 0||index >= size) {
			throw new IllegalArgumentException("修改角标非法");
		}
		if(index == 0) {
			head.next.data = e;
		} else if(index == size-1) {
			rear.data = e;
		}
		Node p = head;
		for(int i=0;i<index;i++) {
			p = p.next;
		}
		p.data = e;
	}
	/**
	 * 看链表中是否包含某个元素
	 * */
	@Override
	public boolean contains(E e) {
		
		return find(e) != -1;
	}
	/**
	 * 查询链表元素的位置
	 * */
	@Override
	public int find(E e) {
		int index = -1;
		if(isEmpty()) {
			return index;
		}
		Node p = head;
		while(p.next != null) {
			p = p.next;
			index++;
			if(p.data == e) {
				return index;
			}
		}
		return -1;
	}
	/**
	 * 链表元素的一般删除
	 * */
	@Override
	public E remove(int index) {
		if(index < 0||index >= size) {
			throw new IllegalArgumentException("删除角标非法");
		}
		E res = null; 
		if(index == 0) { //相当于头删除
			Node p = head;
			res = p.data;
			head.next = p.next;
			p.next = null;
			p = null;
			if(size == 1) {
				rear = head;
			}
			size--;
			return res;
		} else if(index == size-1) { //相当于尾删除
			Node p = head;
			res = rear.data;
			while(p.next != rear) {
				p = p.next;
			}
			p.next = null;
			rear = p;
			size--;
			return res;
		} else {			
			Node p = head;
			for(int i=0;i<index;i++) {
				p = p.next;
			}
			Node del = p.next;
			res = del.data;
			p.next = del.next;
			del.next = null;
			del = null;
		}
		size--;
		return res;
	}
	/**
	 * 删除链表头元素
	 * */
	@Override
	public E removeFirst() {
		return remove(0);
	}
	/**
	 * 删除链表尾元素
	 * */
	@Override
	public E removeLast() {
		return remove(size-1);
	}
	/**
	 * 删除元素
	 * */
	@Override
	public void removeElement(E e) {
		int index = find(e);
		if(index==-1) {
			throw new IllegalArgumentException("元素不存在");
		}
		remove(index);
	}
	/**
	 * 清空链表
	 * */
	@Override
	public void clear() {
		head.next = null;
		rear = head;
		size = 0;
	}
	/**
	 * toString实现
	 * */
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("LinkedList:size="+getSize());
		if(isEmpty()) {
			sb.append("[]");
		}else {
			sb.append('[');
			Node p = head;
			while(p.next != null) {
				p = p.next;
				if(p == rear) {
					sb.append(p.data + ",");
				} else {
					sb.append(']');
				}
			}
		}
		
		return sb.toString();
	}
	
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值