目录
前言
你好,这里是小空,一个初入互联网的萌新,在这里记录下自己的学习笔记,方便自己学习并分享给正在努力学习的。你这是我第一次在CSDN写文章,如果文章哪里有问题或者需要修改的地方可以私信我或者评论留言,感谢支持!
一、数据结构的定义
数据结构是计算机存储、组织数据的方式,而且相互之间存在一种或多种特定关系的数据元素的集合。
数据元素都不是独立存在的,这种数据元素相互之间存在关系称为结构。
1.数据(Data)
它是计算机中可以操作的对象,能够被计算机识别,并输入给计算机处理的符号集合。
2.数据元素(Data Element)
它是组成数据的基本单位,通常作为一个整体来进行处理。
3.数据项(Data Item)
数据元素由若干个数据项组成,数据项是数据不可分割的最小单位。
4.数据对象(Data Object)
数据对象是性质相同的数据元素的集合,是数据的子集。
二、逻辑结构与物理结构
2.1 逻辑结构
逻辑结构指的是数据对象中数据元素之间的相互关系。
逻辑结构有以下四种:
集合结构:同一集合之间的元素存在关系,在集合之外它们之间没有任何关系。
线性结构:元素之间是一对一的关系
树形结构:元素之间是一对多的关系
图形结构:元素之间是多对多的关系
2.2 物理结构
物理结构指的是数据的逻辑结构在计算机中的存储形式。
下面详细介绍两种常用的存储结构
2.2.1 顺序存储结构
顺序存储结构就是用一组地址连续的存储单元依次存储线性表的数据。
优点:节省空间,不需要为元素间的逻辑关系添加额外的存储空间,而且存取数据元素快,
之所以这么快,是因为每个存储单元对应着一个序号,每个序号存储一个数据元素,可以直接
通过序号来快速查找到对应的数据,最典型的代表就是数组。
缺点:插入、删除元素慢,耗费的时间多,当线性表长度变化较大时,难以确定存储空间的容量。
插入元素
public void insert(int index, Object element) throws Exception {
//线性表已满,抛出异常!
if (currentLen == dataList.length) {
throw new Exception("Sequence List is full!");
}
//index越界或者不在范围内,抛出异常
if (index > currentLen || index < 0) {
throw new Exception("index out of Bounds!");
}
//将插入位置后面的所有元素后移一位
for (int i = currentLen; i > index; i--) {
dataList[i] = dataList[i - 1];
}
dataList[index] = element; //插入新元素
currentLen++; //表长+1
}
删除元素
public void delete(int index) throws Exception {
//线性表为空,抛出异常!
if (currentLen == 0) {
throw new Exception("Sequence List has no element");
}
if (index >= currentLen || index < 0) {
throw new Exception("index out of Bounds!");
}
//将删除位置后继元素前移
for (int i = index; i < currentLen - 1; i++) {
dataList[i] = dataList[i + 1];
}
currentLen--; //表长-1
}
2.2.2 链式存储结构
链式存储结构又叫做链接存储结构,在计算机中用一组任意的存储单元来存储线性表的数据元素
(存储单元可以是连续的,也可以是不连续的)。
优点:插入、删除元素快,不需要移动元素,只需要改变相邻元素的地址值即可。
缺点:存储密度小,查找需要重头遍历整个链表。相比于顺序存储结构,链式存储结构查找数据花费的时间更多。
单向链表
在链式结构中每个结点不仅需要存数据元素信息,还需要存储它下一个结点的地址,这种称之为单向链表,整个链表的存取都是从头开始的,最后的一个节点指针指向了NULL。
与数组相同,链表也支持数据的插入、查找、删除等操作,但是数组在对数据操作时,为了保证内存数据的连续性,往往需要做大量的数据位移操作。
而链表在操作数据时,因为链表结构中的结点并不需要连续的存储空间,所以在链表中操作数据不需要移动结点,我们只需要改变相邻结点的后继指针地址即可。
单链表插入
public void add(int index, E element) throws Exception {
//index越界或者不在范围内,抛出异常
if (index < 0 || index > listSize) {
throw new Exception("index out of Bounds!");
}
//新结点
Node newNode = new Node();
newNode.data = element;
//从头开始遍历
Node p = head;
int i = 0;
while (p.next != null && i < index) {
p = p.next;
++i;
}
newNode.next = p.next; //新结点的指针域存入原p结点指向的下一节点地址
p.next = newNode; //插入新节点后,p结点的指针域存入了新结点
listSize++; //插入新结点后表长+1
}
单链表删除
public E remove(int index) throws Exception {
//index越界或者不在范围内,抛出异常
if (index < 0 || index > listSize - 1) {
throw new Exception("index out of Bounds!");
}
//从头开始遍历
Node p = head;
int i = 0;
while (p.next != null && i < index) {
p = p.next;
++i;
}
Node q = p.next; //q即准备要删除的结点
E e = q.data; //把q结点的数据提取出来
p.next = q.next; //把q结点的指针域地址存入p结点的指针域
q.next = null; //删除q结点
q.data = null;
listSize--; //表长-1
return e;
}
循环链表
循环链表是一种特殊的链表,它和单链表的区别在于最后的一个结点指针不为NULL,改为指向了头结点这样就形成了链表循环。
和单链表相比,循环链表的优点是从链尾到链头比较方便。当要处理的数据具有环形结构特点时(约瑟夫环),就可以采用循环链表。
双向链表
在单链表中,有了Next指针,就可以让我们很轻松地查找到后继指向的结点,但是如果我们想要找的是上一个结点,
那我们就需要从头开始依次查找了,这样会浪费很多时间。这是单链表的一个缺点,所以就有了双向链表,
双向链表不同于单向链表的是,它每个数据结点中都有两个指针,分别为直接前驱指针和直接后继指针。
双向链表需要额外的空间来存储指向前驱的指针和指向后继的指针,所以在存储同样多的数据时,双向链表比单向链表要的存储空间更多,但是这也让双向链表更加灵活,它可以通过前驱指针立马找到前驱结点。在特殊场景下,对于结点的删除和插入操作,双向链表比单向链表会更高效。
双向链表插入操作
新结点A的前驱和后继分别指向Data1和Data2,然后结点Data2的前驱指向新结点A,结点Data1的后继指向新结点A。
public void add(int index, E element) throws Exception {
//index越界或者不在范围内,抛出异常
if (index < 0 || index > linkedListSize) {
throw new Exception("index out of Bounds!");
}
Node newNode = new Node(null, element, null); //需要插入的新结点
Node p = head;
int i = 0;
while (p != null && i < index) {
p = p.next;
++i;
}
Node q = p.next; //转存当前结点的后继结点
p.next = newNode; //将新结点存入新结点前一结点的指针域中
newNode.prev = p; //新结点的前驱指针指向了前一结点
newNode.next = q; //新结点的后继指向了转存的结点
linkedListSize++; //表长+1
}
双向链表删除操作
删除结点2,结点1的后继指向结点3,结点3的前驱指向结点1。
public E remove(int index) throws Exception {
//index越界或者不在范围内,抛出异常
if (index < 0 || index > linkedListSize - 1) {
throw new Exception("index out of Bounds!");
}
//从头开始遍历
Node p = head;
int i = 0;
while (p.next != null && i < index) {
p = p.next;
++i;
}
Node delNode = p.next; //需要删除的结点
E e = delNode.data; //将需要删除结点的数据提取出来
//把删除结点的下一结点地址 存入 删除结点上一节点中的指针域里面
delNode.prev.next = delNode.next;
//把删除结点的前一结点地址 存入 删除结点的后继结点的前驱指针中
delNode.prev = p;
//释放被删除的结点
delNode.data = null;
delNode.prev = null;
delNode.next = null;
linkedListSize--; //表长-1
return e;
}
三、参考与源码下载
参考书籍:《大话数据结构》
结语
第一期#数据结构与算法在这里就告一段落了,内容不多都是很简单的一些理论知识,还实现了基础的顺序结构和链式结构,其余的链表实现后期我会在学习中慢慢填上。感谢你看到最后,那么我们下期再见!
欲穷千里目
更上一层楼