定义:
线性表全名线性存储结构是最基本、最简单、也是最常用的一种数据结构,专门用于存储逻辑关系为"一对一"的数据。
特性:
- 数据类型一致
- 数据的逻辑关系都具有一对一关系
- 数据元素有限(专业术语为:存在唯一一个被称为“第一个”的数据元素,存在唯一一个被称为“最后一个”的数据元素)
- 是有序序列(专业术语为:除第一个元素外,每个元素都有唯一的一个”直接前驱“。除最后一个元素外,每一个元素都有唯一的”直接后继“)
存储方式:
线性表,基于数据在实际物理空间中的存储状态,又可细分为**顺序表(顺序存储结构)**和**链表(链式存储结构)**。
顺序表(顺序存储结构):
定义:
顺序表,一般使用数组实现。也就是在内存中找个初始地址,然后数据元素依次排队,数组大小有两种方式指定——静态分配和是动态扩展。
特点:
- 随机访问:可通过首地址和元素序号在单位时间O(1)内找到指定的元素。
- 存储密度高:存储密度高是因为每个结点存储空间指用来存储数据元素,没有别的额外开销。
物理位置相邻:物理位置和逻辑位置一样,保持相邻,因此插入和删除元素需要移动大量元素,比较耗时。这是物理结构相邻的所有数据结构的通病,虽然访问快,但是如果有频繁的增删移动操作,就会效率很低。
优点:
- 空间利用率高(理由:连续存放)
- 存取速度高效,通过下标直接进行存储和读取。(想想排队哈)
- 顺序存储,随机读取(不算是优点,算是特点)
缺点:
- 插入和删除比较慢。(想想插队,后面的全部需要往后移一个位)
- 顺序表开始时就要定义一个长度,因此是存在空间限制的。当需要存储的元素个数可能多于顺序表元素时,可能出现“溢出”问题。
链式存储结构:
定义:
用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的),包括数据域和指针域,数据域存数据,指针域指示其后继的信息。
特点:
随机存储、顺序读取:物理位置和逻辑位置不同,物理位置随机存储,但都通过指针域连成线,可以按逻辑顺序读取
优点:
- 插入和删除的速度快,保留原有的物理顺序(只改变下指针。想想上述的插队问题就明白了)。
- 没有空间限制,基本只与内存空间大小有关。(满天飞)
- 随机存储,顺序读取(算是特点)
缺点:
- 查找元素比较慢(不能进行索引访问,只能从头结点开始顺序查找)
- 占用额外的空间用来存储指针(不是连续存放的(满天飞了解下),空间碎片多)
造成空间浪费。
实操:
创建线性表接口类
/**
* 线性表接口
* @author gxw
* @date 2021/11/1 晚上21:50
*/
public interface LinearList {
/**
* 查找方法
*/
//查找,是否包含g元素
boolean contains(Object g);
//查找,返回 g 元素所在位置
int indexOf(Object g);
//查找,返回第i位置的元素
Object getByIndex(int i);
/**
* 添加方法
*/
//将元素插入到第i个位置
void add(int i, Object g) throws Exception ;
//将元素插入到最后
void add(Object g) throws Exception ;
//将g元素插入到x元素之前
boolean addBefore(Object x, Object g) throws Exception;
//将g元素插入到x元素之后
boolean addAfter(Object x, Object g) throws Exception;
/**
* 删除方法
*/
//删除第i个元素
void remove(int i) throws Exception ;
//删除第一个为g的元素
void remove(Object g) throws Exception ;
/**
* 修改方法
*/
//将第i个元素替换为g
boolean replace(int i, Object g);
//将x元素全部替换为g
boolean replaceAll(Object x, Object g);
/**
* 返回线性表大小
*/
int getSize();
/**
* 判断线性表是否为空
*/
boolean isEmpty();
/**
* 输出线性表
*/
void display();
}
顺序表:
第一步:创建顺序表实现类
/**
* 线性表之顺序表(顺序存储结构)
* @author gxw
* @date 2021/11/1 晚上21:51
*/
public class SequenceList implements LinearList {
//声明大小为maxSize的顺序表
private Object[] myList;//顺序表的存储空间
private int size;//顺序表当前的长度
private int maxSize;//顺序表最大的长度
//构造方法来初始化顺序表
public SequenceList(int maxSizeParam){
//设置顺序表的初始长度为0
size = 0;
//设置顺序表的最大长度
maxSize = maxSizeParam;
//实例化顺序表
myList = new Object[maxSize];
}
//是否包含g元素
@Override
public boolean contains(Object g) {//时间复杂度O(n)
for (Object x : myList) {
if(x.equals(g))
return true;
}
return false;
}
//获取g元素在第几个位置
@Override
public int indexOf(Object g) {//时间复杂度O(n)
for(int i = 0;i < size;i++){
if(myList[i].equals(g))
return i;
}
return -1;
}
//获取第i位置的元素
@Override
public Object getByIndex(int i) {//时间复杂度O(1)
//判断是否符合现有长度
if(i < 0 || i > size)
return null;
return myList[i];
}
@Override
public void add(int i, Object g) throws Exception {//时间复杂度O(n)
//判断顺序表是否已经满员
if(size == maxSize)
throw new Exception("顺序表已满员,不可插入!");//手动抛出异常
//判断i是否在正确范围内
if(i < 0 || i > size)
throw new Exception("插入位置不合理,不可插入!");//手动抛出异常
//准备执行插入,在插入之前,将要插入位置的元素往后移
for(int j = size;j > i;j--){
myList[j] = myList[j - 1];
}
//执行插入
myList[i] = g;
//现有顺序表长度增加1
size++;
}
@Override
public void add(Object g) throws Exception {//时间复杂度O(1)
//判断顺序表是否已经满员
if(size == maxSize)
throw new Exception("顺序表已满员,不可插入!");//手动抛出异常
//执行插入,插入到最后
myList[size] = g;
//现有长度加一
size++;
}
@Override
public boolean addBefore(Object x, Object g) throws Exception {//时间复杂度O(n)
//判断顺序表是否已经满员
if(size == maxSize)
return false;
//先获取x元素所在位置
int index = indexOf(x);
if(index < 0)
return false;
//将x元素以及后继元素往后移
add(index, g);
return true;
}
@Override
public boolean addAfter(Object x, Object g) throws Exception {//时间复杂度O(n)
//判断顺序表是否已经满员
if(size == maxSize)
return false;
//先获取x元素所在位置
int index = indexOf(x);
if (index < 0)
return false;
//将x元素的后继元素往后移
add(index + 1, g);
return true;
}
@Override
public void remove(int i) throws Exception {//时间复杂度O(n)
//判断顺序表是否已经为空
if(size == 0)
throw new Exception("顺序表已空,无法删除!");//手动抛出异常
//判断i元素是否在0-size范围内
if(i < 0 || i > size)
throw new Exception("角标超出范围,无法删除!");//手动抛出异常
//将i元素之后后继元素往前移
for(int j = i;j < size;j++){
myList[j] = myList[j + 1];
}
//顺序表长度减一
size--;
}
@Override
public void remove(Object g) throws Exception {//时间复杂度O(n)
//判断顺序表是否已经为空
if(size == 0)
throw new Exception("顺序表已空,无法删除!");//手动抛出异常
//查找元素的位置
int index = indexOf(g);
if(index >= 0){
//删除元素,并将后继元素往前移
remove(index);
}else{
throw new Exception("顺序表无此元素,无法删除!");//手动抛出异常
}
}
@Override
public boolean replace(int i, Object g) {//时间复杂度O(1)
//判断顺序表是否已经为空
if(size == 0)
return false;
//判断i是否在0-size范围内
if(i < 0 || i > size)
return false;
//执行替换,将i元素的值替换为g
myList[i] = g;
return true;
}
@Override
public boolean replaceAll(Object x, Object g) {//时间复杂度O(n)
//判断顺序表是否已经为空
if(size == 0)
return false;
//暂存角标字符串
String indexStr = "";
//取出元素为x的所有角标
for (int i = 0;i < size;i++){
if(myList[i].equals(x)){
indexStr += i + ",";
}
}
indexStr = indexStr.substring(0, indexStr.length() - 1);
String[] indexArr = indexStr.split(",");
//将取出的所有角标全替换为g元素
for (int i = 0;i < indexStr.split(",").length;i++){
int index = Integer.parseInt(indexArr[i]);
//设置值
myList[index] = g;
}
return true;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public void display() {
for (int i = 0;i < size;i++){
System.out.print(myList[i] + " ");
}
System.out.println("展示顺序表结束:一共长度为:" + size);
}
}
第二步:测试
public static void main(String[] args) throws Exception {
/**
* 顺序表
*/
SequenceList sequenceList = new SequenceList(10);
//添加
sequenceList.add("sequence ");
sequenceList.add("hello ");
sequenceList.add("world!");
sequenceList.add("hello ");
sequenceList.display();
//指定角标添加
sequenceList.add(1, "哈哈 ");
sequenceList.display();
//在指定元素之前添加
sequenceList.addBefore("哈哈 ", "呼呼 ");
sequenceList.display();
//在指定元素之后添加
sequenceList.addAfter("哈哈 ", "嘿嘿 ");
sequenceList.display();
//替换
sequenceList.replace(1, "略略 ");
sequenceList.display();
//将指定元素值全都替换成设置元素值
sequenceList.replaceAll("hello ", "HELLO ");
sequenceList.display();
//删除
sequenceList.remove(1);
sequenceList.display();
//指定元素删除
sequenceList.remove("HELLO ");
sequenceList.display();
}
结果:
链表:
第一步:因为单链表依赖于Node,所以创建Node类
/**
* 单链表采用了结点,所以需要以下定义
* 主要两个属性,一个是数据域(存放数据内容),一个是指针域(存放下一个的结点的地址)
*/
public class Node {
//数据域
private Object data;
//指针域
private Node next;
public Node(){
this.data = new Object();
this.next = null;
}
public Node(Object data, Node next){
this.data = data;
this.next = next;
}
public Node(Object data){
this.data = data;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Node getNext() {
return next;
}
public void setNext(Node next) {
this.next = next;
}
}
第二步:创建单链表实现类
/**
* 线性表之链表(链式存储结构)
* @author gxw
* @date 2021/11/1 晚上21:53
*/
public class LinkedList implements LinearList {
private Node headNode = new Node();//定义一个头空结点
private int size;//定义一共有几个结点
@Override
public boolean contains(Object g) {
Node node = headNode;//从头结点开始
for(int i = 0;i < size;i++){
if(node.getData().equals(g)){
return true;
}
//访问下一个结点
node = node.getNext();
}
return false;
}
@Override
public int indexOf(Object g) {
Node node = headNode;//从头结点开始
//因为头结点不存数据,所以直接获取子结点数据
node = node.getNext();
for(int i = 0;i < size;i++){
if(node.getData().equals(g)){
return i;
}
//访问下一个结点
node = node.getNext();
}
return -1;
}
@Override
public Object getByIndex(int i) {
Node node = headNode;//从头结点开始
for(int j = 0;j < i;j++){
//访问下一个结点,直到访问的第i个位置
node = node.getNext();
}
return node.getData();
}
@Override
public void add(int i, Object g) throws Exception {
//如果i不在0-size范围
if(i < 0 || i > size)
throw new Exception("角标超出正常范围 i:" + i);
//获取到第i个前面的结点,只需要让前面的结点指针域指到新要添加的结点上,
// 自己的指针域指到第i个后面的结点上,就可以实现添加了
Node node = headNode;
for(int j = 0;j < i;j++){
node = node.getNext();//持续访问指针域,直到访问到第i个结点
}
//创建新要添加的结点
Node newNode = new Node();
//设置新结点的数据域
newNode.setData(g);
//设置新结点的指针域,将前面获取到的结点的指针域的值,赋值给新要添加的结点
newNode.setNext(node.getNext());
//然后让前面获得的结点的指针域指向新添加的指针,即可成功添加
node.setNext(newNode);
//结点数加一
size++;
}
@Override
public void add(Object g) throws Exception {
//在最后添加结点
add(size, g);
}
@Override
public boolean addBefore(Object x, Object g) throws Exception {
//获取结点所在位置
int index = indexOf(x);
//如果没有匹配的结点
if(index < 0)
return false;
//在x结点前添加结点,相当于就是查找到结点位置当前位置
add(index, g);
return true;
}
@Override
public boolean addAfter(Object x, Object g) throws Exception {
//获取结点所在位置
int index = indexOf(x);
//如果没有匹配的结点
if(index < 0)
return false;
//在x结点后添加结点
add(index + 1, g);
return true;
}
@Override
public void remove(int i) throws Exception {
//如果i不在0-size范围
if(i < 0 || i > size)
throw new Exception("角标超出正常范围 i:" + i);
//删除结点,原理就是让此结点的前一个结点的指针域,指到此结点的下一个结点
Node node = headNode;
for (int j = 0;j < i;j++){
node = node.getNext();
}
//将前一个结点的指针域,指到此结点的下一个结点
node.setNext(node.getNext().getNext());
//结点数减1
size--;
}
@Override
public void remove(Object g) throws Exception {
//查找元素所在位置
int index = indexOf(g);
//执行删除
remove(index);
}
@Override
public boolean replace(int i, Object g) {
//如果i不在0-size范围
if(i < 0 || i > size)
return false;
//找到第i个结点
Node node = headNode;
for (int j = 0;j < i + 1;j++){ //因为头结点是空结点,所以需要略过,就i + 1
node = node.getNext();
}
//然后将第i个结点的数据域更换为g
node.setData(g);
return true;
}
@Override
public boolean replaceAll(Object x, Object g) {
return false;
}
@Override
public int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public void display() {
Node node = headNode;
for (int i = 0;i < size;i++){
node = node.getNext();
System.out.print(node.getData() + " ");
}
System.out.println("展示完成,结点长度为:" + size);
}
}
第三步:测试
public static void main(String[] args) throws Exception {
/**
* 单链表
*/
LinkedList linkedList = new LinkedList();
//添加
linkedList.add("1");
linkedList.add("2");
linkedList.add("3");
linkedList.display();
//在2之前添加
linkedList.addBefore("2", "4");
linkedList.display();
//在2之后添加
linkedList.addAfter("2", "5");
linkedList.display();
//查找位置
System.out.println(linkedList.indexOf("1"));
System.out.println(linkedList.indexOf("2"));
System.out.println(linkedList.indexOf("3"));
//替换
linkedList.replace(1, "22 ");
linkedList.display();
//删除
linkedList.remove("3");
linkedList.display();
}
结果:
其它补充:
前驱:
数据结构中,一组数据中的每个个体被称为“数据元素”(简称“元素”)。
直接前驱:
某一元素的左侧相邻元素称为“直接前驱”
前驱元素:
位于此元素左侧的所有元素都统称为“前驱元素”
后继
直接后继:
某一元素的右侧相邻元素称为“直接后继”
后继元素:
位于此元素右侧的所有元素都统称为“后继元素”
————————————END—————————————
数据结构之线性表篇,在此结束,学习中的总结,希望可以帮助认真学习的你,如果总结有问题,也希望贵人忘请指正,谢谢!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。