上次与各位谈了谈有关头指针与头结点在链表操作中的重要性,今天想与大家正式来谈谈链表操作,我们先从链表的建立开始引入。(本文的论述均建立在链表有头指针有头结点的基础之上)
头插法
头插法相对来说是新手较为容易理解的一种建立链表的方式,众所周知,使用头插法后,读取数据的顺序与生成的链表中的元素的顺序是相反的。我们根据字面意思也非常好理解,头插法顾名思义就是将新结点插入到当前链表的开头。
LinkList List_HeadInsert(LinkList &L){
// 先将我要盖房子的这块地给包下来,而这里的L就是一个地标,也就是不久将来的头结点
// 使用C语言malloc()语法进行内存的分配,这种方法本身属于本地方法
// 于是你可以通过本地方法来记忆,"需要在本地建造一个房子",以防自己遗忘了这个头结点的内存分配
L = (LinkList)malloc(sizeof(LNode));
L->next = nullptr;
int x = 0;
// 我这里为这个小区建造30幢楼房
while ( x != 30 ) {
s = (LNode *)malloc(sizeof(LNode)); // 盖起一幢新房子s
s->data = ++x; // 写上门牌号
s->next = L->next; // s的路牌指向原来L的路牌
L->next = s; // 同时L的路牌指向s这幢新楼
}
由于这幢楼本身沿用的是这个保安室的路牌一开始所指向的房子,而保安室的路牌所指向的又是新房子,这就是为什么头插法形成的小区门牌号倒序的缘故。
由于头插法本身建立的链表倒序,在链表反转这类题中,头插法有着非常出色的发挥。
尾插法
而平常建立一个正常序列的链表则要用到我接下来要和大家讨论的尾插法,不多废话,先上代码,然后我会指出新手理解的困难之处。
LinkList List_TailInsert(LinkList &L){
L = (LinkList)malloc(sizeof(LNode));
LNode *r = L, *s;
int x = 0;
// 我这里为这个小区建造30幢楼房
while ( x != 30 ) {
s = (LNode *)malloc(sizeof(LNode)); // 盖起一幢新房子s
s->data = ++x; // 写上门牌号
r->next = s;
r = s;
}
新手往往会在这个r = s上栽了跟头表示十分不理解,其实非常简单,我把代码换成:
...
r->next = s;
r = r->next; // 两个版本的代码实质其实是一样的
这样你就可以明白了吧,将r看作一群建造房子的工人,这幢房子造完了自然要前往下一个目的地进行建造
建造好2号房屋后将路牌树立起来,同时R工人团队向2号房迈进。
ps:很多同学在最开始学习的时候总是会将插入步骤写反,但按照我这个流程去佐以想象是不会有这种问题的!
掌握好头插法和尾插法的是受益无穷的。如上文所说,在刷链表算法题时,利用头插法将原本按一定序列排序的链表直接逆序的应用也算是屡见不鲜。本周即将更新的LeetCode 206.Reversed Linked List中,我有一种方法就是利用头插法。尾插法更加常用,在一些题目中,将筛查出来的符合题目条件的结点提取出来,利用尾插法将他们组合起来得出答案。这在之后的刷题中我会带着大家一起感受。