目录
为什么会有静态链表?
怎么做到的呢?
静态链表是什么?
静态链表的代码实现:
准备工作:
初始化静态链表:
添加元素:
插入元素:
删除元素:
总结:
为什么会有静态链表?
Basic、Fortran 等早期的编程高级语言,由于没有指针,链表结构按照指针的方式,它就没有办法实现。
有人就想出了用数组来代替指针,来描述单链表。
怎么做到的呢?
首先我们让数组的元素都是有两个数据域组成,data 和 cur(Cursor:游标)。
数组的每个下标都对应一个 data 和一个 cur。
- 数据域 data,用来方法数据元素。
- 游标 cur,相当于单链表中的next指针,存放该元素的后继在数组中的下标,
用数组描述的链表叫做静态链表,也叫做游标实现法。
静态链表是什么?
静态链表其实是为了给没有指针的高级语言设计的一种实现单链表能力的方法。
静态链表的代码实现:
由于水平有限,就使用Java来实现。
准备工作:
既然是一个数据结构那么我们就先创建一个类:StaticLinkedList
然后实现链表的结点类:Node(内部类)
public class StaticLinkedList <T> {
private StaticNode<T>[] data;
private static final int MAX_SIZE = 1000; //初始化先对建立的大一些,以便有空闲空间保证插入时不至于溢出
private int size = 0;//有效元素的个数
private int lastNodeIndex = 0; //链表中最后一个有值结点的下标
/**
* 结点类
*/
static class Node<T>{
T data; //数据域
int cur; //游标Corsor,存放后继结点在数组中的下标
}
}
初始化静态链表:
要对数组第一个和最后一个元素作为特殊元素处理,不存数据。我们通常把未被使用的数组元素称为备用链表。
- 数组第一个元素,即下标为0的元素的 cur 就存放备用链表的第一个结点的下标;
- 数组的最后一个元素的 cur 则存放第一个有数值的元素的下标,相当于单链表中的头结点作用,当整个链表为空时,则为0。
public class StaticLinkedList <T> {
。。。。
public StaticLinkList(){
data = new StaticNode[MAX_SIZE];
initList(data);
}
/**
* 初始化静态链表
* @param data
*/
private void initList(StaticNode<T>[] data) {
for (int i = 0; i < data.length - 1; i++) {
data[i] = new StaticNode<T>(null, i+1); //初始化每个节点的cur值,为其数组中下标加1,指向下一个元素
}
data[data.length-1] = new StaticNode<T>(null, 0); //数组最后一个元素的cur,
//用来存放第一个插入元素的下标,即第一个有值元素的下标
}
。。。。
}
添加元素:
分析:
- 向链表申请空闲空间(申请空间需要自己实现)
- 判断是否是第一次添加元素 size == 0
- 是:将数组中最后一个结点的cur指向1,指向链表中第一个有值元素的下标
- 否:将新添加元素的下标与最后一个有值元素的游标联系起来。
- 更新存储最后一个有值元素变量的值,其实就是,申请空间返回的下标
代码:
/**
* 顺序添加元素
* @param item 待添加的元素
* @return
*/
public boolean add(T item){
int newCur = malloc_SLL(); //申请空闲空间
StaticNode<T> newNode = new StaticNode<>(item, 0); //新添加元素,由于是最后一个元素,其游标置0
data[newCur] = newNode; //存储新结点
if (size == 0){ //链表为空,
//添加的时候不能改变起始的结点下标,除非是第一次添加结点
data[data.length-1].cur = 1; //最后一个元素的游标存放第一个有值元素的下标,为1
}else{
//尾插法,获取到最后一个有值元素的下标
//将最后一个有值结点的游标与新结点的下标连接起来
data[lastNodeIndex].cur = newCur;
}
//最后一个有值结点的下标,就是新添加的结点的下标
lastNodeIndex = newCur;
size ++;
return true;
}
mallc_SLL():
分析:
- 需要获取 空闲空间的下标 ,这不就是 data[0].cur 的值嘛!
- 获取完元素后要将更改 data[0].cur 的值,因为现在的值,已经被我们申请了
/**
* 返回空闲空间的下标
* @return
*/
private int malloc_SLL(){
int i = data[0].cur; //获取到空闲空间的下标
if (i != 0) {
data[0].cur = data[i].cur; //第一个空闲空间被我们拿走了,就要将后一个空闲空间的下标赋值给,data[0]的cur
}
return i;
}
效果:
测试代码:
@Test
public void test(){
StaticLinkedList list = new StaticLinkedList();
list.addElem("甲");
list.addElem("乙");
list.addElem("丁");
list.addElem("戊");
list.addElem("己");
System.out.println(list);
}
效果:
注意:要看到效果需要重写 toString() 方法
public String toString() {
String str = "StaticLinkedList = [ ";
int i = data[length - 1].cur;
while(data[i].data != null){
str += data[i].data + " ";
i = data[i].cur;
}
str += "]";
return str;
}
按顺序添加元素已经完成了!接下来就写插入元素吧
插入元素:
分析:
- 判断插入的位置是再最后一个有值元素结点的后面,还是其他位置
- 将数据赋值给申请到的空闲空间
- 把前一元素的cur赋值给新结点的cur
- 把新结点的下标,赋值给前一元素的cur
代码:
/**
* 静态链表的插入操作
* @param item
* @param index
* @return
*/
public boolean insert(T item, int index){
//插入到最后面
if (size == index -1){
//调用add()
return this.add(item);
}
rangeCheck(index);
int ccur = MAX_SIZE -1; //获取第一个有值结点的下标
int fIndex = malloc_SLL(); //获得空闲分量的下标
if (fIndex != 0){
data[fIndex].data = item; //将数据赋值给新得到的分量
for (int i = 1; i <= index - 1; i++) { //遍历,找到第i个元素之前的位置
ccur = data[ccur].cur;
}
data[fIndex].cur = data[ccur].cur; //把第i个结点之前的cur赋值给新结点的cur
data[ccur].cur = fIndex; //把新结点的下标赋值给第i个结点之前结点的cur
size ++;
return true;
}
return false;
}
测试:
测试的数据就是之前插入的数据
@Test
public void insertTest(){
list.insert("丙",5); //插入到末尾
System.out.println(list);
list.insert("中间", 3); //插入到中间
System.out.println(list);
list.insert("头", 1); //插入到开头
System.out.println(list);
}
效果:
rangeCheck():方法
/**
* 检测index是否合法
* @param index
*/
private void rangeCheck(int index) {
//1. 索引过小 2.索引过大
if (index < 1 || index > size){
throw new RuntimeException("[StaticLinkList] 索引越界,index:"+index+",size:"+size);
}
}
删除元素:
分析:
- 循环,获取到前一元素的下标
- 判断是否删除的是最后一个元素
- 是:将lastNodeIndex改为前一元素的下标
- 保存前一元素的游标
- 保存要删除元素的游标值
- 将前一元素的游标替换为后一元素的游标
- 将被删除的元素,添加到备用链表
代码:
/**
* 静态链表的删除操作
* @param index 待删除的元素的位置
* @return
*/
public T delete(int index){
//检测index是否合法
rangeCheck(index);
int ccur = MAX_SIZE - 1; //获取到第一个有值元素的下标
for (int i = 1; i <= index - 1; i++) { //获取到指定位置前一元素
ccur = data[ccur].cur;
}
//判断是否是删除最后一个结点
if (index == size){
lastNodeIndex = ccur; //将lastNodeIndex更改为前一个结点的下标
}
int j = data[ccur].cur; //将被删除结点游标的值赋值给保存起来
data[ccur].cur = data[j].cur; //将要被删除的结点的后续结点的下标,赋值给前一结点的游标(完成删除操作)
freeSLL(j); //将空闲空间回收到备用链表
size --; //有效元素-1
//返回被删除结点的数据
return data[j].data;
}
测试:
@Test
public void delete(){
System.out.println("删除第一个:"+list.delete(1));
System.out.println(list);
System.out.println("删除中间:"+list.delete(2));
System.out.println(list);
System.out.println("删除最后一个:"+list.delete(list.getSize()));
System.out.println(list);
}
效果:
free_SLL(int index):将空闲空间回收到备用链表
/**
* 回收结点,到备用链表
* @param j
*/
private void freeSLL(int j) {
data[j].cur = data[0].cur; //将被删除的结点,添加到备用链表中去
data[0].cur = j; //让data[0]的游标,存储第一个空闲空间的下标 j下标所指的结点就是第一个空闲空间了
}
好了,静态链表的基本操作就是这些了!!!
总结:
静态链表优缺点:
优点:
- 在插入和删除操作时,只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中的插入和删除操作需要移动大量元素的缺点。
缺点:
- 没有解决连续存储分配带来的表长难以确定的问题
- 失去了顺序存储结构随机存取的特性
总的来说,静态链表其实就是为了给没有指针的高级语言设计的一种实现单链表能力的方法。
如果,本文中有错的话,恳请大家指出,我一定改正!!!