双向链表
前面讲过线性表中[顺序表和链表].但双向链表无论在考察还是运用中都占有很大的比例,笔者旨在通过本文与读者一起学习分享双链表相关知识。
逻辑上没有区别。他们均是完成线性表的内容。主要的区别是结构上的构造有所区别。 对于单链表:
data
。和next
后驱节点(指针)。也就是这个单链表想要一些遍历的操作都得通过前节点
—>后节点
。
对于双链表:
data
,指向后方的next
(指针)。它拥有单链表的所有操作和内容。但是他还有一个前驱节点pre
(指针)。
带头节点
的。带头节点可以方面首位的插入和删除。而这次我们抱着学习的态度搞清链表故该双链表是不带头节点
的.tail
。这样我们就接触了几乎所有类型啦!遇到啥也不怕了。所以我们构造的这个双链表的的性质:
对于node节点:
class node<T> {
T data;
node<T> pre;
node<T> next;
public node() {
}
public node(T data) {
this.data = data;
}
}
对于链表:
public class doubleList<T> {
private node<T> head;// 头节点
private node<T> tail;// 尾节点
private int length;
//各种方法
}
头插
、尾插
、中间插
。并且还要考虑其中的一些细节处理。指针的运算。防止链表崩掉。因为这些操作如果不当往往会对链表的结构和证悟性带来致命的打击。而像查找
那些逻辑稍微简单
。也很容易排查错误。null
的。那么对于这个不带头节点
的双链表而言。它的head
始终指向第一个真实有效的数据。tail也是如此。那么在最初没数据的时候当然要head=null,并且tail=head
。(tail和head需要在一个链上)。public doubleList() {
head = null;
tail = head;
length = 0;
}
head
和tail
均为null。但head和tail又需要实实在在指向链表中的真实数据(带头指针就不需要考虑)。所以这时候就新建一个node
让head、tail等于它。node<T> teamNode = new node(data);
if (isEmpty()) {
head = teamNode;
tail = teamNode;
}
对于头插入来说。步骤很简单,只需考虑head节点的变化。
重新赋值
)
对于尾插入来说。只需考虑尾节点tail节点的变化。
重新赋值
等于node即可)
对于编号插入来说。要考虑查找和插入两部,而插入既和head无关也和tail无关。
pre
。和后一个节点after
整个流程的动态图为:
无论头删还是尾删,遇到单节点删除的需要将链表从新初始化!
if (length == 1)// 只有一个元素
{
head = null;
tail = head;
length--;
}
头删除需要注意的就是删除不为空时候头删除只和head节点有关
大致分为:
断绝关系
)head
就指向我们需要的第一个节点了。如果有需要处理内存的语言就可以把第一个被孤立的节点删除了)尾删除需要注意的就是删除不为空时候尾删除只和tail节点有关。记得在普通链表中,我们删除尾节点需要找到尾节点的前驱节点。需要遍历整个表。而双向链表可以直接从尾节点遍历到前面。
删除的时tail所在位置的点。也就是tail所在节点要断绝和双链表的关系。
tail.pre.next=null
尾节点的前一个节点(pre)的后驱节点等于nulltail=tail.pre
尾节点指向它的前驱节点,此时尾节点由于步骤1
next已经为null。完成删除
普通删除需要重点掌握,因为前两个删除都是普通删除的一个特例而已。(普通删除要确保不是头删除和尾删除)
team.next
是要删除的节点)team.next.next.pre=team
.(欲被删除节点的后一个节点的前驱指向team,双向链表需要处理pre和next。这步处理了pre)
team.next=team.next.next;
此时team.next也跳过被删除节点。
整个流程的动态图为:
代码:
package LinerList;
/*
* 不带头节点的
*/
public class doubleList<T> {
class node<T> {
T data;
node<T> pre;
node<T> next;
public node() {
}
public node(T data) {
this.data = data;
}
}
private node<T> head;// 头节点
private node<T> tail;// 尾节点
private int length;
public doubleList() {
head = null;
tail = head;
length = 0;
}
boolean isEmpty() {
return length == 0 ? true : false;
}
void addfirst(T data) {
node<T> teamNode = new node(data);
if (isEmpty()) {
head = teamNode;
tail = teamNode;
} else {
teamNode.next = head;
head = teamNode;
}
length++;
}
void add(T data)// 尾节点插入
{
node<T> teamNode = new node(data);
if (isEmpty()) {
head = teamNode;
tail = teamNode;
} else {
tail.next = teamNode;
teamNode.pre=tail;
tail = teamNode;
}
length++;
}
int length()
{
return length;
}
T getElum(int index)//为了简单统一从头找
{
node<T> team=head;
for(int i=0;i<index;i++)//不带头节点 遍历次数-1
{
team=team.next;
}
return team.data;
}
void add(int index, T data)// 编号插入
{
if (index == 0) {
addfirst(data);
} else if (index == length) {
add(data);
} else {// 重头戏
node teampre = head;// 为插入的前qu
for (int i = 0; i < index -1; i++)// 无头节点,index-1位置找到前驱节点
{
teampre = teampre.next;
}
node<T> team = new node(data);// a c 中插入B 找打a
team.next = teampre.next;// B.next=c
teampre.next.pre = team;// c.pre=B
team.pre = teampre;// 关联a B
teampre.next = team;
length++;
}
}
void deletefirst()// 头部删除
{
if (length == 1)// 只有一个元素
{
head = null;
tail = head;
length--;
} else {
head = head.next;
length--;
}
}
void deletelast() {
if(length==1)
{
head=null;
tail=head;
length--;
}
else {
tail.pre.next=null;
tail=tail.pre;
length--;
}
}
void delete(int index)
{
if(index==0)deletefirst();
else if (index==length-1) {
deletelast();
}
else {//删除 为了理解统一从头找那个节点
node<T>team=head;
for(int i=0;i<index-1;i++)
{
team=team.next;
}
//team 此时为要删除的前节点 a c 插入B a为team
team.next.next.pre=team;//c的前驱变成a
team.next=team.next.next;//a的后驱变成c
length--;
}
}
void set(int index,T data)
{
node<T>team=head;
for(int i=0;i<index-1;i++)
{
team=team.next;
}
team.data=data;
}
@Override
public String toString() {
node<T> team = head;
String vaString = "";
while (team != null) {
vaString += team.data + " ";
team = team.next;
}
return vaString;
}
}
测试:
插入、删除顺序问题:
完成相同的结果
即可!画个图
。你也可以先建一个节点,用变量名完成操作,可能会更容易一些。比如删除操作,你找到pre
节点(删除前的节点)。你可以node delete=pre.next
,node next=delete.next
。这样你直接操作pre。delete。next三个节点会更简单。可以参考笔者的代码实现
)。其他操作问题:
并没有写的太多
。比如查找时候可以根据长度判断这个链表从头查找
还是从尾查找
。另外,代码写的可能不是太好,链表也没考虑线程安全问题。算法效率可能不太优。如果有什么改进或者漏洞还请大佬指出!