前驱元素
:若A元素在B元素的前面,则称A为B的前驱元素
后继元素
:若B元素在A元素的后面,则称B为A的后继元素
线性表是最基本最常用的一种数据结构,一个线性表是n个具有相同特性的数据元素的有限序列;
线性表的特征:数据元素之间具有一对一的逻辑关系
头结点
;尾结点
;有且仅有一个前驱和一个后继
。线性表的分类(按照数据的存储方式不同):
顺序表、链表
顺序表是在内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元,依次存储线性表中的各个元素,使得线性表中逻辑结构上相邻的数据元素存储在相邻的物理存储单元中
顺序表API设计:
类名 | SequenceList |
---|---|
构造方法 | SequenceList(int capacity):创建容量为capacity的SequenceList对象 |
成员方法:
public void clear():空置线性表
publicboolean isEmpty():判断线性表是否为空,是返回true,否返回false
public int length():获取线性表中元素的个数
public T get(int i):读取并返回线性表中的第i个元素的值
public void insert(int i,T t):在线性表的第i个元素之前插入一个值为t的数据元素
public void insert(T t):向线性表中添加一个元素t
public T remove(int i):删除并返回线性表中第i个数据元素
public int indexOf(T t):返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1
成员变量:
private T[] eles:存储元素的数组
private int N:当前线性表的长度
实现:
public class SequenceList<T> {
/*用数组存储元素*/
private T[] elements;
/*记录当前顺序表中元素的个数*/
private int N;
public SequenceList(int capacity) {
//初始化数组
this.elements = (T[]) new Object[capacity];
this.N = 0;
}
public void clear() {
this.N = 0;
}
public boolean isEmpty() {
return this.N == 0;
}
public int length() {
return this.N;
}
public T get(int i) {
return elements[i];
}
public void insert(T t) {
elements[N++] = t;
}
public void insert(int i, T t) {
/*先把i索引的元素及其后的元素向后移动一位*/
for (int index = N ; index > i; --index) {
elements[index] = elements[index-1];
}
elements[i] = t;
N++;
}
public T remove(int i) {
//记录索引i的值
T current = elements[i];
//索引i后的元素向前移动一位
for (int index = i; index < N - 1; index++) {
elements[i] = elements[i + 1];
}
//元素个数减1
N--;
return current;
}
public int indexOf(T t) {
for (int index = 0; index < N; index++) {
if (elements[index].equals(t)) {
return index;
}
}
return -1;
}
}
测试:
public static void main(String[] args) {
//创建顺序表对象
SequenceList<String> s = new SequenceList<>(10);
//测试插入
s.insert("A");
s.insert("B");
s.insert("C");
s.insert(1, "D");
s.insert(1, "E");
s.insert(0, "F"); // F A E D B C
for (int index = 0; index < 6; index++) {
System.out.println("获取索引"+index+"处的结果为:" + s.get(index));
}
//测试删除
String removeResult = s.remove(3);
System.out.println("删除的元素是:" + removeResult);
//测试清空
s.clear();
System.out.println("清空后的线性表中的元素个数为:" + s.length());
}
一般作为容器存储数据,都需要向外部提供遍历的方式
在java中,遍历集合的方式一般都是用的是foreach循环,如果想让自定义类SequenceList支持foreach循环,则
需要:
1.让SequenceList实现Iterable接口,重写iterator方法;
2.在SequenceList内部提供一个内部类SIterator,实现Iterator接口,重写hasNext方法和next方法;
public class SequenceList<T> implements Iterable<T> {
/*用数组存储元素*/
private T[] elements;
/*记录当前顺序表中元素的个数*/
private int N;
public SequenceList(int capacity) {
//初始化数组
this.elements = (T[]) new Object[capacity];
this.N = 0;
}
public void clear() {
this.N = 0;
}
public boolean isEmpty() {
return this.N == 0;
}
public int length() {
return this.N;
}
public T get(int i) {
return elements[i];
}
public void insert(T t) {
elements[N++] = t;
}
public void insert(int i, T t) {
/*先把i索引的元素及其后的元素向后移动一位*/
for (int index = N; index > i; --index) {
elements[index] = elements[index - 1];
}
elements[i] = t;
N++;
}
public T remove(int i) {
//记录索引i的值
T current = elements[i];
//索引i后的元素向前移动一位
for (int index = i; index < N - 1; index++) {
elements[i] = elements[i + 1];
}
//元素个数减1
N--;
return current;
}
public int indexOf(T t) {
for (int index = 0; index < N; index++) {
if (elements[index].equals(t)) {
return index;
}
}
return -1;
}
@Override
public Iterator iterator() {
return new SIterator();
}
private class SIterator implements Iterator {
private int curosr;
public SIterator() {
this.curosr = 0;
}
@Override
public boolean hasNext() {
return curosr < N;
}
@Override
public T next() {
return elements[curosr++];
}
}
}
public static void main(String[] args) {
//创建顺序表对象
SequenceList<String> s = new SequenceList<>(10);
//测试插入
s.insert("A");
s.insert("B");
s.insert("C");
s.insert(1, "D");
s.insert(1, "E");
s.insert(0, "F");
for (String element : s) {
System.out.println(element);
}
}
当使用SequenceList时,先new SequenceList(10)创建一个对象,若已经插入了10个元素,继续插入数据,则会报错
这种设计不符合容器的设计理念,考虑到容器的容量伸缩性,那什么时候需要改变数组的大小?
1.添加元素时
:
添加元素时,应该检查当前数组的大小是否能容纳新的元素,如果不能容纳,则需要创建新的容量更大的数组(原数组大小2倍)
2.移除元素时
:
移除元素时,应该检查当前数组的大小是否太大,造成内存空间的浪费,应该创建一个容量更小的数组存储元素。(数据元素的数量不足数组容量的1/4,创建原数组容量的1/2的新数组)
实现:
public class SequenceList<T> implements Iterable<T> {
/*用数组存储元素*/
private T[] elements;
/*记录当前顺序表中元素的个数*/
private int N;
public SequenceList(int capacity) {
//初始化数组
this.elements = (T[]) new Object[capacity];
this.N = 0;
}
public void clear() {
this.N = 0;
}
public boolean isEmpty() {
return this.N == 0;
}
public int length() {
return this.N;
}
public T get(int i) {
return elements[i];
}
/*插入元素前,比较顺序表元素的个数和数组容量*/
public void insert(T t) {
if (N == elements.length) {
resize(2 * elements.length);
}
elements[N++] = t;
}
public void insert(int i, T t) {
if (N == elements.length) {
resize(2 * elements.length);
}
/*先把i索引的元素及其后的元素向后移动一位*/
for (int index = N; index > i; --index) {
elements[index] = elements[index - 1];
}
elements[i] = t;
N++;
}
/*移除元素之后,比较顺序表元素的个数和数组容量*/
public T remove(int i) {
//记录索引i的值
T current = elements[i];
//索引i后的元素向前移动一位
for (int index = i; index < N - 1; index++) {
elements[i] = elements[i + 1];
}
//元素个数减1
N--;
if (N < elements.length / 4) {
resize(elements.length / 2);
}
return current;
}
public int indexOf(T t) {
for (int index = 0; index < N; index++) {
if (elements[index].equals(t)) {
return index;
}
}
return -1;
}
public void resize(int newSize) {
T[] temp = elements;
elements = (T[]) new Object[newSize];
for (int i = 0; i < N; i++) {
elements[i] = temp[i];
}
}
@Override
public Iterator iterator() {
return new SIterator();
}
private class SIterator implements Iterator {
private int curosr;
public SIterator() {
this.curosr = 0;
}
@Override
public boolean hasNext() {
return curosr < N;
}
@Override
public T next() {
return elements[curosr++];
}
}
}
测试:
public static void main(String[] args) {
//创建顺序表对象
SequenceList<String> s = new SequenceList<>(4);
//测试插入
s.insert("A");
s.insert("B");
s.insert("C");
s.insert(1, "D");
s.insert(1, "E");
s.insert(0, "F");
for (String element : s) {
System.out.println(element);
}
}
get(i):时间复杂度为O(1);
insert(int i,T t):每一次插入,都需要把i位置后面的元素移动一次,时间复杂为O(n);
remove(int i):每一次删除,都需要把i位置后面的元素移动一次,时间复杂度为O(n);
由于顺序表的底层由数组实现,数组的长度是固定的,所以在操作的过程中涉及到了容器扩容操作。这样会导致顺序表在使用过程中的时间复杂度不是线性的,在某些需要扩容的结点处,耗时会突增。
java中ArrayList集合的底层也是一种顺序表,使用数组实现
int newCapacity = oldCapacity + (oldCapacity >> 1);
ArrayList是允许调用带参数的构造方法设置初始容量的,然后按照1.5倍当前设置的初始容量进行扩容
3.有没有提供遍历方式?
/**
* Returns an iterator over the elements in this list in proper sequence.
*
* The returned iterator is fail-fast.
*
* @return an iterator over the elements in this list in proper sequence
*/
public Iterator<E> iterator() {
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列的结点(链表中的每一个元素称为结点)组成,结点可以在运行时动态生成
结点API设计:
类名 | Node |
---|---|
构造方法 | Node(T t,Node next):创建Node对象 |
成员变量:
T item:存储数据
Node next:指向下一个结点
结点类实现:
public class Node<T> {
//存储元素
public T item;
//指向下一个结点
public Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
生成链表:
public static void main(String[] args) throws Exception {
Node<Integer> first = new Node<Integer>(11, null);
Node<Integer> second = new Node<Integer>(13, null);
Node<Integer> third = new Node<Integer>(12, null);
Node<Integer> fourth = new Node<Integer>(8, null);
Node<Integer> fifth = new Node<Integer>(9, null);
//生成链表
first.next = second;
second.next = third;
third.next = fourth;
fourth.next = fifth;
}
单向链表是链表的一种,它由多个结点组成,每个结点都由一个数据域和一个指针域组成,数据域用来存储数据,指针域用来指向其后继结点。链表的头结点的数据域不存储数据,指针域指向第一个真正存储数据的结点
单向链表API设计:
类名 | LinkList |
---|---|
构造方法 | LinkList():创建LinkList对象 |
成员方法:
public void clear():空置线性表
public boolean isEmpty():判断线性表是否为空,是返回true,否返回false
public int length():获取线性表中元素的个数
public T get(int i):读取并返回线性表中的第i个元素的值
public void insert(T t):往线性表中添加一个元素
public void insert(int i,T t):在线性表的第i个元素之前插入一个值为t的数据元素
public T remove(int i):删除并返回线性表中第i个数据元素
public int indexOf(T t):返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1
成员内部类:
private class Node:结点类
成员变量:
private Node head:记录首结点
private int N:记录链表的长度
单向链表代码实现:
public class LinkList<T> implements Iterable<T> {
private Node head;
private int N;
public LinkList() {
//初始化头节点
this.head = new Node(null, null);
this.N = 0;
}
public void clear() {
this.head.next = null;
this.N = 0;
}
public boolean isEmpty() {
return this.N == 0;
}
public int length() {
return this.N;
}
public T get(int i) {
Node n = head.next;
for (int index = 0; index < i; index++) {
n = n.next;
}
return n.item;
}
public void insert(T t) {
//找到当前最后一个节点
Node n = head;
while (n.next != null) {
n = n.next;
}
Node newNode = new Node(t, null);
n.next = newNode;
N++;
}
public void insert(int i, T t) {
Node pre = head;
for (int index = 0; index < i; index++) {
pre = pre.next;
}
Node curr = pre.next;
Node newNode = new Node(t, curr);
pre.next = newNode;
N++;
}
public T remove(int i) {
//找到i位置前一个结点
Node pre = this.head;
for (int index = 0; index < i; index++) {
pre=pre.next;
}
Node curr = pre.next;
//找到i位置下一个结点
Node next = curr.next;
//前一个结点指向下一个节点
pre.next = next;
N--;
return curr.item;
}
public int indexOf(T t) {
//从头节点开始,逐个对比
Node n = head;
for (int i = 0; n.next != null; i++) {
n = n.next;
if (n.item.equals(t)) {
return i;
}
}
return -1;
}
@Override
public Iterator<T> iterator() {
return new LIterator();
}
private class LIterator implements Iterator {
private Node n;
public LIterator() {
n = head;
}
@Override
public boolean hasNext() {
return n.next != null;
}
@Override
public Object next() {
n = n.next;
return n.item;
}
}
private class Node {
public T item;
//指向下一个结点
public Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
}
测试:
public static void main(String[] args) throws Exception {
LinkList<String> list = new LinkList<>();
list.insert(0, "张三");
list.insert(0, "李四");
list.insert(2, "王五");
list.insert(3, "赵六");
list.insert("张1");
list.insert( "张2");
list.insert( "张4");
list.insert( 4,"张5");
for (String s : list) {
System.out.println(s);
}
System.out.println(list.length());
System.out.println("-------------------");
System.out.println(list.get(2));
System.out.println("------------------------");
String remove = list.remove(1);
System.out.println(remove);
System.out.println(list.length());
System.out.println("----------------");
for (String s : list) {
System.out.println(s);
}
}
双向链表由多个结点组成,每个结点都由一个数据域和两个指针域组成
,数据域用来存储数据,其中一个指针域用来指向其后继结点,另一个指针域用来指向前驱结点。链表的头结点的数据域不存储数据,指向前驱结点的指针域值为null,指向后继结点的指针域指向第一个真正存储数据的结点
双向链表API设计:
类名 | TwoWayLinkList |
---|---|
构造方法 | TwoWayLinkList():创建TwoWayLinkList对象 |
成员方法:
public void clear():空置线性表
public boolean isEmpty():判断线性表是否为空,是返回true,否返回false
public int length():获取线性表中元素的个数
public T get(int i):读取并返回线性表中的第i个元素的值
public void insert(T t):往线性表中添加一个元素;
public void insert(int i,T t):在线性表的第i个元素之前插入一个值为t的数据元素。
public T remove(int i):删除并返回线性表中第i个数据元素。
public int indexOf(T t):返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1。
public T getFirst():获取第一个元素
public T getLast():获取最后一个元素
成员内部类:
private class Node:结点类
成员变量:
private Node head:记录首结点
private Node last:记录尾结点
private int N:记录链表的长度
双向链表代码实现:
public class TwoWayLinkList<T> implements Iterable<T> {
private Node head;
private Node last;
private int N;
public TwoWayLinkList() {
this.head = new Node(null, null, null);
this.last = null;
this.N = 0;
}
public void clear() {
this.head.next = null;
this.last = null;
this.N = 0;
}
public boolean isEmpty() {
return N == 0;
}
public int length() {
return N;
}
public T get(int i) {
Node n = this.head.next;
for (int index = 0; index < i; index++) {
n = n.next;
}
return n.item;
}
public void insert(T t) {
//链表为空
if (isEmpty()) {
Node newNode = new Node(t, head, null);
last = newNode;
head.next = newNode;
} else {
Node oldLast = this.last;
//创建新结点
Node newNode = new Node(t, oldLast, null);
//当前的尾结点指向新结点
oldLast.next = newNode;
//让新结点称为尾结点
last = newNode;
}
N++;
}
public void insert(int i, T t) {
Node pre = this.head;
//找到i位置前一个节点
for (int index = 0; index < i; index++) {
pre = pre.next;
}
//找到i位置节点
Node curr = pre.next;
Node newNode = new Node(t, pre, curr);
pre.next = newNode;
curr.pre = newNode;
N++;
}
public T remove(int i) {
//找到前一个结点
Node pre = this.head;
for (int index = 0; index < i; index++) {
pre = pre.next;
}
//找到索引处结点
Node curr = pre.next;
//找到索引处下一个结点
Node next = curr.next;
pre.next = next;
next.pre = pre;
return curr.item;
}
public int indexOf(T t) {
Node n = this.head;
for (int index = 0; index < N; index++) {
n = n.next;
if (n.item.equals(t)) {
return index;
}
}
return -1;
}
public T getFirst() {
if (isEmpty()) {
return null;
}
Node first = head.next;
return first.item;
}
public T getLast() {
if (isEmpty()) {
return null;
}
return last.item;
}
@Override
public Iterator<T> iterator() {
return new TIterator();
}
private class TIterator implements Iterator {
Node n = head;
@Override
public boolean hasNext() {
return n.next != null;
}
@Override
public Object next() {
n = n.next;
return n.item;
}
}
private class Node {
public Node(T item, Node pre, Node next) {
this.item = item;
this.pre = pre;
this.next = next;
}
public T item;
public Node pre;
public Node next;
}
}
测试:
public static void main(String[] args) throws Exception {
TwoWayLinkList<String> list = new TwoWayLinkList<>();
list.insert("乔峰");
list.insert("虚竹");
list.insert("段誉");
list.insert(1, "鸠摩智");
list.insert(3, "叶二娘");
list.insert("a");
list.insert(5, "b");
for (String str : list) {
System.out.println(str);
}
System.out.println("----------------------");
String two = list.get(2);
System.out.println(two);
System.out.println("-------------------------");
String remove = list.remove(3);
System.out.println(remove);
System.out.println(list.length());
System.out.println("--------------------");
System.out.println(list.getFirst());
System.out.println(list.getLast());
}
get(int i):每一次查询,都需要从链表的头部开始,依次向后查找,时间复杂度为O(n)
insert(int i,T t):每一次插入,需要先找到i位置的前一个元素,然后完成插入操作,时间复杂度为O(n)
remove(int i):每一次移除,需要先找到i位置的前一个元素,然后完成插入操作,时间复杂度为O(n)
相比较顺序表,链表插入和删除的时间复杂度虽然一样,但因为链表的物理地址是不连续的,不需要预先指定存储空间大小,或者在存储过程中涉及到扩容操作,同时并没有涉及的元素的交换。
相比较顺序表,链表的查询操作性能会比较低。
因此,若查询多,建议使用顺序表;若增删多,建议使用链表
反转API:
public void reverse():对整个链表反转
public Node reverse(Node curr):反转链表中的某个结点curr,并把反转后的curr结点返回
代码实现:
public class TwoWayLinkList<T> implements Iterable<T> {
private Node head;
private Node last;
private int N;
public TwoWayLinkList() {
this.head = new Node(null, null, null);
this.last = null;
this.N = 0;
}
/*反转整个链表*/
public void reverse() {
if (isEmpty()) {
return;
} else {
reverse(head.next);
}
}
/*反转指定结点并返回反转后的结点*/
public Node reverse(Node curr) {
if(curr.next==null){
head.next=curr;
return curr;
}
//获取 当前结点的下一个结点
Node currNext = reverse(curr.next);
//递归地反转 当前结点 和 当前结点的下一个结点
currNext.next=curr;
curr.next=null;
return curr;
}
public void clear() {
this.head.next = null;
this.last = null;
this.N = 0;
}
public boolean isEmpty() {
return N == 0;
}
public int length() {
return N;
}
public T get(int i) {
Node n = this.head.next;
for (int index = 0; index < i; index++) {
n = n.next;
}
return n.item;
}
public void insert(T t) {
//链表为空
if (isEmpty()) {
Node newNode = new Node(t, head, null);
last = newNode;
head.next = newNode;
} else {
Node oldLast = this.last;
//创建新结点
Node newNode = new Node(t, oldLast, null);
//当前的尾结点指向新结点
oldLast.next = newNode;
//让新结点称为尾结点
last = newNode;
}
N++;
}
public void insert(int i, T t) {
Node pre = this.head;
//找到i位置前一个节点
for (int index = 0; index < i; index++) {
pre = pre.next;
}
//找到i位置节点
Node curr = pre.next;
Node newNode = new Node(t, pre, curr);
pre.next = newNode;
curr.pre = newNode;
N++;
}
public T remove(int i) {
//找到前一个结点
Node pre = this.head;
for (int index = 0; index < i; index++) {
pre = pre.next;
}
//找到索引处结点
Node curr = pre.next;
//找到索引处下一个结点
Node next = curr.next;
pre.next = next;
next.pre = pre;
return curr.item;
}
public int indexOf(T t) {
Node n = this.head;
for (int index = 0; index < N; index++) {
n = n.next;
if (n.item.equals(t)) {
return index;
}
}
return -1;
}
public T getFirst() {
if (isEmpty()) {
return null;
}
Node first = head.next;
return first.item;
}
public T getLast() {
if (isEmpty()) {
return null;
}
return last.item;
}
@Override
public Iterator<T> iterator() {
return new TIterator();
}
private class TIterator implements Iterator {
Node n = head;
@Override
public boolean hasNext() {
return n.next != null;
}
@Override
public Object next() {
n = n.next;
return n.item;
}
}
private class Node {
public Node(T item, Node pre, Node next) {
this.item = item;
this.pre = pre;
this.next = next;
}
public T item;
public Node pre;
public Node next;
}
}
测试
public static void main(String[] args) throws Exception {
TwoWayLinkList<String> list = new TwoWayLinkList<>();
list.insert("乔峰");
list.insert("虚竹");
list.insert("段誉");
list.insert(1, "鸠摩智");
list.insert(3, "叶二娘");
list.insert("a");
list.insert(5, "b");
for (String str : list) {
System.out.print(str+" ");
}
list.reverse();
System.out.println();
for (String str : list) {
System.out.print(str+" ");
}
}