1.顺序表
将具有“一对一”关系的数据“线性”地存储到物理空间中,这种存储结构就称为线性存储结构(简称线性表)
线性表,全名为线性存储结构。使用线性表存储数据的方式可以这样理解,即“把所有数据用一根线穿起来,再存储到物理空间中”
把这“一串儿”数据放置到物理空间,我们可以选择以下两种方式,如图2 所示
使用线性表存储的数据,如同向[数组]中存储数据那样,要求数据类型必须一致,也就是说,线性表存储的数据,要么全部都是整形,要么全部都是字符串。一半是整形,另一半是字符串的一组数据无法使用线性表存储。
顺序存储结构和链式存储结构
图2中我们可以看出,线性表存储数据可细分为一下2中
1.如图2中3a)所示,将数据存放在整块物理空间中,这种存储结构称为顺序存储结构(简称顺序表)
2.如图2中3b)所示,数据分散的存储在物理空间中,通过一根线保存着他们之间的逻辑关系,这种存储结构称为链式存储结构(简称链表)
也就是说,线性表存储结构可细分为顺序存储结构(顺序表)和链式存储结构(链表)
前驱和后继
- 一组数据中的每个个体被称为“数据元素”(简称“元素”)。
- 某一元素左侧相邻元素称为“直接前驱”,位于此元素左侧的所有元素都统称为“前驱元素”。
- 某一元素右侧相邻元素称为“直接后继”,位于此元素右侧的所有元素都统称为“后继元素”。
顺序表
顺序表存储数据时,会提前申请一整块足够大小的物理空间,然后将数据依次存储起来,存储时做到数据元素之间不留一丝缝隙。
将“具有 '一对一' 逻辑关系的数据按照次序连续存储到一整块物理空间上”的存储结构就是顺序存储结构。
顺序表初始化
使用顺序表存储数据之前,除了要申请足够大小的物理空间之外,还需要实时记录以下2项数据。
- 1.顺序表申请的存储容量
- 2.顺序表的长度
int size = 10; //顺序表申请的存储容量
int[] seqList = new int[size]; //申请内存空间
int length = 0 ;//空表的长度初始化为0
顺序表插入元素
向已有顺序表中插入数据元素,根据插入位置不同,分为以下三种情况
- 1.插入到顺序表的表头
- 2.插入到中间位置
- 3.插入到表中最后一个元素
虽然数据元素插入顺序表中位置不同,但都是通过遍历,找到数据元素要插入的位置,然后做如下两步工作:
- 将要插入位置的元素以及后续元素整体后移一个位置
- 将元素放到腾出来的位置上
例如,在 {1,2,3,4,5} 的第 3 个位置上插入元素 6,实现过程如下:
顺序表的删除
从顺序表中删除指定元素,实现起来非常简单,只需找到目标元素,并将其后续所有元素整体前移 1 个位置即可。
后续元素整体前移一个位置,会直接将目标元素删除,可间接实现删除元素的目的。
顺序表的查找
顺序表查找元素根据下标快速查询,时间复杂度为O(1)
顺序表的更改
顺序表更改元素的实现过程是:
- 1.找到目标元素
- 2.直接修改该元素的值
2.链表
链表,全名为链式存储结构或单链表,用于存储逻辑关系为"1对1"的数据.与顺序表不同,链表不限制物理存储状态,换句话说,使用链表存储的数据元素,其物理位置是随机的。
我们看到,图1无法体现出各数据之间的逻辑关系,对此,链表的解决方案是,每个数据元素在存储时都配备一个指针,用于指向自己的直接后继元素 如图2。
像图2这样,数据元素随机存储,并通过指针表示数据之间逻辑关系的存储结构就是链式存储结构。
链表的节点
从图 2 可以看到,链表中每个数据的存储都由以下两部分组成:
1.数据元素本身,其所在的区域称为数据域;
2.指向直接后继元素的指针,所在的区域称为指针域;
图 3所示的结构在链表中称为节点。也就是说,链表实际存储的是一个一个的节点,真正的数据元素包含在这些节点中,如图 4 所示
//java代码实现链表
char elem;
Node next;
头节点,头指针和首元节点
图 4 所示的链表结构并不完整。一个完整的链表需要由以下几部分构成:
- 1.头指针 :一个普通的指针,它的特点是永远指向链表第一个节点的位置。很明显,头指针用于指明链表的位置,便于后期找到链表并使用表中的数据;
- 2.节点 : 表中的节点又细分为头节点、首元节点和其他节点:
- 1.头节点: 其实就是一个不存任何数据的空节点,通常作为链表的第一个节点。对于链表来说,头节点不是必须的,它的作用只是为了方便解决某些实际问题;
- 2.首元节点 :由于头节点(也就是空节点)的缘故,链表中称第一个存有数据的节点为首元节点。首元节点只是对链表中第一个存有数据节点的一个称谓,没有实际意义;
- 3.其他节点 :链表中其他的节点;
因此,一个存储 {1,2,3} 的完整链表结构如图 5 所示:
注意:链表中有头节点时,头指针指向头节点;反之,若链表中没有头节点,则头指针指向首元节点。
链表的初始化
创建一个链表需要做如下工作:
1.声明一个头指针(如果有必要,可以声明一个头节点);
2.创建多个存储数据的节点,在创建的过程中,要随时与其前驱节点建立逻辑关系;
//TODO 代码实现
链表插入元素
同顺序表一样,向链表中增添元素,根据添加位置不同,可分为以下 3 种情况:
1.插入到链表的头部(头节点之后),作为首元节点;
2.插入到链表中间的某个位置;
3.插入到链表的最末端,作为链表中最后一个数据元素;
虽然新元素的插入位置不固定,但是链表插入元素的思想是固定的,只需做以下两步操作,即可将新元素插入到指定的位置:
1.将新结点的 next 指针指向插入位置后的结点;
2.将插入位置前结点的 next 指针指向插入结点;
例如,我们在链表 {1,2,3,4}
的基础上分别实现在头部、中间部位、尾部插入新元素 5,其实现过程如图 所示:
/**
* 向链表中插入数据 (尾部插入)
*
* @param data
*/
public void addNode(int data) {
Node newNode = new Node(d);// 实例化一个节点
if (head == null) {
head = newNode;
return;
}
Node tmp = head;
while (tmp.next != null) {
tmp = tmp.next;
}
tmp.next = newNode;
}
:链表插入元素的操作必须是先步骤 1,再步骤 2;反之,若先执行步骤 2,会导致插入位置后续的部分链表丢失,无法再实现步骤
链表删除元素
从链表中删除指定数据元素时,实则就是将存有该数据元素的节点从链表中摘除,从链表中删除数据元素需要进行以下 2 步操作:
1.将结点从链表中摘下来;
2.手动释放掉结点,回收被结点占用的存储空间;(java中自动回收,不需要此步骤)
/**
*
* @param index:删除第index个节点
* @return
*/
public boolean deleteNode(int index) {
if (index < 1 || index > length()) {
return false;
}
if (index == 1) {
head = head.next;
return true;
}
int i = 1;
Node preNode = head;
Node curNode = preNode.next;
while (curNode != null) {
if (i == index) {
preNode.next = curNode.next;
return true;
}
preNode = curNode;
curNode = curNode.next;
i++;
}
return false;
}
/**
* 在不知道头指针的情况下删除指定节点
*
* @param n
* @return
*/
public boolean deleteNode11(Node n) {
if (n == null || n.next == null)
return false;
int tmp = n.data;
n.data = n.next.data;
n.next.data = tmp;
n.next = n.next.next;
System.out.println("删除成功!");
return true;
}
链表查询元素
从表头依次遍历表中节点,用被查找元素与各节点数据域中存储的数据元素进行比对,直至比对成功或遍历至链表最末端的 NULL
(比对失败的标志)。
/*
* 查找值为num的元素位置,没有返回-1*/
public class SeqSearch {
public static void main(String[] args) {
Node head=ListNode.getSingleList();
ListNode.printList(head);
int num=9;
int id=new SeqSearch().searchNumId(head,num);
System.out.println("要查找的元素位置为:"+id);
}
public int searchNumId(Node head,int num){
int id=1;
while(head!=null&&head.data!=num){
head=head.next;
id++;
}
if(head==null) id=-1;
return id;
}
}
链表修改元素
更新链表中的元素,只需通过遍历找到存储此元素的节点,对节点中的数据域做更改操作即可。
其实就是查找到对应元素,更新值。实现方法与查到相同,不以代码展示。