一、线性表
线性表的基本概念
数据结构的三要素:逻辑结构、数据的运算、存储结构(物理结构)
线性表的定义
线性表是具有相同数据类型的n(n>=0)个元素的有限序列。
线性表的基本操作
什么时候要传入参数的引用“&”?
一种是值类型,使用时会直接复制原值,修改参数不会影响原值
一种是引用类型,使用时操作的是原值,修改时直接修改原值!(C语言不支持这种引用类型!)
为什么要实现对数据结构的基本操作?
- 团队合作编程,你定义的数据结构要让别人能够很方便的使用(封装)
- 将常用的操作/运算封装称函数,避免重复工作,降低出错风险。
总结
注意⚠️:位序是用1开始计算的!!!
二、顺序表
顺序表的基本概念
顺序表的定义
顺序表的初始化
静态分配
具体实现:
//初始化(静态分配)
void InitList(SqList &L){
for (int i = 0; i < MaxSize; i++) {
L.data[i]=0;//将所有元素的初始值默认设置为0
//这一步其实可以省略,但是省略之后,有可能受到内存中"脏数据"的影响
}
L.length=0;
}
问题反思
- 如果“数组”存满留怎么办?
可以放弃治疗,顺序表长刚开始确定后就无法更改(存储空间是静态的)
- 如果一开始就声明一个很大的内存空间呢?会存在什么问题?
浪费,会造成大量的浪费。
动态分配
具体实现方式
//初始化(动态方式)
bool InitList(SeqList &L){
//用 malloc 函数申请一片连续的存储空间
L.data=(int *)malloc(InitSize*sizeof(int));
if (L.data==NULL)
//要细心呀,这里不小心写成了赋值语句,但是没有报错,找了半天错误!
return false;
//(int *) 是指针的强制类型转换
L.length=0;
L.MaxSize=InitSize;
return true;
}
总结
顺序表的基本操作
插入
ListInsert(&L,i,e):插入操作。在表L中的第i个位置上插入指定元素e。
详细实现方式:
优化之后:
bool ListInsert(SqList &L,int i,int e){
//判断插入的位置是否合法,
if (i<1||i>L.length+1)
return false;
//判断表是否存满了
if (L.length>=MaxSize)
return false;
//后面的元素后移
for (int j = L.length; j >=i ; j--) {
L.data[j]=L.data[j-1];
}
L.data[i-1]=e;
L.length++;
return true;
}
插入操作的时间复杂度分析
删除
//删除
bool ListDelete(SqList &L,int i,int &e){
//判断i的位置是否合法
if(i<0||i>L.length){
return false;
}
//取出将要被删除的数
e=L.data[i-1];
//将其后的数据前移
for (int j = i; j <=L.length ; j++) {
L.data[j-1]=L.data[j];
}
//线性表长度减一
L.length--;
return true;
}
删除操作的时间复杂度分析
总结反思
查找
按位查找
GetElem(L,i):按位查找操作,获取表L中第i个位置的元素的值
静态分配状态下的实现方式
动态分配状态下的实现方式
用指针加数组下标的方式取数据的时候,数组类型决定着取数据时取几个字节!!
//按位查找
int GetElem(SeqList L,int i){
//判断是否越界
if (i<0||i>L.length)
return -1;
return L.data[i-1];
}
按位查找的时间复杂度分析
按值查找
//按值查找
int LocateElem(SeqList L,int e){
//循环出查找
for (int i = 0; i <L.length ; i++) {
if (L.data[i]==e)
return i+1; //返回位序
}
return -1;
}
结构类型的比较
注意:考研初试中华,手写代码可以直接用“==”,无论是ElemType是基本数据类型还是结构类型,手写代码主要考察学生是否理解算法思想,不会严格要求代码完全可运行
有的学校复试考《C语言程序设计》,那么。。。也许就要语法严格一些!
按值查找的时间复杂度
总结反思
三、单链表
什么是单链表?
单链表的定义
别名
注释:或者可以理解为指向头节点的指针既可以表示整个单链表也可以表示头节点,为了便于区分才建议使用 typedef 进行重命名,以方便区别其不同的含义
头插法建立单链表
单链表的基本操作
单链表的初始化
不带头节点的单链表的初始化
带头节点的单链表的初始化
两者区别是什么?
总结
插入和删除
插入
按位序插入(带头节点的单链表)
具体实现
分析在表头插入
分析为什么不能颠倒
分析在表中插入
分析在表尾插入
分析插入位置超出表长
总结
按位插入(不带头节点)
具体实现
结论:不带头节点的单链表,写代码更不方便,除非特别声明,默认推荐使用带头节点的实现方式,还有要注意在考试中带头、不带头都有可能考察,注意审题。
指定节点的后插操作
指定节点的前插操作
通过传入头指针实现前插
先进行后插,然后交换前后数据,以此实现前插
删除
带有头节点版本
具体实现
删除指定结点
如果P是最后一个节点,咋办?
只能从表头表头依次寻找前驱,时间复杂度O(n)
单链表的局限性:无法逆向检索!!
封装的好处
总结
查找
按位查找(带头节点)
按值查找(带头节点)
求表的长度(带头节点)
总结
单链表的建立方法
PS:找不到对象就娶一个数据元素吧!哈哈
尾插法
第一种方法:
问题:时间复杂度太高!!可以用一个指针记录最后一个数据元素的位置来优化时间。
优化之后:
头插法
具体实现:
总结
四、双链表
单链表VS双链表
双链表基本操作
初始化
插入
优化之后
删除
遍历
总结反思
五、循环链表
循环单链表
具体实现:
优势:
循环双链表
初始化
插入
删除
总结反思
六、静态链表
什么是静态链表?
定义一个静态链表
方法1:
方法2:
验证方法2的定义方法
基本操作
总结反思
七、线性表章节复习反思
逻辑结构对比
存储结构对比
基本操作对比
初始化(创建)
销毁
增加/删除
查找
总结
具体使用时,需要根据具体场景去选择