学习这件事,如果没有及时正反馈真的很难坚持啊!
我要通过做笔记的习惯让我时刻保持学习的动力,好了,无聊的吐槽结束了。
线性表
- 顺序存储线性表
- 链式存储线性表
- 单链表
- 双向链表
- 循环链表
🌼定义
线性表是指n (n ≥ 0)个相同类型的数据元素a0,a1,…,an-1所构成的有限线性序列,其一般描述为:LinearList= (a0,a1,…,an-1)
-
其中
LinearList
称为线性表的名称; -
每个ai(n-1 ≥ i ≥0)称为线性表的数据元素,可以是整数、浮点数、字符或类,但元素必须是同一数据类型;
-
表中相邻元素之间存在着顺序关系:将ai-1称为ai的直接前驱,ai+1称为ai的直接后继;
-
具体n的值称为线性表中包含有数据元素的个数,也称为线性表的长度;
-
当n的值等于0时,表示该线性表是空表.
抽象数据类型定义如下:
ADT
LinearList
{
数据对象:D={ai | ai∈元素集合,i=0,1,2,…n-1 (n≥0)}
数据关系:R={ <ai-1,ai> |ai-1,ai∈元素集合,i=1,2,…n}
基本操作:{插入,删除,查找等}
}
endADT
注解:抽象数据类型(Abstract Data Type,ADT)是指一个数学模型以及定义在此数学模型上的一组操作。它通常是对数据的某种抽象,定义了数据的取值范围及其结构形式,以及数据操作集合。
🚪 线性表接口
//链表的API:即方法相同,就实现存储结构不同
public interface LinearList<T> {
//清空线性表
void ClearList();
//判断线性表是否为空
boolean ListEmpty();
//获取线性表上第i个位置上的元素
T GetElem(int i);
//查找指定元素所在链表位置序号
int LocateElem(T e);
//在最后一位增加元素
public void addElem(T e);
//在线性表第i个位置插入元素e
void ListInsert(int i,T e);
//删除线性表第i个位置的元素
T ListDelete(int i);
//更换线性表上指定的元素
boolean ListReplace(T target,T replace);
//返回线性表元素个数
int ListLength();
Object[] toArray();
//遍历线性表
void ListTraverse();
}
顺序存储线性表
顺序表存储数据时,会提前申请一整块足够大小的物理空间,然后将数据依次存储起来,存储时做到数据元素之间不留一丝缝隙。
图1 顺序存储结构示意图
两种代码格式
- 类似数组的静态顺序存储线性表
/**
* 顺序表是用一组连续的存储单元顺序存放线性表的数据元素,
* 数据元素在内存的物理存储次序与他们在线性表中的逻辑次序是一致的,
* 即数据元素ai与其前驱ai-1以及后继元素ai+1的位置相邻。
*/
//顺序存储结构
public class SqList<T> {
//用于保存数据的数组
private T[] data;
//顺序线性表的初始大小
private int size;
//顺序线性表里实际元素
private int length;
//初始化存储单元
SqList(int size){
this.size =size;
data = (T[])new Object[size];
this.length = 0;
}
//判断顺序表是否为空
public boolean isEmpty(){
return (length == 0);
}
//清空链表
public void ClearList(){
data = null;
length = 0;
}
//获取线性表上某个位置的元素
public boolean GetElem(int i, T e){
if(i<0 || i>length){
return false;
}else {
e = data[i];
return true;
}
}
//获取元素的位置
public int LocateElem(T e){
for (int i = 0; i < length; i++) {
if (e == data[i]){
return i;
}
}
return -1;
}
/**
* 如果插入位置不合理,抛出异常;
* 如果线性表长度大于数组长度,则抛出异常或动态增加容量
* 从最后一个元素开始向前遍历到第i个位置,分别将它们向后移一位
* 将要插入的元素填入i处
* 表长+1
* @return 插入是否成功
*/
public boolean ListInsert(int i,T e){
if (length == size){
System.out.println("线性表已满,插入不成功");
return false;
}
if (i < 0 || i>length){
throw new IndexOutOfBoundsException("插入元素位置超过线性表范围");
}
//在插入数据之前必须先将数据依次向后挪一个位置
for (int j = length; j >= i; j--) {
data[j+1] = data[j];
}
data[i] = e;
length++;
return true;
}
/**
* 删除线性表L中第i个位置元素,并用e返回其值
* @param i
* //@param e 遗憾的是这里改变的e不能返回去影响实参
* @return
*/
public T LstDelete(int i){
if (i < 0 || i>length){
throw new IndexOutOfBoundsException("删除位置不在线性表索引范围内");
}else{
T temp = data[i];
for (int j = i; j < length; j++) {
data[j] = data[j+1];
}
length--;
return temp;
}
}
//返回线性表L的元素个数
public int ListLength(){
return length;
}
//遍历输出顺序存储链表
public void ListTraverse(){
int n =0;
System.out.println("---顺序输出元素---");
for(T i:data){
System.out.println(n+" "+i);
n++;
}
}
public static void main(String[] args) {
SqList<Integer> list = new SqList<Integer>(5);
try {
for (int i = 0; i < 3; i++) {
list.ListInsert(i,i*i);
}
}catch (IndexOutOfBoundsException e){
System.out.println("正确输入要插入的下标");
}
list.ListTraverse();
try {
System.out.println("1号位被删除的元素为:"+list.LstDelete(1));
}catch (IndexOutOfBoundsException e){
System.out.println("正确输入要删除的下标");
}
list.ListTraverse();
System.out.println("线性链表长度为"+list.ListLength());
list.ClearList();
System.out.println("线性链表长度为"+list.ListLength());
}
}
注:泛型和Object的区别
1、使用object作为形参当需要进行类型强制转换时,编译期不会检查类型是否安全,运行期才会检查
2、泛型写法范型的指定保证了代码的健壮性,避免了强转的风险.
- 推荐的动态扩充顺序存储线性表
//数据对象:
//默认存储容器大小
private int defaultCapacity = 10;
//存储容器
private Object[] elemData;
//元素个数
private int size;
//动态扩展存储容器
/**
* 确保容量够用,动态扩展容量
* @param length 添加后的容量
*/
private void ensureCapacity(int length) {
if (length > elemData.length) {
extendedCapacity(length);
}
}
/**
* 1.5倍扩容
* @param length 至少需要的大小
* @throws OutOfMemoryError 分配内存失败
*/
private void extendedCapacity(int length) throws OutOfMemoryError {
int extendedLength = length;
extendedLength = extendedLength + extendedLength >> 1;
try {
elemData = Arrays.copyOf(elemData, extendedLength);
} catch (OutOfMemoryError error) {
throw new OutOfMemoryError("扩容失败");
}
}
数据操作
-
获取元素
时间复杂度O(1)
return (E) elemData[i]
-
插入
- 检查插入位置是否合理,不合理抛出
IndexOutOfBoundsException
; - 如果线性表长度 ≥ 数组长度,则抛出异常或动态增加容量;
- 从最后一个元素开始向前遍历到第index个位置,分别将它们都向后移动一个位置;
- 将要插入元素填入位置i处;
- 表长+1。
- 检查插入位置是否合理,不合理抛出
-
删除
- 检查要删除的位置是否合理,不合理抛出异常
IndexOutOfBoundsException
- 用一个变量暂存删除元素,若有必要把这个变量返回
- 从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置;
- 表长减1。
- 检查要删除的位置是否合理,不合理抛出异常
完整代码:ArrayList.java
注:此处不粘贴代码是因为感觉直接把代码放这,只会让自己自大,而没彻底弄清楚这是如何来的,这样是无论如何也学不会代码的,所以如果想参考就去看源码吧
线性表顺序存储结构的优缺点
链式存储线性表
指针将存储线性表中数据元素的那些单元依次串联在一起。这种方法避免了在数组中用连续的单元存储元素的缺点,因而在执行插入或删除运算时,不再需要移动元素来腾出空间或填补空缺。然而我们为此付出的代价是,需要在每个单元中设置指针来表示表中元素之间的逻辑关系,因而增加了额外的存储空间的开销。
单链表
双向链表
循环链表
训环链表与单链表的区别在于链表的结尾:
单链表:
while(temp.next!=null)
{
temp=temp.next;
}
循环链表:
while(temp.next!=header)
{
temp=temp.next;
}
单向循环链表

双向循环链表


完整代码:SingleCircularLinkedList.java
参考链接:
- 数据结构->线性表,https://blog.csdn.net/ncepuzhuang/article/details/8162891,ncepustrong
- 数据结构与算法之链表,https://www.jianshu.com/p/49f1f6c1cd3d,冰河winner
还要经常补充,如果各位看官看到有何不妥的地方,希望指导一下这个菜鸟,万分感谢。