引言:C语言中具有的指针能力,可以使它非常容易地操作内存中的地址和数据。后来的面向对象语言,如Java,C#等,虽然没有使用指针,但由于启动了对象引用机制,从某种角度也间接实现了指针的某些作用。但是对于一些早期编程高级语言:Basic、fortran等,不存在指针。在没有指针的情况下,要想使用链表,就要用到静态链表了。
用数组描述的链表称为静态链表(游标实现法) 。
那么怎么实现呢?
首先,我们让数组中的元素都是由两个数据域组成,data和next。data用来存放元素,next相当于链表中的next指针,用来存放元素的后继在数组中的下标。
在定义结构体之前,先进行准备工作:
1 #define MAX 100 //假设此静态链表的最大长度是100 2 #define true 1 //返回值,true为真 3 #define false 0; //返回值,false为假 4 typedef int ElemType; //存放数据元素的数据类型,这里设置为int类型 5 typedef int Boolean; //伪装成Java代码的boolean类型,返回1相当于True,返回0相当于返回false
结构体实现如下:
1 typedef struct space{ 2 ElemType data; //存放数据元素 3 int next; //存放元素的后继在数组中的下标 4 }array[MAX];
其次,先进行静态链表的初始化:
我们对数组的第一个元素和最后一个元素作为特殊元素处理,不存数据。
我们通常把未被使用的数组元素称为备用链表。
数组的第一个元素(下标为0的元素)存放备用链表的第一个结点的下标,数组的最后一个元素,即(下标为MAX-1的元素),存放第一个有数值元素的下标(在这里相当于头节点的作用),当整个链表为空时,存放的下标为0。
初始化代码如下:
1 void init(array a){ 2 int i; 3 for(i = 0; i < MAX-1; i++){ 4 a[i].next = i+1; //通过这个循环将前MAX-1个元素的下标初始化 5 } 6 a[MAX-1].next = 0; //由于链表为空,所以最后一个元素的next指向0 7 }
初始化完成之后,下面进行元素的插入和删除操作
我们需要用静态模拟动态链表结构的存储空间的分配,需要时申请,无用时释放。当然在这里指的的逻辑上的申请和释放。
所以我们也需要一个malloc()函数和free()函数。
注意:在这里需要重新起名字,因为c语言中本身就有这两个函数!!!今天上午程序出现错误耽误了两个小时,就是因为忘了给malloc函数改名字!!而且编译不会出现错误!!只是运行时会崩掉!!
在这里,我们起名字为M_malloc()和F_free()函数。
mallloc()函数本来是c语言中用来申请空间的函数,在这里M_malloc()的作用就是通过修改下标为0元素的next,返回备用链表第一个空间的下标。
同理,free()函数本来是释放空间的函数,在这里F_free()的作用就是通过改变下标为0元素的next和释放节点的next,将释放节点改成备用链表的第一个空间。
int m_malloc(array a){ //要先判断分配的空间是否有,若有,返回下标,若无,返回0 int i = a[0].next; if(a[0].next){ a[0].next = a[i].next; //将a[0].next"指向"备用链表的第二个空间 } return i; //返回备用链表第一个空间的下标值,当i为0时,说明无可分配的空间 }
int F_free(int j,array a){ //j为当前要删除的节点的下标 a[j].next = a[0].next; //将j节点插入备用链表的开头 a[0].next = j; }
接下来,插入和删除操作就变得简单了。
在这里,我写了两个关于插入的函数,一个是头插法(永远插入在第一个),一个是在指定位置插入元素。两个函数本质差不多,就是参数不一样。
1.头插法(这里和链表的头插法及其相似)
分配好空间并获取到了待插入节点的下标值以后,若下标值不为0,如下图所示,改变下标为MAX-1的next值和待插入节点的next值。待插入节点就变成了逻辑上的链表的第一个元素啦。
下图方框中的数字代表的是数组下标。
代码如下:
Boolean headInsert(int data,array a){ //Boolean其实就是int类型 int i = m_malloc(a); //获取到了待插入节点的下标 if(i){ //若i==0,直接返回false,否则,执行代码 a[i].data = data; //将待插入值赋给i下标对应的data int j = a[MAX - 1].next; //获取到静态链表的第一个元素下标 a[MAX - 1].next = i; //将静态链表的第一个元素下标改为待插入节点下标 a[i].next = j; //将待插入节点的next改为以前的静态链表第一个元素的下标 return true; } return false; //fase在前面定义过,代表0 }
2.在指定位置插入元素
关于指定位置,严格的来说,还会有校验插入位置的操作,在这里没有写出来。(关于下标的校验,定义一个显示实际长度的函数,判断插入位置,和实际长度进行比较就好)
将头插法中的MAX-1元素换成插入位置的前驱结点元素,就变成了在指定位置插入元素。
只要确定了插入位置的前驱结点元素的下标,剩下的步骤差不多。代码如下:
Boolean insert(int data,int j,array a){ //把data插入到第j个位置前 ,下标是j,因为0下标没有存东西 int i = m_malloc(a); //返回待插入节点的下标 if(i){ //如果i不为0,执行 a[i].data = data; //把data赋给待插入节点 int k; //k是用来计数的 int f = MAX-1; for(k = 0; k < j -1; k++){ //一共循环j-1次 f = a[f].next; //用f来查找第j-1个节点的下标 } a[i].next = a[f].next; //f为第j-1个位置的下标 a[f].next = i; //f的值为要插入点的前驱结点 return true; } return false; }
接下来是删除元素,有了F_free()函数,删除操作也很简单。
这里写的是删除指定位置的函数
由图知:需要先遍历到要删除结点的前驱结点,然后更改结点的next值。
1 Boolean deleteNode(int index,array a){ 2 //index代表第index个元素, 3 int k = MAX - 1; 4 int i,j; 5 for(i = 0; i < index-1;i++){ 6 k = a[k].next; 7 } 8 //k是index元素的前驱结点 9 j = a[k].next; //j为index元素的下标 10 a[k].next = a[j].next; 11 freeNode(j,a); 12 }
静态链表的优点:
在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中的插入和删除操作需要移动大量元素的缺点。
缺点:
没有解决连续存储分配带来的表长难以确定的问题。失去了顺序存储结构随机存取的特性。