文章首发:http://pjf.name/post-114.html
本文基于"姓名标识-非商业性-相同方式分享 4.0国际"协议创作或转载,转载原创文章请注明来源于疯子的自留地,否则请勿转载或再转载,谢谢合作:)
说明:这几天有点忙,本来前几天就应该写了的推迟到了今天.
写在前面的话:如果您读到此文,请允许我理解你已经学习了该部分知识,只是对此知识点有不解才被搜索引擎指到这里.如果不是,请自己去看相关数据结构知识.抱歉本文帮不到你:)
首先,什么是静态链表呢?我自己的话说就是为那些没有指针的语言设计的"链表"这种数据结构.其思想就是运用了一维结构数组(当然N维也可以),该结构内不仅有数据域,同时还用一个int型来保存我们的next值,即下一个数据的下标.这样就可以模拟出链表的结构了.这样增删数据的时间复杂度就为O(1)了.因为这个数组的长度是事先规定好了的,不能更改,所以才有了静态链表这个名字.在Weiss的<<数据结构与算法---C语言实现>>里面称此法为游标实现法.
既然明白了原理,那么链表里面的头指针那些也应该在静态链表里面体现不是?不然也不能称之为"链表"了.对于静态链表,事先占用一个结点也就是数组的一个单元来存储首结点的下标.同时再另外占用一个结点用来存储那些未被占用的结点的下标.同时把这些结点也组成一个链表,<<大话>>上称之为"备用链表".这个备用链表的头指针的next值用来指向备用链表的头结点的下标.同时,规定0为null.好了,
但是看<<大话>>时有个疑惑,集中在两个方面,即初始化链表和分配内存处.<<大话>>中说初始化的时候把0到MAXSIZE-2的next值都给设定为其值得下一个值的下标,这我能理解.但是到了分配内存函数时又说,如果分配成功会返回其内存的下标,若失败则返回0.好了,假设MAXSIZE为10,即最大可以存储8个数字,那么如果我们此时先一次性给写入7个数字,即L[1]-L[7]都存有值,而备用链表根据定义只剩一个结点,即L[8],根据初始化的函数,L[8]的next值应该是9.好了,我们假设现在插入一个数,具体插入哪里先不管,肯定要用到分配内存函数,好了.给我们返回8,也正是备用链表的唯一一个结点的下标.但是....根据分配内存函数的原理,此时L[0].next的值应该是0,但是当我们再用一次分配内存函数或者跟踪我们的静态链表就会发现值居然是9.怪了!就这个问题把我折腾了2天.google,baidu各种博客都没有人有这个疑问,难道是我理解错了?可是按照这个写法写的程序是错的!而我也在csdn一名程序猿的博客留言了此问题,回复我我的想法应该是对的,这种方法确实有缺陷.所以,我就用了下面的办法,即在初始化时把链表可用的最后一个结点也就是当MAXSIZE为10是,L[8]的next赋值为0,而非9,这样就解决了这个问题.而经过测试运行良好.所以再一次证明了只有不要尽信书
好了,以下是静态链表的数据结构:
1 | typedef struct Node{ |
2 | char Data; |
3 | int Next; |
4 | }Node; |
5 |
6 | typedef struct Node List[MAXSIZE]; |
1.链表初始化:
01 | BOOL InitializeStaticList(List L) //初始化静态链表时L[0]用来表示备用链表的头指针,而L[MAXSIZE-1]则用来表示存数据的链表的头指针 |
02 | { |
03 | int temp; |
04 |
05 | for (temp=0;temp<MAXSIZE-1;temp++) // |
06 | L[temp].Next=temp+1; //指定下一个数据的下标 |
07 |
08 | L[MAXSIZE-1].Next=L[MAXSIZE-2].Next=0; //这里L[MAXSIZE-2]也就是存数据的那个链表的最后一个结点,我们赋值为0,表示为其为尾结点 |
09 | L[MAXSIZE-1].Data=0; //这个头指针的data值我们用来存放静态链表的长度 |
10 | L[0].Data=MAXSIZE-2; //我们用备用链表的data值来存储备用链表的可用数 |
11 | |
12 | return true ; |
13 | } |
2.分配内存:
1 | int MallocNode(List L) |
2 | { |
3 | int empty=L[0].Next; |
4 | if (L[0].Next) |
5 | L[0].Next=L[empty].Next; |
6 |
7 | return empty; |
8 | } //相当于链表的malloc功能 |
3.释放内存:
01 | BOOL FreeNode(List L, int FreePosition) //相当于free()函数 |
02 | { |
03 | int FirstEmpty; |
04 |
05 | if (FreePosition<1||FreePosition>L[MAXSIZE].Next) //如果删除数据的位置不正确,返回失败 |
06 | return false ; |
07 |
08 | FirstEmpty=L[0].Next; //取得原来的第一个备用链表的第一个结点下标 |
09 | L[FreePosition].Next=FirstEmpty; //把新的备用链表的头结点的next值指向原来的头结点下标(变向的地址) |
10 | L[FreePosition].Data=0; //把新的备用链表的头结点的值清空 |
11 | L[0].Next=FreePosition; //把备用链表的头指针的next值指向新的头结点 |
12 |
13 | return true ; |
14 | } |
4.插入操作:
01 | BOOL InsertDataStaticList(List L, int InsertPosition, char InsertData) |
02 | { |
03 | int FirstEmpty,temp,PriorNode; |
04 |
05 | if (InsertPosition<1) //如果插入位置小于1,则默认插入的位置为1 |
06 | InsertPosition=1; |
07 | if (InsertPosition>L[MAXSIZE-1].Data) //如果插入的位置大于当前链表的长度,则默认插入位置为表尾 |
08 | InsertPosition=L[MAXSIZE-1].Data+1; |
09 |
10 | FirstEmpty=MallocNode(L); //分配内存 |
11 | if (0==FirstEmpty) //如果分配内存失败,返回false |
12 | return false ; |
13 |
14 | L[FirstEmpty].Data=InsertData; //把数据插入到这个空余结点下标处 |
15 |
16 | PriorNode=MAXSIZE-1; //先把有数据的前一个结点指向头指针 |
17 | for (temp=1;temp<InsertPosition;temp++) //把结点指向欲插入结点的前一个结点 |
18 | { |
19 | PriorNode=L[PriorNode].Next; |
20 | } |
21 |
22 | if (0==L[MAXSIZE-1].Data) //注意,如果此插入操作是初始化链表后的第一次插入数据操作,那么我们代表我们这个链表里面就只有一个数据,那么它的next值应为null(在静态链表里面用0代表null) |
23 | L[FirstEmpty].Next=0; |
24 | else |
25 | L[FirstEmpty].Next=L[PriorNode].Next; //把新的插入结点的next值指向原来的当前位置结点 |
26 | L[PriorNode].Next=FirstEmpty; //原来当前位置的前一个结点的next指向新的当前位置结点 |
27 | //注意为什么这里没有写if(插入位置为链首或链尾的情况),因为我们用到的只有插入位置的前一个结点而不用管插入位置的后一个结点,当为最后一个结点时,新的尾结点的next值自动继承了原来的尾结点的next值即0,而原来的尾结点的next则指向了新的尾结点,而当插入的是表头的时候,头结点的前一个结点时头指针,而根据上面两个赋值语句已经把头指针的next值指向了新的头结点:) |
28 |
29 | L[MAXSIZE-1].Data++; //数据链表长度加1 |
30 | L[0].Data--; //备用链表长度减1 |
31 |
32 | return true ; |
33 | } |
5.删除操作:
01 | BOOL DeleteNode(List L, int DeletePosition, char *OutputData) |
02 | { |
03 | int temp,PriorNode; |
04 |
05 | if (DeletePosition<1||DeletePosition>L[MAXSIZE].Next) //如果删除数据的位置不正确,返回失败 |
06 | return false ; |
07 |
08 | PriorNode=MAXSIZE-1; //先让前一个结点的位置指向数据链表的头指针 |
09 | for (temp=1;temp<DeletePosition;temp++) |
10 | PriorNode=L[PriorNode].Next; |
11 |
12 | L[PriorNode].Next=L[DeletePosition].Next; //让删除结点的前一个结点的next值指向欲删除结点的后一个结点的下标(变向地址) |
13 | *OutputData=L[DeletePosition].Data; //记录删除的值 |
14 | |
15 | if (!FreeNode(L,DeletePosition)) //如果释放内存失败,返回false |
16 | return false ; |
17 | L[MAXSIZE-1].Data--; //链表长度减一 |
18 | L[0].Data++; //备用链表长度加1 |
19 | return true ; |
20 | } |
6.遍历操作:
01 | BOOL BrowseList(List L) //遍历链表并输出值,函数返回值为链表的长度 |
02 | { |
03 | int temp,count,CurrentNode; |
04 |
05 | CurrentNode=L[MAXSIZE-1].Next; //当前结点首先指向数据链表的第一个结点 |
06 |
07 | for (temp=0,count=1;temp<L[MAXSIZE-1].Data;count++,temp++) |
08 | { |
09 | printf ( "%3c" ,L[CurrentNode].Data); //输出当前结点的值 |
10 | if (0==count%6) |
11 | printf ( "\n" ); |
12 | CurrentNode=L[CurrentNode].Next; //当前结点后移一位 |
13 | } |
14 | printf ( "\n" ); |
15 |
16 | return temp; |
17 | } |
用一个程序跑一下,以下是代码:
01 | #define MAXSIZE 28 |
02 | #define true 1 |
03 | #define false 0 |
04 | #define BOOL int |
05 |
06 | #include <stdlib.h> |
07 | #include <stdio.h> |
08 |
09 | int main( void ) |
10 | { |
11 | List St_L; |
12 | char InputData= 'a' ,OutputData; |
13 | int temp; |
14 |
15 | if ( true ==InitializeStaticList(St_L)) |
16 | printf ( "静态链表初始化成功!\n" ); |
17 | else |
18 | { |
19 | printf ( "Sorry,静态链表初始化失败!程序结束!" ); |
20 | return 0; |
21 | } |
22 | |
23 | for (temp=0;temp<MAXSIZE-2;temp++) |
24 | { |
25 | if ( false ==InsertDataStaticList(St_L,temp+1,InputData)) |
26 | { |
27 | printf ( "插入值失败,程序退出!\n" ); |
28 | return 0; |
29 | }; |
30 | InputData++; |
31 | } |
32 | printf ( "链表赋值成功!以下为当前链表数据!\n" ); |
33 | if (0!=BrowseList(St_L)) |
34 | printf ( "\n" ); |
35 | else |
36 | { |
37 | printf ( "抱歉,链表初始化失败,可能链表无数据!程序结束!\n" ); |
38 | return 0; |
39 | } |
40 | |
41 | if ( false ==DeleteNode(St_L,12,&OutputData)) |
42 | { |
43 | printf ( "sorry,删除数据失败!\n" ); |
44 | } |
45 | else |
46 | { |
47 | printf ( "删除数据成功!删除数据为:%c,以下是新的链表数据:\n" ,OutputData); |
48 | if (0!=BrowseList(St_L)) |
49 | printf ( "\n" ); |
50 | else |
51 | { |
52 | printf ( "抱歉,链表数据显示失败,可能链表无数据!程序结束!\n" ); |
53 | return 0; |
54 | } |
55 | } |
56 | |
57 | |
58 | if ( false ==InsertDataStaticList(St_L,12, '@' )) |
59 | { |
60 | printf ( "插入值失败,程序退出!\n" ); |
61 | return 0; |
62 | } |
63 | else |
64 | { |
65 | printf ( "插入数据成功!\n插入的数据为:@\n新的链表的数据长度为:%d,以下是新的链表数据:\n" ,St_L[MAXSIZE-1].Data); |
66 | if (0!=BrowseList(St_L)) |
67 | printf ( "\n" ); |
68 | else |
69 | { |
70 | printf ( "抱歉,链表数据显示失败,可能链表无数据!程序结束!\n" ); |
71 | return 0; |
72 | } |
73 | } |
74 | |
75 | if ( false ==InsertDataStaticList(St_L,12, '@' )) |
76 | { |
77 | printf ( "插入值失败,程序退出!\n" ); |
78 | return 0; |
79 | } |
80 | else |
81 | { |
82 | printf ( "插入数据成功!\n插入的数据为:@\n新的链表的数据长度为:%d,以下是新的链表数据:\n" ,St_L[MAXSIZE-1].Data); |
83 | if (0!=BrowseList(St_L)) |
84 | printf ( "\n" ); |
85 | else |
86 | { |
87 | printf ( "抱歉,链表数据显示失败,可能链表无数据!程序结束!\n" ); |
88 | return 0; |
89 | } |
90 | } |
91 | |
92 | return 0; |
93 | } |
下面是运行效果:
好了,明天开始学习Stack and Queue,加油!