当前位置: 首页 > news >正文

大话数据结构-线性表

1 定义

  线性表是零个或多个数据元素的有限序列。

2 抽象数据类型

ADT 线性表(List)Data:线性表的数据对象集合为{al,a2,a3,....an},每个元素的类型均为DataType。其中,除第一个元素a1外,每一个元素有且只有一个直接前驱元素,除了最后一个元素an外,每一个元素有且只有一个后继元素。数据元素之间的关系是一对一的关系。Operation:init(int maxSize):初始化操作,建立一个空的线性表;isEmpty():若线性表为空,返回true,否则返回false;clear():清空线性表;getElement(int position):获取线性表中第position个元素值;locateElement(DataType e):在线性表中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号,否则返回0表示失败;insert(DataType e, int position):在线性表的第position个位置插入元素e;delete(int position):删除纯属表中第position个位置的元素;length():返回线性表的元素个数;
endADT

3 顺序存储结构

3.1 概述

image.png

  线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。

  线性表的顺序存储结构的抽象数据类型在标准类型的基础上,新增了length和maxSize两个属性:

ADT 线性表(List)Data:线性表的数据对象集合为{al,a2,a3,....an},每个元素的类型均为DataType。其中,除第一个元素a1外,每一个元素有且只有一个直接前驱元素,除了最后一个元素an外,每一个元素有且只有一个后继元素。数据元素之间的关系是一对一的关系。length:线性表中实际元素个数,应小于等于maxSize。maxSize:线性表可以存储的元素个数。Operation:init(int maxSize):初始化操作,建立一个空的线性表;isEmpty():若线性表为空,返回true,否则返回false;clear():清空线性表;getElement(int position):获取线性表中第position个元素值;locateElement(DataType e):在线性表中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号,否则返回0表示失败;insert(DataType e, int position):在线性表的第position个位置插入元素e;delete(int position):删除纯属表中第position个位置的元素;length():返回线性表的元素个数;
endADT

3.2 初始化

  定义一个空的线性表,初始化maxSize和data数组:

/*** 初始化线性表** @param maxSize 线性表最大长度* @author Korbin* @date 2023-01-04 18:29:17**/
public void init(int maxSize) {this.maxSize = maxSize;data = new Object[maxSize];
}

3.3 判断是否为空

  判断length是否为0即可,为避免其他异常,使用小于等于,如下:

/*** 判断线性表是否为空** @return 线性表为空返回true,非空返回false* @author Korbin* @date 2023-01-11 15:54:53**/
public boolean isEmpty() {return length <= 0;
}

3.4 清空线性表

  把length和data重新初始化即可(C语言则需要回收内存):

/*** 清空线性表** @author Korbin* @date 2023-01-11 15:58:15**/
public void clear() {this.length = 0;this.data = new Object[this.maxSize];
}

3.5 获取指定位置元素

  线性表长度为0或要获取的位置小于1或大于线性表长度时抛出异常,实际位置是索引加1:

/*** 获取线性表中的第position个元素** @param position 位置* @return 获取到的元素* @throws RuntimeException 线性表长度为0或要获取的位置小于1或大于线性表长度时抛出异常* @author Korbin* @date 2023-01-04 18:32:10**/
@SuppressWarnings("unchecked")
public T getElement(int position) {if (position < 1 || position > length) {throw new RuntimeException("error position");} else {return (T) (data[position - 1]);}
}

  时间复杂度为O(1)。

3.6 在线性表中查找与给定值e相等的元素

  使用equals判断是否相等,因此DataType需要实现equals方法:

/*** 查找element在线性表中的位置** @param element 待查找的元素* @return 元素在线性表中的位置* @author Korbin* @date 2023-01-11 15:59:23**/
@SuppressWarnings("unchecked")
public int locateElement(T element) {int position = -1;for (int i = 0; i < length; i++) {T e = (T) (data[i]);if (e.equals(element)) {position = i;break;}}return position + 1;
}

  时间复杂度为O(n)。

3.7 插入操作

image.png
  线性表已满,或位置异常时抛出异常,插入时,其后的所有元素需要往后移,实际元素个数要加1:

/*** 在线性表中的指定位置position插入一个元素** @param position 指定位置* @param element  要插入的元素* @throws RuntimeException 线性表已满,或位置异常时抛出异常* @author Korbin* @date 2023-01-04 19:22:34**/
public void insert(int position, T element) {if (length == maxSize) {throw new RuntimeException("already full");}if (position < 1 || position > length + 1) {throw new RuntimeException("error position");}if (position <= length) {for (int k = length - 1; k >= position - 1; k--) {data[k + 1] = data[k];}}data[position - 1] = element;length++;
}

  时间复杂度为O(n)。

3.8 删除操作

image.png
  线性表为空或位置不对时抛出异常,实际元素个数要减1:

/*** 删除线性表中第position位的元素** @param position 位置* @throws RuntimeException 线性表为空或位置不对时抛出异常* @author Korbin* @date 2023-01-05 09:46:58**/
public void delete(int position) {if (length == 0) {throw new RuntimeException("empty list");}if (position < 1 || position > length) {throw new RuntimeException("error position");}if (position < length) {for (int k = position; k < length; k++) {data[k - 1] = data[k];}}// 如果要删除的位置就是线性表的长度所在的位置的话,那直接把长度减1就行了length--;
}

  时间复杂度为O(n)。

3.9 返回实际元素个数

/*** 获取线性表真实长度** @return 线性表长度* @author Korbin* @date 2023-01-05 10:15:41**/
public int length() {return length;
}

3.10 完整代码

/*** 顺序存储线性表** @author Korbin* @date 2023-01-04 18:19:38**/
public class SequentialList<T> {/*** 线性表数据**/private Object[] data;/*** 线性表当前长度**/private int length;/*** 线性表最大长度*/private int maxSize = 20;/*** 清空线性表** @author Korbin* @date 2023-01-11 15:58:15**/public void clear() {this.length = 0;this.data = new Object[this.maxSize];}/*** 删除线性表中第position位的元素** @param position 位置* @throws RuntimeException 线性表为空或位置不对时抛出异常* @author Korbin* @date 2023-01-05 09:46:58**/public void delete(int position) {if (length == 0) {throw new RuntimeException("empty list");}if (position < 1 || position > length) {throw new RuntimeException("error position");}if (position < length) {for (int k = position; k < length; k++) {data[k - 1] = data[k];}}// 如果要删除的位置就是线性表的长度所在的位置的话,那直接把长度减1就行了length--;}/*** 获取线性表中的第position个元素** @param position 位置* @return 获取到的元素* @throws RuntimeException 线性表长度为0或要获取的位置小于1或大于线性表长度时抛出异常* @author Korbin* @date 2023-01-04 18:32:10**/@SuppressWarnings("unchecked")public T getElement(int position) {if (position < 1 || position > length) {throw new RuntimeException("error position");} else {return (T) (data[position - 1]);}}/*** 初始化线性表** @param maxSize 线性表最大长度* @author Korbin* @date 2023-01-04 18:29:17**/public void init(int maxSize) {this.maxSize = maxSize;data = new Object[maxSize];}/*** 在线性表中的指定位置position插入一个元素** @param position 指定位置* @param element  要插入的元素* @throws RuntimeException 线性表已满,或位置异常时抛出异常* @author Korbin* @date 2023-01-04 19:22:34**/public void insert(int position, T element) {if (length == maxSize) {throw new RuntimeException("already full");}if (position < 1 || position > length + 1) {throw new RuntimeException("error position");}if (position <= length) {for (int k = length - 1; k >= position - 1; k--) {data[k + 1] = data[k];}}data[position - 1] = element;length++;}/*** 判断线性表是否为空** @return 线性表为空返回true,非空返回false* @author Korbin* @date 2023-01-11 15:54:53**/public boolean isEmpty() {return length <= 0;}/*** 获取线性表真实长度** @return 线性表长度* @author Korbin* @date 2023-01-05 10:15:41**/public int length() {return length;}/*** 查找element在线性表中的位置** @param element 待查找的元素* @return 元素在线性表中的位置* @author Korbin* @date 2023-01-11 15:59:23**/@SuppressWarnings("unchecked")public int locateElement(T element) {int position = -1;for (int i = 0; i < length; i++) {T e = (T) (data[i]);if (e.equals(element)) {position = i;break;}}return position + 1;}/*** 打印线性表** @throws RuntimeException 线性表为空时抛出异常* @author Korbin* @date 2023-01-05 09:55:55**/@Overridepublic String toString() {if (length == 0) {throw new RuntimeException("empty list");}StringBuilder result = new StringBuilder("[");for (int i = 0; i < length; i++) {result.append(i + 1).append(":").append(data[i]).append(",");}result.append("]");return result.toString();}}

3.11 优缺点

image.png

4 链式存储结构

4.1 概述

  用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的,只是通过数据元素中的后继元素地址来指向后继元素的存储地址,即称为链式存储。

  在链式存储结构中,每个数据元素除了存储其本身的信息外,还需存储一个指示其直接后继的信息,即直接后继的存储位置,存储数据元素信息的域称为数据域,存储直接后继位置的域称为指针域,指针域中存储的信息称做指针或链。数据域和指针域组成数据元素的存储映像,称为结点。

  因为每个结点中只包含一个指针域,因此这种链表也被称为单链表。

image.png

  单链表的第一个结点称为头结点,头结点不存储数据,只存储链表中第一个实际元素的存储位置,称为头指针。

  单链表的最后一个结点没有后继元素,因此其指针域的值是Null。

image.png

  单链表的抽象存储结构与线性表的顺序存储结构的抽象存储结构略有区别:

ADT 线性表(List)Data:线性表的数据对象集合为{al,a2,a3,....an},每个元素的类型均为DataType。其中,除第一个元素a1外,每一个元素有且只有一个直接前驱元素,除了最后一个元素an外,每一个元素有且只有一个后继元素。数据元素之间的关系是一对一的关系。单链表中只存储头结点对象即可。length:线性表中实际元素个数。Operation:init:初始化操作,建立一个空的线性表;isEmpty:若线性表为空,返回true,否则返回false;clear:清空线性表;getElement(int position):获取线性表中第position个元素值;locateElement(DataType e):在线性表中查找与给定值e相等的元素,如果查找成功,返回该元素在表中序号,否则返回0表示失败;insert(DataType e, int position):在线性表的第position个位置插入元素e;delete(int position):删除纯属表中第position个位置的元素;length():返回线性表的元素个数;
endADT

4.2 结点结构

DataType需要进行特殊定义,必须包含后继元素指针,如下:

import lombok.Data;import java.util.UUID;/*** 单链表** @author Korbin* @date 2023-01-05 10:58:05**/
@Data
public class LinkListNode<T> {/*** 数据**/private T data;/*** 设置一个ID,已进行equals判断**/private String id = UUID.randomUUID().toString();/*** 后继结点**/private LinkListNode<T> next;/*** 构造子** @author Korbin* @date 2023-01-05 12:38:08**/public LinkListNode() {}@Override@SuppressWarnings("unchecked")public boolean equals(Object obj) {if (obj instanceof LinkListNode) {try {LinkListNode<T> node = (LinkListNode<T>) obj;return data.equals(node.getData()) && id.equals(node.getId());} catch (ClassCastException e) {return false;}} else {return false;}}@Overridepublic String toString() {return "{id=" + id + ", data=" + data.toString() + "}";}}

  构造子、equals和toString方法可以按需,添加一个id的主要目的是用于equals,如有其他判断相等的方式,也可以不加id。

4.3 初始化和清空

  初始化和清除在Java中因为垃圾回收机制,因此实现方式相同。

  单链表定义的Data,不需要是一个数组,只需要放置headNode,即头结点即可,因此初始化和清空时只需要将length置为0,将headNode的值和后继结点置为空即可。

/*** 清空* <p>* 直接调用初始化方法即可** @author Korbin* @date 2023-01-11 15:58:15**/
public void clear() {init();
}
/*** 初始化** @author Korbin* @date 2023-01-04 18:29:17**/
public void init() {headNode = new LinkListNode<>();headNode.setData(null);headNode.setNext(null);length = 0;
}

4.4 获取指定位置的元素

  从头结点开始,迭代后继结点:

/*** 获取单链表中第position位的元素的值** @param position 要获取的结点所在的位置* @return 指定位置结点的值* @throws RuntimeException 未获取到结点时抛出* @author Korbin* @date 2023-01-05 11:06:01**/
public LinkListNode<T> getElement(int position) {if (position == 0) {return headNode;}LinkListNode<T> result = headNode.getNext();int j = 1;while (null != result && j < position) {result = result.getNext();j++;}if (null == result || j > position) {throw new RuntimeException("node not exists");}return result;
}

  时间复杂度为O(n)。

4.5 插入操作

image.png
  先获取待插入位置的前驱结点,然后让前驱结点指向新结点,新结点指向原结点即可:

/*** 在单链表的指定位置插入一个结点** @param position 要插入的位置* @param value    要插入的结点的值* @throws RuntimeException 要插入的位置不存在结点时抛出* @author Korbin* @date 2023-01-05 11:12:05**/
public void insert(int position, T value) {LinkListNode<T> positionNode = getElement(position - 1);LinkListNode<T> newNode = new LinkListNode<>();newNode.setData(value);newNode.setNext(positionNode.getNext());positionNode.setNext(newNode);length++;}

  时间复杂度为O(n)。

4.6 删除操作

image.png
  先获取待删除结点的前驱结点,然后让前驱结点指向其后继的后继即可:

/*** 删除单链表中指定位置的元素** @param position 要删除结点所在位置* @throws RuntimeException 要删除的位置不存在结点时抛出异常* @author Korbin* @date 2023-01-05 11:15:21**/
public void delete(int position) {LinkListNode<T> lastNode = getElement(position - 1);lastNode.setNext(lastNode.getNext().getNext());length--;
}

  删除操作的时间复杂度为O(n)。

4.7 查找与给定值e相等的元素

  从头结点开始迭代后继结点,直到没有后续结点或者找到的结点等于给定的元素:

/*** 查找element在单链表中的位置** @param element 待查找的元素* @return 元素在单链表中的位置* @author Korbin* @date 2023-01-11 15:59:23**/
public int locateElement(LinkListNode<T> element) {LinkListNode<T> result = headNode.getNext();int j = 1;while (null != result) {if (result.equals(element)) {break;}result = result.getNext();j++;}if (null == result) {throw new RuntimeException("node not exists");}return j;
}

4.8 完整代码

/*** 单链表** @author Korbin* @date 2023-01-05 16:19:13**/
public class LinkList<T> {/*** 头结点**/private LinkListNode<T> headNode;/*** 结点数量**/private int length;/*** 清空* <p>* 直接调用初始化方法即可** @author Korbin* @date 2023-01-11 15:58:15**/public void clear() {init();}/*** 删除单链表中指定位置的元素** @param position 要删除结点所在位置* @throws RuntimeException 要删除的位置不存在结点时抛出异常* @author Korbin* @date 2023-01-05 11:15:21**/public void delete(int position) {LinkListNode<T> lastNode = getElement(position - 1);lastNode.setNext(lastNode.getNext().getNext());length--;}/*** 获取单链表中第position位的元素的值** @param position 要获取的结点所在的位置* @return 指定位置结点的值* @throws RuntimeException 未获取到结点时抛出* @author Korbin* @date 2023-01-05 11:06:01**/public LinkListNode<T> getElement(int position) {if (position == 0) {return headNode;}LinkListNode<T> result = headNode.getNext();int j = 1;while (null != result && j < position) {result = result.getNext();j++;}if (null == result || j > position) {throw new RuntimeException("node not exists");}return result;}/*** 初始化** @author Korbin* @date 2023-01-04 18:29:17**/public void init() {headNode = new LinkListNode<>();headNode.setData(null);headNode.setNext(null);length = 0;}/*** 在单链表的指定位置插入一个结点** @param position 要插入的位置* @param value    要插入的结点的值* @throws RuntimeException 要插入的位置不存在结点时抛出* @author Korbin* @date 2023-01-05 11:12:05**/public void insert(int position, T value) {LinkListNode<T> positionNode = getElement(position - 1);LinkListNode<T> newNode = new LinkListNode<>();newNode.setData(value);newNode.setNext(positionNode.getNext());positionNode.setNext(newNode);length++;}/*** 判断是否为空** @return 为空返回true,非空返回false* @author Korbin* @date 2023-01-11 15:54:53**/public boolean isEmpty() {return null == headNode.getNext() && length == 0;}/*** 获取真实长度** @return 长度* @author Korbin* @date 2023-01-05 10:15:41**/public int length() {return length;}/*** 查找element在单链表中的位置** @param element 待查找的元素* @return 元素在单链表中的位置* @author Korbin* @date 2023-01-11 15:59:23**/public int locateElement(LinkListNode<T> element) {LinkListNode<T> result = headNode.getNext();int j = 1;while (null != result) {if (result.equals(element)) {break;}result = result.getNext();j++;}if (null == result) {throw new RuntimeException("node not exists");}return j;}@Overridepublic String toString() {LinkListNode<T> next = headNode.getNext();StringBuilder builder = new StringBuilder("[");while (null != next) {builder.append(next).append(",");next = next.getNext();}builder.append("length=").append(length);builder.append("]");return builder.toString();}}

4.9 单链表结构与顺序存储结构优缺点

image.png

  因此,通常情况下:

  (1) 需要频繁查找,很少进行插入和删除时,建议使用顺序存储结构;

  (2) 需要频繁插入和删除时,建议使用单链表结构,实际上,如果需要批量插入或删除时,单链表的优势才能体现出来,即,如果需要插入/删除指定位置的10个元素时,单链表只需要迭代一次找到待插入/删除位置的元素,然后就可以直接对10个元素进行操作了,而顺序存储结构则需要将待插入/删除元素之后的元素移动;

  (3) 元素个数变化较大或根本不知道多大时,建议使用单链表,否则使用顺序存储结构;

5 静态链表

5.1 概述

  在一些没有指针也没有引用的语言中,用数组来代替指针,来描述的单链表,称为静态链表。

  在静态链表中,每一个结点由两个数据域组成,data用于存储数据,cursor用于存储后继结点的指针(数组的索引),使用cursor代替单链表中的后继结点。

  由于静态链表中存储的数据是一个数组,因此静态链表也会定义length和maxSize,分别用于表示结点的实际数量和最多可存放的结点数量。

  数组的所有元素并不都存储着数据(结点),未存储数据的元素被称为备用链表,而数组的第一个元素,即下标为0的元素存储的结点不包含数据,只包含cursor,cursor存放着备用链表的第一个结点的下标,即下一个可存储数据的数组下标。

  数组的最后一个元素存储的结点也不存储数据,只包含cursor,cursor存放着第一个结点的下标,即头结点的下标,因此数组的最后一个元素起着头指针的作用。

  因此,静态链表初始化状态会如下所示:

image.png

  下标当然是从0、1、2一直到999,而cursor比较特殊:

  (1) 假设数组为array,那么array[0]的cursor值指向下一个可存储结点的元素的下标,因此应为1;

  (2) array[1]还没有存储结点,那我们让它的cursor指向下一个结点,即array[2],因此array[1]的cursor为2,依此类推;

  (3) 最后一个节点,array[999],它应该指向第一个结点的下标,此时还没有任何一个结点,因此array[999]的cursor应为0;

  此时,假设静态链表中依次存储了甲、乙、丁、戊、己、庚,则其结构应如下所示:

image.png

  (1) array[0],不存储数据,存储的是下一个可存储数据的下标,如图,下一个可存储的下标是7,因此array[0]的cursor为7;

  (2) array[1],存储第1个结点甲,指向第二个结点乙,乙是array[2],因此array[1]的cursor为array[2]的下标,即2,依此类推;

  (3) array[6],此时静态链表中的最后一个结点,存储的数据是庚,因其没有后继结点,因此令其cursor为0;

  (4) array[999],作为数组的最后一个元素,其存储的是静态链表中第一个结点的下标,我们知道,静态链表中的第一个结点是甲,存储在array[1],下标是1,因此array[999]的cursor是1;

5.2 结点定义

  与单链表相同,添加一个属性id用于判断结点是否相等:

import lombok.Data;import java.util.UUID;/*** 静态链表节点** @author Korbin* @date 2023-01-05 16:25:49**/
@Data
public class StaticLinkNode<T> {/*** 游标,指向下一个节点,为0时表示无指向**/private int cursor;/*** 节点值**/private T data;/*** 设置一个ID,已进行equals判断**/private String id = UUID.randomUUID().toString();@Override@SuppressWarnings("unchecked")public boolean equals(Object obj) {if (obj instanceof StaticLinkNode) {try {StaticLinkNode<T> node = (StaticLinkNode<T>) obj;return data.equals(node.getData()) && id.equals(node.getId());} catch (ClassCastException e) {return false;}} else {return false;}}}

5.3 初始化和清除

  上文有提到初始化过程,即初始化maxSize、data,并设置数组中每个元素的cursor默认值,而清除静态链表和初始化的逻辑是一致的:

/*** 判断是否为空** @return 为空返回true,非空返回false* @author Korbin* @date 2023-01-11 15:54:53**/
public boolean isEmpty() {return length == 0;
}
/*** 初始化静态链表** @param maxSize 链表的最大长度* @throws RuntimeException 最大长度为0时抛出* @author Korbin* @date 2023-01-05 16:30:25**/
@SuppressWarnings("unchecked")
public void init(int maxSize) {if (maxSize < 1) {throw new RuntimeException("error maxSize");}this.maxSize = maxSize;data = new StaticLinkNode[maxSize];for (int i = 0; i < maxSize - 1; i++) {StaticLinkNode<T> node = new StaticLinkNode<>();node.setCursor(i + 1);data[i] = node;}StaticLinkNode<T> node = new StaticLinkNode<>();node.setCursor(0);data[maxSize - 1] = node;length = 0;
}

5.4 插入

  插入过程:

  (1) 先从array[0]中获取到下一个可存放数据的数组元素下标,设为nextIndex;

  (2) 然后找到待插入位置的上一个结点:先从array[length-1]中获取第1个节点下标,然后批第2个节点下标,然后找第3个节点下标,直到找到待插入位置position - 1个节点的下标,设找到的值为lastIndex;

  (3) 我们知道,array[0]存储中下一个可存放数据的数组元素下标,也知道array[nextIndex]存储着下下个可存放数据的数组元素下标y,因此如果array[nextIndex]被使用了,那么array[0]应存放的cursor值则为y,因此代码中需要对array[0]的cursor值进行修改;

  (4) 然后设置array[nextIndex]的数据域为待插入的结点,cursor则为待插入位置上一个结点的cursor,再把待插入位置上一个结点的cursor设置为nextIndex,即插入的结点下标,那么插入操作就完成了;

  (5) 最后注意,结点数量需要加1;

/*** 下一个可插入的索引位** @return 下一个可插入的索引位* @author Korbin* @date 2023-01-05 20:20:14**/
public int getNextIndex() {return data[0].getCursor();
}
/*** 在指定位置插入** @param position 位置* @param value    要插入的元素* @throws RuntimeException 插入到数组的0位,或者跳跃进行插入,或者数组长度满后插入时抛出异常* @author Korbin* @date 2023-01-05 19:32:06**/
public void insert(int position, T value) {// 数组的第0个元素存储着数组下一个可存放节点的位置,获取到这个位置int nextIndex = getNextIndex();if (position < 1 || position > length + 1) {// 插入位置不能小于1,也不能大于链表的实际长度+1// 可以为length+1的原因,是因为允许在最后面插入throw new RuntimeException("error position");}if (length == maxSize - 2) {// 如果实际长度等于最大长度时,表示链表已满,不能再插入throw new RuntimeException("fulled");}// 默认数组的最后一个元素存储的是第一个节点的下标int lastIndex = maxSize - 1;// 获取到待插入的节点的上一个节点的游标// 首先通过最后一个节点的游标,可以找到第一个节点// 通过第一个节点的游标,可以找到第二个节点// 依此类推for (int i = 1; i <= position - 1; i++) {lastIndex = data[lastIndex].getCursor();}// 设置第0个元素的游标值为待插入节点的游标值,初始化时有设置data[0].setCursor(data[nextIndex].getCursor());// 设置这个节点的节点值为待插入值data[nextIndex].setData(value);// 设置这个节点的游标为上一个节点的游标值data[nextIndex].setCursor(data[lastIndex].getCursor());// 设置上一个节点的游标值为这个节点的位置data[lastIndex].setCursor(nextIndex);++length;
}

  时间复杂度为O(position-1),即O(n)。

5.5 删除

  (1) 先找到待删除结点的上一个结点,寻找方法与插入时一致,此时找到了lastNode;

  (2) 然后找到待删除结点,即lastNode.getCursor()对应的结点,此时找到了toDeleteNode;

  (3) 令lastNode指向toDeleteNode的下一个结点,删除操作就完成了;

  (4) toDeleteNode被删除了,那么它所在的数组元素,就可以存放新的结点了,这时,令toDeleteNode的cursor指向array[0]的cursor,令array[0]的cursor指向toDeleteNode的下标,即:让toDeleteNode所在的位置成为下一个可存储数据的下标,即备用链表的第一位,同时,把备用链表的其他可存储结点的下标依次往后挪一位;

  (5) 最后,注意结点数量应减1;

/*** 删除指定位置的节点** @param position 待删除节点位置* @throws RuntimeException 删除第0个节点或者要删除的位置超出节点实际数量时抛出异常* @author Korbin* @date 2023-01-05 19:34:18**/
public void delete(int position) {// 不得删除第0个元素// 不得删除超出实际节点数量的元素if (position == 0 || position > length()) {throw new RuntimeException("error position");}// 找到要删除的节点的上一个节点int lastIndex = data[maxSize - 1].getCursor();int lastIndex = maxSize - 1;// 获取到待插入的节点的上一个节点的游标// 首先通过最后一个节点的游标,可以找到第一个节点// 通过第一个节点的游标,可以找到第二个节点// 依此类推for (int i = 1; i <= position - 1; i++) {lastIndex = data[lastIndex].getCursor();}StaticLinkNode<T> lastNode = data[lastIndex];// 待删除的节点int toDeleteIndex = lastNode.getCursor();StaticLinkNode<T> toDeleteNode = data[toDeleteIndex];// 上一个节点的游标指向下一个节点lastNode.setCursor(toDeleteNode.getCursor());data[lastIndex] = lastNode;// 当前节点的值设置为空toDeleteNode.setData(null);// 当前节点的游标设置为0toDeleteNode.setCursor(0);data[toDeleteIndex] = toDeleteNode;// 把已删除的节点的索引位置作为下一次插入时的位置data[toDeleteIndex].setCursor(data[0].getCursor());data[0].setCursor(toDeleteIndex);length--;}

  同样,时间复杂度为O(n)。

5.6 获取指定位置的元素

  从第一个节点一直往后找即可:

/*** 获取指定位置的元素** @param position 指定的位置* @return 指定位置的元素* @throws RuntimeException 当试图获取第0个或超出实际节点数量的元素时抛出异常* @author Korbin* @date 2023-01-05 19:59:26**/
public StaticLinkNode<T> getElement(int position) {// 不得获取第0个元素// 不得获取超出实际节点数量的元素if (position == 0 || position > length()) {throw new RuntimeException("error position");}// 依次往下迭代即可int positionIndex = data[maxSize - 1].getCursor();for (int i = 1; i < position; i++) {positionIndex = data[positionIndex].getCursor();}return data[positionIndex];
}

5.7 获取指定结点在静态链表中的位置

  同样,从第一个结点一直往后找:

/*** 查找element在单链表中的位置** @param element 待查找的元素* @return 元素在单链表中的位置* @author Korbin* @date 2023-01-11 15:59:23**/
public int locateElement(StaticLinkNode<T> element) {// 依次往下迭代即可int index = data[maxSize - 1].getCursor();for (int i = 0; i < length; i++) {StaticLinkNode<T> node = data[index];if (node.equals(element)) {break;}index = data[index].getCursor();}return index;
}

5.8 完整代码

/*** 静态链表** @author Korbin* @date 2023-01-05 16:27:11**/
public class StaticLinkList<T> {/*** 节点数据*/private StaticLinkNode<T>[] data;/*** 实际节点数量**/private int length;/*** 链表的最大长度*/private int maxSize;/*** 清空** @author Korbin* @date 2023-01-11 15:58:15**/public void clear() {init(maxSize);}/*** 删除指定位置的节点** @param position 待删除节点位置* @throws RuntimeException 删除第0个节点或者要删除的位置超出节点实际数量时抛出异常* @author Korbin* @date 2023-01-05 19:34:18**/public void delete(int position) {// 不得删除第0个元素// 不得删除超出实际节点数量的元素if (position == 0 || position > length()) {throw new RuntimeException("error position");}// 找到要删除的节点的上一个节点int lastIndex = data[maxSize - 1].getCursor();int lastIndex = maxSize - 1;// 获取到待插入的节点的上一个节点的游标// 首先通过最后一个节点的游标,可以找到第一个节点// 通过第一个节点的游标,可以找到第二个节点// 依此类推for (int i = 1; i <= position - 1; i++) {lastIndex = data[lastIndex].getCursor();}StaticLinkNode<T> lastNode = data[lastIndex];// 待删除的节点int toDeleteIndex = lastNode.getCursor();StaticLinkNode<T> toDeleteNode = data[toDeleteIndex];// 上一个节点的游标指向下一个节点lastNode.setCursor(toDeleteNode.getCursor());data[lastIndex] = lastNode;// 当前节点的值设置为空toDeleteNode.setData(null);// 当前节点的游标设置为0toDeleteNode.setCursor(0);data[toDeleteIndex] = toDeleteNode;// 把已删除的节点的索引位置作为下一次插入时的位置data[toDeleteIndex].setCursor(data[0].getCursor());data[0].setCursor(toDeleteIndex);length--;}/*** 返回节点信息**/public StaticLinkNode<T>[] getData() {return data;}/*** 获取指定位置的元素** @param position 指定的位置* @return 指定位置的元素* @throws RuntimeException 当试图获取第0个或超出实际节点数量的元素时抛出异常* @author Korbin* @date 2023-01-05 19:59:26**/public StaticLinkNode<T> getElement(int position) {// 不得获取第0个元素// 不得获取超出实际节点数量的元素if (position == 0 || position > length()) {throw new RuntimeException("error position");}// 依次往下迭代即可int positionIndex = data[maxSize - 1].getCursor();for (int i = 1; i < position; i++) {positionIndex = data[positionIndex].getCursor();}return data[positionIndex];}/*** 获取首节点索引** @return 首节点索引* @author Korbin* @date 2023-01-05 20:09:43**/public int getFirstNodeIndex() {return data[maxSize - 1].getCursor();}/*** 下一个可插入的索引位** @return 下一个可插入的索引位* @author Korbin* @date 2023-01-05 20:20:14**/public int getNextIndex() {return data[0].getCursor();}/*** 初始化静态链表** @param maxSize 链表的最大长度* @throws RuntimeException 最大长度为0时抛出* @author Korbin* @date 2023-01-05 16:30:25**/@SuppressWarnings("unchecked")public void init(int maxSize) {if (maxSize < 1) {throw new RuntimeException("error maxSize");}this.maxSize = maxSize;data = new StaticLinkNode[maxSize];for (int i = 0; i < maxSize - 1; i++) {StaticLinkNode<T> node = new StaticLinkNode<>();node.setCursor(i + 1);data[i] = node;}StaticLinkNode<T> node = new StaticLinkNode<>();node.setCursor(0);data[maxSize - 1] = node;length = 0;}/*** 在指定位置插入** @param position 位置* @param value    要插入的元素* @throws RuntimeException 插入到数组的0位,或者跳跃进行插入,或者数组长度满后插入时抛出异常* @author Korbin* @date 2023-01-05 19:32:06**/public void insert(int position, T value) {// 数组的第0个元素存储着数组下一个可存放节点的位置,获取到这个位置int nextIndex = getNextIndex();if (position < 1 || position > length + 1) {// 插入位置不能小于1,也不能大于链表的实际长度+1// 可以为length+1的原因,是因为允许在最后面插入throw new RuntimeException("error position");}if (length == maxSize - 2) {// 如果实际长度等于最大长度时,表示链表已满,不能再插入throw new RuntimeException("fulled");}// 默认数组的最后一个元素存储的是第一个节点的下标int lastIndex = maxSize - 1;// 获取到待插入的节点的上一个节点的游标// 首先通过最后一个节点的游标,可以找到第一个节点// 通过第一个节点的游标,可以找到第二个节点// 依此类推for (int i = 1; i <= position - 1; i++) {lastIndex = data[lastIndex].getCursor();}// 设置第0个元素的游标值为待插入节点的游标值,初始化时有设置data[0].setCursor(data[nextIndex].getCursor());// 设置这个节点的节点值为待插入值data[nextIndex].setData(value);// 设置这个节点的游标为上一个节点的游标值data[nextIndex].setCursor(data[lastIndex].getCursor());// 设置上一个节点的游标值为这个节点的位置data[lastIndex].setCursor(nextIndex);++length;}/*** 判断是否为空** @return 为空返回true,非空返回false* @author Korbin* @date 2023-01-11 15:54:53**/public boolean isEmpty() {return length == 0;}/*** 获取真实长度** @return 长度* @author Korbin* @date 2023-01-05 10:15:41**/public int length() {return length;}/*** 查找element在单链表中的位置** @param element 待查找的元素* @return 元素在单链表中的位置* @author Korbin* @date 2023-01-11 15:59:23**/public int locateElement(StaticLinkNode<T> element) {// 依次往下迭代即可int index = data[maxSize - 1].getCursor();for (int i = 0; i < length; i++) {StaticLinkNode<T> node = data[index];if (node.equals(element)) {break;}index = data[index].getCursor();}return index;}@Overridepublic String toString() {int length = length();int index = data[maxSize - 1].getCursor();StringBuilder builder = new StringBuilder("[");for (int i = 0; i < length; i++) {StaticLinkNode<T> node = data[index];builder.append(i + 1).append(":").append(node.getData()).append(", ");index = node.getCursor();if (index == 0) {break;}}builder.append("]");return builder.toString();}}

5.9 静态链表的优缺点

image.png

  总的来说,静态链表其实是为了给没有指针的高级语言设计的一种实现单链表能力的方法。

6 其他线性表及总结

  单链表还可以继续扩展:

  (1) 将单链表的终端结点的指针端由空指针改为指向头结点,就使单链表形成一个环,这种头尾相接的单链表称为单循环链表,简称循环链表,通过循环链表,可以从任何一个结点出发,访问到链表中的所有结点;

  (2) 在单链表的每个结点中,再设置一个指针指向其前驱结点的指针域的链表,称为双向链表,双向链表方便了从某一个结点,获取其前、后结点;

  总之,比较常见的线性表包括以下:

image.png
  注:本文为程 杰老师《大话数据结构》的读书笔记,其中一些示例和代码是笔者阅读后自行编制的。

相关文章:

大话数据结构-线性表

1 定义 线性表是零个或多个数据元素的有限序列。 2 抽象数据类型 ADT 线性表(List)Data&#xff1a;线性表的数据对象集合为{al,a2,a3,....an}&#xff0c;每个元素的类型均为DataType。其中&#xff0c;除第一个元素a1外&#xff0c;每一个元素有且只有一个直接前驱元素&…...

分布式缓存 Memcached Linux 系统安装

1.Memcached简介 Memcached是一个开源、高性能&#xff0c;将数据分布于内存中并使用key-value存储结构的缓存系统。它通过在内存中缓存数据来减少向数据库的频繁访问连接的次数&#xff0c;可以提高动态、数据库驱动之类网站的运行速度。 Memcached在使用是比较简单的&#…...

【数据结构】链表:看我如何顺藤摸瓜

&#x1f451;专栏内容&#xff1a;数据结构⛪个人主页&#xff1a;子夜的星的主页&#x1f495;座右铭&#xff1a;日拱一卒&#xff0c;功不唐捐 文章目录一、前言二、链表1、定义2、单链表Ⅰ、新建一个节点Ⅱ、内存泄漏Ⅲ、插入一个节点Ⅳ、销毁所有节点Ⅴ、反转一个链表3、…...

linux shell 入门学习笔记18 函数开发

概念 函数就是将你需要执行的shell命令组合起来&#xff0c;组成一个函数体。一个完整的函数包括函数头和函数体&#xff0c;其中函数名就是函数的名字。 优点 将相同的程序&#xff0c;定义&#xff0c;封装为一个函数&#xff0c;能减少程序的代码数量&#xff0c;提高开发…...

如何最巧妙回答HR面试“送命题”:你为什么离开上家公司?

一 HR面试存在“送命题”? 一个资深HR朋友聊到,他最近pass掉一个名校高材生。 其实洽谈过程还比较愉悦,小姑娘名校毕业,落落大方,薪酬要求比较合理,各方面都比较符合,最后就在决定要录用时,HR朋友随口问了句 “你为什么离开上家公司?”,小姑娘也是随口说了句“我不喜…...

注意力机制详解系列(五):分支与时间注意力机制

&#x1f468;‍&#x1f4bb;作者简介&#xff1a; 大数据专业硕士在读&#xff0c;CSDN人工智能领域博客专家&#xff0c;阿里云专家博主&#xff0c;专注大数据与人工智能知识分享&#xff0c;公众号&#xff1a;GoAI的学习小屋&#xff0c;免费分享书籍、简历、导图等资料&…...

创宇盾重保经验分享,看政府、央企如何防护?

三月重保已经迫近&#xff0c;留给我们的准备时间越来越少&#xff0c;综合近两年三月重保经验及数据总结&#xff0c;知道创宇用实际案例的防护效果说话&#xff0c;深入解析为何创宇盾可以在历次重保中保持“零事故”成绩&#xff0c;受到众多部委、政府、央企/国企客户的青睐…...

软件测试面试汇总

在浏览器中输入 URL&#xff0c;回车后发生了什么&#xff1f; 在浏览器中输入URL并按下回车键后&#xff0c;大致流程如下&#xff1a; 1、浏览器解析 URL&#xff0c;提取出协议&#xff08;例如HTTP、HTTPS)、主机名和路径等信息。 2、浏览器查找该URL的缓存记录&#xff0…...

空指针,野指针

空指针在C/C中&#xff0c;空指针&#xff08;null pointer&#xff09;是指向内存地址0的指针变量。NULL在C/C中的定义为&#xff1a;#ifndef NULL#ifdef __cplusplus#define NULL 0#else#define NULL ((void *)0)#endif #endif从上面的代码定义中&#xff0c;我们可以发现在C…...

Mysql Nested-Loop Join算法和MRR

MySQL8之前仅支持一种join 算法—— nested loop&#xff0c;在 MySQL8 中推出了一种新的算法 hash join&#xff0c;比 nested loop 更加高效。&#xff08;后面有时间介绍这种join算法&#xff09; 1、mysql驱动表与被驱动表及join优化 先了解在join连接时哪个表是驱动表&a…...

Spark 广播/累加

Spark 广播/累加广播变量普通变量广播分布式数据集广播克制 Shuffle强制广播配置项Join Hintsbroadcast累加器Spark 提供了两类共享变量&#xff1a;广播变量&#xff08;Broadcast variables&#xff09;/累加器&#xff08;Accumulators&#xff09; 广播变量 创建广播变量…...

飞天云动,站在下一个商业时代的门口

ChatGPT的爆火让AIGC再度成为热词&#xff0c;随之而来的是对其商业化的畅想——不是ChatGPT自身如何盈利&#xff0c;而是它乃至整个AIGC能给现在的商业环境带来多大改变。 这不由得令人想起另一个同样旨在改变世界的概念&#xff0c;元宇宙。不同的是&#xff0c;元宇宙更侧…...

上海分时电价机制调整对储能项目的影响分析

安科瑞 耿敏花 2022年12月16日&#xff0c;上海市发改委发布《关于进一步完善我市分时电价机制有关事项的通知》(沪发改价管〔2022〕50号)。通知明确上海分时电价机制&#xff0c;一般工商业及其他两部制、大工业两部制用电夏季&#xff08;7、8、9月&#xff09;和冬季&#x…...

产品新人如何快速上手工作

三百六十行&#xff0c;行行出产品经理&#xff1a;上至封神的乔布斯&#xff0c;下至卖鸡蛋罐饼的阿姨&#xff0c;他们对如何打造自己的产品都会有一套完整的产品思路&#xff0c;这也是为什么说“人人都是产品经理”。这个看似光鲜的“经理”有时也会被戏称产品汪&#xff0…...

Linux: ARM GIC仅中断CPU 0问题分析

文章目录1. 前言2. 分析背景3. 问题4. 分析4.1 ARM GIC 中断芯片简介4.1.1 中断类型和分布4.1.2 拓扑结构4.2 问题根因4.2.1 设置GIC SPI中断的CPU亲和性4.2.2 GIC初始化&#xff1a;缺省的CPU亲和性4.2.2.1 boot CPU亲和性初始化流程4.2.2.1 其它非 boot CPU亲和性初始化流程5…...

第20篇:Java运算符全面总结(系列二)

目录 4、逻辑运算符 4.1 逻辑运算符 4.2 代码示例 5、赋值运算符 5.1 赋值运算符...

OpenCV4.x图像处理实例-OpenCV两小时快速入门(基于Python)

OpenCV两小时快速入门(基于Python) 文章目录 OpenCV两小时快速入门(基于Python)1、OpenCV环境安装2、图像读取与显示3、图像像素访问、操作与ROI4、图像缩放5、几何变换5.1 平移5.2 旋转6、基本绘图6.1 绘制直线6.2 绘制圆6.3 绘制矩形6.4 绘制文本7、剪裁图像8、图像平滑与…...

【Git】Mac忽略.DS_Store文件

我们在github上经常看到某些仓库里面包含了.DS_Store文件&#xff0c;或者某些sdk的压缩包里面可以看到&#xff0c;这都是由于随着git的提交把这类文件也提交到仓库&#xff0c;压缩也是一样&#xff0c;压缩这个先留着后面处理。 Mac上的.DS_Store文件 .DS_Store 文件&#…...

12.2 基于Django的服务器信息查看应用(CPU信息)

文章目录CPU信息展示图表展示-视图函数设计图表展示-前端界面设计折线图和饼图展示饼图测试折线图celery和Django配合实现定时任务Windows安装redis根据数据库中的数据绘制CPU折线图CPU信息展示 图表展示-视图函数设计 host/views.py def cpu(request):logical_core_num ps…...

【软件测试】接口测试总结

本文主要分为两个部分&#xff1a; 第一部分&#xff1a;主要从问题出发&#xff0c;引入接口测试的相关内容并与前端测试进行简单对比&#xff0c;总结两者之前的区别与联系。但该部分只交代了怎么做和如何做&#xff1f;并没有解释为什么要做&#xff1f; 第二部分&#xff1…...

代码随想录算法训练营第52天 || 300.最长递增子序列 || 674. 最长连续递增序列 || 718. 最长重复子数组

代码随想录算法训练营第52天 || 300.最长递增子序列 || 674. 最长连续递增序列 || 718. 最长重复子数组 300.最长递增子序列 题目介绍 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或…...

gitblit 安装使用

1 安装服务端 简而言之&#xff1a;需要安装 java&#xff0c;gitblit&#xff0c; git 三个软件 Windows 10环境使用Gitblit搭建局域网Git服务器 前言 安装Java并配置环境安装gitblit并配置启动gitblit为windows服务使用gitblit创建repository并管理用户 1.1 安装Java并配…...

使用 TensorFlow、Keras-OCR 和 OpenCV 从技术图纸中获取信息

简单介绍输入是技术绘图图像。对象检测模型获取图像后对其进行分类&#xff0c;找到边界框&#xff0c;分配维度&#xff0c;计算属性。示例图像&#xff08;输入&#xff09;分类后&#xff0c;找到“IPN”部分。之后&#xff0c;它计算属性&#xff0c;例如惯性矩。它适用于不…...

ESP32设备驱动-GUVA-S12SD紫外线检测传感器驱动

GUVA-S12SD紫外线检测传感器驱动 文章目录 GUVA-S12SD紫外线检测传感器驱动1、GUVA-S12SD介绍2、硬件准备3、软件准备4、驱动实现1、GUVA-S12SD介绍 GUVA-S12SD 紫外线传感器芯片适用于检测太阳光中的紫外线辐射。 它可用于任何需要监控紫外线量的应用,并且可以简单地连接到任…...

WIN7下 program file 权限不足?咋整?!!

在WIN7下对Program Files目录的权限问题 [问题点数&#xff1a;40分&#xff0c;结帖人mysunck] 大部分人说要使用manifest&#xff0c;但是其中一个人说&#xff1a; “安装程序要求管理员很正常&#xff0c;你的程序可以在programfiles,但用户数据不能放那里&#xff0c;因…...

119.(leaflet篇)文字碰撞

听老人家说:多看美女会长寿 地图之家总目录(订阅之前建议先查看该博客) 文章末尾处提供保证可运行完整代码包,运行如有问题,可“私信”博主。 效果如下所示: 下面献上完整代码,代码重要位置会做相应解释 <!DOCTYPE html> <html>...

cuda编程以及GPU基本知识

目录CPU与GPU的基本知识CPU特点GPU特点GPU vs. CPU什么样的问题适合GPU&#xff1f;GPU编程CUDA编程并行计算的整体流程CUDA编程术语&#xff1a;硬件CUDA编程术语&#xff1a;内存模型CUDA编程术语&#xff1a;软件线程块&#xff08;Thread Block&#xff09;网格&#xff08…...

Python 机器学习/深度学习/算法专栏 - 导读目录

目录 一.简介 二.机器学习 三.深度学习 四.数据结构与算法 五.日常工具 一.简介 Python 机器学习、深度学习、算法主要是博主从研究生到工作期间接触的一些机器学习、深度学习以及一些算法的实现的记录&#xff0c;从早期的 LR、SVM 到后期的 Deep&#xff0c;从学习到工…...

Springboot怎么实现restfult风格Api接口

前言在最近的一次技术评审会议上&#xff0c;听到有同事发言说&#xff1a;“我们的项目采用restful风格的接口设计&#xff0c;开发效率更高&#xff0c;接口扩展性更好...”&#xff0c;当我听到开头第一句&#xff0c;我脑子里就开始冒问号&#xff1a;项目里的接口用到的是…...

Jetpack Compose 深入探索系列六:Compose runtime 高级用例

Compose runtime vs Compose UI 在深入讨论之前&#xff0c;非常重要的一点是要区分 Compose UI 和 Compose runtime。Compose UI 是 Android 的新 UI 工具包&#xff0c;具有 LayoutNodes 的树形结构&#xff0c;它们稍后在画布上绘制其内容。Compose runtime 提供底层机制和…...

郑州 (网站建设/中国十大seo公司

变量和赋值Name’千寻’Name’小寻’Print(name)运行之后&#xff0c;我们可以发现计算机打印出了我们所输入的第二个name&#xff0c;而没有显示我们第一个name。这里面的Name就是变量&#xff0c;变量就相当于我们独一无二的标签&#xff0c;一次print只能输出显示我们最后定…...

网站建设代理商/网站运营主要做什么

我们肯定遇到过打开别人的项目时一直处于Building‘XXX’Gradle project info的情况。 然后就耐心等待了一会&#xff0c;然后就烦躁地再等待了一会&#xff0c;发现还没动静就果断点击了Cancel&#xff0c;但是发现并不能Cancel掉。最后只能强制结束掉AS。 事件的起因是别人项…...

毕设做网站什么能过/培训机构连锁加盟

今天在本文中&#xff0c;KlipC的风险总监和机器学习专家Philip Nucci将教我们的用户如何使用名为支持向量回归&#xff08;SVR&#xff09;的机器学习算法创建直观的货币预测Python程序。该程序将读取EUR / USD的历史数据和波动性&#xff0c;并根据当天价格预测开盘价。我们选…...

流量型网站 cms/制作网站费用

链接地址:http://www.wrclub.net/news/listnews.aspx?id2630ANDhttp://www.csdn.net/news/newstopic/17/17128.shtml...

南京做网站的额/网站建设策划书

之前在“圳品”信息系统使用了tab选项卡来显示信息&#xff0c;详见&#xff1a; JavaScript编程实现tab选项卡切换的效果 在tab选项卡中使用其它<div>来显示信息就出现了问题&#xff0c;乱套了&#xff0c;比如下面的这段代码&#xff1a; <!DOCTYPE html> &l…...

国内网页设计公司前十名/郑州seo优化服务

【Java基础-java反射】Java反射知识点(有这一篇就够了) 反射是框架设计的灵魂 &#xff08;使用的前提条件&#xff1a;必须先得到代表的字节码的Class&#xff0c;Class类用于表示.class文件&#xff08;字节码&#xff09;&#xff09; 文章目录【Java基础-java反射】Java反射…...