今天是双链表
1、链表节点
先上代码
public class ListNode {
Object data;//数据域
ListNode next;//后继指针域
ListNode pre;//前驱指针域
public ListNode(Object data) {
this.data = data;
}//构造器
}
节点比起单链表来说加了前驱的指针域
2、双链表实现的类
双链表,我对于它的理解我认为它应该叫做双向链表,当然也有可能是为了简洁而叫双链表,从字面上很容易让人以为是两条链表,之所以我认为叫它双向链表,是因为,这就是单链表的反向链接,即在双链表中间的节点除了保存自己的数据域,下一个节点的指针域,还保存了前一个节点的指针域,我对于双链表出现的理解是,为了补足单链表无法获得前驱节点的问题,有了双指针域的节点,可以从任何一个节点起步,到达任何另一个节点。
/*
哑元:我所理解的哑元是指可以提供给其他方法使用的“空壳”,也就是没有数据的节点
*/
public class DoubleLinkList implements MyList {
ListNode first = new ListNode(null);//虽然是创建的节点实体,但是可以理解为头指针
ListNode last = new ListNode(null);//同上可以理解为尾指针
int size;//链表长度
public DoubleLinkList() {//该构造器是为了将链表首尾链接,初始中的链表中没有元素,但有基本的头尾节点,故有头的后继指向尾,尾的前驱指向头
first.next = last;
last.pre = first;
}
/*
添加节点需要维护4条指向
设有一个双向链表是 ac 需要添加b到中间
1、修改a的后继为b
2、b的后继指向c
3、b的前驱指向a
4、c的前驱指向b
*/
@Override
public void add(Object element) {//添加节点
ListNode newNode = new ListNode(element);//创建新的节点
last.pre.next = newNode;//将尾节点(last)的前驱(last.pre)的后继指针(last.pre.next)改为指向新节点 ps:(原本尾节点的前驱指针是指向尾节点的)
newNode.next = last;//新节点的后继指针指向尾节点
newNode.pre = last.pre;//新节点的前驱指针(newNode.pre)指向尾节点之前指向的前驱(last.pre) ps:(现在尾节点的前驱还没有改变)
last.pre = newNode;//最后将尾节点的前驱指向新节点
size++;//长度加一
}
/*原理:假设链表为 a b c d e 我们想要删除p节点,我们先创建了一个指向头节点的指针p,并一直往下指,当
*p指向了c的时候,现在p就相当于c,对于p的操作就相当于对于c的操作
*为了方便理解这里用c继续说
* 将c的前驱(b)的后继指针(b.next)转而指向c的后继(d),这里从头来的链表链接成功,即b.next=d.
* 将c的后继(d)的前驱指针(d.pre)转而指向c的前驱(b),这里从尾来的链表链接成功,即d.pre=b.
* 这样链表成为了 a b d e
*/
@Override
public void deleteByElement(Object element) {//根据元素删除节点
ListNode p = first.next;//创建新节点p使其指向头节点的后继,即第一个有数据的节点
while (p != last) {//当p不是尾节点
if (p.data.equals(element)) {//当p的数据与给定参数相等时表面找到了要删除的节点
p.pre.next = p.next;//p的前驱(p.pre)的后继指针(p.pre.next)指向了p的后继指针(p.next)
p.next.pre = p.pre;//p的后继(p.next)的前驱指针(p.pre)指向了p的前驱
p.next = null;//架空删除的p节点,使其可以快速回收
p.pre = null;
size--;
break;
}
p = p.next;
}
}
@Override
public void deleteByIndex(int index) {
if (index < 0 || index >= size) {//限制index的边界,提高效率
return;
}
int i = 0;//设置计数器
ListNode p = first.next;//创建新节点p使其指向头节点的后继,即第一个有数据的节点
while (p != last) {
if (i == index) {//当计数器与给定的index相当时表面找到了要删除的节点
p.pre.next = p.next;
p.next.pre = p.pre;
p.pre = null;
p.next = null;
size--;
break;
}
p = p.next;
i++;
}
}
/*
*更新要简单多了,只需要设置计数器找到给定参数index与计数器相等的节点,改变现在p指针指向的节点的数据域即可
*/
@Override
public void update(int index, Object newElement) {
if (index < 0 || index >= size) {//限制index的边界,提高效率
return;
}
int i = 0;
ListNode p = first.next;
while (p != last) {
if (i == index) {
p.data = newElement;
break;
}
p = p.next;
i++;
}
}
//找到即返回true,其他情况返回false
@Override
public boolean contatins(Object target) {
ListNode p = first.next;
while (p != last) {
if (p.data.equals(target)) {
return true;
}
p = p.next;
}
return false;
}
/*
查找的本质就是添加一个计数器,当计数器(i)与给的的查找(index)相同时,返回该元素
*/
@Override
public Object at(int index) {
if (index < 0 || index >= size) {
return null;
}
int i = 0;
ListNode p = first.next;
while (p != last) {
if (i == index) {
return p.data;
}
p = p.next;
i++;
}
return null;
}
@Override
public int indexOf(Object element) {
ListNode p = first.next;
int i = 0;
while (p != last) {
if (p.data.equals(element)) {
return i;
}
p = p.next;
i++;
}
return -1;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[");//构建一个StringBuilder,先加上“[”
ListNode p = first.next;//p指针指向了头节点
while (p != last) {//当p指向的节点不空
sb.append(p.data);//将p指向的节点的数据域添加到sb(StringBuilder)
if (p.next != last)//若p指向的节点不空
sb.append(",");//则添加一个“,”到sb
p = p.next;//p指向下一个节点
}
sb.append("]");//p全部走完链表以后,加上“]”
return sb.toString();//StringBuilder构建完成,使用toString转换成字符串
}
private ListNode now = first;
/*
*下面两个方法是因为这个类实现的接口MyList继承了Iterator(迭代器)的接口,而重写的方法
* 迭代器:我理解的迭代器是一个个的“查看链表”的东西
*/
@Override
public boolean hasNext() {
return now.next != last;
}//判定某个节点有无下一个节点
@Override
public Object next() {//返回节点的数据
ListNode next = now.next;
now = now.next;
return next.data;
}
}
老样子我在相关的地方加了详细的注释,而且全是我的理解
3、测试类
下面是我所用的测试类,问题不大
public class DoubleLinkListTest {
DoubleLinkList list = new DoubleLinkList();
@Test
public void add() {
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");//添加元素
// System.out.println(list);//输出元素
}
@Test
public void deleteByElement() {
add();
list.deleteByElement("b");
System.out.println(list);
list.deleteByElement("a");
System.out.println(list);
}
@Test
public void deleteByIndex() {
add();
list.deleteByIndex(2);
System.out.println(list);
list.deleteByIndex(2);
System.out.println(list);
list.deleteByIndex(0);
System.out.println(list);
}
@Test
public void update() {
add();
System.out.println(list);
list.update(2, "x");
System.out.println(list);
list.update(0, "第一个");
System.out.println(list);
}
@Test
public void contatins() {
add();
boolean a = list.contatins("a");
System.out.println(a);
}
@Test
public void at() {
add();
System.out.println(list.at(0));
System.out.println(list.at(3));
}
@Test
public void indexOf() {
add();
System.out.println(list.indexOf("b"));
System.out.println(list.indexOf("a"));
System.out.println(list.indexOf(1));
}
@Test
public void iter(){
add();
while (list.hasNext()){
System.out.println(list.next());
}
}
}
4、总结
今天了解了双链表,其实我第一次听到双链表,以为是两个链表在某个节点相交,但是仔细一想这样的话就不具有普遍性了,直达我又看见了“桶”
今天背疼,感觉像有东西在戳我背,对于高数有了新的理解,特别是随着算法的逐渐实现,越来越明白数学的博大精深,因为数学太过厉害,以至于不能很好的找到它的用处,但是计算机出现了,计算机是数学表述自己的一个非常重要的工具。
算法是程序的灵魂,这句话一点也没错
花开如火,也如寂寞。
——顾城