线性表是具有相同数据类型的n个元素的有限序列,其中n为表长,当n=0时线性表是一个空表。
- 表中元素的个数有限。(所有整数组成的序列不是线性表)
- 表中元素具有逻辑上的顺序性,表中元素有其先后次序。
- 表中的元素是数据元素,其数据类型都相同,这意味着每个元素占有相同大小的存储空间。
线性表属于一种逻辑结构,可以由两种物理结构实现:
- 顺序表就是线性表的顺序存储结构实现,它是用一组地址连续的存储单元依次存储线性表中的数据元素,从而使得逻辑上相邻的两个元素在物理位置上也相邻。
数组的结构代码可能包括:储存的数据元素,数组最大储存容量,数组当前的长度。
- 线性链表就是线性表的链式储存结构实现,有八种组合,带头不带头,双向还是单向,循环还是不循环。
单链表的结点的结构代码可能包括:该结点储存的数据元素,指向后继元素的指针元素。
引入头结点的两个优点:
- 改变第一个元素的位置时,无需改变头指针的指向(头指针始终指向头结点),只需改变头结点中指针域的位置即可。
- 无论链表是否为空,其头指针都是指向头结点的非空指针(空表中头结点的指针域为空),因此空表和非空表的处理就得到了统一。
双向链表方便进行一个位置的前插操作时不需要遍历链表找到这个位置的前一个结点位置。
循环链表中可以从表中任意一个结点开始遍历整个链表。此外,有时我们对链表操作是在表头和表尾进行的,利用头指针找到表尾时间复杂度为O(n),而在循环链表中我们可以不设头指针,改设尾指针r,r->next即为头指针,此时对表头或表尾操作都需O(1)的时间复杂度。
选择链表或者数组作为线性表的依据在与:
- 需不需要对其中的某一个位置的元素进行操作,这样的话数组方便,用下标就行,支持随机访问。单链表的话要从头指针遍历链表,因为不是连续储存。
- 对元素的存储空间大小是不是明确,数组扩容空间会造成空间浪费而且本身有损耗,链表有多少数据元素就可以创造新的结点空间。
- 删除和增加元素时数组需要对后续位置上的元素进行移位,链表只需改变少数结点指针就行。
- 此外链表每存一个数据还要多存一个指针。
在明确的问题下才能分析两者优劣。
考频分析:同样是数据结构的基础知识,这一块博客就不总结了,配合这一节去理解指针、结构体相关知识。考点为大题的算法设计:
- 代码如何写(结构体,引用,指针等,不写代码真的会忘)
- 合理注释和命名风格
- 如何设计算法
- 失误总结
直接针对大题来总结这一块的博客,挖坑二轮来填。
单链表的各种操作
#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);
}