1.章节梳理
考纲内容
知识框架
复习提示
注意时间复杂度和空间复杂度在线性表中的比较。
线性表的各种基本操作(基于两种存储结构),这方面我第一轮的题目出错较多,二轮的时候要结合大题,着重复习。
2.线性表的定义和基本操作
定义:
线性表是具有相同数据类型的n个元素的有限序列,其中n为表长,当n=0时线性表是一个空表。
- 表中元素的个数有限。(所有整数组成的序列不是线性表)
- 表中元素具有逻辑上的顺序性,表中元素有其先后次序。
- 表中的元素是数据元素,其数据类型都相同,这意味着每个元素占有相同大小的存储空间。
线性表的分类
注:对受限的理解-(前端、后端),之后会在第三、四章通过过程,会逐渐理解。
考题:每一个元素都只有一个后继(或前驱)元素。
对&符号的理解-对参数的修改的结果要带回来
运算的操作(注一轮的时候强调理解,二论在此基础上学会运用)
3.顺序表
定义:就是线性表的顺序存储结构实现,它是用一组地址连续的存储单元依次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理位置上也相邻。
注:考点与链式的优缺点分析
性质如下表
储存结构:线性表的顺序储存结构是一种随机存取的存储结构。通常用高级程序设计语言中的数组来描述线性表的顺序存储结构。
注:动态分配不是链式存储,它同样是属于顺序存储,物理结果没有变化,依然是随机存取方式,只是在分配的空间大小可以在运行时动态所决定。
静动态分配-(对此掌握薄弱,复习时注重对这个理解)
4.顺序表的链式表示
4.1单链表
注:考点与顺序表的区别
单链表的结点的结构代码可能包括:该结点储存的数据元素,指向后继元素的指针元素。
单链表这一块注重对指针的理解
引入头结点的两个优点:(作为考点)
- 改变第一个元素的位置时,无需改变头指针的指向(头指针始终指向头结点),只需改变头结点中指针域的位置即可。
- 无论链表是否为空,其头指针都是指向头结点的非空指针(空表中头结点的指针域为空),因此空表和非空表的处理就得到了统一。
4.2双链表
总结:双向链表方便进行一个位置的前插操作时不需要遍历链表找到这个位置的前一个结点位置。
4.3循环链表
中可以从表中任意一个结点开始遍历整个链表。此外,有时我们对链表操作是在表头和表尾进行的,利用头指针找到表尾时间复杂度为O(n),而在循环链表中我们可以不设头指针,改设尾指针r,r->next即为头指针,此时对表头或表尾操作都需O(1)的时间复杂度。
4.4静态链表
定义:借助数组来描述线性表的链式存储结构
考点:静态链表需要分配较大空间,插入和删除不需要移动元素的线性表。
5.从时间复杂度上对比顺序表和链表
对于存储结构的选择
选择链表或者数组作为线性表的依据在与:(高频考点)
- 需不需要对其中的某一个位置的元素进行操作,这样的话数组方便,用下标就行,支持随机访问。单链表的话要从头指针遍历链表,因为不是连续储存。
- 对元素的存储空间大小是不是明确,数组扩容空间会造成空间浪费而且本身有损耗,链表有多少数据元素就可以创造新的结点空间。
- 删除和增加元素时数组需要对后续位置上的元素进行移位,链表只需改变少数结点指针就行。
- 此外链表每存一个数据还要多存一个指针。
在明确的问题下才能分析两者优劣。
考频分析:同样是数据结构的基础知识,这一块博客就不总结了,配合这一节去理解指针、结构体相关知识。考点为大题的算法设计:
- 代码如何写(结构体,引用,指针等,不写代码真的会忘)
- 合理注释和命名风格
- 如何设计算法
- 失误总结
直接针对大题来总结这一块的博客,挖坑二轮来填。
晴落学长的代码:
单链表的各种操作
#define MAX(a, b) ((a) > (b) ? (a) : (b))
typedef struct ListNode{ //定义单链表结点
int val;
struct ListNode*next;
}ListNode;
typedef struct { //定义单链表,指明头指针和结点数
struct ListNode *head;
int size;
}LinkedList;
struct ListNode *ListNode_Creat(int val) { //创建一个链表结点并赋值val
struct ListNode * node = (struct ListNode *)malloc(sizeof(struct ListNode)*1);
node->val = val;
node->next = NULL;
return node;
}
LinkedList* LinkedList_Create() { //创建一个链表,初始为空
LinkedList * obj = (LinkedList *)malloc(sizeof(LinkedList));
obj->head = ListNode_Creat(0);
obj->size = 0;
return obj;
}
int LinkedList_Get(LinkedList* obj, int index) {
//找到链表中第index个结点,第0个即头结点
if (index < 0 || index >= obj->size) {
return -1;
}
struct ListNode *cur = obj->head;
for (int i = 0; i <= index; i++) {
cur = cur->next;
}
return cur->val;
}
void LinkedList_Add_At_Index(LinkedList* obj, int index, int val) {
//在第index个结点处添加一个新结点,赋值val
if (index > obj->size) {
return;
}
index = MAX(0, index); //index为负值或0时意为在头节点后插入
obj->size++;
struct ListNode *pred = obj->head;
for (int i = 0; i < index; i++) {
pred = pred->next;
}
struct ListNode *toAdd = ListNode_Creat(val);
toAdd->next = pred->next;
pred->next = toAdd;
}
void LinkedList_Add_At_Head(LinkedList* obj, int val) {//头插
LinkedList_Add_At_Index(obj, 0, val);
}
void LinkedList_Add_At_Tail(LinkedList* obj, int val) {//尾插
LinkedList_Add_At_Index(obj, obj->size, val);
}
void LinkedList_Delete_At_Index(LinkedList* obj, int index) {
//删除第index位置的结点
if (index < 0 || index >= obj->size) {
return;
}
obj->size--;
struct ListNode *pred = obj->head;
for (int i = 0; i < index; i++) {
pred = pred->next;
}
struct ListNode *p = pred->next;
pred->next = pred->next->next;
free(p);
}
void LinkedList_Free(LinkedList* obj) {
//释放一个链表的所有空间
struct ListNode *cur = NULL, *tmp = NULL;
for (cur = obj->head; cur;) {
tmp = cur;
cur = cur->next;
free(tmp);
}
free(obj);
}