单向链表

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单向链表相关的知识,希望对你有一定的参考价值。

  单向链表每个节点由两个成员组成:一个是数据域,一个是指向自身结构的指针类型成员。如:

    struct slist

    {

      int data;

      struct slist *next;

    };

    typedef struct slist SLIST;

  单向链表的基本算法包括:链表的建立、节点数据域的输出、节点的插入和删除。

  (1) 建立带有头结点的单向链表

  建立单向链表的主要步骤如下:

  1. 读取数据
  2. 生成新节点
  3. 将数据存入节点的成员变量中
  4. 将新节点插入到链表中,重复上述操作直至输入结束。

  例1 编写函数 creat_slist,建立带有头结点的单向链表。节点数据域中的数值从键盘输入,以 -1 作为输入结束标志。链表头结点的地址由函数值返回。

program

技术分享
 1 //建立带有头结点的单向链表
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 
 5 //声明结构体类型 SLIST
 6 struct slist
 7 {
 8     int data;
 9     struct slist *next;
10 };
11 typedef struct slist SLIST;
12 
13 //声明链表建立函数
14 SLIST* creat_slist();
15 void print_slist(SLIST* head);
16 void insert_snode(SLIST* head, int x, int y);
17 
18 int main()
19 {
20     SLIST *head;
21     head = creat_slist();    //调用链表建立函数,得到头结点地址
22     print_slist(head);
23     return 0;
24 }
25 
26 //定义链表建立函数
27 SLIST* creat_slist()
28 {    
29     //s指向新生成的节点;r指向链表的尾节点
30     SLIST *h, *s, *r;
31     int c;
32 
33     h = (SLIST*)malloc(sizeof(SLIST));
34     r = h;
35     scanf("%d", &c);        //读入数据
36     
37     while (c != -1)            //-1作为输入结束标志
38     {
39         s = (SLIST*)malloc(sizeof(SLIST));        //生成新节点
40         s->data = c;        //将读入的数据存入新节点的数据域
41         r->next = s;        //新节点链接到表尾
42         r = s;                //r指向当前表尾
43         scanf("%d", &c);    //读入数据
44     }
45     r->next = \\0;            //链表结束标志
46     return h;
47 }
48 
49 //顺序输出链表各节点数据
50 void print_slist(SLIST* head)
51 {
52     SLIST* p;
53     p = head->next;            //p指向头结点后的第一个节点
54     
55     if (p == \\0)
56     {
57         printf("Linklist is null !\\n");    //链表为空(只有头结点)
58     }
59     else
60     {
61         printf("head");
62         do
63         {
64             printf("->%d", p->data);    //输出当前节点数据域中的值
65             p = p->next;                //p指向下一节点
66         } while (p != \\0);            //未到链表尾,循环继续
67     }
68 }
69 
70 //在值为x的节点前插入值为y的节点
71 void insert_snode(SLIST* head, int x, int y)
72 {
73     SLIST *s, *p, *q;
74     s = (SLIST*)malloc(sizeof(SLIST));    //生成新节点
75     s->data = y;                        //在新节点中存入y值
76     q = head;
77     p = head->next;                        //工作指针初始化,p指向第一个节点
78     while ( (p != \\0) && (p->data != x) )
79     {
80         q = p;
81         p = p->next;                    //q指向p的前趋节点
82     }
83     //x存在,插在x之前;x不存在,p的值为NULL,插在表尾
84     s->next = p;
85     q->next = s;
86 }
View Code

  技术分享

  我们在函数中定义了一个名为 h 的指针变量,用于存放头结点的地址;另外还定义了两个工作指针:s 和 r,其中指针 s 用来指向新生成的节点,指针 r 总是指向当前链表的尾节点。每当把 s 所指的新开辟的节点连接到表尾后,r 便移向这一新的表尾节点,这时再用 s 去指向下一个新开辟的节点,就是使 r 承上,用 s 启下。

  链表最后一个节点的指针域中置 ‘\\0‘(NULL),作为单向链表的结束标志。链表建成后,头结点的地址作为 creat_slist 函数的返回值由 return 语句返回并赋给 main 函数中的指针变量 head,因此函数的类型应该是基类型为 SLIST 的指针类型。

  调用 creat_slist1 函数时,若一开始就输入-1,控制流程不会进入 while 循环,而直接执行循环之后的 r->next = ‘\\0‘; 语句。这时建立的是一个空链表,其结构如图1所示。由此可见,判断此链表是否为空链表,可用条件:h->next == ‘\\0‘;

技术分享

图1 带有头结点的空链表

  (2) 顺序访问链表中各节点的数据域

  所谓“访问”,可以理解为取各节点的数据域中的值进行各种运算、修改各节点的数据域中的值等一系列的操作。

  输出单向链表各节点数据域中的内容的算法比较简单,只需利用一个工作指针 p ,从头到尾依次指向链表中的每个节点;当指针指向某个节点时,就输出该节点数据域中的内容,直到遇到链表结束标志为止。如果是空链表,就只输出相关信息并返回调用函数。

  例2 编写函数 print_slist,顺序输出单向链表各节点数据域中的内容。

program

技术分享
 1 //建立带有头结点的单向链表
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 
 5 //声明结构体类型 SLIST
 6 struct slist
 7 {
 8     int data;
 9     struct slist *next;
10 };
11 typedef struct slist SLIST;
12 
13 //声明链表建立函数
14 SLIST* creat_slist();
15 void print_slist(SLIST* head);
16 void insert_snode(SLIST* head, int x, int y);
17 
18 int main()
19 {
20     SLIST *head;
21     head = creat_slist();    //调用链表建立函数,得到头结点地址
22     print_slist(head);
23     return 0;
24 }
25 
26 //定义链表建立函数
27 SLIST* creat_slist()
28 {    
29     //s指向新生成的节点;r指向链表的尾节点
30     SLIST *h, *s, *r;
31     int c;
32 
33     h = (SLIST*)malloc(sizeof(SLIST));
34     r = h;
35     scanf("%d", &c);        //读入数据
36     
37     while (c != -1)            //-1作为输入结束标志
38     {
39         s = (SLIST*)malloc(sizeof(SLIST));        //生成新节点
40         s->data = c;        //将读入的数据存入新节点的数据域
41         r->next = s;        //新节点链接到表尾
42         r = s;                //r指向当前表尾
43         scanf("%d", &c);    //读入数据
44     }
45     r->next = \\0;            //链表结束标志
46     return h;
47 }
48 
49 //顺序输出链表各节点数据
50 void print_slist(SLIST* head)
51 {
52     SLIST* p;
53     p = head->next;            //p指向头结点后的第一个节点
54     
55     if (p == \\0)
56     {
57         printf("Linklist is null !\\n");    //链表为空(只有头结点)
58     }
59     else
60     {
61         printf("head");
62         do
63         {
64             printf("->%d", p->data);    //输出当前节点数据域中的值
65             p = p->next;                //p指向下一节点
66         } while (p != \\0);            //未到链表尾,循环继续
67     }
68 }
69 
70 //在值为x的节点前插入值为y的节点
71 void insert_snode(SLIST* head, int x, int y)
72 {
73     SLIST *s, *p, *q;
74     s = (SLIST*)malloc(sizeof(SLIST));    //生成新节点
75     s->data = y;                        //在新节点中存入y值
76     q = head;
77     p = head->next;                        //工作指针初始化,p指向第一个节点
78     while ( (p != \\0) && (p->data != x) )
79     {
80         q = p;
81         p = p->next;                    //q指向p的前趋节点
82     }
83     //x存在,插在x之前;x不存在,p的值为NULL,插在表尾
84     s->next = p;
85     q->next = s;
86 }
View Code

  技术分享

  (3)在单向链表中插入节点

  在单向链表中插入节点,首先要确定插入的位置。当待插节点插在指针 p 所指的节点之前称为“前插”;当待插节点插在指针 p 所指节点之后称为“后插”。图2示意了“前插”操作过程中各指针的指向。

技术分享

图2 单向链表中节点的插入

  当进行“前插”操作时,需要三个工作指针:图中s 用来指向新开辟的节点;用 p 指向插入的位置;q 指向 p 的前趋节点(由于是单向链表,没有指针 q,就无法通过 p 去指向它前趋节点)。

  例3 编写函数 insert_snode,它的功能是:在值为 x 的节点前插入值为 y 的节点,若值为 x 的节点不存在,则插在表尾。

program

技术分享
 1 //建立带有头结点的单向链表
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 
 5 //声明结构体类型 SLIST
 6 struct slist
 7 {
 8     int data;
 9     struct slist *next;
10 };
11 typedef struct slist SLIST;
12 
13 //声明链表建立函数
14 SLIST* creat_slist();
15 void print_slist(SLIST* head);
16 void insert_snode(SLIST* head, int x, int y);
17 
18 int main()
19 {
20     SLIST *head;
21     head = creat_slist();    //调用链表建立函数,得到头结点地址
22     print_slist(head);
23     return 0;
24 }
25 
26 //定义链表建立函数
27 SLIST* creat_slist()
28 {    
29     //s指向新生成的节点;r指向链表的尾节点
30     SLIST *h, *s, *r;
31     int c;
32 
33     h = (SLIST*)malloc(sizeof(SLIST));
34     r = h;
35     scanf("%d", &c);        //读入数据
36     
37     while (c != -1)            //-1作为输入结束标志
38     {
39         s = (SLIST*)malloc(sizeof(SLIST));        //生成新节点
40         s->data = c;        //将读入的数据存入新节点的数据域
41         r->next = s;        //新节点链接到表尾
42         r = s;                //r指向当前表尾
43         scanf("%d", &c);    //读入数据
44     }
45     r->next = \\0;            //链表结束标志
46     return h;
47 }
48 
49 //顺序输出链表各节点数据
50 void print_slist(SLIST* head)
51 {
52     SLIST* p;
53     p = head->next;            //p指向头结点后的第一个节点
54     
55     if (p == \\0)
56     {
57         printf("Linklist is null !\\n");    //链表为空(只有头结点)
58     }
59     else
60     {
61         printf("head");
62         do
63         {
64             printf("->%d", p->data);    //输出当前节点数据域中的值
65             p = p->next;                //p指向下一节点
66         } while (p != \\0);            //未到链表尾,循环继续
67     }
68 }
69 
70 //在值为x的节点前插入值为y的节点
71 void insert_snode(SLIST* head, int x, int y)
72 {
73     SLIST *s, *p, *q;
74     s = (SLIST*)malloc(sizeof(SLIST));    //生成新节点
75     s->data = y;                        //在新节点中存入y值
76     q = head;
77     p = head->next;                        //工作指针初始化,p指向第一个节点
78     while ( (p != \\0) && (p->data != x) )
79     {
80         q = p;
81         p = p->next;                    //q指向p的前趋节点
82     }
83     //x存在,插在x之前;x不存在,p的值为NULL,插在表尾
84     s->next = p;
85     q->next = s;
86 }
View Code

  由于本例中的单向链表采用了带有头结点的结构,不需要单独处理新节点插在表头的情况,从而简化了操作。函数 insert_snode 中综合运用了“查找”和“前插”的算法。在进行插入操作的过程中,可能遇到三种情况,函数 insert_snode 将对这三种情况进行处理:

  (1)链表非空,值为 x 的节点存在,新节点应插在该节点之前。

  (2)链表非空,但值为 x 的节点不存在,新节点应插在表尾。

  (3)链表为空表,这种情况相当于 x 的节点不存在,新节点应插在表尾,即插在头结点之后,作为表的第一个节点。

  在函数中,对于空表,在执行 p = head->next; 后,p 的值就为 NULL ,因此不会进入循环;当链表非空时进入循环,在 while 循环中,当出现 p == ‘\\0‘ 时,将退出循环,这意味着查找结束,在链表中不存在值为 x 的节点。对于这两种情况,语句 s->next = p; q->next = s; 将新节点插在表尾。当链表中存在值为 x 的节点时,p 的值一定部位 ‘\\0‘,语句 s->next = p; q->next = s; 将新节点插在 x 节点之前。

  需要注意的是:while 循环中两个条件的顺序不能对调。当 p == NULL 时,C 编译程序将“短路”掉第二个条件,不做判断;否则如果先判断 p->data != x 的条件,由于 p 中的值已为 NULL,这时再要访问 p->data, 就会发生访问虚地址的错误操作。

  (4)删除单向链表中的节点

  为了删除单项链表中的某个节点,首先要找到待删除节点的前趋节点,然后将此前趋节点的指针域去指向待删除节点的后续节点(q->next = p->next),最后释放被删除节点所占空间 free(p) 即可。图3示意了节点的删除操作。

技术分享

图3 单向链表节点的删除

以上是关于单向链表的主要内容,如果未能解决你的问题,请参考以下文章

数据结构 | TencentOS-tiny中的单向链表的实现及使用

什么叫带头结点的链表? 什么叫不带头结点的链表?

用Java语言实现单向链表

链表——带表头的单向链表

数据结构-链表链表的基本操作

单向链表的构建以及翻转算法_图文详解