动态单链表的传统存储方式和10种常见操作-C语言实现

顺序线性表的优点:方便存取(随机的),特点是物理位置和逻辑为主都是连续的(相邻)。但是也有不足,比如;前面的插入和删除算法,需要移动大量元素,浪费时间,那么链式线性表 (简称链表) 就能解决这个问题。

一般链表的存储方法

一组物理位置任意的存储单元来存放线性表的数据元素,当然物理位置可以连续,也可以不连续,或者离散的分配到内存中的任意位置上都是可以的。故链表的逻辑顺序和物理顺序不一定一样。

因为,链表的逻辑关系和物理关系没有必然联系,那么表示数据元素之间的逻辑映象就要使用指针,每一个存储数据元素的逻辑单元叫结点(node)。

结点里有两个部分,前面存储数据元素内容,叫数据域,后面部分存储结点的直接后继在内存的物理位置,叫指针域。那么就可以实现用指针(也叫链)把若干个结点的存储映象链接为表,就是链式线性表。

上面开始介绍的是最简单的链表,因为结点里只有一个指针域,也就是俗称的单链表(也叫线性链表)。

单链表可以由一个叫头指针的东东唯一确定,这个指针指向了链表(也就是直接指向第一个结点)。因此单链表可以用头指针的名字来命名。且最后一个结点指针指向NULL。

链表类别

1、实现的角度:动态链表,静态链表(类似顺序表的动态和静态存储)

2、链接方式的角度:单链表,双向链表,循环链表

单链表

单链表的头结点

一般是使用单链表的头指针指向链表的第一个结点,有的人还这样做,在第一个结点之前再加一个结点(不保存任何数据信息,只保存第一个结点的地址,有时也保存一些表的附加信息,如表长等),叫头结点(头结点是头结点,第一个结点是第一个结点)。那么此时,头指针指向了头结点。并且有无头结点都是可以的。链表还是那个链表,只不过表达有差异。

那么问题来了!为什么还要使用头结点?

作用是对链表进行操作时,可以对空表、非空表的情况以及对首元结点进行统一处理,编程更方便。

描述动态单链表

有两种做法,一种是传统的动态链表结构,暨只定义结点的存储结构,使用4个字节的指针变量表示线性表。还有一种是直接采用结构体变量(类似顺序表)来表示线性表。

顾名思义,肯定是第二种方法比较方便。

先看传统存储动态单链表结构的操作

 1 /************************************************************************/
 2 /* 文件名称:ADT.h
 3 /* 文件功能:动态单链表传统存储结构和常见操作
 4 /* 作    者:dashuai
 5 /* 备    注:以下算法,并没有考证是否全部都是最佳优化的,关于算法的优化后续研究
 6 /************************************************************************/
 7 #include <stdio.h>
 8 #include <stdlib.h>
 9
10 //传统的单链表动态存储结构(带头指针)
11 typedef struct Node//Node标记可以省略,但如结构里声明了指向结构的指针,那么不能省略!
12 {
13     char data;//数据域
14     struct Node *next;//指针域
15 } Node, *LinkList;//LinkList是指向Node结构类型的指
16
17 /*
18     1、查找(按值)算法
19     找到第一个值等于value的结点,找到则返回存储位置
20 */
21 LinkList getElemByValue(LinkList L, char value);
22
23 /*
24     2、查找(按序号)算法
25     查找第i个元素,并用变量value返回值,否则返回提示,即使知道了序号,也必须使用指针顺次查访
26 */
27 void getElemByNum(LinkList L, int num, char *value);
28
29 /*
30     3、删除结点
31     删除元素,并用value返回值
32 */
33 void deleteList(LinkList L, int i, char *value);
34
35 /*
36     4、插入结点
37     在第i个位置之前插入结点e
38 */
39 void insertList(LinkList L, int i, char value);
40
41 /*
42     5、头插法建立单链表(逆序建表)
43     从一个空结点开始,逐个把 n 个结点插入到当前链表的表头
44 */
45 void createListByHead(LinkList *L, int n);
46
47 /*
48     6、尾插法建立单链表(顺序建表)
49     从一个空结点开始,逐个把 n 个结点插入到当前链表的表尾
50 */
51 void createListByTail(LinkList *L, int n);
52
53 /*
54     7、尾插法建立有序单链表(归并链表)
55     把两个有序(非递减)链表LA LB合并为一个新的有序(非递减)链表LC(空表)
56     要求:不需要额外申请结点空间来完成
57 */
58 void mergeListsByTail(LinkList LA, LinkList LB, LinkList LC);
59
60 /*
61     8、销毁单链表
62 */
63 void destoryLinkList(LinkList *L);
64
65 /*
66     9、求表长,长度保存到头结点
67 */
68 void getLength(LinkList L);
69
70 /*
71     10、遍历单链表
72 */
73 void traversalList(LinkList L);

C变量的随用随定义,可以确定C99之后新增加的,和c++一样,貌似一些编译器还不支持

 1 #include "ADT.h"
 2
 3 /*
 4     1、查找(按值)算法
 5     找到第一个值等于value的结点,找到则返回存储位置
 6 */
 7 LinkList getElemByValue(LinkList L, char value)
 8 {
 9     //定义指示指针指向L第一个结点
10     LinkList p = L->next;//L开始指向了头结点,L->next指向的就是第一个结点
11
12     while (p && p->data != value)
13     {
14         //p++;显然错误,离散存储,非随机结构
15         p = p->next;//指针顺链移动,直到找到或者结束循环
16     }
17     //当找不到,则p最终指向NULL,循环结束
18     return p;//返回存储位置或NULL
19 }

算法执行时间和value有关,时间复杂度为0(n),n表长

 1 /*
 2     2、查找(按序号)算法
 3     查找第i个元素,并用变量value返回值,否则返回提示,即使知道了序号,也必须使用指针顺次查访
 4 */
 5 void getElemByNum(LinkList L, int num, char *value)
 6 {
 7     int count = 1;
 8     LinkList p = L->next;
 9     //控制指针p指向第num个结点
10     while (p && count < num)//num若为0或者负数直接跳出循环,若超出表长则遍历完毕,跳出循环,找到了元素也跳出循环
11     {
12         p = p->next;//p指向第num个结点,count此时为num值
13         count++;
14     }
15     //如果num大于表长,则count值增加到表长度时,p恰好指向表尾结点,遍历完整个链表也找不到结点,此时再循环一次
16     //p指向null,count = 表长 + 1,循环结束,这里也隐含说明了num大于 表长 的不合法情况
17     if (!p || count > num)//说明num至少比 表长 大
18     {
19         //num <= 0或者num大于表长时,跳出while循环,来到if语句,判断不合法
20         puts("num值非法!");
21     }
22     else
23     {
24         *value = p->data;
25         printf("找到第%d个元素的值 = %c\n", num, *value);
26     }
27 }

//时间复杂度0(n),n为表长

 1 /*
 2     3、删除结点
 3     删除第i个元素,并用value返回值
 4 */
 5
 6 void deleteList(LinkList L, int i, char *value)
 7 {
 8     LinkList p = L;//头脑一定要清晰!这里p应该指向头结点
 9     LinkList temp;
10     int j = 0;//对应着头结点的序号0
11
12     /*while (p && j < i)
13     {
14         p = p->next;此时,p指向的是i元素位置,要删除i元素,需要知道i的前驱!
15         j++;
16     }*/
17
18     while (p->next && j < i - 1)//i - 1可以保证指向其前驱 ,j=0需要注意,删除是从1-n都可以
19     {
20         p = p->next;//指向i元素前驱(i-1)
21         j++;
22     }
23     //必须想到判断合法性
24     if (!(p->next) || j > i - 1)//同样判断i的下边界,和上边界
25     {
26         puts("i值不合法!");
27     }
28     else
29     {
30         //找到了前驱p
31         temp = p->next;//temp指针指向i元素
32         //p->next = p->next->next;等价于
33         p->next = temp->next;//链表删除结点的关键逻辑
34         *value = temp->data;
35         free(temp);//释放temp指向的结点的内存空间
36         temp = NULL;
37         puts("删除成功!");
38     }
39 }

时间复杂度,虽然没有移动任何元素,还是0(n),因为最坏时核心语句频度为n(表长)

为什么不是p?

//判断表为非空,因为不止一次删除操作!总会删空,则p还是指向的头结点!如果依然是while(p &&……),表空时,按道理函数不应该再执行核心语句,提前判断出错,但此时却还要执行循环体,循环结束才能到if(!p),而使用while(p->next && ……),表空就直接跳出循环,到if语句,提示错误。这是删除算法总是需要注意的细节,插入算法则是如果内存有限,或者是顺序的表,或者静态链表,那么总是要注意存储空间满足大小的问题

 1 /*
 2     4、插入结点
 3     在第i个位置之前插入结点e,换句话说就是在第i-1个位置之后插入,类似前面的删除操作思想
 4 */
 5 void insertList(LinkList L, int i, char value)
 6 {
 7     LinkList p = L;
 8     LinkList s;
 9     int j = 0;
10     //和删除不同,插入操作不用注意表空的情况
11     while (p && j < i - 1)
12     {
13         p = p->next;
14         j++;
15     }
16
17     if (!p || j > i - 1)//i小于1或者大于表长+1不合法
18     {
19         puts("i不合法!");
20     }
21     else
22     {
23         //先分配结点存储空间
24         s = (LinkList)malloc(sizeof(Node));
25         //依次传入插入的元素内容和修改逻辑链接
26         s->data = value;
27         //链表插入算法的关键逻辑
28         s->next = p->next;
29         p->next = s;//顺序不可颠倒,原则是指针在修改前,先保留再修改,不能先修改再保留
30         puts("插入成功!");
31     }
32 }

插入算法时间复杂度分析:0(n),最坏情况下频度是n

/插入和删除算法,还有一个思路,就是既然需要每次都找前驱,那么为什么不弄两个指针呢?一个指向当前位置,一个紧随其后指向前,个人其实感觉是脱裤子放屁……

 1 //以删除为例
 2 void deleteList(LinkList L, int i, char *value)
 3 {
 4     LinkList prePoint = L;//前驱指针初始化指向头结点
 5     LinkList point = L->next;//当前指针初始化指向第一个结点
 6     LinkList temp = NULL;
 7     int j = 1;
 8     //i要>0,且小于等于表长
 9     while (point && j < i)//如果表非空,找到要删除的元素位置
10     {
11         point = point->next;
12         prePoint = prePoint->next;//分别顺次后移
13         j++;
14     }
15
16     if (!point || j > i)
17     {
18         puts("i不合法!");
19     }
20     else
21     {
22         temp = point;
23         prePoint->next = point->next;
24         *value = temp->data;
25         free(temp);
26         temp = NULL;
27         puts("删除成功!");
28     }
29 }

时间复杂度依然是O(n)

 1 /*
 2     5、头插法建立单链表(逆序建表)
 3     从一个空结点开始,逐个把 n 个结点插入到当前链表的表头
 4 */
 5
 6 //开始就空表,则肯定先分配结点(带头结点)
 7 void createListByHead(LinkList *L, int n)
 8 {
 9     LinkList p = NULL;
10     int i = 0;
11     *L = (LinkList)malloc(sizeof(Node));//L指向头结点
12     (*L)->next = NULL;//空表建立
13     //头插法
14     for (i = 1; i <= n; i++)
15     {
16         p = (LinkList)malloc(sizeof(Node));//先创建要插入的结点
17         scanf("%c", &(p->data));//给结点数据域赋值    再次验证 -> 优先级高于取地址 &
18
19         while (getchar() != ‘\n‘)
20         {
21             continue;
22         }
23
24         p->next = (*L)->next;//再让插入的结点的next指针指向后继(链接的过程),注意后继不能为空(除去第一次插入)
25         //p->next = NULL;//错误
26         (*L)->next = p;//最后保证插入结点是第一个结点,把头结点和第一个结点链接起来。
27     }
28
29     printf("头插法建表成功\n");
30 }

时间复杂度:必然是O(n),插入了n个元素

链表和顺序表存储结构不同,动态,整个可用内存空间可以被多个链表共享,每个链表无需事先分配存储容量,由系统应要求自动申请。建立链表是动态的过程。

//如a b c d,依次头插法(头插 总是在第一个结点前插入,暨插入的结点总是作为第一个结点)到空链表里,那么完成之后是

//d c b a

下面是正序的尾插法,如图

 1 /*
 2     6、尾插法建立单链表(顺序建表)
 3     //对 5 算法的改进
 4 */
 5 //头插法算法简单,但生成的链表结点次序和输入的顺序相反。有时不太方便。
 6 //若希望二者次序一致,可采用尾插法建表。该方法是将新结点顺次的插入到当前链表的表尾上,为此必须增加一个尾指针tail,
 7 //使其始终指向当前链表的尾结点。
 8 void createListByTail(LinkList *L, int n)
 9 {
10     LinkList tail = NULL;//尾指针
11     int i = 0;
12     LinkList p = NULL;//代表前驱
13     *L = (LinkList)malloc(sizeof(Node));
14     (*L)->next = NULL;
15     tail = *L;
16
17     for (i = 1; i <= n; i++)
18     {
19         p = (LinkList)malloc(sizeof(Node));
20         //_CRTIMP int __cdecl scanf(_In_z_ _Scanf_format_string_ const char * _Format, ...);返回int,传入的参数个数
21         /*如果被成功读入,返回值为参数个数
22         如果都未被成功读入,返回值为0
23             如果遇到错误或遇到end of file,返回值为EOF*/
24         scanf("%c", &(p->data));
25         //清空输入队列的剩余的所有字符
26         while (getchar() != ‘\n‘)
27         {
28             continue;
29         }
30         //尾插操作,后继总是空的
31         p->next = NULL;
32         //链接前驱
33         tail->next = p;//万万不能写成*L->next = p;
34         //保证尾指针tail总是指向最后一个结点
35         tail = p;
36     }
37
38     printf("尾插法建表成功! \n");
39 }

这样操作,输入的序列和输出的序列是正序的,且时间复杂度为O(n)

 1 /*
 2     7、尾插法建立有序单链表(归并链表)
 3     把两个有序(非递减)链表LA LB合并为一个新的有序(非递减)链表LC(空表)
 4     要求:不需要额外申请结点空间来完成
 5 */
 6
 7 //1、比较数据域,保证有序
 8 //2、尾插法思想
 9 //3、不需要额外申请结点空间来完成!
10 //使用现有的内存空间,完成操作,那么可以想到用LC的头指针去指向其中某个表的头结点,内存共享
11
12 void mergeListsByTail(LinkList LA, LinkList LB, LinkList LC)
13 {
14     //因为要比较数据大小,需要两个标记指针,分别初始化为标记AB的第一个结点
15     LinkList listA = LA->next;
16     LinkList listB = LB->next;
17     //还要不开辟内存,那么内存共享,需要把表C让其他表去表示
18     LinkList listC;//声明一个标记C的指针
19     LC = LA;//比如表A。C表的头指针指向A表的头结点,做自己的头结点
20     listC = LA;//C表的标记指针需要初始化,指向A的头结点,待命
21     //接下来比较AB表数据,标记指针会顺次后移,早晚有一个先指向末尾之后的NULL,故判断是哪一个表的
22     while (listA && listB)
23     {
24         //判断数据大小,非递减
25         if (listA->data <= listB->data)
26         {
27             //则A的结点插入到C表(尾插),单链表不可使用头指针做遍历
28             listC->next = listA;//先把A的结点链到C表
29             listC = listA;//listC等价于尾指针
30             //A指针后移,继续循环比较
31             listA = listA->next;
32         }
33         else
34         {
35             //把B的结点插入到C(尾插)
36             listC->next = listB;
37             listC = listB;
38             listB = listB->next;
39         }//end of if
40     }//end of while
41     //循环结束,只需要把剩下的某个表的结点一次性链接到C表尾
42     if (listA)
43     {
44         //说明B空
45         listC->next = listA;
46     }
47
48     if (listB)
49     {
50         //A空
51         listC->next = listB;
52     }
53     //最后AB表比较之前一定有一个表都被遍历了(也就是链接到了C),剩下的结点比如属于某个表的,最后也都链接到C尾部
54     //那么,此时就还有一个结点,那就是B表的头结点!勿忘把B表头结点释放,这才是完全的两个归并为一个
55     free(LB);
56     LB = NULL;//杜绝野指针
57 }

算法时间复杂度,和头插法比较的话,还是O(n),其实顺序表的有序归并也是这个时间复杂度O(A.length + B.length),但是链表的尾插法归并没有移动元素,只是解除和重建链接的操作,也没有额外开辟内存空间。空间复杂度不同。

 1 /*
 2     8、销毁单链表
 3 */
 4 void destoryLinkList(LinkList *L)
 5 {
 6     LinkList p = NULL;
 7
 8     while (*L)
 9     {
10         p = (*L)->next;
11         free(*L);//free不能对指向NULL的指针使用多次!
12         *L = p;
13         //彻底搞掉指针本身,free(L)仅仅是销毁指针指向的内存,故还要连头结点一起干掉,不过while循环里隐形的包含了
14     }
15
16     *L = NULL;
17     puts("链表L已经销毁,不存在!");
18 }

函数内部有动态分配内存的情形,应该把参数设定为指向指针的指针,当然还有别的方法,我习惯而已。

记得说:值传递函数,是把实参的一个拷贝送到函数体,函数体修改的是那份传入的拷贝,不是函数跑到main里去给它修改。

且形参和传入的拷贝,还有函数体内的变量(栈中分配的内存),都是是代码块内的自动存储类型的变量,也就是局部变量,函数执行完毕,变量自动销毁,改变就不起作用。

指针形参可以改变实参,但是如果是针对函数内动态分配了内存的情况,把堆分配的内存地址赋给了指针参数,改变的是指针指向的内容,而指针变量(形参)本身的内存地址没有改变,故根本句不会成功修改实参。

指向指针的指针,存放的是指向实参内存地址A的指针的地址B,修改B地址,改变了B指向的内容,而B指向的内容恰恰就是一级指针A本身,一级指针A的修改,使得实参被改变,对实参(指针变量C),需要取出指针C自己的的地址,传入函数。达到间接修改实参的目的。

 1 /*
 2     9、求表长,长度保存在头结点
 3 */
 4 void getLength(LinkList L)
 5 {
 6     int length = 0;
 7     LinkList p = NULL;
 8
 9     if (L)
10     {
11         p = L->next;
12
13         while (p)
14         {
15             p = p->next;
16             length++;
17         }
18
19         L->data = length;
20         printf("%d\n", L->data);
21     }
22     else
23     {
24         puts("表已经销毁!无法计算长度了……");
25     }
26 }
 1 /*
 2     10、遍历链表
 3 */
 4 void traversalList(LinkList L)
 5 {
 6     int i = 0;
 7     int length = 0;
 8     LinkList p = L->next;
 9     length = L->data;//遍历之前,务必先求表长!
10     puts("遍历之前,务必先求表长!");
11
12     for (; i < length; i++)
13     {
14         putchar(p->data);
15         p = p->next;
16     }
17     putchar(‘\n‘);
18 }

测试

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <assert.h>
 4 #include "ADT.h"
 5
 6 int main(void)
 7 {
 8     LinkList L = NULL;//完全的空表,连头结点都没有
 9     LinkList LTwo = NULL;
10     LinkList val = NULL;
11     int i = 0;
12     char value = ‘0‘;
13
14     puts("请输入5个字符变量(一行一个):");
15     puts("使用尾插法建立单链表L,5个结点,一个头结点");
16     //输入 12345
17     createListByTail(&L, 5);//尾插法建表
18
19     //12345正确的存入到了5个结点里,尾插法创建了一个单链表L
20
21     //先求长度
22     puts("L长度为");
23     getLength(L);
24
25     //遍历  12345
26     puts("遍历这个链表结点元素");
27     traversalList(L);
28
29     //用完必须销毁
30     puts("把链表L销毁,L = NULL;");
31     destoryLinkList(&L);
32
33     //头插法 abcde
34     //void createListByHead(&LTwo, 5);//报错;语法错误 : 缺少“;”(在“类型”的前面)
35     puts("使用头插法建立新的单链表LTwo,5个结点,一个头结点");
36     createListByHead(&LTwo, 5);
37
38     //求长度
39     getLength(LTwo);
40
41     //遍历  edcba
42     puts("遍历表中结点元素");
43     traversalList(LTwo);
44
45     //按值查找
46     puts("查找LTwo表的结点数据 = ‘2’的结点");
47     val = getElemByValue(LTwo, ‘2‘);
48
49     if (val)
50     {
51         printf("找到了val,地址 = %p \n", &val);
52     }
53
54     puts("‘2’在表 LTwo 里没找到!");
55
56     //插入结点
57     puts("在位置 1 之后插入一个结点,里面数据是 ‘p’");
58     insertList(LTwo, 1, ‘p‘);
59
60     //遍历  pedcba
61     puts("开始遍历表LTwo");
62     traversalList(LTwo);
63
64     //按序查找
65     puts("查找位置=2的结点,并打印出它的数据内容");
66     getElemByNum(LTwo, 2, &value);
67
68
69     //删除结点
70     puts("删除位置 1 的结点,并打印出删除结点的数据");
71     deleteList(LTwo, 1, &value);
72     printf("%c\n", value);
73
74     //遍历  pedcba
75     puts("再次遍历链表LTwo");
76     traversalList(LTwo);
77
78     //求链表长度,把长度保存的头结点
79     puts("计算链表长度,并把长度保存到了LTwo的头结点");
80     getLength(LTwo);
81     printf("%d\n", LTwo->data);
82
83     //必须有销毁
84     puts("动态存储的结构用完一定要销毁");
85     destoryLinkList(&LTwo);
86
87     //此时销毁的表长规定是0
88     puts("销毁之后,链表长度:");
89     getLength(LTwo);
90
91     system("pause");
92     return 0;
93 }

scanf函数的特点是接受单词,而不是字符串,字符串一般是gets函数,单个字符接收是getchar函数,因为scanf函数遇到空白字符(tab,空格,回车,制表符等)就不再读取输入,那字符串怎么能方便输入?

但是输入队列里如果还有字符,那么会留到缓存内,需要在定义里使用getchar函数来消除回车带来的影响。

时间: 2024-10-17 15:36:36

动态单链表的传统存储方式和10种常见操作-C语言实现的相关文章

静态单链表和动态单链表的区别

链表中结点的分配和回收是由系统提供的标准函数malloc和free动态实现的,称之为动态链表. 如果程序支持指针,则可按照我们的一般形式实现链表, 需要时分配,不需要时回收即可. 动态链表的空间是可以动态扩展的. typedef struct  node{ EleType data; struct node * pNext; }Node; 有些高级语言中没有"指针"数据类型,只能用数组来模拟线性链表的结构, 数组元素中的指针"域"存放的不是元素在内存中的真实地址,而

人工智能改进传统云ERP的10种方法

http://blog.itpub.net/31542119/viewspace-2168809/ 随着数字化转型的进程加快,企业开始重新评估ERP的作用.传统ERP经过多年僵硬化定制过于追求生产的一致性,而忽视了客户的需求变化,导致系统缺乏灵活性,已经无法满足当今数字业务模型的增长需求.目前,人工智能(AI).机器学习发展迅速,成为了很多企业的必备帮手,云ERP供应商要想解决传统ERP系统的问题,或许需要这两大王者的帮助! 用更高的智慧和洞察力挽救传统ERP系统 要想新的商业模式取得成功,企业

创建动态单链表

#include <stdio.h>#include<string.h>#include<stdlib.h> struct Student{ long num; float score; struct Student* next;};int n; struct Student* creat(void)//构造一个函数,函数返回值为指针,该指针是指向结构体类型{ struct Student* head; struct Student *p1,*p2;// 定义三个结构体

链式存储结构之单链表

1.线性表的链式存储结构 线性表的链式存储结构允许数据元素存在任意未被占用的内存空间,因为在线性表的链式存储结构中,除了存储数据元素相关的数据信息之外,还存储了数据元素的后继元素存储地址,这样通过当前数据元素很容易找到下一个数据元素. 链式存储结构中,存取一个数据元素内容信息和直接后继的存储位置的结构称为结点,存储数据元素内容信息的域称为数据域,存储直接后继存储位置的域称为指针域.n个结点链接成一个链表,每个结点中只包含一个指针域的称为单链表. 2.单链表 2.1 结点 代码实现单链表,首先需要

算法数据结构 单链表的实现+操作 以及和顺序表的对比

顺序表和单链表的优缺点对比: 顺序表的优点,无需为表示表中元素之间的逻辑关系而增加额外的存储空间: 可以快速的存取表中的任意位置的元素. 顺序表的缺点,插入后删除操作需要移动大量元素: 当线性表长度不稳定时,存储空间难确定,容易造成存储空间碎片. 对于单链表 链式存储即元素存储的内存单元可以是不连续,分散的.对于元素间如何来维护他们的关系(即逻辑结构,每个元素的前驱和后继.) 即用到一个指针域来存储他和前驱或是后继直接的关系. 如上面的是一个单链表的指针结构,即每个元素中存储了他的后继元素的内存

单链表 之c代码

我们知道数据结构就是数据及其相互关系,包括逻辑结构和物理结构.单链表的逻辑结构是一种一对一的线性关系,物理结构是利用节点把数据结合起来,在计算机中体现这种一对一的数据关系.单链表节点包括包含数据本身信息的数据域和体现数据一对一关系的指针域.因为单链表只有一个指向后一节点的单一指针域next 所以单链表只能从前往后遍历,而不能从后向前遍历,这就意味着一旦单链表的某一节点丢失 ,后面所有的数据信息都会丢失,并且单链表有头指针唯一确定,要想查找单链表的 某一数据只能从头开始遍历,最坏时间复杂度为O(n

python中的单链表实现

引子 数据结构指的是是数据的组织的方式.从单个数据到一维结构(线性表),二维结构(树),三维结构(图),都是组织数据的不同方式. 为什么需要链表? 顺序表的构建需要预先知道数据大小来申请连续的存储空间,而在进行扩充时又需要进行数据的搬迁,所以使用起来并不是很灵活. 链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理. 链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是不像顺序表一样连续存储数据,而是在每一个节点(数据存储单元)里存放下一个节点的位置信息(即地址).

数据结构学习总结(2) 线性表之单链表

一,回忆链表 链表,别名链式存储结构或单链表,用于存储逻辑关系为 "一对一" 的数据.与顺序表不同,链表不限制数据的物理存储状态,换句话说,使用链表存储的数据元素,其物理存储位置是随机的. 例如,使用链表存储 {1,2,3},数据的物理存储状态如图 1 所示: 图 1 链表随机存储数据 我们看到,图 1 根本无法体现出各数据之间的逻辑关系.对此,链表的解决方案是,每个数据元素在存储时都配备一个指针,用于指向自己的直接后继元素.如图 2 所示: 图 2 各数据元素配备指针 像图 2 这样

线性链表其他种类(静态,双向,循环)的存储结构和常见操作

一.静态单链表 在不支持动态空间分配的环境中,要使用链表存储数据,那么可采用静态链表的方法:即在一块预分配的存贮空间中,用下标作为指针链来构成链式结构. //既然是静态链表,那么可以使用一维数组实现存储,java没有指针,那么就用这来使用链表结构 //在不支持动态空间分配的环境中,要使用链式结构技术,可采用静态链表的方法:即在一块预分配的存贮空间中,用下标作为指针. //存储结构:在数组中增加一个“指针”域,存放下一元素在数组中的下标.且0为代表空指针 //设S为SLinkList型变量,若第i