前面讲过线性表中顺序表和链表的实现
和性质。可是在数据结构与算法中,双向链表不管在考察仍是运用中都占有很大的比例,笔者旨在经过本文与读者一块儿学习分享双链表相关知识。java
逻辑上没有区别。他们均是完成线性表的内容。主要的区别是结构上的构造有所区别。
对于单链表:node
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; } }
对于链表:ide
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也跳过被删除节点。
整个流程的动态图为:this
代码:
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; } }
测试:
package LinerList; public class test { public static void main(String[] args) throws Exception { // TODO Auto-generated method stub System.out.println("线性表测试:"); doubleList<Integer> list = new doubleList<Integer>(); list.add(66); list.addfirst(55); list.add(1, 101); list.add(-22); list.add(555); list.addfirst(9999); System.out.println(list.toString() + " lenth " + list.length());// 9999 55 101 66 -22 555 // System.out.println(list.getElum(0)+" "+list.getElum(2)+" "+list.getElum(4)); list.deletefirst(); System.out.println(list.toString() + " lenth " + list.length());// 55 101 66 -22 555 lenth 5 list.delete(1); System.out.println(list.toString() + " length " + list.length());// 55 66 -22 555 length 4 list.delete(1); System.out.println(list.toString() + " length " + list.length());// 55 -22 555 length 3 list.deletelast(); System.out.println(list.toString() + " lenth " + list.length());// 55 -22 lenth 2 list.deletelast(); System.out.println(list.toString() + " lenth " + list.length());// 55 lenth 1 list.deletelast(); System.out.println(list.toString() + " lenth " + list.length());// lenth 0 System.err.println("欢迎关注公众号:bigsai"); } }
结果图
插入、删除顺序问题:
完成相同的结果
便可!画个图
。你也能够先建一个节点,用变量名完成操做,可能会更容易一些。好比删除操做,你找到pre
节点(删除前的节点)。你能够node delete=pre.next
,node next=delete.next
。这样你直接操做pre。delete。next三个节点会更简单。能够参考笔者的代码实现
)。其余操做问题:
并无写的太多
。好比查找时候能够根据长度判断这个链表从头查找
仍是从尾查找
。另外,代码写的可能不是太好,链表也没考虑线程安全问题。算法效率可能不太优。若是有什么改进或者漏洞还请大佬指出!
最后(last but not least):
关注
一下把,关注后回复: 数据结构 有精心准备的系列。我的公众号交流:bigsai