事实上,看懂链表的结构并非很难,可是一旦把它和指针混在一块儿,就和容易让人摸不着头脑。因此,要写好代码,首先就要理解好指针。node
有些语言有“指针”的概念 ,好比C语言;有些语言没有指针,取而代之的是“引用”。实际上,它们的意思都是同样的,都是存储所指对象的内存地址。算法
对于指针的理解:将某个变量赋值给指针,实际上就是将这变量的地址赋值给指针,或者反过来讲,指针中存储了这个变量的内存地址,指向了这个变量,经过指针就能够找到这个变量。浏览器
在编写链表代码的时候,咱们常常会这样写:p->next=q,这行代码是说,p结点的next指针存储的是q结点的内存地址。数据结构
还有一个比较复杂的:p->next=p->next->next,这行代码表示,p结点的next指针存储的是p结点的下下个结点的内存地址。post
写链表代码的时候,指针指来指去,一会就不知道指哪里了。因此,咱们在写的时候,必定注意不要丢了指针。指针
指针是怎么弄丢的呢?举例说明一下:假如一个链表有头结点a和尾结点b两个结点,咱们但愿在a和b之间插入结点x,假设当前指针p指向结点a,即p=a若是向下述同样实现代码,就会发生指针丢失和内存泄漏。调试
p->next=x;//将p的next指针指向x结点;
code
x-next=p->next;//将x的next结点指向b结点
对象
可是这里有个问题:p->next指针在完成第一步操做以后,已经再也不指向结点p了,而是指向结点x。第二行代码至关于将x赋给x->next,本身指向本身。所以,整个链表就断成了两半,从b之后的结点都没法访问了。内存
首先,回顾一下单链表的插入和删除操做。若是我么在p结点的后边插入一个新的结点,下面两行代码就能实现:
new_node->next=p-next;
p->next=new_node;
可是,对于一个空链表插入一个新节点,上述代码逻辑就不能用了。咱们须要进行以下的特殊处理:
if(head=null){head=new_node}
单链表删除结点,要删除p结点的后继结点,只须要一行代码就能实现:
p->next=p->next->next
可是,若是咱们要删除链表的尾结点,上述代码逻辑就不work了。跟插入相似,咱们也须要作以下的特殊处理:
if(p->next==null){p=null;}
从上述的分析得出,针对链表的插入、删除操做,须要对插入第一个结点和删除最后一个结点的状况作特殊处理。这样代码就会看起来很繁琐,不简洁,并且也容易由于考虑不全而出错。如何来解决这个问题呢?
哨兵就能够派上用场了。
若咱们引入哨兵结点,在任什么时候候,无论链表是否是空,head指针都会一直指向这哨兵结点。咱们把这种有哨兵结点的链表称做带头链表。相反,没有哨兵的链表叫作不带头链表。
哨兵结点是不存储数据的。由于哨兵结点一直存在,因此插入第一个结点和插入其余结点,删除最后一个结点和删除其余结点,均可以统一为相同的代码了。
软件开发中,代码在如下边界或者异常状况下,最容易产生Bug。
咱们常常用来检查链表代码是否正确的边界条件有这几个:
1)若是链表为空时,代码是否正常工做?
2)若是链表只包含一个结点时,代码是否正常工做?
3)若是链表只包含两个结点时,代码是否正常工做?
4)代码处理头结点和尾结点时,代码是否能正常工做?
若是已理解不可以掌握以前的技巧,可是手写代码仍是会出现各类各样的错误,也不要着急,就是把常见的链表操做都本身多写几遍,出问题就一点一点调试,孰能生巧。
下述选了链表的五个经常使用操做。将这几个操做都能写熟练,不熟就多写几遍,链表代码就不用惧怕了。
1)单链表反转
2)链表中环的检测
3)两个有序的链表合并
4)删除链表倒数第n个结点
5)求链表的中间结点