初识单链表--创建、插入、遍历

    前两天由于"q->next=p->next; p->next=q;"这两句代码的困扰,花了两天时间也算是搞清楚一点,记录下来与大家一起分享。该篇的核心内容在于:

  • 链表中每一个节点的"原生输出"与使用头指针访问的形式"遍历输出"
  • 头节点引入的目的、意义
  • 链表的遍历是如何一步一步演化成为我们所看到的样子的

一、创建链表

注意:

一个链表中可以没有头结点,但是绝对不可能没有头指针的。链表中第一个节点的存储位置叫头指针。这里的第一个节点可以是头结点(不存放有效数据)也可以是存储有效数据的第一个节点。

使用堆内存来创建一个链表节点的步骤:
1.申请堆内存,大小是一个节点的大小(检查申请的结果是否正确)
2.清除申请到的堆内存
3.填充新节点的data域和指针域

下面的代码是没有头结点的一个简单的链表。后面会提到带有头结点的,以及为什么要带有头结点?

/*

目的:实现只包含一个节点的链表的初始化,以及数据输出
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Node
{
int data;
struct Node *next;
};
int main()
{
struct Node *pHead = NULL;//定义头指针,为了安全起见,给pHead置NULL
struct Node *p = (struct Node*)malloc(sizeof(struct Node));
pHead = p;
if(NULL == p)
{
printf("malloc error.\n");
return -1;
}
memset(p,0,sizeof(struct Node));
p->data = 1;
p->next = NULL;

printf("p->data = %d\n",p->data);
printf("pHead->data = %d\n",p->data);
return 0;

}

输出结果:

初识单链表--创建、插入、遍历

从实验结果可以得知:p->data和pHead->data的结果是一致的,这个对链表的理解非常重要。先搁在这,请往下看。

/*
目的:"原原本本的使用每一个节点打印输出数据"与"使用头指针的方式打印出各个节点的数据"
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Node
{
int data;
struct Node *next;
};
int main()
{
struct Node *pHead = NULL;
struct Node *p = (struct Node*)malloc(sizeof(struct Node));
pHead = p;
if(NULL == p)
{
printf("malloc error.\n");
return -1;
}
memset(p,0,sizeof(struct Node));
p->data = 11;
p->next = NULL;
printf("p->data = %d\n",p->data);
printf("pHead->data = %d\n\n",pHead->data);

struct Node *p1 = (struct Node*)malloc(sizeof(struct Node));
if(NULL == p1)
{
printf("malloc error.\n");
return -1;
}
memset(p1,0,sizeof(struct Node));
p1->data = 22;
p1->next = NULL;

p->next = p1;
printf("p1->data = %d\n",p1->data);
printf("pHead->next->data = %d\n\n",pHead->next->data);

struct Node *p2 = (struct Node*)malloc(sizeof(struct Node));
if(NULL == p2)
{
printf("malloc error.\n");
return -1;
}
memset(p2,0,sizeof(struct Node));
p2->data = 33;
p2->next = NULL;

p1->next = p2;
printf("p2->data = %d\n",p2->data);
printf("pHead->next->next->data = %d\n",pHead->next->next->data);
return 0;
}

实验结果:

初识单链表--创建、插入、遍历

实验结果得出来的结论:p->data和pHead->data的结果仍然是一致的。

初识单链表--创建、插入、遍历

以上两组代码,其实就是在说明,上图的那一个意思(图画的比较丑),但是要表达的意思就在图里面了。

二、链表插入

1. 尾部插入(比较简单)

<1>在上面的链表的创建中,我们明显可以看到:我们每创建一个节点都写了相同的一段代码出来。在这里我们把它封装成为一个具体的函数。

<2>要想在链表的尾部插入一个节点,我们需要两个步骤:a.找到最后的那一个节点  b.把将要添加的新的节点与先前的最后一个节点连接起来即可。

初识单链表--创建、插入、遍历

/*
目的:一个头指针加上一个封装的节点,实现链表的尾部插入操作。
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct  Node
{
int data;
struct Node *next;
};
struct Node *create_node(int data)
{
struct Node *p = (struct Node*)malloc(sizeof(struct Node));
if(NULL == p)
{
printf("malloc error\n");
return -1;
}
memset(p,0,sizeof(struct Node));
p->data = data;
p->next = NULL;
return p;
}
void insert_tail(struct Node *pHead,struct Node *pNew)
{
struct Node *p = pHead;
while(NULL != p->next)
{
p = p->next;
}
p->next = pNew;
}
int main()
{
struct Node *pHead = create_node(1);
insert_tail(pHead,create_node(2));
insert_tail(pHead,create_node(3));

insert_tail(pHead,create_node(4));

printf("pHead->data = %d\n",pHead->data);
printf("pHead->next->data = %d\n",pHead->next->data);
printf("pHead->next->next->data = %d\n",pHead->next->next->data);
printf("pHead->next->next->next->data = %d\n",pHead->next->next->next->data);
return 0;

}

实验结果:

初识单链表--创建、插入、遍历

加入自己亲自一步一步的看下来的话,会不会有一个疑问呢?先前struct Node *pHead = NULL;的,而现在却是:

struct Node *pHead = create_node(1);为什么呢?

我们可以试着写成struct Node *pHead = NULL这时候,会出现Segmentation fault (core dumped)段错误提示。

这里已经开始有一点头结点的味道出来了,接着往下看。

2. 头部插入

在进行头部插入的过程 ,很难想象没有头结点是什么样子的。

初识单链表--创建、插入、遍历

头结点的特点:
<1>紧跟在头指针的后面
<2>头节点的数据部分是空的(可以存放链表的节点数)
<3>指针部分指向第一个有效的节点。
<4>头结点的创建在创建头指针的时候与头指针一并的创建,并且和头指针关联起来。
/*
目的:头部插入
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Node
{
int data;
struct Node *next;
};
struct Node *create_node(int data)
{
struct Node *p = (struct Node*)malloc(sizeof(struct Node));
if(NULL == p)
{
printf("malloc error\n");
return -1;
}
memset(p,0,sizeof(struct Node));
p->data = data;
p->next = NULL;
return p;
}
void insert_head(struct Node *pHead,struct Node *pNew)
{
pNew->next = pHead->next;
pHead->next = pNew;

}
void insert_tail(struct Node *pHead,struct Node *pNew)
{
struct Node *p = pHead;
while(NULL != p->next)
{
p = p->next;
}
p->next = pNew;
}
int main()
{
struct Node *pHead = create_node(-1);//头节点里面不存放数据也是白搁在那了,我们给它置-1
insert_head(pHead,create_node(1));
insert_head(pHead,create_node(2));

printf("head node = %d\n",pHead->data);
printf("the  first node = %d\n",pHead->next->data);
printf("the  second node = %d\n",pHead->next->next->data);
return 0;

}

实验结果:

初识单链表--创建、插入、遍历

三、链表遍历

前面的链表创建、插入可以到,我们在每一次输出一个节点数据的时候很麻烦的,在这里我们把它封装成为一个函数。

注意:

1. 在traverse_list_1()中我们使用"struct Node *p = pHead->next"的手法来跳过头结点里面的数据,从第一个有效数据开始。

但是,引出了打印不完全的问题,我们在while()循环的外面,额外的补充了一个printf()函数,显得格格不入。

2. 这才引出了traverse_list_2()的降临。仔细的品味while()循环中的那个判断条件是实现链表遍历的关键所在。

/*
代码实现的功能:实现链表创建、插入、遍历
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Node
{
int data;
struct Node *next;
};
struct Node *create_node(int data)
{
struct Node *p = (struct Node*)malloc(sizeof(struct Node));
if(NULL == p)
{
printf("malloc error.\n");
return -1;
}
memset(p,0,sizeof(struct Node));
p->data = data;
p->next = NULL;
return p;
}
void insert_tail(struct Node *pHead,struct Node *pNew)
{
struct Node *p = pHead;
while(NULL != p->next)
{
p = p->next;
}
p->next = pNew;
}
void insert_head(struct Node *pHead,struct Node *pNew)
{
struct Node *p = pHead;
pNew->next = p->next;
p->next = pNew;
}
void traverse_list_1(struct Node *pHead)
{
struct Node *p = pHead->next;
while(NULL != p->next)
{
printf("data = %d\n",p->data);
p = p->next;
}
printf("data = %d\n",p->data);
}
void traverse_list_2(struct Node *pHead)
{
struct Node *p = pHead;
while(NULL != p->next)
{
p = p->next;
printf("data = %d\n",p->data);
}
}
int main()
{
struct Node *pHead = create_node(0);
insert_tail(pHead,create_node(1));
insert_tail(pHead,create_node(2));
insert_tail(pHead,create_node(3));

insert_head(pHead,create_node(123));

traverse_list_1(pHead);
printf("*********************\n");
traverse_list_2(pHead);
#if 0
printf("node 1 = %d\n",pHead->data);
printf("node 2 = %d\n",pHead->next->data);
printf("node 3 = %d\n",pHead->next->next->data);
printf("node 4 = %d\n",pHead->next->next->next->data);
printf("node 5 = %d\n",pHead->next->next->next->next->data);
#endif
return 0;
}

实验结果:

初识单链表--创建、插入、遍历

总结:

以上便是自己这两天的对链表的创建、插入、遍历操作的一些感悟、总结。

写的十分的仓促,加上自己水平有限,假如有什么错误的地方,欢迎大家指正、纠错!!