链表,也称为列表,即List
一、链表的结构
1.链表的逻辑结构
- 链表的第一个元素叫表名,最后一个元素叫表尾
- 每个元素有一个指向下一元素地址的指针
2.链表的存储结构
- 每个链表的元素的存储单元未必是连续的空间
3.链表元素的插入
(1)在元素A后面插入元素C
(2)将元素A的下一元素的指针指向元素C,并将元素C的下一个元素的指针指向元素B
4.链表元素的删除
(0)删除元素F
(1)将删除元素的前一个元素的指向下一元素的指针指向删除元素的下一个元素
二、单向链表
- 每个元素不仅有存储了本元素指向的数据,还有一个指向下一元素的指针
三、双向链表
- 每个元素不仅有存储了本元素指向的数据,还有一个指向上一元素的指针,一个指向下一元素的指针
四、循环链表
- 相对于单(双)向链表,循环链表的表尾指向了表头,整个链表形成了一个循环
五、通过java实现简单的链表
1.基于数组实现的数组列表ArrayList
自定义实现一个可扩容的动态数组
package com.shixinke.demo.dataStruct;
public class MyArrayList<E> {
/**
* 存储的真实的元素个数
*/
private int size;
/**
* 存储的元素值集合
*/
private E[] data;
/**
* 默认的列表容量
*/
private static final int DEFAULT_CAPACITY = 10;
public MyArrayList() {
clearList(DEFAULT_CAPACITY);
}
public MyArrayList(int capacity) {
clearList(capacity);
}
/**
* 清空列表
*/
public void clear() {
clearList(DEFAULT_CAPACITY);
}
private void clearList(int capacity) {
this.size = 0;
if (capacity < 0) {
capacity = DEFAULT_CAPACITY;
}
ensureCapacity(capacity);
}
/**
* 获取数据长度
* @return
*/
public int size() {
return this.size;
}
/**
* 是否为空链表
* @return
*/
public boolean isEmpty() {
return this.size == 0;
}
/**
* 设置值
* @param index 索引值
* @param value 设置的值
*/
public void set(int index, E value) {
if (index > this.data.length) {
throw new ArrayIndexOutOfBoundsException();
}
this.data[index] = value;
}
/**
* 获取值
* @param index 索引值
* @return
*/
public E get(int index) {
if (index > this.data.length) {
throw new ArrayIndexOutOfBoundsException();
}
return this.data[index];
}
public void add(E value) {
add(this.size(), value);
}
/**
* 添加元素
* @param index
* @param value
*/
public void add(int index, E value) {
//1.如果没有剩余的空间,则扩容
if (this.size == this.data.length) {
ensureCapacity(this.size* 2 + 1);
}
//2.插入位置的后面的元素依次向后移动(从最后的一个元素开始移动,因为插入后,最后一个元素的位置从size-1到了size)
for (int i = this.size; i > index; i --) {
this.data[i] = this.data[i-1];
}
//3.设置插入元素的值
this.data[index] = value;
//4.增加元素个数
this.size ++;
}
/**
* 删除元素
* @param index
* @return
*/
public E remove(int index) {
//1.判断索引值是否超过存储的长度
if (index > this.size) {
throw new ArrayIndexOutOfBoundsException();
}
//2.获取要删除的元素的值
E oldData = this.data[index];
//3.删除的元素后面的元素依次向前移动
for (int i = index; i < this.size; i++) {
this.data[i] = this.data[i + 1];
}
//4.减少元素个数
this.size --;
return oldData;
}
/**
* 是否包含某个元素
* @param value
* @return
*/
public boolean contains(E value) {
for (E e : this.data) {
if (value.equals(e)) {
return true;
}
}
return false;
}
/**
* 返回某个数值的索引值
* @param value
* @return
*/
public int indexOf(E value) {
for (int i = 0; i < this.data.length; i++) {
if (value.equals(this.data[i])) {
return i;
}
}
return -1;
}
public void print() {
System.out.println();
for (int i = 0; i < this.size; i++) {
System.out.println(this.data[i]);
}
System.out.println();
}
/**
* 列表扩容
* @param capacity
*/
public void ensureCapacity(int capacity) {
if (capacity < this.size) {
return;
}
E[] oldData = data;
this.data = (E[]) new Object[capacity];
for (int i = 0; i < this.size; i++) {
this.data[i] = oldData[i];
}
}
}
注:
(1) 列表的属性:
- size : 列表元素的个数
- data : 列表元素的值
(2)添加元素(插入元素)
- 第一步:判断当前是否有剩余空间(当元素个数和存储的空间一致时,即size == data.length),如果没有剩余空间,也采用二倍的长度进行扩容
- 第二步:将插入的位置后面的所有元素都往后移动
- 第三步:设置插入的元素的值
- 第四步:增加元素的个数
(3)删除元素
- 第1步.判断索引值是否超过存储的长度
- 第2步.获取要删除的元素的值
- 第3步.删除的元素后面的元素依次向前移动
- 第4步.减少元素个数
(4)测试
public class MyArrayListTest {
public static void main(String[] args) {
MyArrayList<Integer> arrayList = new MyArrayList<Integer>(5);
arrayList.add(11);
arrayList.add(12);
arrayList.add(13);
arrayList.print();
arrayList.add(1, 20);
arrayList.print();
arrayList.add(3, 35);
arrayList.add(100);
arrayList.print();
System.out.println(arrayList.contains(10)); //false
System.out.println(arrayList.contains(100)); //true
System.out.println(arrayList.indexOf(13)); //4
arrayList.set(5, 50);
System.out.println(arrayList.get(5)); //50
arrayList.remove(4);
arrayList.print();
arrayList.clear();
arrayList.print();
System.out.println(arrayList.isEmpty()); //true
}
}
2.基于双向链表实现的链表LinkedList
(1)双向链表添加(插入)元素的思路
- 将插入的元素的前一个元素指针指向原位置元素的前一个元素,将插入元素的后一个元素指针指向原位置所在的元素
- 原位置元素的前一个元素的后一个元素指针指向新插入的元素,原位置元素的前一个元素的指针指向插入的元素
(2)双向链表添加和删除元素的核心是查找元素(根据索引查找到元素,即原位置的元素)
- 第一种方法:从表头开始遍历,从前往后依次到index元素,共遍历index次,当index较大时(在中间坐标的后面),遍历的次数较多
- 第二种方法:从表尾开始遍历,从后往前依次到index元素,共遍历
size - index
次,当index值较小时(从中间坐标的前面),遍历的次数较多 - 第三种方法:先计算出中间坐标,判断查找的位置是在中间位置的左边,还是右边,再选择是从开始遍历,还是从结尾遍历,它综合了第一种和第二种方法的优点
(3)通过java来实现自定义的LinkedList
package com.shixinke.demo.dataStruct;
public class MyLinkedList<E> {
/**
* 节点数
*/
private int size;
/**
* 开始节点
*/
private Node<E> beginNode;
/**
* 结束节点
*/
private Node<E> endNode;
public MyLinkedList() {
clearList();
}
/**
* 清空列表
*/
public void clear() {
clearList();
}
public void clearList() {
beginNode = new Node<E>(null, null, null);
endNode = new Node<E>(null, beginNode, null);
this.size = 0;
}
public int size() {
return this.size;
}
public boolean isEmpty() {
return this.size == 0;
}
/**
* 默认添加到最后
* @param value
*/
public void add(E value) {
add(size(), value);
}
public void add(int index, E value) {
Node<E> node = getNode(index);
addBefore(node, value);
}
public E remove(int index) {
Node<E> node = getNode(index);
return remove(node);
}
public void set(int index, E value) {
if (index > this.size) {
throw new IndexOutOfBoundsException();
}
Node<E> node = getNode(index);
node.data = value;
}
public E get(int index) {
if (index > this.size) {
throw new IndexOutOfBoundsException();
}
Node<E> node = getNode(index);
return node.data;
}
public boolean contains(E value) {
Node<E> node = beginNode;
for (int i= 0; i < size; i++) {
if (value.equals(node.data)) {
return true;
}
node = node.next;
}
return false;
}
public int indexOf(E value) {
Node<E> node = beginNode;
for (int i= 0; i < size; i++) {
if (value.equals(node.data)) {
return i;
}
node = node.next;
}
return -1;
}
private Node<E> getNode(int index) {
return getNode(index, 0, size() );
}
/**
* 通过索引获取节点
* @param index 节点索引
* @param min 起始位置
* @param max 结束位置
* @return
*/
private Node<E> getNode(int index, int min, int max) {
Node<E> p;
if (index > max || index < min) {
throw new IndexOutOfBoundsException();
}
/**
* 类似于对半查找
*/
int half = this.size() / 2;
if (index < half) {
/**
* 1.索引值在前半部分(总共只有size个元素),则搜索的元素从开始元素开始往后,最后面的元素即为要查找的元素
*/
p = beginNode;
for (int i = 0; i < index; i++) {
p = p.next;
}
} else {
/**
* 索引值在后半部分,则搜索的元素从结束元素开始往前,最前面的一个元素即是要查找的元素
*/
p = endNode;
for (int i = size(); i > index; i --) {
p = p.prev;
}
}
return p;
}
private E remove(Node<E> node) {
E oldData = node.data;
//1.修改删除元素的前一个元素的next指针的指向,指向删除元素的下一个元素
node.prev.next = node.next;
//2.修改删除元素的下一个元素的prev指针的指向,指向删除元素的前一个元素
node.next.prev = node.prev;
//3.减少元素个数
this.size -- ;
//4.要删除节点本身的资源释放
node.next = null
node.prev = null
node.data = null
return oldData;
}
/**
* 从某个元素前面插入元素(因为新插入的元素,要占用原来元素的位置,所以是在原元素前面插入)
* @param node
* @param value
*/
private void addBefore(Node<E> node, E value) {
//1.生成新节点,它的值为value,它的prev指针为当前节点的前一个元素,它的next指针为当前元素
Node<E> newNode = new Node<E>(value, node.prev, node);
//2.修改当前节点的前一个元素的next指针,指向新插入的元素
node.prev.next = newNode;
//3.修改当前元素的prev指针,指向当前元素
node.prev = newNode;
//4.增加元素个数
this.size ++;
}
public void print() {
System.out.println();
Node<E> node = beginNode;
for (int i = 0; i< this.size; i++) {
node = node.next;
System.out.println(node.data);
}
System.out.println();
}
/**
* 元素节点
* @param <E>
*/
private class Node<E> {
/**
* 存储的数据
*/
private E data;
/**
* 前一个节点指针
*/
private Node<E> prev;
/**
* 后一节点指针
*/
private Node<E> next;
public Node(E value, Node<E> p, Node<E> n) {
this.data = value;
this.prev = p;
this.next = n;
}
}
}
插入节点:
- 第1步:通过index获取插入的位置的原节点元素
- 第2步.生成新节点,它的值为value,它的prev指针为当前节点的前一个元素,它的next指针为当前元素
- 第3步.修改当前节点的前一个元素的next指针,指向新插入的元素
- 第4步.修改当前元素的prev指针,指向当前元素
- 第5步.增加元素个数
删除节点:
- 第1步:通过index获取插入的位置的原节点元素
- 第2步.修改删除元素的前一个元素的next指针的指向,指向删除元素的下一个元素
- 第3步.修改删除元素的下一个元素的prev指针的指向,指向删除元素的前一个元素
- 第4步.减少元素个数
(4)测试功能代码
package com.shixinke.demo.dataStruct;
public class MyLinkedListTest {
public static void main(String[] args) {
MyLinkedList<String> linkedList = new MyLinkedList<String>();
linkedList.add("红1");
linkedList.print();
linkedList.add("蓝2");
linkedList.print();
linkedList.add("白3");
linkedList.add("黄4");
linkedList.add("绿5");
linkedList.add("黑6");
linkedList.print();
System.out.println(linkedList.size()); //6
System.out.println(linkedList.contains("紫7")); //false
System.out.println(linkedList.contains("绿5")); //true
System.out.println(linkedList.indexOf("黄4")); //4
System.out.println(linkedList.indexOf("青8")); //-1
linkedList.clear();
System.out.println(linkedList.isEmpty()); //true
}
}