链表
首先为啥叫链表
由一个个的节点组成,并且把这些节点关联起来,类似于使用一根链将他们串联起来,就产生了链表。
所以第一步,我们需要先创造节点:
先以最简单的例子来说,每个节点储存一个int型的变量就可以了;
如上图,我们创建了5个节点,分别给他们赋值了一个int类型的变量
第二步,我们应该思考一下,我们怎么进行节点的关联:
进行节点的关联,大概的意思就是,我通过节点一可以再去访问到节点二(如下图)
到这边我们先插入一点题外话,数组是怎么访问每一个元素的:
首先明确一点,访问一个元素,我们都是拿到地址去访问
int main(){
int a[10]={1,2,3,4,5,6,7,8,9,10};
cout<<&a[0]<<endl;
cout<<*(&a[0]+28)<<endl;//这句话的意思就是我们拿到首地址,并且将首地址加上28(7*sizeof(int)=7*4=28),就得到了a[7]的地址
//看到上面,是不是很神奇,我们只需要有一个变量的地址,我们马上可以通过该地址访问到该地址上面的变量
int c;
int *p;
p=&c;
//这和我们的指针是不是就是一样的,所以我们说的定义了一个指针变量(int *p),其实我们直接理解成我们定义了一个地址(p在这里就是一个地址,没有别的意思)
//怎么去访问这个地址的变量,*p就是访问这个地址的变量,
// (比如说我们定义了一个变量c(int c),那么这样子的话,p=&c就代表我们将变量c的地址给了地址p)
//可以类比一下上面的内容,会发现,指针其实就是一个地址,不要把自己绕进去就可以了,
// 并且我们得出一个结论,我们只要有地址,我们就可以访问到地址上面的每一个变量
return 0;
}
得出结论:拿到地址,我们就可以去访问地址上面的变量
例如:我定义了 这样一个数组,然后我们需要去打印a[7];
int a[10];
cout<<a[7];
我们在定义这个数组的时候,系统会去分配一块连续存储的空间给a,但是我们想一下,系统对于一个数组,会存储a[0],a[1],a[2],a[3]…a[9]这些的地址吗?系统应该是没有这么傻,因为这样子数据量很大的时候,占用的空间也会特别大,所以会去选择只保存a,也就是数组a的地址,我们只需要保存了这个地址,我们访问数组的每一个元素,比如说a[7]我们只需要拿到数组a的首地址,也就是a,假设为0x61fdf0,那么a[7]的地址就是0x61fdf0+7*sizeof(int),前面那句话的意思代表着拿到数组a的地址,然后往下加7个int类型的长度,也就是4个字节
a[7]的地址=首地址地址+28=0x61fe0c;
所以对于一个数组来说,因为是连续存储的空间,你只需要给定一个下标,就可以访问到数组中每一个元素的地址,通过地址,我们也就能够拿到该元素;(存储空间的连续,我们可以通过首地址和下标拿到每一个元素的地址)
好了,我们讲了这么多的废话,回到正题
怎么去关联节点,是不是我们只需要将我们的每一个节点里面存储一个地址,这个地址储存的是什么呢?储存的是下一个结点的地址,这样子的话,我们在访问到第一个节点的时候,我们不仅拿到了这个结点的值,并且我们可以拿到这个节点储存的地址,拿到储存的地址,我们就可以通过这个地址去访问下一个节点了,这样子我们第一个节点和我们的第二个节点就关联了起来
然后我们现在怎么去访问每一个节点呢?
假设我们只知道首地址:0x61fdf0
第一步,我们先拿到0x61fdf0的数据值,然后打印出来
第二步,我们拿到0x61fdf0储存的地址值,也就是下一个节点的地址,然后我们就可以直接去访问下一个节点了
大概整体的思路就是这样子,然后这时候我们的每一个节点不仅仅存了一个int类型的变量,并且我们还存储了一个地址,所以我们应该用一个结构体来表示
typedef struct ListNode{
data val; //数据
ListNode *next;//我们在前面就说过,指针就是一个地址,存放下一个节点的地址
};
通过代码演示创建节点:
//创建第一个节点
ListNode head;
head.val=12;//赋值给数据域
head.next=NULL;//我们下一个节点还没有创建,所以地址先为空
//创建第二个节点
ListNode second;
second.val=13;//赋值给数据域
second.next=NULL;//同样我们下一个节点还没有创建,所以地址先为空
连接两个节点
head.next = &second;//让第一个节点储存的地址域变成第二个节点的地址
这样子,我们两个节点和他们之间的关系就创建好了。
访问每一个节点
cout<<head.val;
cout<<head.next->val;//head.next代表的就是第二个节点的地址,地址访问元素,用的是->
接下来我们通过循环创建多个节点
ListNode head;
head.val=12;//赋值给数据域
head.next=NULL;//我们下一个节点还没有创建,所以地址先为空
ListNode *p = &head;//先储存一下首地址,不可以直接拿head直接进行操作,不然你不知道你跑到哪去了
for (int i = 1; i <=5 ; ++i) {
ListNode newListNode;
newListNode.val=i;//给新节点进行赋值
newListNode.next = nullptr;//同样,下一个节点还没有创建,所以地址为空
p->next=&newListNode;//节点的连接
p = &newListNode;//并且实时更新p的位置
}
不过,其实上面的代码是有错误的,为什么这样子说呢?
因为在这个for循环里面,我们定义的这个变量newListNode始终系统给他的地址都是一样的,所以我们这边不可以用系统给分配的地址,所以需要我们自己分配地址
ListNode *newListNode=new ListNode();//C++的分配空间操作
ListNode head;
head.val=12;//赋值给数据域
head.next=NULL;//我们下一个节点还没有创建,所以地址先为空
ListNode *p = &head;//先储存一下首地址,不可以直接拿head直接进行操作,不然你不知道你跑到哪去了
for (int i = 1; i <=5 ; ++i) {
ListNode *newListNode=new ListNode();//C++的分配空间操作
newListNode->val=i;//给新节点进行赋值
newListNode->next = nullptr;//同样,下一个节点还没有创建,所以地址为空
p->next=newListNode;//节点的连接,这边就不需要取地址符号了,因为newListNode就是一个地址
p = newListNode;//并且实时更新p的位置,这边同理
}
这样子我们的链表就创建好了。
然后关于链表的遍历
ListNode *p= new ListNode();
p =head->next;//这里需要注意一下看你的链表有没有一个头节点是没有存数据的,我们基本上的头节点是不存数据,方便以后的擦汗如删除操作
while (p!= nullptr){
cout<<p->val<<" ";
p=p->next;
}
完整的插入删除的代码就放在下面了,代码的健壮性不强,没有对一些特殊情况进行处理,因为我太懒了,自己改改bug,然后下面的代码都有比较详细的注释,可以对照下:
#include <iostream>
using namespace std;
struct ListNode{
int val;//节点的数据域
ListNode * next;//下一个节点的地址
};
void delete_ListNode(ListNode*head,int number){
ListNode * p = head;//首先我们先找到链表的地址,也就是头节点的地址
while (p->next!=NULL){//遍历链表
if(p->next->val==number){//寻找到指定的数字
ListNode * temp = p->next;//先保存这块地址,不然下面执行p->next=p->next->next之后,找不到这一块地址
p->next=p->next->next;//原本这个节点储存的是下一个节点的地址,现在改变成从该节点往后数两个节点的地址
cout<<"删除成功"<<endl;
free(temp);//释放被我们删除的节点的地址
break;//跳出循环
}
p=p->next;//跳转到下一个地址
}
}
void insert_ListNode(ListNode*head,int location,int data){
ListNode*p =head;
int ans = 0;//标记走过了多少个结点
while (p!= nullptr){//遍历链表
if(ans==location-1){//找到要插入节点的前一个节点的位置
ListNode* temp = (ListNode*)malloc(sizeof(ListNode));//新创建一个结点
temp->val = data;//将你输入的data值赋值给结点
temp->next = p->next;
p->next = temp;
break;
}
p=p->next;
ans++;
}
}
int main(){
ListNode head1;
//创建一个新的节点
ListNode *head = (ListNode*)malloc(sizeof(ListNode));
head->val= NULL;
head->next = NULL;
//找到头节点的地址,并且用p来进行操作,不然以后我们找不到头节点
ListNode * p = head;
for (int i = 0; i < 5; ++i) {
ListNode* temp = (ListNode*)malloc(sizeof(ListNode));//创建一个新节点
cin>>temp->val;//输入新节点的值
temp->next =NULL;//让新节点的存放的地址域为空,避免乱指
p->next = temp;//将这一个节点连接到链表,就是将链表的最后一个节点储存的地址域从空赋值为新节点的地址域
p=temp;//p原来是指链表最后一个节点的地址,现在我们新加入一个节点,并且这个节点位于链表的最后面,所以我们所以将这个p赋值为temp的地址,因为temp现在是链表的最后一个节点
}
cout<<"先遍历一遍链表:"<<endl;
p =head->next;
while (p!= nullptr){
cout<<p->val<<" ";
p=p->next;
}
delete_ListNode(head,12);
cout<<"再遍历一遍删除了数字12之后的链表:"<<endl;
p = head->next;//定位到第一个带有数据的节点
while (p!= nullptr){//遍历链表的每一个节点
cout<<p->val<<" ";
p=p->next;
}
cout<<endl;
cout<<"再遍历一遍在第5个位置插入数字10链表:"<<endl;
insert_ListNode(head,5,10);
p = head->next;
while (p!= nullptr){
cout<<p->val<<" ";
p=p->next;
}
return 0;
}
新人初次写博客,有些东西理解不够到位,希望各位的指正(^-^)