本文旨作于收集整理使用,在学习链表 之前,需自行回顾数组的知识点。
导航
- Java数据结构算法(二)栈和队列
- Java数据结构算法(三)树
- Java数据结构算法(四)图
- Java数据结构算法(五)排序
- ......
一、链表
链表是一种物理存储单元上非连续、非顺序的存储结构。数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
使用链表结构可以克服数组需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。
1.1 单向链表
一个单向链表是由许多的节点组成的,每个节点由数据域:保存或者显示节点信息,指针域:存储下一个节点的地址和保存数据的数据域组成。
单向链表只可向一个方向遍历:
- 查询一个节点的时候需要从第一个节点开始每次访问下一个节点,一直访问到需要的位置。
- 插入一个节点,对于单向链表,我们需要将插入位置的前节点的指针指向自己,而自己的指针指向下一个节点。
- 删除一个节点,我们将该节点的上一个节点的next指向该节点的下一个节点即可。
使用数组简单实现单向链表,实现基本的操作。
public class SingleLinkedList {
private Node headNode;
private int size;
public SingleLinkedList() {
headNode = null;
size = 0;
}
/**
* 在指定位置插入节点
*
* @param index 位置
* @param e 对象
*/
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new IllegalArgumentException("Add failed. Illegal index.");
}
if (size == 0) {
headNode = new Node(e);
size++;
return;
} else {
//插入为头结点时
if (index == 0) {
Node currentNode = new Node(e);
currentNode.next = headNode;
headNode = currentNode;
size++;
return;
} else {
Node preNode = headNode;
Node currentNode = headNode;
//通过循环,遍历节点直至获取插入位置的上一个节点preNode,及插入位置的节点currentNode
for (int i = 0; i < index; i++) {
preNode=currentNode;
currentNode = currentNode.next;
}
//插入位置为最后时,插入指针没有可以指向的节点,因此赋值为NULL
if (index == size) {
currentNode = null;
}
Node newNode = new Node(e);
//使插入位置的上一个节点指向当前节点
preNode.next = newNode;
newNode.next = currentNode;
}
size++;
}
}
public void addFirst(E e) {
add(0, e);
}
public void addLast(E e) {
add(size,e);
}
public E get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Get failed. Illegal index.");
}
if (index==0){
return headNode.e;
}else {
Node currentNode = headNode;
for (int i = 0; i < index; i++) {
currentNode = currentNode.next;
}
return currentNode.e;
}
}
public E getFirst() {
return get(0);
}
public E getLast() {
return get(size - 1);
}
public void set(int index, E e) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("set failed. Index is illegal.");
}
Node currentNode = headNode;
for (int i = 0; i < index; i++) {
currentNode = currentNode.next;
}
currentNode.e = e;
}
public void remove(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Remove failed. Index is illegal.");
}
if (size == 0) {
size--;
headNode = null;
return;
}
//移除的为头指针时,使头指针的下一个节点
if (index == 0) {
headNode = headNode.next;
size--;
} else {
Node preNode = headNode;
for (int i = 0; i < index-1; i++) {
preNode = headNode.next;
}
//使删除节点的上一个节点指向删除节点的下一个节点
preNode = preNode.next.next;
size--;
}
/**
*
* 扩展
* 1.查询是否包含某个元素
* 2.根据元素的值移除某个元素
* 3.判断链表是否为空
* 4.返回链表的长度
* 5.遍历查询
* ......
*/
}
class Node {
public E e;
public Node next;
public Node(E e) {
this.e = e;
}
}
}
1.2 双端链表
双端链表主要是为了解决在链表末端插入数据的问题。
我们知道单向链表需要在末端插入节点,往往需要遍历获取末端节点,然后指向新节点,因此,考略到这种情况,我们可以使用双端链表:包含一个头结点和一个尾节点,它的简单使用我么可以放在队列中学习。
public class DoublePointLinkedList {
private Node headNode;
private Node tailNode;
private int size;
public DoublePointLinkedList() {
headNode=null;
tailNode=null;
size=0;
}
/**
* 根据索引插入数据可以参考单向链表
*/
public void addHead(E e){
Node currentNode=new Node(e);
if (size==0){
headNode=currentNode;
tailNode=currentNode;
}else{
currentNode.next=headNode;
headNode=currentNode;
}
size++;
}
public void deleteHead(){
if (size==0){
throw new ArrayIndexOutOfBoundsException("Index is illegal");
}
if (headNode.next==null){
headNode=null;
tailNode=null;
}else {
headNode=headNode.next;
}
size--;
}
public void addTail(E e){
Node currentNode=new Node(e);
if (size==0){
headNode=currentNode;
tailNode=currentNode;
}else{
tailNode.next=currentNode;
tailNode=currentNode;
}
size++;
}
public void deleteTail(){
if (size==0){
throw new ArrayIndexOutOfBoundsException("Index is illegal");
}
if (headNode.next==null){
headNode=null;
tailNode=null;
}else {
Node currentNode=headNode;
while (currentNode.next!=tailNode){
currentNode=currentNode.next;
}
currentNode.next=null;
tailNode=currentNode;
}
}
}
1.3 双向链表
双向链表如下图所示,我们知道链表插入速度快,在单向链表中我们若需要查询后面的数据时往往需要从第一个遍历至最后一个,因此我们可以使用双向链表,一个节点指向下个节点,一个节点指向上一个节点。
public class TwoWayLinkedList {
private int size;
private Node head;
private Node tail;
public TwoWayLinkedList() {
size=0;
head=null;
tail=null;
}
public void addHead(E e){
Node node=new Node(e);
if (size==0){
head=node;
tail=node;
}else {
head.prev=node;
node.next=head;
head=node;
}
size++;
}
public void addTail(E e){
Node node=new Node(e);
if (size==0){
head=node;
tail=node;
}else {
node.prev=tail;
tail.next=node;
tail=node;
}
size++;
}
public void deleteHead(){
if (size>0){
head=head.next;
head.prev=null;
size--;
}
}
public void deleteTail(){
if (size>0){
tail=tail.prev;
tail.next=null;
size--;
}
}
class Node {
public E e;
public Node next;
public Node prev;
public Node(E e) {
this.e = e;
}
}
}
当然常用的链表不止这些,还有有序列表等,在以后的算法题中解决!