08链表
来源:互联网 发布:多媒体课件制作软件 编辑:程序博客网 时间:2024/06/05 12:43
链表
- 链表
- 热身
- 基本概念
- 静态链表的局限性
- 单向带头结点的动态链表
- 传统链表和Linux内核链表的区别
1.热身
- 结构体里面套一个自己类型的结构体元素是不行的,但是可以套指向自己类型的结构体指针
- 结构体不能嵌套定义(确定不了数据类型的内存大小,分配不了内存)
- 函数的嵌套调用和数据类型的嵌套定义是两个不同的概念
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>#include <string.h>typedef struct __Student{ char name[64]; int id;}Student;typedef struct __Teacher{ char name[64]; int id; char *p; char **p2; Student stu1;//结构体里面套一个结构体 Student *p3;//结构体里面套一个结构体指针}Teacher;//结构体里面套一个自己类型的结构体元素是不行的,但是可以套指向自己类型的结构体指针//结构体不能嵌套定义(确定不了数据类型的内存大小,分配不了内存)typedef struct __AdvTeacher{ char name[64]; int id; struct __AdvTeacher *p;}AdvTeacher;int main(void){ Teacher t1; AdvTeacher advt2; system("PAUSE"); return 1;}
2.基本概念
- 链表是一种物理存储单元上非连续的存储结构,由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成,节点与节点之间通过指针链接。每个结点包括两个部分:一部分是存储数据元素的数据域,另一部分是存储下一个结点地址的指针域。
涉及术语
- 结构体
- 两个域:指针域和数据域
- 引用自身结构体
- 非顺序存储
链表分类:
- 带头链表 & 不带头链表
- 结点的组织形式
- 单向链表、双向链表、循环链表等
- 静态链表 & 动态链表
- 少用(原因) & 常用
- 少用(原因) & 常用
相关概念
头结点、当前节点、前驱节点、后继节点、新建节点
3. 静态链表的局限性:
- 结点个数固定
- 数据全部在临时区(栈)
- 类似于数组
typedef struct __Teacher{ int data; struct __Teacher *next;}Teacher;Teacher * createListTeacher(){ Teacher t1, t2, t3; Teacher *ptr = NULL; t1.data = 1; t2.data = 2; t3.data = 3; t1.next = &t2; t2.next = &t3; t3.next = NULL; ptr = &t1; while (ptr) { printf("data:%d\n",ptr->data); ptr = ptr->next; } return &t1;}int main(void){ Teacher *head = createListTeacher(); system("PAUSE"); return 1;}
4.单向带头结点的动态链表
注意分清楚辅助指针变量和链表操作的关系
在创建链表的时候需要三个辅助变量pHead、pCur、pM
typedef struct __node{ int data;//数据域 struct __node *next;//(指向自己结构体类型的)指针域}SLIST;/*创建链表(尾插法插入新节点)*/SLIST *SList_Create(){ SLIST * pHead = NULL;//辅助指针变量之一,表示头结点 SLIST * pCur = NULL;//让新节点变成当前节点的辅助指针变量 SLIST * pM = NULL;//用于指向信malloc出的节点的辅助指针变量 int data = 0; /*0.创建头结点*/ pHead = (SLIST*)malloc(sizeof(SLIST)); if (pHead == NULL) { return NULL; } pHead->data = 0; pHead->next = NULL; pCur = pHead;//pCur永远指向最后一个节点 printf("\nplease input your data:"); scanf("%d",&data); /*1.循环malloc出新节点*/ while (data != 0) { pM = (SLIST*)malloc(sizeof(SLIST)); if (pM == NULL) { return NULL; } pM->data = data; pM->next = NULL; /*2.新节点入链表*/ pCur->next = pM; /*3.新节点变成当前节点*/ pCur = pM;//pCur永远指向最后一个节点 printf("\nplease input your data:"); scanf("%d", &data); } return pHead;}
链表是单向的,当前节点的位置保存在前驱节点的指针域,所以在插入的时候,要先将新节点的指针域指向当前节点(新节点插入在该节点之前),然后当前节点的前驱结点的指针域指向新节点。需要的辅助变量有三个:
pPre、pCur(指向要比较的节点)、pM(新增节点)
/*在x之前插入y*/int SList_NodeInsert(SLIST * pHead,int x,int y){ SLIST * pPre = NULL;//辅助指针变量之一,表示要插入新节点的前驱结点 SLIST * pCur = NULL;//表示要插入新节点的后继结点 SLIST * pM = NULL;//用于指向新节点 if (pHead == NULL)//合法性判断 { return -1; } pPre = pHead;//初始化的时候让前驱结点指向头结点 pCur = pHead->next;//初始化的时候让后继结点指向第一个节点(不是头结点) pM = (SLIST*)malloc(sizeof(SLIST));//为新节点开辟空间 if (pM == NULL) { return -1; } /*初始化新节点*/ pM->data = y; pM->next = NULL; while (pCur)//pCur指向要插入节点的后继节点,当pCur==NULL的时候表示遍历到链表尾 { if (pCur->data == x){//没有到链表尾的时候判断该节点是否是要寻找的比较节点(这里是后继节点) break; } /*不满足条件时往后挪动辅助指针变量的位置*/ pPre = pCur; pCur = pCur->next; } /*不管原链表存不存在X节点,都会将新节点插入链表,不存在的时候插入到链表尾,存在的时候插入到X节点前面*/ pM->next = pCur; pPre->next = pM; return 0;}
在删除节点的时候只需要两个辅助指针变量:pPre(指向要删除节点的前驱结点)、pCur(指向当前节点即要删除的节点)
/*删除链表中数据域为y的节点*/int SList_NodeDelete(SLIST * pHead, int y){ SLIST *pPre = NULL;//指向要删除节点的前驱结点 SLIST *pCur = NULL;//指向要删除的节点或者直到链表尾 if (pHead == NULL)//合法性检测 { return -1; } /*初始化的时候前驱结点指向头结点,要删除节点指向第一个非头结点的节点即使是空指针(即链表除了头结点没有任何元素)*/ pPre = pHead; pCur = pHead->next; while (pCur)//只要当前节点不是空指针(即没有到链表尾) { if (pCur->data == y)//满足条件不在移动辅助指针变量的指向 { break; } /*不满足条件时往后挪动辅助指针变量的位置*/ pPre = pCur; pCur = pCur->next; } if (pCur == NULL)//寻找到链表尾没有发现要删除的节点 { printf("cant find the node of y\n"); return -2; } pPre->next = pCur->next;//删除节点 if (pCur != NULL) { free(pCur);//内存回收 pCur = NULL;//避免野指针 } return 0;}
销毁链表的时候需要两个辅助指针
pTmp
(用于缓存要销毁的节点的数据,否则一旦销毁以后找不到后继节点)以及pCur
(指向要销毁的当前节点),因为删除链表是逐个节点的删除。先从头结点开始。
/*销毁链表*/int SList_Destroy(SLIST ** pHead){ SLIST *pCur = NULL;//指向要销毁的当前节点 SLIST *pTmp = NULL;//用于缓存要销毁的节点的数据,否则一旦销毁以后找不到后继节点,实际上就是指向后一个节点 if (NULL == pHead) { return -1; } if (NULL == *pHead) { return -1; } pCur = (SLIST *)(*pHead);//初始化的时候将pCur指向头结点 while (pCur) { pTmp = pCur->next;//缓存上一个节点的数据以便找到后继节点 free(pCur); pCur = pTmp;//将pCur指向后继节点 } *pHead = NULL;//避免野指针 return 0;}
逆置链表的时候需要三个辅助指针变量,分别指向当前节点,前驱结点以及后继节点。只有头结点或者只有一个节点和头结点也不需要逆置,所以初始化辅助指针变量的时候要从第一二个非头结点的节点开始。
/*链表逆置*/int SList_Reverse(SLIST * pHead){ SLIST * pPre = NULL;//前驱节点 SLIST * pCur = NULL;//当前节点 SLIST * pNext = NULL;//后继节点 /*合法性检测或者只有头节点或者只有一个节点和头结点*/ if (pHead == NULL || pHead->next == NULL || pHead->next->next == NULL) { return -1; } /*初始化的时候将前驱结点指向第一个节点*/ pPre = pHead->next; pCur = pHead->next->next;//当前节点指向第二个节点 while (pCur) { pNext = pCur->next;//保存第三个节点的位置或者是NULL(链表尾) pCur->next = pPre;//当前节点的指针域指向前驱结点 /*当前节点和前驱结点一起往后移动一个节点*/ pPre = pCur; pCur = pNext; } pHead->next->next = NULL;//修正第一个节点的指针域指向NULL变成新的链表尾 pHead->next = pPre;//修正头结点指向新链表的第一个节点(旧链表的最后一个节点) return 0;}
在遍历链表的时候需要一个辅助变量,初始化的时候指向第一个非头结点的节点。每次遍历一个节点即可。
/*顺序遍历单向动态链表*/int SList_Print(SLIST *pHead){ SLIST *tmp = NULL; if (pHead == NULL) { return -1; } tmp = pHead->next;//tmp指向第一个节点(不是头结点) printf("\nBegin\n"); while (tmp) { printf("%d\n",tmp->data); tmp = tmp->next;//tmp永远指向要打印的当前节点 } printf("\nEnd\n"); return 0;}
整个工程完整代码如下:
#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>#include <string.h>typedef struct __node{ int data;//数据域 struct __node *next;//(指向自己结构体类型的)指针域}SLIST;/*创建链表(尾插法插入新节点)*/SLIST *SList_Create(){ SLIST * pHead = NULL;//辅助指针变量之一,表示头结点 SLIST * pCur = NULL;//让新节点变成当前节点的辅助指针变量 SLIST * pM = NULL;//用于指向信malloc出的节点的辅助指针变量 int data = 0; /*0.创建头结点*/ pHead = (SLIST*)malloc(sizeof(SLIST)); if (pHead == NULL) { return NULL; } pHead->data = 0; pHead->next = NULL; pCur = pHead;//pCur永远指向最后一个节点 printf("\nplease input your data:"); scanf("%d",&data); /*1.循环malloc出新节点*/ while (data != 0) { pM = (SLIST*)malloc(sizeof(SLIST)); if (pM == NULL) { return NULL; } pM->data = data; pM->next = NULL; /*2.新节点入链表*/ pCur->next = pM; /*3.新节点变成当前节点*/ pCur = pM;//pCur永远指向最后一个节点 printf("\nplease input your data:"); scanf("%d", &data); } return pHead;}/*顺序遍历单向动态链表*/int SList_Print(SLIST *pHead){ SLIST *tmp = NULL; if (pHead == NULL) { return -1; } tmp = pHead->next;//tmp指向第一个节点(不是头结点) printf("\nBegin\n"); while (tmp) { printf("%d\n",tmp->data); tmp = tmp->next;//tmp永远指向要打印的当前节点 } printf("\nEnd\n"); return 0;}/*在x之前插入y*/int SList_NodeInsert(SLIST * pHead,int x,int y){ SLIST * pPre = NULL;//辅助指针变量之一,表示要插入新节点的前驱结点 SLIST * pCur = NULL;//表示要插入新节点的后继结点 SLIST * pM = NULL;//用于指向新节点 if (pHead == NULL)//合法性判断 { return -1; } pPre = pHead;//初始化的时候让前驱结点指向头结点 pCur = pHead->next;//初始化的时候让后继结点指向第一个节点(不是头结点) pM = (SLIST*)malloc(sizeof(SLIST));//为新节点开辟空间 if (pM == NULL) { return -1; } /*初始化新节点*/ pM->data = y; pM->next = NULL; while (pCur)//pCur指向要插入节点的后继节点,当pCur==NULL的时候表示遍历到链表尾 { if (pCur->data == x){//没有到链表尾的时候判断该节点是否是要寻找的比较节点(这里是后继节点) break; } /*不满足条件时往后挪动辅助指针变量的位置*/ pPre = pCur; pCur = pCur->next; } /*不管原链表存不存在X节点,都会将新节点插入链表,不存在的时候插入到链表尾,存在的时候插入到X节点前面*/ pM->next = pCur; pPre->next = pM; return 0;}/*删除链表中数据域为y的节点*/int SList_NodeDelete(SLIST * pHead, int y){ SLIST *pPre = NULL;//指向要删除节点的前驱结点 SLIST *pCur = NULL;//指向要删除的节点或者直到链表尾 if (pHead == NULL)//合法性检测 { return -1; } /*初始化的时候前驱结点指向头结点,要删除节点指向第一个非头结点的节点即使是空指针(即链表除了头结点没有任何元素)*/ pPre = pHead; pCur = pHead->next; while (pCur)//只要当前节点不是空指针(即没有到链表尾) { if (pCur->data == y)//满足条件不在移动辅助指针变量的指向 { break; } /*不满足条件时往后挪动辅助指针变量的位置*/ pPre = pCur; pCur = pCur->next; } if (pCur == NULL)//寻找到链表尾没有发现要删除的节点 { printf("cant find the node of y\n"); return -2; } pPre->next = pCur->next;//删除节点 if (pCur != NULL) { free(pCur);//内存回收 pCur = NULL;//避免野指针 } return 0;}/*链表逆置*/int SList_Reverse(SLIST * pHead){ SLIST * pPre = NULL;//前驱节点 SLIST * pCur = NULL;//当前节点 SLIST * pNext = NULL;//后继节点 /*合法性检测或者只有头节点或者只有一个节点和头结点*/ if (pHead == NULL || pHead->next == NULL || pHead->next->next == NULL) { return -1; } /*初始化的时候将前驱结点指向第一个节点*/ pPre = pHead->next; pCur = pHead->next->next;//当前节点指向第二个节点 while (pCur) { pNext = pCur->next;//保存第三个节点的位置或者是NULL(链表尾) pCur->next = pPre;//当前节点的指针域指向前驱结点 /*当前节点和前驱结点一起往后移动一个节点*/ pPre = pCur; pCur = pNext; } pHead->next->next = NULL;//修正第一个节点的指针域指向NULL变成新的链表尾 pHead->next = pPre;//修正头结点指向新链表的第一个节点(旧链表的最后一个节点) return 0;}/*销毁链表*/int SList_Destroy(SLIST ** pHead){ SLIST *pCur = NULL;//指向要销毁的当前节点 SLIST *pTmp = NULL;//用于缓存要销毁的节点的数据,否则一旦销毁以后找不到后继节点,实际上就是指向后一个节点 if (NULL == pHead) { return -1; } if (NULL == *pHead) { return -1; } pCur = (SLIST *)(*pHead);//初始化的时候将pCur指向头结点 while (pCur) { pTmp = pCur->next;//缓存上一个节点的数据以便找到后继节点 free(pCur); pCur = pTmp;//将pCur指向后继节点 } *pHead = NULL;//避免野指针 return 0;}int main(void){ int ret = 0; SLIST * pHead = SList_Create(); ret = SList_Print(pHead); ret = SList_NodeInsert(pHead, 7, 19); ret = SList_Print(pHead); ret = SList_NodeDelete(pHead, 19); ret = SList_Print(pHead); ret = SList_Reverse(pHead); ret = SList_Print(pHead); SList_Destroy(&pHead); system("PAUSE"); return 1;}
5.传统链表和Linux内核链表的区别
传统链表的节点分为指针域和数据域,其数据域与具体的业务模型有关,所以一旦节点的结构体发生变化,都要重新实现一套链表相关的程序,尽管这些结构体的数据类型很相近。
Linux内核链表的思想是:既然结构体节点不能包含万事万物,那就让万事万物包含链表节点(结构体),也就是说Linux内核中的链表节点的数据类型在宏观上不一样,在微观上是相同的(每个节点都是纯链表节点,只有指针域没有数据域)。通过链表节点(他在结构体中是一个成员)在其结构体重的偏移量可以得到具体结构体变量的起始位置,所以可以将一堆堆数据块串起来。在进程控制块中有很充分的体现。
通用链表可以克服内核链表求偏移量的过程:将结构体中要作为链表节点的成员放在结构体的开头位置,这样链表节点的地址就是他所在结构体的起始位置。
- 08链表
- 08-数据结构_线性结构-离散存储-链表_1
- 链表
- 链表
- 链表
- 链表
- 链表
- 链表
- 链表
- 链表
- 链表
- 链表
- 链表
- 链表
- 链表
- 链表
- 链表
- 链表
- 看代码写结果——C++类的静态成员
- LeetCode Weekly Contest 25
- Android与JS调用
- 什么是支付账户、备付金、网络支付、银行卡清算、贷记卡、代扣、代付....
- 爬取网站段子
- 08链表
- Linggle常用命令
- LeetCode 94. Binary Tree Inorder Traversal
- 再次理解dfs,poj1014
- 中缀表达式树及其结果计算
- 【VS2013】错误处理error C4996: 'fopen': This function or variable may be unsafe
- 第二章:Oracle数据库的用户和表空间
- 计161_Problem : 字符串替换(串)
- C++菱形继承