看了老唐的静态链表,越发的觉得自己是菜鸟了,因为看的过程实在是太纠结了。下面就把自己看老唐静态链表的内容写下来。
一、静态链表的基础知识
1.单链表的缺陷:单链表的实现严重依赖指针,每一个数据元素都要有额外的指针域。
2.在静态表中我们把数据元素放在一个数组里,那么这个数据元素就有了对应的唯一下标,那么我们就有了这个数据元素的地址,准确说是数据元素在这个数组里的地址。每一个数据结点都包括数据元素和存放下一个数据元素的数组下标。当下标为0的时候,这个时候已经到达了静态表的末尾,也就是NULL。
具体看截图:
header = 1代表静态表存储的第一个数据元素在数组里面的下标是1
length = 3代表静态表存储的数据元素个数为3
capactiy = n代表整个的静态表是以数组来实现的,存在一个最大值的限制,代表可以
存储n个数据元素,真正的数组长度是n + 1(因为包含了头结点)
3.静态链表是在顺序表的基础上实现的单链表。老唐说静态链表是柔性数组的一种典型应用。
4.最后的最后老唐说,C语言中有指针,谁还会用一个数组来管理链表呢?但是这种静态链表的管理方法深刻的体现出来了内存的管理方法,这才是最重要的。
二、静态表的实现
1.静态表的使用了两个结构体来定义链表的整体信息和每一个数据结点的信息
/*定义的是每一个数据结点*/
typedef struct _tag_StaticListNode
{
unsigned int data; //用来保存我们真正的数据元素
int next; //保存下一个数据元素在数组里的下标
} TStaticListNode;
/*定义的是链表*/
typedef struct _tag_StaticList
{
int capacity; //链表最多可以容纳的数据结点数量
TStaticListNode header;//表头
TStaticListNode node[];//真正用于容纳数据元素的数组
//int length; /*这里是否需要定义一个length?*/
}TStaticList;
在描述链表的结构体中我留下了一个疑问,程序中有注释,这个一会再做解释。
2.在静态链表中获取第pos个元素的操作
StaticListNode* StaticList_Get(StaticList* list, int pos) // O(n)
{
TStaticList* sList = (TStaticList*)list;
StaticListNode* ret = NULL;
int current = 0;
int object = 0;
int i = 0;
/*
三个合法性的检测
(1)链表不为空 (2)插入的位置要大于0(3)同时插入的位置要小于链表长度
这里,pos < sList->header.data,依然是data的复用。
*/
if( (sList != NULL) && (0 <= pos) && (pos < sList->header.data) )
{
/*
将头结点的信息送到node[0]中去
*/
sList->node[0] = sList->header;
for(i=0; inode[current].next;
}
object = sList->node[current].next;
ret = (StaticListNode*)(sList->node[object].data);
}
return ret;
}
在for循环中有这样一行代码
current = sList->node[current].next;
这行代码的含义如下:
当i = 0的时候,current的值也为0,sList->node[current].next为sList->node[0].next,而sList->node[0] = sList->header;这个时候是头结点的位置,再取头接点的next是下一个数据结点的下标,依次进行循环,最后找出第pos个结点所对应的下标,然后取出元素。
3.插入一个数据结点到pos位置
int StaticList_Insert(StaticList* list, StaticListNode* node, int pos) // O(n)
{
/*强制类型转换*/
TStaticList* sList = (TStaticList*)list;
/*合法性判断*/
int ret = (sList != NULL);
int current = 0;
//定义一个空闲位置的下标index
int index = 0;
int i = 0;
/*判断单链表要还有位置*/
ret = ret && (sList->header.data + 1 <= sList->capacity);
ret = ret && (pos >=0) && (node != NULL);
if( ret )
{
/*寻找空闲位置*/
for(i=1; i<=sList->capacity; i++)
{
/*只要数组里面的next元素为-1的时候就证明这个位置是空的*/
if( sList->node[i].next == AVAILABLE )
{
index = i;
break;
}
}
sList->node[index].data = (unsigned int)node;
/*把头结点放在这个位置就是为了后续的遍历*/
sList->node[0] = sList->header;
//sList->node[current].next保护机制,为了满足新元素的插入。
for(i=0; (inode[current].next != 0); i++)
{
current = sList->node[current].next;
}
sList->node[index].next = sList->node[current].next;
sList->node[current].next = index;
/*链表长度+1(复用的方法)*/
sList->node[0].data++;
/*更新一下表头信息*/
sList->header = sList->node[0];
}
return ret;
}
在这个插入元素的代码里有两个地方需要注意:
(1)
sList->node[index].next = sList->node[current].next;
sList->node[current].next = index;
这两行代码的实现机制和单链表中插入元素的实现机制是一样的,原则就是表不可以中断。
(2)
sList->header = sList->node[0];
这行代码的作用是更新表头信息,老唐是这么说的,但是具体为什么我不知道原因,而且后面的代码确实因为这个因素出现了问题,等下再说。
上面的插入代码我认为是整个静态链表实现中最难的一部分代码了,首先从整体上来说一下静态链表是如何实现插入一个元素的。
插入元素的步骤大体如下图:
想额外的说一句,一定要注意标红的地方,这些都是理解插入的关键点。
整个插入元素的思想:
(1)寻找空闲位置
for(i=1; i<=sList->capacity; i++)
{
//只要数组里面的next元素为-1的时候就证明这个位置是空的
if( sList->node[i].next == AVAILABLE )
{
index = i;
break;
}
}
(2)通过上面的语句找到一个空的位置,然后再通过下面的语句
sList->node[index].data = (unsigned int)node;
把要插入的值放在这个位置上
(3)再通过
for(i=0; (inode[current].next != 0); i++)
{
current = sList->node[current].next;
}
遍历到要插入的位置。
(4)
sList->node[index].next = sList->node[current].next;
sList->node[current].next = index;
将原来已经各种都搞好的一个数据结点插入到指定的位置,完成了整个插入。
这么做的最主要目的我认为是:保证链表在插入的过程中不断,但同时是因为是下标操作,所以必须要寻找到一个中间位置来进行操作。
我自己想到的插入的流程图如下所示:
4.从pos位置在静态链表中删除一个元素
StaticListNode* StaticList_Delete(StaticList* list, int pos) // O(n)
{
TStaticList* sList = (TStaticList*)list;
StaticListNode* ret = NULL;
int current = 0;
int object = 0;
int i = 0;
if( (sList != NULL) && (0 <= pos) && (pos < sList->header.data) )
{
sList->node[0] = sList->header;
for(i=0; inode[current].next;
}
object = sList->node[current].next;
/*以上部分代码和get函数部分的实现是一样的*/
sList->node[current].next = sList->node[object].next;
/*表头里存放的长度要进行自-操作*/
sList->node[0].data--;
sList->header = sList->node[0];
//这个位置要在数组里标为可以使用。
sList->node[object].next = AVAILABLE;
ret = (StaticListNode*)(sList->node[object].data);
}
return ret;
}
整个函数大体包含两个部分,第一部分和get函数的实现是一样的,也就是获取pos位置上的数据信息,第二个部分的代码如下。
sList->node[current].next = sList->node[object].next;
这行代码实现了元素的删除操作。
同时在删除操作的函数中,在for循环的上方有这样一行代码:
sList->node[0] = sList->header;
这行代码的作用是把表头的结点信息给数组中0位置。
for循环结束的时候有这样一行代码:
sList->header = sList->node[0];
这里是又把数组中位置为0的信息重新返回给了头结点。
这里存在一个疑问,如果我没有在for循环结束的时候把数组0位置保存的信息返还给header,那么程序将会出现死循环,我的理解是这样的,在sList->node[0] = sList->header;之后进行了一轮for循环,所以在for循环执行结束之后要对链表头的信息进行更新,但是我没有理解究竟是什么操作导致了slist > header这个成员的改变呢?马上推翻刚才的结论,是因为程序的上面存在sList->node[0].data--;对结构体的长度进行自减的操作,所以在执行完毕以后会出现跟新表头信息的操作,同理,insert函数中也存在这样的操作。
5.一些其他注意的地方
(1)链表的建立函数
StaticList* StaticList_Create(int capacity) // O(n)
{
TStaticList* ret = NULL;
int i = 0;
if( capacity >= 0 )
{
ret = (TStaticList*)malloc(sizeof(TStaticList) + sizeof(TStaticListNode) * (capacity + 1));
}
if( ret != NULL )
{
ret->capacity = capacity;
ret->header.data = 0;
ret->header.next = 0;
for(i=1; i<=capacity; i++)
{
ret->node[i].next = AVAILABLE;
}
}
return ret;
}
在链表的建立函数有这样一行代码:
ret = (TStaticList*)malloc(sizeof(TStaticList) + sizeof(TStaticListNode) * (capacity + 1));
这行代码的作用是进行空间的动态分配,之所以出现(capacity + 1)是因为在TStaticList这个结构体中有一个header
所以要也要给hedaer分配一个空间。
(2)把所有空闲的数组(node)位置中的next全部置-1。
for(i=1; i<=capacity; i++)
{
ret->node[i].next = AVAILABLE;
}
这段代码在create函数和clear函数中都出现了,目的就是把所有空闲数组中的next都设置为-1 。这个时候这个next只是一个标志,并不是确切代表什么。你可能会问,那为什么不使用data来做标志呢,因为data可以是int类型的啊,所以data也可以为-1啊,所以data不能作为标志。在看到这里的时候不要想着next指向的东西,只要单纯的去想数据结点结构体里的这个next值就可以了。
(3)data的复用问题
clear函数的代码如下:
void StaticList_Clear(StaticList* list) // O(n)
{
TStaticList* sList = (TStaticList*)list;
int i = 0;
if( sList != NULL )
{
sList->header.data = 0;
sList->header.next = 0;
/*把所有位置弄为空*/
for(i=1; i<=sList->capacity; i++)
{
sList->node[i].next = AVAILABLE;
}
}
}
在这个函数中有如下两行代码:
sList->header.data = 0;
sList->header.next = 0;
第二行的不用说了,是把next清零。但是第一个开始的时候令我很郁闷,后来老唐说这是代表着链表的长度,这是一个data的复用。
不过这里data的复用,那么header的类型是
typedef struct _tag_StaticListNode
{
unsigned int data; //用来保存我们真正的数据元素
int next; //保存下一个数据元素在数组里的下标
} TStaticListNode;
这个结构体,但是这个结构同时代表着一个数据结点。这个时候data的类型是(unsigned int )类型,而链表的长度也是int类型的,所以复用没问题了,但是假如,我们链表中的数据结点要存放的是char类型呢?如果还是这么复用的话,那么我们的header就变成了这样:
typedef struct _tag_StaticListNode
{
char data;
int next;
} TStaticListNode
那么我们的链表长度还可以是char类型的么?我觉得显然不可以啊。
我的解决办法:
1)额外定义一个结构体,专门用来描述header,不使用复用。
2)把描述链表的结构体改变成为如下所示:
typedef struct _tag_StaticList
{
int capacity;
TStaticListNode header;
TStaticListNode node[];
int length;
}TStaticList;
(4)关于老唐的思考题
老唐的问题是:为什么静态链表的表头不直接使用node[0],而是要额外的定义一个header?
最开始我看完程序的解释是:
假如header里面的信息被更新了,仍然可以从node[0]里面取出信息放在header里面。但是后来我发现sList->header = sList->node[0];这个代码的作用是这样的,但是我始终不明白究竟的原因是什么?所以暂时先留下来一个疑问吧。
三、静态链表的整体程序
(1).h文件
#ifndef _STATICLIST_H_
#define _STATICLIST_H_
typedef void StaticList;
typedef void StaticListNode;
StaticList* StaticList_Create(int capacity);
void StaticList_Destroy(StaticList* list);
void StaticList_Clear(StaticList* list);
int StaticList_Length(StaticList* list);
int StaticList_Capacity(StaticList* list);
int StaticList_Insert(StaticList* list, StaticListNode* node, int pos);
StaticListNode* StaticList_Get(StaticList* list, int pos);
StaticListNode* StaticList_Delete(StaticList* list, int pos);
#endif
(2).c文件
#include
#include
#include "StaticList.h"
#define AVAILABLE -1
/*定义的是每一个数据结点*/
typedef struct _tag_StaticListNode
{
unsigned int data; //用来保存我们真正的数据元素
int next; //保存下一个数据元素在数组里的下标
} TStaticListNode;
/*定义的是链表*/
typedef struct _tag_StaticList
{
int capacity; //链表最多可以容纳的数据结点数量
TStaticListNode header;//表头
TStaticListNode node[];//真正用于容纳数据元素的数组
//int length; /*这里是否需要定义一个length?*/
}TStaticList;
StaticList* StaticList_Create(int capacity) // O(n)
{
TStaticList* ret = NULL;
int i = 0;
if( capacity >= 0 )
{
ret = (TStaticList*)malloc(sizeof(TStaticList) + sizeof(TStaticListNode) * (capacity + 1));
}
if( ret != NULL )
{
ret->capacity = capacity;
ret->header.data = 0;
ret->header.next = 0;
for(i=1; i<=capacity; i++)
{
ret->node[i].next = AVAILABLE;
}
}
return ret;
}
void StaticList_Destroy(StaticList* list) // O(1)
{
free(list);
}
void StaticList_Clear(StaticList* list) // O(n)
{
TStaticList* sList = (TStaticList*)list;
int i = 0;
if( sList != NULL )
{
sList->header.data = 0;
sList->header.next = 0;
/*把所有位置弄为空*/
for(i=1; i<=sList->capacity; i++)
{
sList->node[i].next = AVAILABLE;
}
}
}
int StaticList_Length(StaticList* list) // O(1)
{
TStaticList* sList = (TStaticList*)list;
/*把返回值赋值为-1,如果该函数返回-1,那么代表这个静态链表不合法*/
int ret = -1;
if( sList != NULL )
{
ret = sList->header.data;
}
return ret;
}
/*返回capacity的值,程序的逻辑和返回length的函数没有本质性的差别*/
int StaticList_Capacity(StaticList* list) // O(1)
{
TStaticList* sList = (TStaticList*)list;
int ret = -1;
if( sList != NULL )
{
ret = sList->capacity;
}
return ret;
}
int StaticList_Insert(StaticList* list, StaticListNode* node, int pos) // O(n)
{
/*强制类型转换*/
TStaticList* sList = (TStaticList*)list;
/*合法性判断*/
int ret = (sList != NULL);
int current = 0;
//定义一个空闲位置的下标index
int index = 0;
int i = 0;
/*判断单链表要还有位置*/
ret = ret && (sList->header.data + 1 <= sList->capacity);
ret = ret && (pos >=0) && (node != NULL);
if( ret )
{
/*寻找空闲位置*/
for(i=1; i<=sList->capacity; i++)
{
/*只要数组里面的next元素为-1的时候就证明这个位置是空的*/
if( sList->node[i].next == AVAILABLE )
{
index = i;
break;
}
}
sList->node[index].data = (unsigned int)node;
/*把头结点放在这个位置就是为了后续的遍历*/
sList->node[0] = sList->header;
//sList->node[current].next保护机制,为了满足新元素的插入。
for(i=0; (inode[current].next != 0); i++)
{
current = sList->node[current].next;
}
sList->node[index].next = sList->node[current].next;
sList->node[current].next = index;
/*链表长度+1(复用的方法)*/
sList->node[0].data++;
/*更新一下表头信息*/
sList->header = sList->node[0];
}
return ret;
}
StaticListNode* StaticList_Get(StaticList* list, int pos) // O(n)
{
TStaticList* sList = (TStaticList*)list;
StaticListNode* ret = NULL;
int current = 0;
int object = 0;
int i = 0;
/*
三个合法性的检测
(1)链表不为空 (2)插入的位置要大于0(3)同时插入的位置要小于链表长度
这里,pos < sList->header.data,依然是data的复用。
*/
if( (sList != NULL) && (0 <= pos) && (pos < sList->header.data) )
{
/*
将头结点的信息送到node[0]中去
*/
sList->node[0] = sList->header;
for(i=0; inode[current].next;
}
object = sList->node[current].next;
ret = (StaticListNode*)(sList->node[object].data);
}
return ret;
}
StaticListNode* StaticList_Delete(StaticList* list, int pos) // O(n)
{
TStaticList* sList = (TStaticList*)list;
StaticListNode* ret = NULL;
int current = 0;
int object = 0;
int i = 0;
if( (sList != NULL) && (0 <= pos) && (pos < sList->header.data) )
{
sList->node[0] = sList->header;
for(i=0; inode[current].next;
}
object = sList->node[current].next;
/*以上部分代码和get函数部分的实现是一样的*/
sList->node[current].next = sList->node[object].next;
/*表头里存放的长度要进行自-操作*/
sList->node[0].data--;
sList->header = sList->node[0];
//这个位置要在数组里标为可以使用。
sList->node[object].next = AVAILABLE;
ret = (StaticListNode*)(sList->node[object].data);
}
return ret;
}
(3)测试部分代码
#include
#include
#include "StaticList.h"
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
int main(int argc, char *argv[])
{
StaticList* list = StaticList_Create(10);
int index = 0;
int i = 0;
int j = 1;
int k = 2;
int x = 3;
int y = 4;
int z = 5;
StaticList_Insert(list, &i, 0);
StaticList_Insert(list, &j, 0);
StaticList_Insert(list, &k, 0);
for(index=0; index 0 )
{
int* p = (int*)StaticList_Delete(list, 0);
printf("%d\n", *p);
}
printf("\n");
StaticList_Insert(list, &x, 0);
StaticList_Insert(list, &y, 0);
StaticList_Insert(list, &z, 0);
printf("Capacity: %d Length: %d\n", StaticList_Capacity(list), StaticList_Length(list));
for(index=0; index