单向链表
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单向链表相关的知识,希望对你有一定的参考价值。
单向链表每个节点由两个成员组成:一个是数据域,一个是指向自身结构的指针类型成员。如:
struct slist
{
int data;
struct slist *next;
};
typedef struct slist SLIST;
单向链表的基本算法包括:链表的建立、节点数据域的输出、节点的插入和删除。
(1) 建立带有头结点的单向链表
建立单向链表的主要步骤如下:
- 读取数据
- 生成新节点
- 将数据存入节点的成员变量中
- 将新节点插入到链表中,重复上述操作直至输入结束。
例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 }
我们在函数中定义了一个名为 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 }
(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 }
由于本例中的单向链表采用了带有头结点的结构,不需要单独处理新节点插在表头的情况,从而简化了操作。函数 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 单向链表节点的删除
以上是关于单向链表的主要内容,如果未能解决你的问题,请参考以下文章