人小鬼不大

导航

 

 

  将具有“一对一”关系的数据“线性”地存储到物理空间中,这种存储结构就称为线性存储结构(简称线性表)。数据存储的成功与否,取决于是否能将数据完整地复原成它本来的样子。对于线性表,不管是集中存放(顺序存储)还是分散存放(链式存储),数据的位置依旧没有发生改变。

  使用线性表存储的数据,要求数据类型必须一致

  将数据依次存储在连续的整块物理空间中,这种存储结构称为顺序存储结构(简称顺序表)。数据分散的存储在物理空间中,通过一根线保存着它们之间的逻辑关系,这种存储结构称为链式存储结构(简称链表)。对于具有“一对一”逻辑关系的数据,在线性表中:

  某一元素的左侧相邻元素称为“直接前驱”,位于此元素左侧的所有元素都统称为“前驱元素”;某一元素的右侧相邻元素称为“直接后继”,位于此元素右侧的所有元素都统称为“后继元素”。

1、顺序表(顺序存储结构)及初始化

  顺序表存储数据时,会提前申请一整块足够大小的物理空间,然后将数据依次存储起来,存储时做到数据元素之间不留一丝缝隙

1.1 初始化

  使用顺序表存储数据之前,除了要申请足够大小的物理空间之外,为了方便后期使用表中的数据,顺序表还需要实时记录以下 2 项数据:顺序表申请的存储容量顺序表的长度,也就是表中存储数据元素的个数。正常状态下,顺序表申请的存储容量要大于顺序表的长度

1.1.1 自定义顺序表

1 typedef struct Table{
2     int * head; //声明了一个名为head的长度不确定的数组,也叫“动态数组”
3     int length;  //记录当前顺序表的长度
4     int size;     //记录顺序表分配的存储容量
5 }table;
6 
7 //注意:head 是我们声明的一个未初始化的动态数组,不要只把它看做是普通的指针。

1.1.2 建立顺序表

需要做如下工作:

  给 head 动态数据申请足够大小的物理空间;

  给 size 和 length 赋初值。

 1 #define Size 5     //对Size进行宏定义,表示顺序表申请空间的大小
 2 table initTable(){
 3     table t;
 4     //构造一个空的顺序表,动态申请存储空间
 5     t.head=(int*)malloc(Size*sizeof(int));
 6     if (!t.head)
 7     {
 8          //如果申请失败,作出提示并直接退出程序
 9         printf("初始化失败");
10         exit(0);
11     }
12     t.length=0;    //空表的长度初始化为0
13     t.size=Size;    //空表的初始存储空间为Size
14     return t;
15 }

整个顺序表初始化的过程被封装到了一个函数中,此函数返回值是一个已经初始化完成的顺序表。好处是增加了代码的可用性,也更加美观。顺序表初始化过程中,要注意对物理空间的申请进行判断,对申请失败的情况进行处理

  此时通过在主函数中调用 initTable 语句,就可以成功创建一个空的顺序表。可向表中添加元素:

1     //向顺序表中添加元素
2     for (int i=1; i<=Size; i++) {
3         t.head[i-1]=i;
4         t.length++;
5     }

可以输出结果即说明创建顺序表成功。

1.2 顺序表的基本操作

1.2.1 插入元素

向已有顺序表中插入数据元素,根据插入位置的不同,可分为以下 3 种情况:

  ①插入到顺序表的表头;②在表的中间位置插入元素;③尾随顺序表中已有元素,作为顺序表中的最后一个元素。

数据元素插入顺序表中的位置有所不同,但是都使用的是同一种方式去解决,即:通过遍历,找到数据元素要插入的位置,然后做如下两步工作:①将要插入位置元素以及后续的元素整体向后移动一个位置;②将元素放到腾出来的位置上;

 1 //插入函数,其中,elem为插入的元素,add为插入到顺序表的位置
 2 table addTable(table t,int elem,int add)
 3 {
 4     //判断插入本身是否存在问题(如果插入元素位置比整张表的长度+1还大(如果相等,是尾随的情况),或者插入的位置本身不存在,程序作为提示并自动退出)
 5     if (add>t.length+1||add<1) {
 6         printf("插入位置有问题\n");
 7         return t;
 8     }
 9     //做插入操作时,首先需要看顺序表是否有多余的存储空间提供给插入的元素,如果没有,需要申请
10     if (t.length==t.size) {
11         t.head=(int *)realloc(t.head, (t.size+1)*sizeof(int));
12         if (!t.head) {
13             printf("存储分配失败\n");
14             return t;
15         }
16         t.size+=1;
17     } 
18     //插入操作,需要将从插入位置开始的后续元素,逐个后移
19     for (int i=t.length-1; i>=add-1; i--) {
20         t.head[i+1]=t.head[i];
21     }
22     //后移完成后,直接将所需插入元素,添加到顺序表的相应位置
23     t.head[add-1]=elem;
24     //由于添加了元素,所以长度+1
25     t.length++;
26     return t;
27 }
View Code

  注意:动态数组额外申请更多物理空间使用的是 realloc 函数。并且,在实现后续元素整体后移的过程,目标位置其实是有数据的,还是 3,只是下一步新插入元素时会把旧元素直接覆盖。

1.2.2 删除元素

  只需找到目标元素,并将其后续所有元素整体前移 1 个位置即可,这样实现将目标元素间接删除。

1      //删除操作
2     for (int i=add; i<t.length; i++) {
3         t.head[i-1]=t.head[i];
4     }
5     t.length--;

1.2.3 查找元素

  顺序表中查找目标元素,可以使用多种查找算法实现。顺序查找算法查找如下:

1 for (int i=0; i<t.length; i++) {
2         if (t.head[i]==elem) {
3             return i+1;
4         }
5     }

1.2.4 更改元素

  更改元素的实现过程是:找到目标元素;直接修改该元素的值。

1     //顺序查找算法查找到元素位置
2     //由于返回的是元素在顺序表中的位置,所以-1就是该元素在数组中的下标
3      t.head[add-1]=newElem;
4      return t;

2、单链表

  链表,别名链式存储结构或单链表,用于存储逻辑关系为 "一对一" 的数据。与顺序表不同,链表不限制数据的物理存储状态,换句话说,使用链表存储的数据元素,其物理存储位置是随机的。为了体现出各数据之间的逻辑关系,链表的解决方案是,每个数据元素在存储时都配备一个指针,用于指向自己的直接后继元素。数据元素随机存储,并通过指针表示数据之间逻辑关系的存储结构就是链式存储结构。

2.1 链表的节点

  链表中每个数据的存储都由以下两部分组成:数据元素本身,其所在的区域称为数据域;指向直接后继元素的指针,所在的区域称为指针域。链表中每个节点的具体实现:

1 typedef struct Link{
2     char elem;                 //代表数据域
3     struct Link * next;     //代表指针域,指向直接后继元素
4 }link; 
5 //link为节点名,每个节点都是一个 link 结构体

2.2 头节点,头指针和首元节点

一个完整的链表需要由以下几部分构成:

  头指针:一个普通的指针,它的特点是永远指向链表第一个节点的位置。很明显,头指针用于指明链表的位置,便于后期找到链表并使用表中的数据;

  节点:链表中的节点又细分为头节点、首元节点和其他节点:

    头节点:其实就是一个不存任何数据的空节点,通常作为链表的第一个节点。对于链表来说,头节点不是必须的,它的作用只是为了方便解决某些实际问题;

    首元节点:由于头节点(也就是空节点)的缘故,链表中称第一个存有数据的节点为首元节点。首元节点只是对链表中第一个存有数据节点的一个称谓,没有实际意义;

    其他节点:链表中其他的节点;

注意:链表中有头节点时,头指针指向头节点;反之,若链表中没有头节点,则头指针指向首元节点

2.3 链表的初始化

  创建一个链表需要做如下工作:声明一个头指针(如果有必要,可以声明一个头节点);创建多个存储数据的节点,在创建的过程中,要随时与其前驱节点建立逻辑关系。创建一个存储 {1,2,3,4} 且无头节点的链表:

  创建头指针和首元节点;初始化首元节点;循环创建其他节点。

 1 link * initLink(){
 2     link * p=NULL;    //创建头指针
 3     link * temp = (link*)malloc(sizeof(link));    //创建首元节点
 4     //首元节点先初始化
 5     temp->elem = 1;
 6     temp->next = NULL;
 7     p = temp;    //头指针指向首元节点
 8     //从第二个节点开始创建
 9     for (int i=2; i<5; i++) {
10      //创建一个新节点并初始化
11         link *a=(link*)malloc(sizeof(link));
12         a->elem=i;
13         a->next=NULL;
14         //将temp节点与新建立的a节点建立逻辑关系
15         temp->next=a;
16         //指针temp每次都指向新链表的最后一个节点,其实就是 a节点,这里写temp=a也对
17         temp=temp->next;
18     }
19     //返回建立的节点,只返回头指针 p即可,通过头指针即可找到整个链表
20     return p;
21 }
View Code

创建一个存储 {1,2,3,4} 且含头节点的链表:

 1 link * initLink(){
 2     link * p=(link*)malloc(sizeof(link));    //创建一个头结点
 3     link * temp=p;    //声明一个指针指向头结点
 4     //生成链表
 5     for (int i=1; i<5; i++) {
 6         link *a=(link*)malloc(sizeof(link));
 7         a->elem=i;
 8         a->next=NULL;
 9         temp->next=a;
10         temp=temp->next;
11     }
12     return p;
13 }
View Code

注意:若使用带有头节点创建链表的方式,输出链表时判断指针指向的结点的next不是NULL,就输出数据。

2.4 链表的基本操作

2.4.1 插入元素

  同顺序表一样,向链表中增添元素,根据添加位置不同,可分为以下 3 种情况:①插入到链表的头部(头节点之后),作为首元节点;②插入到链表中间的某个位置;③插入到链表的最末端,作为链表中最后一个数据元素。

  只需以下两步操作,即可将新元素插入到指定的位置:①将新结点的 next 指针指向插入位置后的结点;②将插入位置前结点的 next 指针指向插入结点。注意:链表插入元素的操作必须是先步骤 ①,再步骤 ②;反之,若先执行步骤 ②,除非再添加一个指针,作为插入位置后续链表的头指针,否则会导致插入位置后的这部分链表丢失,无法再实现步骤 ①。

 1 //p为原链表,elem表示新数据元素,add表示新元素要插入的位置
 2 link * insertElem(link * p, int elem, int add) {
 3     link * temp = p;    //创建临时结点temp
 4     //首先找到要插入位置的上一个结点
 5     for (int i = 1; i < add; i++) {
 6         temp = temp->next;
 7         if (temp == NULL) {
 8             printf("插入位置无效\n");
 9             return p;
10         }
11     }
12     //创建插入结点c
13     link * c = (link*)malloc(sizeof(link));
14     c->elem = elem;
15     //向链表中插入结点
16     c->next = temp->next;
17     temp->next = c;
18     return p;
19 }
View Code

插入的步骤:

  ①创建临时结点temp;②找到要插入位置的上一个结点;③创建插入结点;④向链表中插入结点。

2.4.2 删除元素

  从链表中删除数据元素需要进行以下 2 步操作:①将结点从链表中摘下来;②手动释放掉结点,回收被结点占用的存储空间。

  实现①只需找到要删除节点的直接前驱节点 temp,执行

1 temp->next=temp->next->next;

即可。

 1 //p为原链表,add为要删除元素的值
 2 link * delElem(link * p, int add) {
 3     link * temp = p;
 4     //遍历到被删除结点的上一个结点
 5     for (int i = 1; i < add; i++) {
 6         temp = temp->next;
 7         if (temp->next == NULL) {
 8             printf("没有该结点\n");
 9             return p;
10         }
11     }
12     //单独设置一个指针指向被删除结点,以防丢失
13     link * del = temp->next;
14     //删除某个结点的方法就是更改前一个结点的指针域
15     temp->next = temp->next->next;
16     free(del);    //手动释放该结点,防止内存泄漏
17     return p;
18 }
View Code

2.4.3 查找元素

  在链表中查找指定数据元素,最常用的方法是:从表头依次遍历表中节点,用被查找元素与各节点数据域中存储的数据元素进行比对,直至比对成功或遍历至链表最末端的 NULL。

 1 //p为原链表,elem表示被查找元素、
 2 int selectElem(link * p,int elem){
 3 //新建一个指针t,初始化为头指针 p
 4     link * t=p;
 5     int i=1;
 6     //由于头节点的存在,因此while中的判断为t->next
 7     while (t->next) {
 8         t=t->next;
 9         if (t->elem==elem) {
10             return i;
11         }
12         i++;
13     }
14     //程序执行至此处,表示查找失败
15     return -1;
16 }
View Code

2.4.4 更新元素

  更新链表中的元素,只需通过遍历找到存储此元素的节点,对节点中的数据域做更改操作即可。

 1 //更新函数,其中,add 表示更改结点在链表中的位置,newElem 为新的数据域的值
 2 link *amendElem(link * p,int add,int newElem){
 3     link * temp=p;
 4     temp=temp->next;    //在遍历之前,temp指向首元结点
 5     //遍历到待更新结点
 6     for (int i=1; i<add; i++) {
 7         temp=temp->next;
 8     }
 9     temp->elem=newElem;
10     return p;
11 }
View Code

3、静态链表

  也是线性存储结构的一种,它兼顾了顺序表和链表的优点于一身。使用静态链表存储数据,数据全部存储在数组中(和顺序表一样),但存储位置是随机的,数据之间"一对一"的逻辑关系通过一个整形变量(称为"游标",和指针功能类似)维持(和链表类似)。通常,静态链表会将第一个数据元素放到数组下标为 1 的位置(a[1])中。通过 "数组+游标" 的方式存储具有线性关系数据的存储结构就是静态链表。

 

 从 a[1] 存储的数据元素 1 开始,通过存储的游标变量 3,就可以在 a[3] 中找到元素 1 的直接后继元素 2;同样,通过元素 a[3] 存储的游标变量 5,可以在 a[5] 中找到元素 2 的直接后继元素 3,这样的循环过程直到某元素的游标变量为 0 截止(因为 a[0] 默认不存储数据元素)。

3.1 节点

  静态链表存储数据元素也需要自定义数据类型,至少需要包含以下 2 部分信息:①数据域:用于存储数据元素的值;②游标:其实就是数组下标,表示直接后继元素所在数组中的位置。可设计为:

1 typedef struct {
2     int data;    //数据域
3     int cur;    //游标
4 }component;

3.2 备用链表

  静态链表中,除了数据本身通过游标组成的链表外,还需要有一条连接各个空闲位置的链表,称为备用链表。备用链表的作用是回收数组中未使用或之前使用过(目前未使用)的存储空间,留待后期使用。也就是说,静态链表使用数组申请的物理空间中,存有两个链表,一条连接数据,另一条连接数组中未使用的空间。

  通常,备用链表的表头位于数组下标为 0(a[0]) 的位置,而数据链表的表头位于数组下标为 1(a[1])的位置。静态链表中设置备用链表的好处是,可以清楚地知道数组中是否有空闲位置,以便数据链表添加新数据时使用。若静态链表中数组下标为 0 的位置上存有数据,则证明数组已满。

3.3 静态链表的创建

大致步骤如下:

  ①在数据链表未初始化之前,数组中所有位置都处于空闲状态,因此都应被链接在备用链表上;

1 //将结构体数组中所有分量链接到备用链表中
2 void reserveArr(component *array){
3     for (int i=0; i<maxSize; i++) {
4         array[i].cur=i+1;//将每个数组分量链接到一起
5         array[i].data=-1;
6     }
7     array[maxSize-1].cur=0;//链表最后一个结点的游标值为0
8 }
View Code

  备用链表摘除节点最简单的方法是摘除 a[0] 的直接后继节点;同样,向备用链表中添加空闲节点也是添加作为 a[0] 新的直接后继节点。

1 //从备用链表上摘下空闲节点的函数
2 int mallocArr(component * array){
3     //若备用链表非空,则返回分配的结点下标,否则返回 0(当分配最后一个结点时,该结点的游标值为 0)
4     int i=array[0].cur;
5     if (array[0].cur) {
6         array[0].cur=array[i].cur;
7     }
8     return i;
9 }
View Code

  ②当向静态链表中添加数据时,需提前从备用链表中摘除节点,以供新数据使用;

  ③循环②,直至游标为0。

 1 //初始化静态链表
 2 int initArr(component *array){
 3     reserveArr(array);
 4     int body=mallocArr(array);
 5     //声明一个变量,把它当指针使,指向链表的最后的一个结点,因为链表为空,所以和头结点重合
 6     int tempBody=body;
 7     for (int i=1; i<4; i++) {
 8         int j=mallocArr(array);//从备用链表中拿出空闲的分量
 9         array[tempBody].cur=j;//将申请的空闲分量链接在链表的最后一个结点后面
10         array[j].data=i;//给新申请的分量的数据域初始化
11         tempBody=j;//将指向链表最后一个结点的指针后移
12     }
13     array[tempBody].cur=0;//新的链表最后一个结点的指针设置为0
14     return body;
15 }
View Code

3.4 静态链表的基本操作

3.4.1 添加元素

实现过程:

  ①从备用链表中摘除一个节点,用于存储元素 ;

  ②找到添加位置的前一个节点,将该节点的游标赋值给新元素 ;

  ③将新元素所在数组中的下标赋值给添加位置的前一个节点的游标。

 1 //向链表中插入数据,body表示链表的头结点在数组中的位置,add表示插入元素的位置,a表示要插入的数据
 2 void insertArr(component * array,int body,int add,char a){
 3     int tempBody=body;    //tempBody做遍历结构体数组使用
 4     //找到要插入位置的上一个结点在数组中的位置
 5     for (int i=1; i<add; i++) {
 6         tempBody=array[tempBody].cur;
 7     }
 8     int insert=mallocArr(array);    //申请空间,准备插入
 9     array[insert].data=a;
10     //新插入结点的游标等于其直接前驱结点的游标
11     array[insert].cur=array[tempBody].cur;
12     //直接前驱结点的游标等于新插入结点所在数组中的下标
13     array[tempBody].cur=insert;
14 }
View Code

3.4.2 删除元素

  静态链表中删除指定元素,只需实现以下 2 步操作:①将存有目标元素的节点从数据链表中摘除;②将摘除节点添加到备用链表,以便下次再用。

 

 1 //备用链表回收空间的函数,其中array为存储数据的数组,k表示未使用节点所在数组的下标
 2 void freeArr(component * array,int k){
 3     array[k].cur=array[0].cur;
 4     array[0].cur=k;
 5 }
 6 //删除结点函数,a 表示被删除结点中数据域存放的数据
 7 void deletArr(component * array,int body,char a){
 8     int tempBody=body;
 9     //找到被删除结点的位置
10     while (array[tempBody].data!=a) {
11         tempBody=array[tempBody].cur;
12         //当tempBody为0时,表示链表遍历结束,说明链表中没有存储该数据的结点
13         if (tempBody==0) {
14             printf("链表中没有此数据");
15             return;
16         }
17     }
18     //运行到此,证明有该结点
19     int del=tempBody;
20     tempBody=body;
21     //找到该结点的上一个结点,做删除操作
22     while (array[tempBody].cur!=del) {
23         tempBody=array[tempBody].cur;
24     }
25     //将被删除结点的游标直接给被删除结点的上一个结点
26     array[tempBody].cur=array[del].cur;
27     //回收被摘除节点的空间
28     freeArr(array, del);
29 }
View Code

3.4.3 查找元素

  只能通过逐个遍历静态链表的方式,查找存有指定数据元素的节点。

 1 //在以body作为头结点的链表中查找数据域为elem的结点在数组中的位置
 2 int selectElem(component * array,int body,char elem){
 3     int tempBody=body;
 4     //当游标值为0时,表示链表结束
 5     while (array[tempBody].cur!=0) {
 6         if (array[tempBody].data==elem) {
 7             return tempBody;
 8         }
 9         tempBody=array[tempBody].cur;
10     }
11     return -1;//返回-1,表示在链表中没有找到该元素
12 }
View Code

3.4.4 更改数据

  只需找到目标元素所在的节点,直接更改节点中的数据域即可。

1 //在以body作为头结点的链表中将数据域为oldElem的结点,数据域改为newElem
2 void amendElem(component * array,int body,char oldElem,char newElem){
3     int add=selectElem(array, body, oldElem);
4     if (add==-1) {
5         printf("无更改元素");
6         return;
7     }
8     array[add].data=newElem;
9 }
View Code

4、双向链表

  双向,指的是各节点之间的逻辑关系是双向的,但通常头指针只设置一个,除非实际情况需要。双向链表中各节点包含以下 3 部分信息:①指针域:用于指向当前节点的直接前驱节点;②数据域:用于存储数据元素。③指针域:用于指向当前节点的直接后继节点。

1 typedef struct line{
2     struct line * prior;     //指向直接前趋
3     int data;
4     struct line * next;     //指向直接后继
5 }line;

 

4.1 创建

  需要注意的是:与单链表不同,双链表创建过程中,每创建一个新节点,都要与其前驱节点建立两次联系,分别是:①将新节点的 prior 指针指向直接前驱节点;②将直接前驱节点的 next 指针指向新节点。

 1 line* initLine(line * head){
 2     //创建链表第一个结点(首元结点)
 3     head=(line*)malloc(sizeof(line));
 4     head->prior=NULL;
 5     head->next=NULL;
 6     head->data=1;
 7     line * list=head;
 8     for (int i=2; i<=3; i++) {
 9         //创建并初始化一个新结点
10         line * body=(line*)malloc(sizeof(line));
11         body->prior=NULL;
12         body->next=NULL;
13         body->data=i;
14       
15         list->next=body;    //直接前趋结点的next指针指向新结点
16         body->prior=list;    //新结点指向直接前趋结点
17         list=list->next;
18     }
19     return head;
20 }
View Code

4.2 双向链表的基本操作

4.2.1 添加节点

根据数据添加到双向链表中的位置不同,可细分为以下 3 种情况:①表头;②中间位置;③表尾。

第一种情况:

  将新数据元素添加到表头,只需要将该元素与表头元素建立双层逻辑关系即可。假设新元素节点为 temp,表头节点为 head,则需要做以下 2 步操作即可:①temp->next=head; head->prior=temp;②将 head 移至 temp,重新指向新的表头。

第二种情况:

  双向链表中间位置添加数据需要经过以下 2 个步骤:①新节点先与其直接后继节点建立双层逻辑关系;②新节点的直接前驱节点与之建立双层逻辑关系。

第三种情况:

  与添加到表头是一个道理,实现过程如下:①找到双链表中最后一个节点;②让新节点与最后一个节点进行双层逻辑关系。

 1 line * insertLine(line * head,int data,int add){
 2     //新建数据域为data的结点
 3     line * temp=(line*)malloc(sizeof(line));
 4     temp->data=data;
 5     temp->prior=NULL;
 6     temp->next=NULL;
 7     //插入到链表头,要特殊考虑
 8     if (add==1) {
 9         temp->next=head;
10         head->prior=temp;
11         head=temp;
12     }else{
13         line * body=head;
14         //找到要插入位置的前一个结点
15         for (int i=1; i<add-1; i++) {
16             body=body->next;
17         }
18         //判断条件为真,说明插入位置为链表尾
19         if (body->next==NULL) {
20             body->next=temp;
21             temp->prior=body;
22         }else{
23             body->next->prior=temp;
24             temp->next=body->next;
25             body->next=temp;
26             temp->prior=body;
27         }
28     }
29     return head;
30 }
View Code

4.2.2 删除元素

  双链表删除结点时,只需遍历链表找到要删除的结点,然后将该节点从表中摘除即可。

 1 //删除结点的函数,data为要删除结点的数据域的值
 2 line * delLine(line * head,int data){
 3     line * temp=head;
 4     //遍历链表
 5     while (temp) {
 6         //判断当前结点中数据域和data是否相等,若相等,摘除该结点
 7         if (temp->data==data) {
 8             temp->prior->next=temp->next;
 9             temp->next->prior=temp->prior;
10             free(temp);
11             return head;
12         }
13         temp=temp->next;
14     }
15     printf("链表中无该数据元素");
16     return head;
17 }
View Code

4.2.3 查找元素

  从表头依次遍历表中元素。

 1 //head为原双链表,elem表示被查找元素
 2 int selectElem(line * head,int elem){
 3 //新建一个指针t,初始化为头指针 head
 4     line * t=head;
 5     int i=1;
 6     while (t) {
 7         if (t->data==elem) {
 8             return i;
 9         }
10         i++;
11         t=t->next;
12     }
13     //程序执行至此处,表示查找失败
14     return -1;
15 }
View Code

4.2.4 更改元素

  更改双链表中指定结点数据域的操作是在查找的基础上完成的。实现过程是:通过遍历找到存储有该数据元素的结点,直接更改其数据域即可。

 1 //更新函数,其中,add 表示更改结点在双链表中的位置,newElem 为新数据的值
 2 line *amendElem(line * p,int add,int newElem){
 3     line * temp=p;
 4     //遍历到被删除结点
 5     for (int i=1; i<add; i++) {
 6         temp=temp->next;
 7     }
 8     temp->data=newElem;
 9     return p;
10 }
View Code

5、循环链表

 实现约瑟夫环,如下:

定义链表结构:

1 typedef struct node{
2     int number;
3     struct node * next;
4 }person;
View Code

初始化链表:

 1 person * initLink(int n){
 2     person * head=(person*)malloc(sizeof(person));
 3     head->number=1;
 4     head->next=NULL;
 5     person * cyclic=head;
 6     for (int i=2; i<=n; i++) {
 7         person * body=(person*)malloc(sizeof(person));
 8         body->number=i;
 9         body->next=NULL; 
10         cyclic->next=body;
11         cyclic=cyclic->next;
12     }
13     cyclic->next=head;    //首尾相连
14     return head;
15 }
View Code

主要功能实现:

 1 void findAndKillK(person * head,int k,int m){
 2     person * tail=head;
 3     //找到链表第一个结点的上一个结点,为删除操作做准备
 4     while (tail->next!=head) {
 5         tail=tail->next;
 6     }
 7     person * p=head;
 8     //找到编号为k的人
 9     while (p->number!=k) {
10         tail=p;
11         p=p->next;
12     }
13     //从编号为k的人开始,只有符合p->next==p时,说明链表中除了p结点,所有编号都出列了,
14     while (p->next!=p) {
15         //找到从p报数1开始,报m的人,并且还要知道数m-1de人的位置tail,方便做删除操作。
16         for (int i=1; i<m; i++) {
17             tail=p;
18             p=p->next;
19         }
20         tail->next=p->next;//从链表上将p结点摘下来
21         printf("出列人的编号为:%d\n",p->number);
22         free(p);
23         p=tail->next;//继续使用p指针指向出列编号的下一个编号,游戏继续
24     }
25     printf("出列人的编号为:%d\n",p->number);
26     free(p);
27 }
View Code

  循环链表和动态链表唯一不同在于它的首尾连接,这也注定了在使用循环链表时,附带最多的操作就是遍历链表。在遍历的过程中,尤其要注意循环链表虽然首尾相连,但并不表示该链表没有第一个节点和最后一个结点。所以,不要随意改变头指针的指向。

posted on 2020-01-23 16:50  人小鬼不大  阅读(274)  评论(0编辑  收藏  举报