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

Posted usingnamespace-caoliu

tags:

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

  单向链表的构建以及翻转算法

  一、基本概念

  单向链表的链接方向是单向的,其中每个结点都有指针成员变量指向链表中的下一个结点,访问链表时要从头节点(带头节点的链表)或存储首个数据的节点(不带头节点的链表)开始顺序查询。本文将以带头结点的非循环单向链表为例,其链表模型如下:

  技术分享图片

  其中head为头结点(不存储数据)、data节点存储数据、pNext存储下一节点的地址。

  当单项链表不包含头结点时,链表首个节点便是所存储的第一个数据的节点;当单项链表是循环链表时,链表中存储最后一个数据的节点中pNext指向的不是NULL而是链表首部。

 

 

  二、构建算法

  1. 单项链表节点结构体的定义

  链表节点包含两个部分:①节点所存储的数据。②节点所存储的下一节点地址。

  其单个节点模型如下:

  技术分享图片

1 typedef int T;
2 
3 typedef struct Node
4 {
5     //节点保存的数据
6     T data;
7     //下一个节点的位置
8     struct Node* next;
9 }Node,Link;

  tips:①用 typedef 定义数据类型能有效提高代码的实用性,②一般用 NODE* 定义节点, LINK* 定义链表,便以区分。

 

  2. 单项链表的始终

  2.1 单项链表的创建

  单项链表的创建主要包含三个部分:①为申请头结点内存,②使pNext指向下一节点(NULL)。③返回头地址。

  其头节点模型如下:

  技术分享图片

 1 //创建一个链表
 2 Link* creat_link()
 3 {
 4     //创建一个节点,表示头节点,该节点并不保存数据
 5     Link* head = (Link*)malloc(sizeof(Node));
 6     //让头节点的next置NULL,表示链表为空
 7     head->next = NULL;
 8     //返回头的地址
 9     return head;
10 }

 

  2.2 单项链表的清空与销毁

  单项链表的清空与销毁主要包含两个步骤:①顺序依次释放存储数据的节点内存。②释放头结点的内存,变量置空。

 1 //清空链表
 2 void clear_link(Link* link)
 3 {
 4     Node* node = link->next;
 5     while(node != NULL)
 6     {
 7         Node* tmp = node;
 8         node = node->next;
 9         free(tmp);
10     }
11     link->next = NULL;
12 }
13 
14 //销毁链表
15 void destroy_link(Link* link)
16 {
17     clear_link(link);
18     free(link);
19     link = NULL;
20 }

 

  3. 单项链表的判定

  3.1 单项判定链表是否为空

1 //判断链表是否为空
2 bool emtpy_link(Link* link)
3 {
4     return !link->next;
5 }

 

  4. 单向链表的查询

  4.1 获取单向链表中数据的个数

  获取单向链表中数据的个数主要包含三个步骤:①创建变量存储数据个数、创建node记录头节点的下一节位置点用于遍历链表。②循环遍历,当当前节点的pNext指向NULL时代表已达链表末尾,结束循环。③返回数据个数。

 1 //获得链表里数据的个数
 2 size_t size_link(Link* link)
 3 {    
 4     size_t i = 0;
 5     //用node记录头节点的下一个位置
 6     Node* node = link->next;
 7     //只要node不为NULL,表示该节点存在
 8     while(node != NULL)
 9     {
10         //让node继续指向下一个节点
11         node = node->next;
12         i++;
13     }
14     return i;
15 }

 

  4.2 获取指定下标节点的前一个节点

  获取指定下标节点的前一个节点主要包含三个步骤:①创建node记录头节点位置用于遍历链表。②循环遍历,当当前节点的pNext指向NULL时代表已达链表末尾,结束循环。③返回指定下标的前一个节点。

 1 //返回下标为index的前一个节点
 2 Node* getNode_link(Link* link, int index)
 3 {
 4     Node* node = link;
 5     //如果index=0 其前一个节点就为link ,并不会进入下面循环
 6     for(int i=0; node != NULL && i<index; i++)
 7     {
 8         node = node->next;
 9     }
10     return node;
11 }

 

  4.3 查找指定数据的下标(第一个)

  查找指定数据的下标主要包含主要包含三个步骤:①创建node记录头节点的下一节位置点用于遍历链表。②循环遍历,当当前节点的pNext指向NULL时代表已达链表末尾,结束循环。③成功返回数据下标,失败则返回 -1 。

 1 //查找数据value的下标
 2 int indexOf_link(Link* link, T value)
 3 {
 4     Node* node = link->next;
 5     for(int i = 0; node != NULL; i++)
 6     {
 7         if(node->data == value)
 8             return i;
 9         node = node->next;
10     }
11     return -1;
12 }

 

  4.4 遍历显示链表

  遍历显示链表主要包含主要包含三个步骤:①创建node记录头节点的下一节位置点用于遍历链表。②循环遍历,顺序输出每一个节点中储存的数据,当当前节点的pNext指向NULL时代表已达链表末尾,结束循环。

 1 //遍历链表
 2 void travel_link(Link* link)
 3 {
 4     Node* node = link->next;
 5     while(node != NULL)
 6     {
 7         printf("%d ",node->data);
 8         node = node->next;
 9     }
10     puts("");
11 }

 

  5. 单项链表的节点插入

  5.1 将一个节点插入指定下标

  将一个节点插入指定下标(index)主要包含三个步骤:①获取指定下标节点的上一个节点位置。②创建node节点存储需要插入的数据。③让插入节点的下一个节点指向index前一个节点的后节点,让index的前节点的下一个节点指向当前插入的节点。

  其演示模型如下:

技术分享图片

 1 //插入一个数据到指定位置 index取值范围[0,size_link(Link* link)]
 2 bool insert_link(Link* link, int index, T value)
 3 {
 4     if(index < 0 || index > size_link(link)) 
 5         return false;
 6     //得到下标为index位置的前一个节点
 7     Node* prevNode = getNode_link(link, index);
 8     //申请内存,用于保存需要插入的数据
 9     Node* node = (Node*)malloc(sizeof(Node));
10     node->data = value;
11     //让插入节点的下一个节点指向index前一个节点的后节点
12     node->next = prevNode->next;
13     //让index的前节点的下一个节点指向当前插入的节点
14     prevNode->next = node;
15     return true;
16 }
17 //插入一个数据到链表末尾
18 bool insertBack_link(Link* link, T value)
19 {
20     return insert_link(link, size_link(link), value);
21 }
22 //插入一个数据到链表首部
23 bool insertFront_link(Link* link, T value)
24 {
25     return insert_link(link, 0, value);
26 }

 

  5.2 将一个节点替换指定下标节点

  将一个节点替换指定下标(index)节点只包含一个步骤:创建node节点获取指定下标节点的位置并存储需要插入的数据。

1 //更新链表下标为index的节点的值
2 bool update_link(Link* link, int index, T value)
3 {
4     if(index < 0 || index > size_link(link)-1)
5         return false;
6     Node* node = getNode_link(link, index+1);
7     node->data = value;
8     return true;
9 }

 

  5. 单项链表的数据删除

  5.1 将一个指定下标的节点删除

  将一个指定下标的节点(index)删除主要包含四个步骤:①获取指定下标节点的上一个节点位置。②保存要删除的节点,用于释放内存。③让要删除节点的前一个节点指向要删除节点的后一个节点。④释放内存。

  其演示模型如下:

技术分享图片

 1 //删除指定下标的数据
 2 bool delete_link(Link* link, int index)
 3 {
 4     if(index < 0 || index > size_link(link)-1) 
 5         return false;
 6     //获得需要删除节点的前一个节点
 7     Node* prevNode = getNode_link(link, index);
 8     //保存要删除的节点,用于释放内存
 9     Node* node  = prevNode->next;
10     //让要删除节点的前一个节点指向要删除节点的后一个节点
11     prevNode->next = prevNode->next->next;
12     free(node);
13     return true;
14 }

 

  5.2 删除链表中所有包含指定数据的节点

  删除链表中所有包含指定数据(value)的节点主要包含三个步骤:①设置标志变量(flag)表示该链表的数据是否发生变化。②循环遍历,顺序删除每一个包含指定数据的节点并将标志变量置位置为1,当当前节点的pNext指向NULL时代表已达链表末尾,结束循环。③返回标志变量。

 1 //删除元素为value的所有节点,返回值表示该链表的数据是否发生变化
 2 bool deleteDatas_link(Link* link, T value)
 3 {
 4     //作为是否删除成功的一个标志
 5     bool flag = false;
 6     Node* node = link->next;
 7     for(int i = 0; node != NULL; i++)
 8     {
 9         if(node->data == value)
10         {
11             delete_link(link, i);
12             //删除后判定下标前移
13             i--;
14             flag = true;
15         }
16         node = node->next;
17     }
18     return flag;
19 }

 

 

  三、拓展应用

  1. 单项链表的整体翻转

  单向链表的整体翻转具体步骤见“代码注释”,其演示模型如下(建议先了解代码):

  0:

   技术分享图片

  1.1:

  技术分享图片

  2.1:

  技术分享图片

  3.1:

  技术分享图片

  1.2:

  技术分享图片

  2.2:

  技术分享图片

  3.2:

  技术分享图片

  4:

    技术分享图片

  5:

    技术分享图片

 1 //链表逆序
 2 void reverse(Link link)
 3 {
 4     if(link == NULL || link->next == NULL)
 5         return;
 6     //0、记录前一个节点与当前节点
 7     Node* prevNode = link->next;
 8     Node* node = prevNode->next;//NULL
 9     //只要当前节点存在
10     while(node != NULL)
11     {
12         //1、先记录当前节点的后一个节点
13         Node* nextNode = node->next;
14         //2、让当前节点(node)的下一个节点(node->next)指向(=)前一个节点(prev)
15         node->next = prevNode;
16         //3、让前一个节点指向当前节点、当前节点指向原先记录的下一个节点
17         prevNode = node;
18         node = nextNode;
19     }
20     //4、让原来的第一个元素变为尾元素,尾元素的下一个置NULL
21     link->next->next = NULL;
22     //5、让链表的头节点指向原来的尾元素
23     link->next = prevNode;
24 }

 

  2. 链表中每k个节点进行翻转,若最后一组节点数量不足k个,则按实际个数翻转。

  具体流程参考“单项链表的整体翻转”。

 1 //链表中每k个节点进行翻转,若最后一组节点数量不足k个,则按实际个数翻转。
 2 void reverseByNum(Node* prev,Node* node,int num)
 3 {
 4     if(node == NULL)
 5         return;
 6     Node* prevNode = node;
 7     Node* curNode = node->next;
 8     int count = 1;
 9     while(curNode != NULL)
10     {
11         Node* nextNode = curNode->next;
12         curNode->next = prevNode;
13         prevNode = curNode;
14         curNode = nextNode;
15         count++;
16         if(count == num)
17         {
18             Node* tmp = prev->next;
19             prev->next->next = curNode;
20             prev->next = prevNode;
21             reverseByNum(tmp,curNode,num);
22             return;
23         }
24     }
25     prev->next->next = curNode;
26     prev->next = prevNode;
27 }

 


 

  以上是个人对链表的一些认识及理解,若有错误欢迎各位指出。

 




以上是关于单向链表的构建以及翻转算法_图文详解的主要内容,如果未能解决你的问题,请参考以下文章

算法_链表篇

算法_链表篇

C语言反转单向链表的代码

leetcode之反转链表图文详解

单向链表的查删改功能,以及约瑟夫环,相交链表的第一个相交节点的查找等相关问题

java数据结构与算法:java代码模拟带头节点单向链表的业务