静态链表-java
本来java中没有游标,不过C语音的思想很厉害,我决定实现一下,过程比较繁琐,此篇幅也较长
主要在各种校验上,剩下处理只能说略微繁琐一点点
为什么使用这种方式
在数据结构中
数组查找O(1),插入和删除O(n),因为涉及到平移.
链表查找O(n),插入和删除O(1),查找慢一点
怎么想办法结合一下两种方式,兼容一下呢?
例如在数组中插入和删除不要再平移
实现起来就是静态链表. 或者叫游标实现法
什么是静态链表
直接上原文吧
首先我们让数组的元素都是由两个数据域组成,data和cur.也就是说,数组的每个下标都对应一个data和一个cur. 数据域data,用来存放数据元素,也就是通常我们要处理的数据; 而游标cur相当于单链表中的next指针,存放该元素的后继在数组中的下标.
大概怎么用
- 为了我们方便插入数据,我们通常会把数组建立的大一些,以便有一些控件可以便于插入时不至于溢出.
- 另外我们对数组第一个和最后一个元素作为特殊元素处理,不存数据.
- 我们通常把未被使用的元素称为备用链表. 而数组第一个元素,即下标为
0
的元素的cur
的就存放备用链表的第一个节点的下标;- 而数组的最后一个元素
cur
则存放第一个有数值的元素的下标,相当于单链表中的头结点作用,当整个链表为空时,则为0
优点
在插入和删除操作时只需要修改游标,不需要移动元素,从而改进了在顺序存储结构中的插入和删除操作需要移动大量元素的缺点
缺点
- 没有解决连续存储分配带来的表长难以确定行的问题
- 失去了顺序存储结构的随机读取的特性
代码
package com.company;
/**
* java 静态游标
* @Author: comfort
* @Date: 2020/8/1
*/
public class ArrCurTest {
ValueAndCur[] valueAndCurList = new ValueAndCur[]{};
public static void main(String[] args) {
ArrCurTest arrCurTest = new ArrCurTest();
// String data = "31,21,17,16,15,26,5,14,23,12,19,30,27,95,22,44,66,99,39";
String data = "31,21,17,16,15";
arrCurTest.init(data.split(","));
arrCurTest.out("初始化");
// arrCurTest.insert(2, 5);
// arrCurTest.out("插入第2个后");
// arrCurTest.insert(1, 1);
// arrCurTest.out("插入第1个后");
// arrCurTest.insert(100, 100);
// arrCurTest.out("插入第100个后");
// arrCurTest.insert(9, 100);
// arrCurTest.insert(9, 100);
// arrCurTest.insert(9, 100);
// arrCurTest.out("插入第9个(数组初始化最后一个)后");
arrCurTest.delete(5);
arrCurTest.out("删除第2个后");
}
public void delete(int index) {
if (index < 0 || index > valueAndCurList.length-1) {
System.out.println("删除"+index+"失败:下标越界");
return;
}
if (index == 0 || index == valueAndCurList.length - 1) {
System.out.println("删除"+index+"失败:数组首尾数据不可删除");
return;
}
//删除第一个,如果恰好第一个是首节点,则失败
if (index == valueAndCurList[valueAndCurList.length - 1].cur) {
return;
}
//计数下标
int k=1;
//找到头
Integer cur = valueAndCurList[valueAndCurList.length - 1].cur;
ValueAndCur valueAndCur=null;
while (cur != 0&&k<index){
valueAndCur = valueAndCurList[cur];
cur = valueAndCur.cur;
k++;
}
//数组都遍历完了,还没找到
if (cur==0&&k<index) {
System.out.println("删除"+index+"失败,未找到");
return;
}
//==0 为了删除防止没找到
if (valueAndCur == null||valueAndCur.cur==0) {
System.out.println("删除"+index+"失败,数据长度越界");
return;
}
//这时 valueAndCur即是删除的上一个节点,找到要删除的cur
Integer deleteCur = valueAndCur.cur;
//删除的节点
ValueAndCur deleteNode = valueAndCurList[deleteCur];
//ABC三节点删除B ==> A.next= b.next===>> A.next=C
valueAndCur.cur=deleteNode.cur;
//处理null节点给[0],处理游标
deleteNode.cur = valueAndCurList[0].cur;
deleteNode.value=null;
valueAndCurList[0].cur = deleteCur;
}
public void insert(int index, int value) {
//-1 是最后一个要存放首个节点
if (index < 1 || index > valueAndCurList.length-1) {
System.out.println("插入"+index+"失败:下标越界");
return;
}
//找空闲下标,并将空闲的下个空闲游标给0
Integer nullIndex = valueAndCurList[0].cur;
//最后一个节点不能用,符合条件即满了
if (nullIndex == valueAndCurList.length-1) {
System.out.println("插入"+index+"失败,数据已满");
return;
}
//计数下标
int k=1;
//找到头
Integer cur = valueAndCurList[valueAndCurList.length - 1].cur;
ValueAndCur valueAndCur=null;
while (cur != 0&&k<index){
valueAndCur = valueAndCurList[cur];
cur = valueAndCur.cur;
k++;
}
if (valueAndCur == null) {
System.out.println("插入"+index+"失败,数据长度越界");
return;
}
valueAndCurList[0].cur = valueAndCurList[nullIndex].cur;
ValueAndCur newDate = new ValueAndCur();
newDate.cur=valueAndCur.cur;
newDate.value=value;
valueAndCurList[nullIndex] = newDate;
valueAndCur.cur = nullIndex;
}
public void init(String[] value ){
//数组,这创建两倍长度,当然可以随意,至少要比数据长2
// 0下标不存,末尾也要用一个null
//全部初始化,因为插入时需要知道下一个cur的值
// 小于length-1节点 因为最后一个要保留开始头
valueAndCurList = new ValueAndCur[value.length *2];
for (int i = 0; i < valueAndCurList.length-2; i++) {
ValueAndCur valueAndCur = new ValueAndCur();
//在数据范围内正常处理
if(i<value.length) {
int v1 = Integer.parseInt(value[i]);
valueAndCur.value = v1;
//结束 cur指向0
if (i + 1 == value.length) {
valueAndCur.cur = 0;
} else {
//String 数组中[2]的值
// 放在链表中的游标,要指向2+1+1=3(当前下标,因为是从1开始的)+1(下一个下标)
valueAndCur.cur = i + 2;
}
}else{
//+2这样空闲 节点就是 List[i+1]=i+2 list[6]=7
valueAndCur.cur=i+2;
}
valueAndCurList[i+1] = valueAndCur;
}
//处理数据最后一个元素cur情况
ValueAndCur vLast = new ValueAndCur();
vLast.cur=value.length+1;
//第0个 无数据,cur指向最后一个null
valueAndCurList[0]=vLast;
//整个数组最后一个元素放第一个数据下标
//因为该方法是初始化 所以下标1就是头结点
//后期维护即可
ValueAndCur vHead = new ValueAndCur();
vHead.cur = 1;
valueAndCurList[valueAndCurList.length - 1] = vHead;
}
/**
* 输出不可以用for了,因为是循环有了游标
*/
public void out(String string) {
System.out.println(string);
System.out.println("===========链表while输出==============");
//校验数据长度要大于1,至少为2
if (valueAndCurList[0].cur <= 1) {
System.out.println("无数据");
}
//找到头
Integer cur = valueAndCurList[valueAndCurList.length - 1].cur;
ValueAndCur valueAndCur;
//先输出,再判断,故 do---while
do {
valueAndCur = valueAndCurList[cur];
System.out.print("<v:" + valueAndCur.value + " c:" + valueAndCur.cur + "> ");
//更换游标
cur = valueAndCur.cur;
} while (cur != 0);
System.out.println("\n===========数组for输出==============");
for (ValueAndCur andCur : valueAndCurList) {
System.out.print("<v:" + andCur.value + " c:" + andCur.cur + "> ");
}
System.out.println();
}
}
class ValueAndCur{
public Integer value;
public Integer cur;
}
输出写了两个,一个是数组全部的结构和数据(for),一个是展现出来的数据(while)
初始化输出
31,21,17,16,15
初始化
===========链表while输出==============
<v:31 c:2> <v:21 c:3> <v:17 c:4> <v:16 c:5> <v:15 c:0>
===========数组for输出==============
<v:null c:6> <v:31 c:2> <v:21 c:3> <v:17 c:4> <v:16 c:5> <v:15 c:0> <v:null c:7> <v:null c:8> <v:null c:9> <v:null c:1>
正常插入并输出
正常插入,看v的值:由初始化的31,21,17,16,15变成31,5,21,17,16,15
调整0下标的空间元素
5下标的结束元素不用动
arrCurTest.insert(2, 5);
arrCurTest.out("插入第2个 值为5:");
插入第2个 值为5:
===========链表while输出==============
<v:31 c:6> <v:5 c:2> <v:21 c:3> <v:17 c:4> <v:16 c:5> <v:15 c:0>
===========数组for输出==============
<v:null c:7> <v:31 c:6> <v:21 c:3> <v:17 c:4> <v:16 c:5> <v:15 c:0> <v:5 c:2> <v:null c:8> <v:null c:9> <v:null c:1>
特殊:插入第一个和第100个并输出
当然没什么变化,报错了,其实插入失败,输出不过是又打印出来了而已
插入1失败,数据长度越界
插入第1个后
===========链表while输出==============
<v:31 c:2> <v:21 c:3> <v:17 c:4> <v:16 c:5> <v:15 c:0>
===========数组for输出==============
<v:null c:6> <v:31 c:2> <v:21 c:3> <v:17 c:4> <v:16 c:5> <v:15 c:0> <v:null c:7> <v:null c:8> <v:null c:9> <v:null c:1>
校验失败了,输出内容没变化
插入100失败:下标越界
插入第100个后
===========链表while输出==============
<v:31 c:2> <v:21 c:3> <v:17 c:4> <v:16 c:5> <v:15 c:0>
===========数组for输出==============
<v:null c:6> <v:31 c:2> <v:21 c:3> <v:17 c:4> <v:16 c:5> <v:15 c:0> <v:null c:7> <v:null c:8> <v:null c:9> <v:null c:1>
特殊:连续插入倒数第二个下标的位置
前三次因为还有空元素,所以依次插入了进去,因为每次插入了最后一个位置,需要调整
- 0下标中
cur
指向的空闲坐标,依次由7改成了8 9 - 链表的最后一个数据的
cur
要求总是0,所以由下标6的cur改成[6]<15,0>
依次改成了下标为7的[7]<91,0>
…等等.作为结束标识
第四次校验没通过
//执行代码 这里为了对比,把初始化代码也输出了一遍
arrCurTest.out("初始化");
arrCurTest.insert(9, 91);
arrCurTest.out("插入第9个 91后");
arrCurTest.insert(9, 92);
arrCurTest.out("插入第9个 92后");
arrCurTest.insert(9, 93);
arrCurTest.out("插入第9个 93后");
arrCurTest.insert(9, 94);
arrCurTest.out("插入第9个 94后");
初始化
===========链表while输出==============
<v:31 c:2> <v:21 c:3> <v:17 c:4> <v:16 c:5> <v:15 c:0>
===========数组for输出==============
<v:null c:6> <v:31 c:2> <v:21 c:3> <v:17 c:4> <v:16 c:5> <v:15 c:0> <v:null c:7> <v:null c:8> <v:null c:9> <v:null c:1>
插入第9个 91后
===========链表while输出==============
<v:31 c:2> <v:21 c:3> <v:17 c:4> <v:16 c:5> <v:15 c:6> <v:91 c:0>
===========数组for输出==============
<v:null c:7> <v:31 c:2> <v:21 c:3> <v:17 c:4> <v:16 c:5> <v:15 c:6> <v:91 c:0> <v:null c:8> <v:null c:9> <v:null c:1>
插入第9个 92后
===========链表while输出==============
<v:31 c:2> <v:21 c:3> <v:17 c:4> <v:16 c:5> <v:15 c:6> <v:91 c:7> <v:92 c:0>
===========数组for输出==============
<v:null c:8> <v:31 c:2> <v:21 c:3> <v:17 c:4> <v:16 c:5> <v:15 c:6> <v:91 c:7> <v:92 c:0> <v:null c:9> <v:null c:1>
插入第9个 93后
===========链表while输出==============
<v:31 c:2> <v:21 c:3> <v:17 c:4> <v:16 c:5> <v:15 c:6> <v:91 c:7> <v:92 c:8> <v:93 c:0>
===========数组for输出==============
<v:null c:9> <v:31 c:2> <v:21 c:3> <v:17 c:4> <v:16 c:5> <v:15 c:6> <v:91 c:7> <v:92 c:8> <v:93 c:0> <v:null c:1>
最后失败了 隔一行,其实输出内容没变化
插入9失败,数据已满
插入第9个 94后
===========链表while输出==============
<v:31 c:2> <v:21 c:3> <v:17 c:4> <v:16 c:5> <v:15 c:6> <v:91 c:7> <v:92 c:8> <v:93 c:0>
===========数组for输出==============
<v:null c:9> <v:31 c:2> <v:21 c:3> <v:17 c:4> <v:16 c:5> <v:15 c:6> <v:91 c:7> <v:92 c:8> <v:93 c:0> <v:null c:1>
平平淡淡删除
这里注意就是更新[0]<null,6> 改成[0]<null,2> 空闲的游标要改一下
arrCurTest.delete(2);
arrCurTest.out("删除第2个后");
arrCurTest.delete(5);
arrCurTest.out("删除第5个后");
初始化
===========链表while输出==============
<v:31 c:2> <v:21 c:3> <v:17 c:4> <v:16 c:5> <v:15 c:0>
===========数组for输出==============
<v:null c:6> <v:31 c:2> <v:21 c:3> <v:17 c:4> <v:16 c:5> <v:15 c:0> <v:null c:7> <v:null c:8> <v:null c:9> <v:null c:1>
删除第2个后
===========链表while输出==============
<v:31 c:3> <v:17 c:4> <v:16 c:5> <v:15 c:0>
===========数组for输出==============
<v:null c:2> <v:31 c:3> <v:null c:6> <v:17 c:4> <v:16 c:5> <v:15 c:0> <v:null c:7> <v:null c:8> <v:null c:9> <v:null c:1>
校验失败了,所以输出内容没变化
删除5失败,数据长度越界
删除第5个后
===========链表while输出==============
<v:31 c:3> <v:17 c:4> <v:16 c:5> <v:15 c:0>
===========数组for输出==============
<v:null c:2> <v:31 c:3> <v:null c:6> <v:17 c:4> <v:16 c:5> <v:15 c:0> <v:null c:7> <v:null c:8> <v:null c:9> <v:null c:1>
Process finished with exit code 0
收获
- 更加透彻理解链表头结点 / 第0个元素中cur / 最后一个元素 / 空闲元素之间的关系
- 插入时要灵活使用空闲元素和插队指针的几个操作,实现插入功能,最后还要处理一下第0个元素的
cur
指向尾节点 - 删除时同理,删除以后要操作数组第0个和最后一个保证一致性.
难点
- 数组中第0个和最后一个元素分别存放了特殊元素,在处理初始化,查找和插入,以及删除时,都要防止操作第一个和最后一个逻辑溢出现象,因为这两个变化了,整个都不能玩了.
- 删除时既需要处理链表结构
A.next=A.next.next
操作以外,还要操作第0个和最后一个元素 - 插入不能乱"插",校验环节比较繁琐,目前实现不能插入在节点头(这里指抢风头抢首节点,我插入第一个,旧的第一个成为第二个),如果繁琐点,应该可以实现的
反思:
代码中"查找"(没有单独写get方法),插入和删除还是利用了"游标"来查找数据,如果是一个有序的数组,即可解决查找时O(n)的问题,实现O(1)的查找,然后删除或者插入操作.