感悟
为什么要先说感悟呢,大概是因为今天下午看完了Carl的算法书对链表的一些介绍之后,感觉原来挺懵逼的链表,现在好像好了不少,而且有了很多很多新的收获,所以会想着迫不及待地来这里写博客。昨天是第一天开始写博客,一下子写了两篇,真实感受。写博客其实和写日记差不多,但是感觉现在来说,我会更加的想要坚持有时间就写一篇博客,这样的感觉真不错,可以记录一天下来对一些算法和数据结构新的认识,以及很多很多新的技能呢,所以如果有缘人看到了我的这篇博客的话,希望也能够尝试坚持写博客,说不定在你开始的那一天起,你就爱上了博客呢!
链表简述
对于蒟蒻的我来说,链表好像就是一辆可以随意增减车厢数量的火车车厢,just like this?
我们可以看到,链表这样的连接的感觉会让人有一种,能不能加一条绳子或者剪断一条绳子的感觉,这样的感觉其实正是链表可以带给我们的好处,如果我们想要添加或者剪掉一节车厢,其实只要把连接的绳子去掉或者连到其他地方就可以了。因此链表对于数据的增减处理起来就会显得特别特别的方便。然而这么好的东西当然会有它的缺点,那就是访问上必须从头开始,也就是我们没有办法像数组一样直接通过下标的方式去访问,正如同你在一辆火车上,你总不可能找人的时候能够穿越中途所有车厢,直接到火车的另一头去吧!
链表的创建
对于链表的创建,有头插法和尾插法,但是我觉得后者应当是更加实用的,因此选择尾插法。
①首先得建立链表结构体
链表内部有两个部分,一个存放数据,一个存放指针。同时我们需要一个头指针指向链表的第一个节点。而结构体中的next便是一个结构体指针用来后续指向其他链表节点。
struct ListNode
{
int date;
ListNode *next;
ListNode(int x) : date(x),next(NULL){}
//此处也可以不加,但是可以通过此句直接给链表date赋值
//不加此句,赋值可以用 head->date=x;进行
};
ListNode *head;
② 开始向链表里面存储数据
为了后续的操作,这里只进行5个链表节点的创建。首先让头节点初始化
head=new ListNode(5);
然后我们开始往后加节点,每次加上一个节点就需要开辟一个新的链表结构体,当然这个操作可以利用c语言的malloc和calloc函数,感兴趣的uu们可以试一试哦,下面以new为例,这个看起来代码也会非常的简洁。
ListNode *p,*temp;
p=head;
for(int i=1;i<=4;i++)
{
temp=new ListNode(i);
p->next=temp; //让每个节点能够连上
//temp->next=NULL; //其实这一句可以省略,因为链表结构体中的next规定存NULL
p=temp; //让p指针移动到当前的节点
}
p=head;
③下面可以把我们建立的链表打印下来。
while(p)
{
cout<<p->date<<" ";
p=p->next;
}
结果是这样子的
至此我们的链表的创建便完成了!
链表的增加和删除
①链表的增加
在涉及到链表的增加上,我想一般都会有两种情况。第一就是在链表头进行增加,再者就是在链表中间进行添加,而刚开始我也会知道一定要进行特判,是否在链表头进行增加,但是这里受到Carl大佬的启发,给大家介绍一种“假表头”的方法。我们可以通过再创立一个假的表头,然后进行一番操作。这个假表头,给它一个初始值0,叫它dummyhead。对于链表,下标也从0开始。
ListNode *dummyhead=new ListNode(0);
然后我们就要定义一个pos(pos<=4),其为我们需要插在链表中的位置。(如果pos等于0其实就代表它要取代头结点的位置。)
void insert(int pos,int val)
{
ListNode *newnode =new ListNode(val);
ListNode *cur=dummyhead;
while(pos--) //记住这里一定是n--
{
cur=cur->next;
}
//此时的cur一定指向需要插入位置的前一个节点
newnode->next=cur->next; //先让新节点的屁股与原先插入位置的节点连接
cur->next=newnode; //将前一个节点与现在的节点连接。
}
然后这个添加函数建立完之后,如果我们插入的真的是0位置,其实还有一个小细节,如果我们要把插入过后的链表打印出来,我们应该让p从dummyhead的next位置开始访问,如果从head开始访问的话,相当于加入的节点在head前面,并没有达到我们想要的效果,下面插入(0,7)为例子给大家看看。
特别注意我们要先进行一个连接的操作,也就是真假表头的连接
dummyhead->next=head; //刚开始要把假表头和真表头连一起,要不然函数不知道怎么操作呢
就因为这里开始没连起来,搞得我刚开始一直没能有输出,给宝宝急死了。
insert(0,7);
p=dummyhead->next;
while(p)
{
cout<<p->date<<" ";
p=p->next;
}
结果是这样子的。
因此,妈妈再也不用担心我不会在链表里面插元素了呢!
②链表的删除
链表的删除和增加其实有异曲同工之妙,但是我们需要注意的就是,当我们删除一个节点的时候,那一个被我们删掉的东西并不会因此自动被回收哦(这里指的是c++,像JAVA等好像可以)。因此在删除一个元素的时候我们需要牢记最后要删掉这一片内存空间。 下面以,我们要删除某个元素y为例子。大家可以看到“假表头”的优化效果。如果我们要删掉节点为val的。如下
void remove(int val)
{
ListNode *cur=dummyhead;
while(cur->next!=NULL)
{
if(cur->next->date==val) //删除我们需要记下要删的前一个节点
{
ListNode *temp=cur->next; //记下需要删除的节点
cur->next=cur->next->next; //让删除节点前后连接,这个代码就很链表啊
delete temp; //删除要删掉的链表节点所占的内存
}
else
{
cur=cur->next;
}
}
}
cout<<endl;
remove(1);
p=dummyhead->next;
while(p)
{
cout<<p->date<<" ";
p=p->next;
}
结果就是这样的。如果我们remove(7),同样也不再需要考虑头结点的问题了,因此很多问题便迎刃而解了。
链表的反转
简单来说就是如何反向输出一个链表,如图。
思路
这里介绍一个新学的双指针法。我们定义两个指针:fir,sec;以及辅助指针tem去存储sec指针的下一个节点。此处不再需要“假表头”了哦!
void reverse()
{
ListNode *fir,*sec,*tem;
ListNode *p;
fir=NULL;
sec=head;
while(sec)
{
tem=sec->next;
sec->next=fir; //反转操作。
fir=sec;
sec=tem; //这里体现了记录下sec后节点的重要性。
}
p=fir;
while(p)
{
cout<<p->date<<" ";
p=p->next;
}
}
如果链表是 1 2 3 4 5最后输出是这样的
啊啊啊,链表真的好有趣,但是真的好难啊,终于搞完了,其实本篇存在相关的问题,就是在建立链表的相关问题,所以读者可以看到里面所用的思想,然后我也得好好纠错一下建立时的问题,因为在最后调试的时候发现,确实建立链表的时候多扔了一个5进来,所以最后是54321的输出,我想大概是在建立的时候最好能够多走走循环,但是我相信大家在建立链表上都不会出现什么问题的,所以大家可以看看该文章的一些优化,如果还有其他的问题,可以评论哦,谢谢各位了,下面是在测试数据的时候用的小小错误代码,仅供大家参考!
#include<bits/stdc++.h>
using namespace std;
struct ListNode
{
int date;
ListNode *next;
ListNode(int x) : date(x),next(NULL){}
//此处也可以不加,但是可以通过此句直接给链表date赋值
//不加此句,赋值可以用 head->date=x;进行
};
ListNode *head;
ListNode *dummyhead=new ListNode(0);
void insert(int pos,int val)
{
ListNode *newnode =new ListNode(val);
ListNode *cur=dummyhead;
while(pos--) //记住这里一定是n--
{
cur=cur->next;
}
//此时的cur一定指向需要插入位置的前一个节点
newnode->next=cur->next; //先让新节点的屁股与原先插入位置的节点连接
cur->next=newnode; //将前一个节点与现在的节点连接。
}
void remove(int val)
{
ListNode *cur=dummyhead;
while(cur->next!=NULL)
{
if(cur->next->date==val) //删除我们需要记下要删的前一个节点
{
ListNode *temp=cur->next; //记下需要删除的节点
cur->next=cur->next->next; //让删除节点前后连接,这个代码就很链表啊
delete temp; //删除要删掉的链表节点所占的内存
}
else
{
cur=cur->next;
}
}
}
void reverse()
{
ListNode *fir,*sec,*tem;
ListNode *p;
fir=NULL;
sec=head;
while(sec)
{
tem=sec->next;
sec->next=fir; //反转操作。
fir=sec;
sec=tem; //这里体现了记录下sec后节点的重要性。
}
p=fir;
while(p)
{
cout<<p->date<<" ";
p=p->next;
}
}
int main()
{
head=new ListNode(5);
ListNode *p,*temp;
p=head;
for(int i=1;i<=4;i++)
{
temp=new ListNode(i);
p->next=temp; //让每个节点能够连上
//temp->next=NULL; //其实这一句可以省略,因为链表结构体中的next规定存NULL
p=temp; //让p指针移动到当前的节点
}
reverse();
//reverse();
/*p=head;
while(p)
{
cout<<p->date<<" ";
p=p->next;
}
cout<<endl;
/*dummyhead->next=head; //刚开始要把假表头和真表头连一起,要不然函数不知道怎么操作呢
insert(0,7);
p=dummyhead->next;
while(p)
{
cout<<p->date<<" ";
p=p->next;
}
cout<<endl;
/*remove(1);
p=dummyhead->next;
while(p)
{
cout<<p->date<<" ";
p=p->next;
}
cout<<endl;
*/
}
nice今日一篇博客get!
版权归原作者 蒟蒻sheep 所有, 如有侵权,请联系我们删除。