实现线性表
线性表是最基本、最简单、也是最常用的一种数据结构。一个线性表是n个具有相同特性的数据元素的有限序列。
前驱元素:
若A元素在B元素的前面,则称A为B的前驱元素
后继元素:
若B元素在A元素的后面,则称B为A的后继元素
线性表的特征:数据元素之间具有一种"一对一”的逻辑关系。
1.第一个数据元素没有前驱,这个数据元素被称为头结点;
2.最后一个数据元素没有后继,这个数据元素被称为尾结点;
3.除了第一个和最后一个数据元素外,其他数据元素有且仅有一个前驱和一个后继。
如果把线性表用数学语言来定义,则可以表示为(a1...ai-1, ai ,ai+1...an) , ai-1领先于ai,ai领先于ai+1 ,称ai-1是ai的前驱元素, ai+1是ai的后继元素
线性表的分类:
线性表中数据存储的方式可以是顺序存储,也可以是链式存储,按照数据的存储方式不同,可以把线性表分为顺序表和链表。这两种方式拥有相同的操作,但是具有不同的实现。
设计:通用的操作可以归纳为一个接口或者一个抽象类。一个好的策略就是在设计中提供接口和便利抽象类,以整合接口和抽象类的优点,这样用户可以认为那个方便就使用哪个。抽象类提供了接口的骨架实现,可以更有限的实现接口。
接口命名:MyList,便利抽象类命名:MyAbstractList
package com.lenovo.data.structure.list; /** * 线性表的通用特性在List接口中定义 * @author Tiger * @param <E>形式泛型类型 */ public interface MyList<E> extends Iterable<E> { /** * 添加一个元素到list末尾 * @param e 新元素 */ void add(E e); /** * 添加一个新元素到list指定位置 * @param index 指定位置 * @param e 新元素 */ void add(int index,E e); /** * 清空线性表list */ void clear(); /** * list中是否包含元素e * @param e 元素e * @return 包含元素e返回true */ boolean contains(E e); /** * 获取list指定位置的元素 * @param index 索引位置 * @return 指定索引位置的元素 */ E get(int index); /** * 返回第一次匹配元素e的索引位置 * @param e 元素e * @return 第一次匹配元素e的索引位置,如果没有匹配,返回-1 */ int indexOf(E e); /** * 该list是否为空 * @return list为空返回true */ boolean isEmpty(); /** * 返回最后匹配元素e的索引位置 * @param e 匹配元素e * @return 最后匹配元素e的索引位置,如果没有匹配,返回-1 */ int lastIndexOf(E e); /** * 从list中删除元素e * @param e 要删除的元素e * @return 如果元素被删除返回true(即元素存在且被删除) */ boolean remove(E e); /** * 删除指定索引位置的元素 * @param index 指定索引 * @return 删除的元素 */ E remove(int index); /** * 替换list中指定位置的元素 * @param index 指定位置 * @param e 替换后的元素 * @return 返回旧元素,即替换前的元素 */ Object set(int index,E e); /** * 返回list中元素个数 * @return 元素个数 */ int size(); }
MyAbstractList声明变量size,表示线性表中元素的个数。可以实现通用的便利操作isEmpty()、size()、add(E)和remove(E)方法
package com.lenovo.data.structure.list; /** * 便利性抽象类,提供接口的部分实现 * @author Tiger * @param <E> 形式泛型类型 */ public abstract class MyAbstractList<E> implements MyList<E> { protected int size = 0;//The size of the list protected MyAbstractList() { } /** * create a list from an array of objects * @param objects */ protected MyAbstractList(E[] objects) { for(int i = 0; i < objects.length;i++) add(objects[i]); } @Override public void add(E e) { add(size,e); } @Override public boolean isEmpty() { return size == 0; } @Override public boolean remove(E e) { if(indexOf(e) >= 0) { remove(indexOf(e)); return true; } return false; } @Override public int size() { return size; } }
顺序表的实现
顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元,依次存储线性表中的各个元素、使得线性表中再逻辑结构上响铃的数据元素存储在相邻的物理存储单元中.即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系。
package com.lenovo.data.structure.list; import java.util.Iterator; /** * 自定义数组线性表 * @author Tiger * @param <E> */ public class MyArrayList<E> extends MyAbstractList<E> implements MyList<E> { private static final int INITIAL_CAPACITY = 16; @SuppressWarnings("unchecked")//创建一个初始数组 private E[] data = (E[])new Object[INITIAL_CAPACITY]; public MyArrayList() { } public MyArrayList(E[] objects) { for(int i = 0; i < objects.length;i++) add(objects[i]); } @Override public Iterator<E> iterator() { return new ArrayListIterator(); } /** * 创建一个新的大数组,大小为当前数组容量的2倍+1 */ private void ensureCapacity() { if(size >= data.length) { @SuppressWarnings("unchecked") E[] newData = (E[])new Object[data.length * 2 + 1]; System.arraycopy(data, 0, newData, 0, size); data = newData; } } @Override public void add(int index, E e) { ensureCapacity(); for(int i = size - 1; i >= index;i--) data[i + 1] = data[i]; data[index] = e; size++; } @SuppressWarnings("unchecked") @Override public void clear() { data = (E[])new Object[INITIAL_CAPACITY]; size = 0; } @Override public boolean contains(E e) { for(int i = 0; i < size; i++) if(e.equals(data[i])) return true; return false; } @Override public E get(int index) { checkIndex(index); return data[index]; } /** * 如果下标超出了线性表的界限,则抛出一个异常 * @param index 索引下标 */ private void checkIndex(int index) { if(index < 0 || index >= size) throw new IndexOutOfBoundsException("index " + index + " out of bounds"); } @Override public int indexOf(E e) { for(int i = 0; i < size; i++) if(e.equals(data[i])) return i; return -1; } @Override public int lastIndexOf(E e) { for(int i = size - 1; i >= 0; i--) if(e.equals(data[i])) return i; return -1; } @Override public E remove(int index) { checkIndex(index); E ret = data[index]; for(int i = index; i < size - 1; i++) data[i] = data[i + 1]; data[size - 1] = null; size--; return ret; } @Override public Object set(int index, E e) { checkIndex(index); E old = data[index]; data[index] = e; return old; } @Override public String toString() { StringBuilder sb = new StringBuilder("["); for(int i = 0; i < size; i++) { sb.append(data[i]); if(i < size - 1) sb.append(", "); } return sb.toString() + "]"; } /** * 将容量调整为当前大小 */ @SuppressWarnings("unchecked" ) public void trimToSize() { if(size != data.length) { E[] newData = (E[])new Object[size]; System.arraycopy(data, 0, newData, 0, size); data = newData; } } /** * ArrayList迭代器 * */ private class ArrayListIterator implements Iterator<E>{ private int current = 0;//current index @Override public boolean hasNext() { return current < size; } @Override public E next() { return data[current++]; } @Override public void remove() { MyArrayList.this.remove(current); } } }
链表
之前我们已经使用顺序存储结构实现了线性表,我们会发现虽然顺序表的查询很快,时间复杂度为0(1 ),但是增删的效率是比较低的,因为每一次增删操作都伴随着大量的数据元素移动。 这个问题有没有解决方案呢?有,我们可以使用另外一种存储结构实现线性表,链式存储结构。
链表是一种物理存储单元上非连续、非顺序的存储结构, 数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列的结点(链表中的每一个元素称为结点 )组成,结点可以在运行时动态生成。
按如下方式定义一个结点
class Node<E>{ E element; Node<E> next; public Node(E element) { this.element = element; } }
变量head指向线性表(链表)的第一个结点,而变量tail指向最后一个结点。如果线性表为空,head和tail这两个变量均为null
package com.lenovo.data.structure.list; import java.util.Iterator; import java.util.NoSuchElementException; /** * 采用链表(链接结构)实现线性表 * @author Tiger * * @param <E> */ public class MyLinkedList<E> extends MyAbstractList<E> implements MyList<E>{ //head指向第一个结点,tail指向最后一个结点 private Node<E> head,tail; public MyLinkedList() { } public MyLinkedList(E[] objects) { super(objects); } /** * 返回list中的第一个(首)元素 */ public E getFirst() { final Node<E> node = head; if (node == null) throw new NoSuchElementException(); return node.element; } /** * 返回list中的最后一个(尾)元素 */ public E getLast() { final Node<E> node = tail; if (node == null) throw new NoSuchElementException(); return node.element; } /** * 创建一个包含元素e的新结点。该新结点称为链表的第一个结点 * @param e 元素e */ public void addFirst(E e) { Node<E> node = new Node<>(e); node.next = head; head = node;//插入后,head应该指向新结点 //创建一个结点后,链表的大小都加1 size++; if(tail == null) tail = head;//如果链表为空,那么head和tail都将指向该结点 } /** * 创建一个新结点来存储元素,并且将它追加到链表的末尾 * @param e */ public void addLast(E e) { Node<E> node = new Node<>(e); //如果链表为空,那么head和tail都将指向该结点 if(tail == null) head = tail = node; else {//将该结点和该链表的最后一个结点相链接。tail应该指向新结点 tail.next = node; tail = tail.next; } size++; //创建一个结点后,链表的大小都加1 } public E removeFirst() { Node<E> temp = head; if(temp == null) throw new NoSuchElementException(); head = head.next; size--; if(head == null) tail = null; return temp.element; } public E removeLast() { if(size == 0) throw new NoSuchElementException(); else if(size == 1) { Node<E> temp = head; head = tail = null; size = 0; return temp.element; }else { Node<E> current = head; for(int i = 0; i < size - 2; i++)current = current.next; Node<E> temp = head; tail = current; tail.next = null; size--; return temp.element; } } @Override public Iterator<E> iterator() { return new LinkedListIterator(); } @Override public void add(int index, E e) { if(index == 0) addFirst(e); else if(index >= size)addLast(e); else { Node<E> current = head; for(int i = 0; i < index; i++)//定位插入位置 current = current.next; Node<E> temp = current.next;//新结点应该插入到current和temp之间 current.next = new Node<E>(e); current.next.next = temp; size++; } } @Override public void clear() { size = 0; head = tail = null; } @Override public boolean contains(E e) { return indexOf(e) >= 0; } @Override public E get(int index) { if(index < 0 || index >= size) throw new IndexOutOfBoundsException(index); else { Node<E> current = head; for(int i = 0; i < index; i++) { current = current.next; } return current.element; } } @Override public int indexOf(E e) { int index = 0; if (e == null) { for (Node<E> node = head; node != null; node = node.next) { if (node.element == null) return index; index++; } } else { for (Node<E> node = head; node != null; node = node.next) { if (e.equals(node.element)) return index; index++; } } return -1; } @Override public int lastIndexOf(E e) { int index = -1; Node<E> node = head; if (e == null) { for(int i = 0;i < size; i++) { if(node.element == null) index = i; node = node.next; } } else { for(int i = 0;i < size; i++) { if(e.equals(node.element)) index = i; node = node.next; } } return index; } /** * 找到指定下标处的结点,然后将它删除 */ @Override public E remove(int index) { //index超出链表范围 if(index < 0 || index >= size) throw new IndexOutOfBoundsException(index); //index == 0,删除第一个结点 else if(index == 0)return removeFirst(); //index == size - 1,删除最后一个结点 else if(index == size - 1) return removeLast(); else { //找到index位置的结点,用current指向这个结点,用previous指向该结点的前一个结点 Node<E> previous = head; for(int i = 1; i < index; i++) previous = previous.next; Node<E> current = previous.next; previous.next = current.next;//删除当前结点 size--; return current.element; } } @Override public Object set(int index, E e) { if(index < 0 || index >= size) throw new IndexOutOfBoundsException(index); Node<E> current = head; for(int i = 0; i < index; i++) { current = current.next; } Object oldValue = current.element; current.element = e; return oldValue; } /** * LinkedList中的每个元素都包含一个称为结点(Node)的结构,每个结点和它相邻的结点相链接 * 该结点类只是使用于LinkedList,所以定义为private * 该结点类不需要访问任何成员变量,所以被定义为static */ private static class Node<E>{ E element; Node<E> next; public Node(E element) { this.element = element; } } private class LinkedListIterator implements Iterator<E> { private Node<E> current = head; @Override public boolean hasNext() { return current != null; } @Override public E next() { E ele = current.element; current = current.next; return ele; } @Override public void remove() { MyLinkedList.this.remove(current.element); } } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!