1、静态链表的概念
我们先来说说什么是静态链表,用一个数组来模拟链表存储元素,数组中的元素都是由两个数据域组成, data 和 cur。也就是说,数组的每个下标都对应一个data和一个 cur。 数据域data,用来存放数据元素,也就是通常我们要处理的数据;而游标 cur 相当于单链表中的 next 指针,存放该元素的后继在数组中的下标。我们把这种用数组描述的链表叫做静态链表,这种描述方法还有起名叫做游标实现法。
另外我们对数组第一个和最后一个元素作为特殊元素处理,不存数据。我们通常把未被使用的数组元素称为备用链表。而数组第一个元素,即下标为 0 的元素的 cur 就存放备用链表的第一个结点的下标;而数组的最后一个元素的 cur 则存放第一个有数值的元素的下标,相当于单链表中的头结点作用。如图所示:
我们来看一个具体的操作图示;
假设我们已经将数据存入静态链表,比如分别存放着"甲"、 “乙”、 “丙"、"丁’等数据,则它们将处于如图所示这种状态。
此时“甲”这里就存有下一元素"乙” 的游标2,"乙"则存有下一元素"丁’的下标3。而"丁"是最后一个有值元素,所以它的 cur 设置为 0。而最后一个元素的cur则因"甲’是第一有值元素而存有它的下标为1。而第一个元素则因空闲空间的第一个元素下标为 5 ,所以它的 cur 存有 5。
这样看起来是不是就很好理解了呢,接下来我们看看如何用代码来操作
2、静态链表的操作
public class StaticLink {
element[] link = null; // 保存数据的数组
int current = 0; // 记录下一个可用空闲容器的下标
int head = 0; // 记录当前静态链表头的下标
int length = 0; // 记录当前装有用数据的大小
int MAXSIZE = 0; // 记录静态链表可装的最大空间
class element {// 相当于链表中的空间单元
int data; // 记录存入的数据
int cur; // 记录下一个数据的下标
}
public StaticLink(int size) { // 初始化静态列表
link = new element[size];// 创建一个新得链表
for (int i = 0; i < size; i++) {
link[i] = new element();
link[i].data = -1;
link[i].cur = i + 1; // 下标0的单元指向下标1的单元
}
current = 0;
head = 0;
length = 0;
MAXSIZE = size;
System.out.println("初始化静态链表...");
}
public void add(int data) { // 向当前静态链表的后面添加一个数据
if (length < MAXSIZE) { // 检查是否还有空闲空间可以分配数据
link[current].data = data; // 将当前空闲单元存入数据
current = link[current].cur; // 将current指向下一个空闲单元
length++; // 实际使用空间增加1
System.out.println("成功添加数据:" + data);
} else {
System.out.println("添加数据失败");
}
}
public void delete() { // 删除当前静态链表最后一个数据
if (length > 0 && link != null) { // 判断是否有数据可以删除
int temp = current; // 暂时记录当前空闲单元的下标
current = getTrulyIndex(length - 1); // 将要删除的使用单元加入到空闲单元链中
link[current].cur = temp; // 将这个刚加入的单元链接上空闲空间
length--; // 实际使用空间自减1
System.out.println("成功删除数据:" + link[current].data);
} else {
System.out.println("删除数据失败");
}
}
public void insert(int data, int index) { // 从指定的下标的地方插入数据
if (length < MAXSIZE && link != null && index < length && index >= 0) {// 判断是否有空间可以插入新数据和判断当前输入的下标是否在范围内
int tCurrent = current; // 保存当前空闲单元的下标
current = link[current].cur; // 将current指向下一个空闲单元
// 上面这两个语句。相当于在空闲链中拿出一个单元出来储存数据
if (index == 0) { // 当要插入的位置在最前面
link[tCurrent].data = data; // 将拿出的空闲单元存入数据
link[tCurrent].cur = head; // 将这个单元指向原来的头位置下标
head = tCurrent; // 重新设置head指向的位置
} else if (index == length - 1) { // 当要插入的是在静态链表末端
link[tCurrent].data = data; // 放入数据
} else { // 要插入的位置前后都有空间单元的时候
int preIndex = getTrulyIndex(index - 1); // 获取要插入的前一个空间单元的index
link[tCurrent].cur = link[preIndex].cur; // 将要插入位置的前一个单元原来的cur赋值给现在要插入的单元的cur(cur:保存下一个单元的位置下标)
link[tCurrent].data = data; // 放入数据
link[preIndex].cur = tCurrent; // 将要插入位置的前一个单元指向现在要插入单元的位置
}
length++; // 大小自增1
System.out.println("成功在 " + index + " 插入数据: " + data);
} else {
System.out.println("在" + index + "插入数据失败");
}
}
public void delete(int index) { // 删除给定位置的数据
if (length > 0 && link != null && index >= 0 && index <= length - 1) { // 判断是否有数据可以删除
if (index == 0) { // 判断是否要删除第一个单元
int tHead = head; // 保存当前的head
head = link[head].cur; // 将head指向一下单元
link[tHead].cur = current; // 将删除的单元加入空闲链表
current = tHead; // 将current指向空闲链的第一个单元
} else if (index == (length - 1)) { // 判断是否要删除最后一个单元
int last = getTrulyIndex(length - 1); // 获取最后一个单元的位置
if (last != -1) { // 如果获取位置成功
link[last].cur = current; // 将删除的单元加入空闲链中
current = last; // 将current指向空闲链的第一个单元
}
} else {// 判断是否要删除第一个到最后一个(单元)之间的单元
int preIndex = getTrulyIndex(index - 1); // 获取要删除单元的前一个单元的位置
int temp = current; // 保存current的值
current = link[preIndex].cur; // 将要删除的单元链入空闲链
link[preIndex].cur = link[current].cur; // 将上一个单元重新指向到下一个单元,例如A、B、C 删除 B ,这个语句相当于 ##将 A 从指向 B 重新指向为 C
// ##(即改变A的指向)
link[current].cur = temp; // 将删除单元链入空闲链
}
length--; // 大小自减1
System.out.println("成功在 " + index + " 删除数据: " + link[current].data);
} else {
System.out.println("在" + index + "删除数据失败");
}
}
public int getTrulyIndex(int index) { // 获取给定下标的实际下标
int i = 0;
int tHead = head;
if (link != null) {
while (i < index) { // 这个是实现的重要代码
tHead = link[tHead].cur; // 每次执行这个语句的时候获取下一个真正的位置下标
i++;
}
if (i == index) {// 判断否是获得真正的下标
return tHead; // 返回真正的下标
}
}
return -1;
}
public int get(int index) {// 获取给定下标储存数据
int tIndex = getTrulyIndex(index);
if (tIndex != -1 && link != null) {
return link[tIndex].data;
}
return -1;
}
public static void main(String[] args) { // 测试代码
int size = 10;
int[] original = null;
original = new int[size];
StaticLink sl = new StaticLink(size);
for (int i = 0; i < size; i++) {
sl.add(i);
original[i] = i;
}
sl.delete();
sl.insert(20, 2);
sl.delete(1);
System.out.print("
操作前的链表内容: ");
for (int i = 0; i < original.length; i++) {
System.out.printf("%d ", original[i]);
}
System.out.print("
操作后的链表内容: ");
for (int j = 0; j < sl.length; j++) {
System.out.printf("%d ", sl.get(j));
}
}
}
最后输出的结果为:
3、静态链表的优缺点
优点:在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中的插人和删除操作需要移动大量元素的缺点
缺点:没有解决连续存储分配带来的表长难以确定的问题;失去了顺序存储结构随机存取的特性
静态链表中要解决的是: 如何用静态模拟动态链表结构的存储空间的分配,需要时申请,无用时释放。
其实静态链表我们用得不多,而且现在大多数编程语言都支持动态链表,这里只是让大家了解下。