链表
链表是由一系列节点(链表中的每一个元素都叫作一个节点)组成的数据结构,节点可以在运行过程中动态生成。每个节点都包括两部分内容:存储数据的数据域;存储下一个节点地址的指针域。
由于链表是随机存储数据的,因此在链表中插入数据的时间复杂度为O(1),比在线性表和顺序表中插入的效率要高。
但在链表中查找一个节点时需要遍历链表中所有元素,因此时间复杂度为O(n);而在线性表和顺序表中查找一个节点的时间复杂度分别为O(logn)和O(1)。
链表有 3种不同的类型:单向链表、双向链表及循环链表。
链表的特点:链表通过一组存储单元存储线性表中的数据元素,这组存储单元可以是连续的,也可以是不连续的。因此,为了表示每个数据元素与其直接后继数据元素之间的逻辑关系,对数据元素来说,除了存储其本身的信息,还需要存储直接后继数据元素的信息(即直接后继数据元素的存储位置)。由这两部分信息组成一个“节点”。链表数据结构的优点是插入快,缺点是数据查询需要遍历整个链表,效率慢。图示如下:
其一个节点就是:
单向链表
单向链表(又称单链表)是链表的一种,其特点是链表的链接方向是单向的,访问链表时要从头部开始顺序读取。单向链表是链表中结构最简单的。一个单向链表的节点(Node)可分为两部分:第1部分为数据区(data),用于保存节点的数据信息;第2部分为指针区,用于存储下一个节点的地址,最后一个节点的指针指向null。具体的数据结构如下图所示:
操作:
(1)查找:单向链表只能向一个方向遍历。一般查找时,需要从头至尾挨个遍历。
(2)插入:插入,只需将插入节点设置为头结点,将next指向原来的头结点。如下图所示:
(3)删除:把该节点的上一个节点指针指到下一个节点的头。然后删除该节点即可:
定义单向链表的数据结构:
public class SingleLinkedList{
private int length;
private Node head;
public SingleLinkList{
length=0;
head=null;
}
public class Node{
private Object data;
private Node next;
public Node (Object data){
this.data=data;
}
}
}
该代码用length表示链表的大小,内部创建了一个Node的内部类,Node包含data 与指针next。
插入单向链表的数据:
public Object addHead(Object obj){
//新建一个节点
Node newhead=new Node(obj);
if(length==0){
head=newhead;
}else{
newhead.next=head;
head=newhead;
}
length+=1;
return obj;
}
链表最常用的还是头插法,复杂度为O(1);头插法:只需将新节点的next指向当前的head节点,然后再讲newhead设置为当前的head,最后让length++
删除单向链表的数据:
public boolean delete(Object value){
if(length==0){
return false;
}
Node current=head;
Node previous=head;
while(current.data!=value){
if(current.next==null){
return false;
}else{
//这样pre指针比cur指针少一步
previous=current;
current=current.next;
}
}
//找到了准备的位置
//若删除的是头结点
if(current==head){
head=current.next
}else{
previous.next=current.next;
length--;
}
return true;
}
以上代码定义了方法delete()来删除单向链表中的数据,具体的删除操作为:首先判断链表的长度,如果链表长度为 0,则说明链表为空,即不包含任何元素,直接返回false;如果链表不为空,则通过while循环找到要删除的元素;如果要删除的节点是头节点,则需要把要删除的节点的下一个节点指定为头节点,删除该节点,把节点长度减 1;如果删除的节点不是头节点,则将该节点的上一个节点的Next指针指向该节点的下一个节点,删除该节点,并把节点长度减1。
单向链表数据查找:
public Node find(Object obj){
Node current =head;
int tempsize=length;
while(tempsize>0){
if (obj.equals(current.data)){
return current;
}else{
current=current.next;
}
tempsize--;
}
return null;
}
以上代码定义了名为find()的单向链表节点查询方法。该方法很简单,定义了一个while循环来查找数据,如果当前数据和要查找的数据相同,则返回该数据;如果不同,则将当前节点的下一个节点设置为当前节点,沿着当前节点向前继续寻找。这里将tempSize减 1的目的是控制while循环的条件,在tempSize为 0时表示遍历完了整个链表还没找到该数据,这时返回null。
双向链表
双向链表每个节点都有两个指针,一个指向前节点,一个指向后节点。
双向链表的发明使得遍历变得更简单,每次只需遍历一半即可,因为可以左右开弓。
定义双向链表的的数据结构:
public class TwoWayLinkedList{
private Node head;
private Node tail;
private int length;
private class Node{
private Object data;
private Node next;
private Node pre;
public Node(Object obj){
this.data=data;
}
}
//对双向链表初始化
public TwoWayLinkedList{
length=0;
head=null;
tail=null;
}
}
以上代码定义了一个名为TwoWayLinkedList的双向链表的数据结构,其中定义了:head,表示链表头;tail,表示链表尾;length,表示链表长度;Node,表示链表的节点,链表的节点包含data、prev、next,分别表示节点数据、上一个节点和下一个节点。这样双向链表的数据结构就定义好了。
在链表头部增加节点:
public void addHead(Object obj){
Node newhNode=new Node(value);
//如果是空节点
if(length==0){
head=newNode;
tail=newNode;
length++
}else{
//头插法
head.pre=newNode;
newNode.next=head;
head=newNode;
length++;
}
}
以上代码定义了addHead()来向链表的头部加入数据,具体操作为:首先,新建一个节点;然后,判断链表的长度,如果链表的长度为 0,则说明链表是空链表,将链表的头部和尾部均设置为当前节点并将链表长度加 1即可;如果链表不是空链表,则将原链表头部的上一个节点设置当前节点,将当前节点的下一个节点设置为原链表头的
节点,将链表的头部节点设置为当前节点,这样就完成了双向链表的头部节点的插入;最后,需要将链表的长度加1。
在链表尾部增加节点:
public void addTail(Object obj){
Node newhNode=new Node(value);
//如果是空节点
if(length==0){
head=newNode;
tail=newNode;
length++
}else{
//尾插法
newNode.pre=tail;
tail.next=newNode;
tail=newNode;
length++;
}
}
与头插法相似。
删除双向链表的头部节点
public Node deleteHead(){
Node temp=head;
if(length!=0){
head=head.next;
head.pre=null;
length--;
return temp;
}else{return null}
}
以上代码定义了一个名为deleteHead()的方法来删除链表的头部节点,具体操作为:首先定义一个临时节点来存储当前头部节点;然后判断节点的长度,如果节点的长度为 0,则直接返回null;如果节点的长度不为 0,则将当前头部节点设置为原头部节点的下一个节点,将头部节点的上一个节点设置为null,然后删除该节点;最后,将节点的长度减 1。
删除双向链表的尾部节点
public Node deleteTail(){
Node temp=head;
if(length!=0){
Tail=Tail.pre;
Tail.next=null;
length--;
return temp;
}else{return null}
}
同上,方法相似。
循环列表
循环列表与单向列表的唯一不同点是,循环链表的最后一个节点的指针指向头结点。整个链表成了一个闭环。
如下图所示:
其他均相同