1. 链表是什么?
- 顺序表的缺点
- 添加和删除操作需要移动元素。
- 当数据量特别大的情况,可能没有连续的内存可使用。
链表,别名链式存储结构或单链表,用于存储逻辑关系为 “一对一” 的数据。与顺序表不同,链表不限制数据的物理存储状态。顺序表通过连续的地址建立元素之间前后连接关系,链表通过指针方式建立元素之间前后连接关系。
2. 链表怎么用?
链表用法与顺序表相似,只是适用场景有所不同。
3. 链表如何实现
3.1 定义结构
使用链表存储的数据元素,其物理存储位置是随机的。数据元素随机存储,并通过指针表示数据之间逻辑关系的存储结构就是链式存储结构。
链表中每个数据的存储都由以下两部分组成:
- 数据元素本身,其所在的区域称为数据域;
- 指向直接后继元素的指针,所在的区域称为指针域;
单链表与字符串有很多相似之处:单链表的结尾为
NULL
,字符串的结尾为\0
。所以,二者处理有许多相似之处。
注意:创建的节点node(1)存放数据和next指针域;
node(2) :用来有特殊用途;
List表示最后的结果;
typedef int LinkType;
typedef struct _Node{
struct _Node* next; // 指针域
LinkType val; // 数据域
}Node;
typedef struct {
Node* head;// 头指针
Node* tail;// 尾指针
int size;
} List;
定义一个存储单元类型LinkType
是为了使顺序表适和更多数据类型,使用的时候修改LinkType
类型即可。
3.2 定义操作
对比 | 序号 |
---|---|
1-2-3 | 创建–初始化–销毁 |
4-5 | 插入 |
1-2-3 | |
1-2-3 |
- 创建链表
// 创建
List list_create(){
List l = {NULL,NULL,0};
return l;
}
- 初始化
(1)首尾指针为空;
(2)size为空;
// 初始化
bool list_init(List* seq){
seq->head = NULL;
seq->tail = NULL;
seq->size = 0;
return true;
}
- 销毁链表
(1)释放首尾指针;
(2)首尾指针为空;
(3)size为空;
// 销毁
void list_destroy(List* seq){
free(seq->head);
free(seq->tail);
seq->head = NULL;
seq->tail = NULL;
seq->size = 0;
}
- 添加元素
(4.1)尾部添加关键:找到最后一个节点)
(4.1.1)循环的方案:
void list_append(List* seq,LinkType val){
// 创建新节点
Node* n = malloc(sizeof(Node));
n->val = val;
n->next = NULL;
if(NULL == seq->head){ // 第一个节点
seq->head = n;
seq->tail = n;
}else{
// 找到最后一个节点
Node* p = seq->head;
while(NULL != p->next){
p = p->next;
}
p->next = n;
seq->tail = n;
}
seq->size++;
}
(4.1.2)通用的方案:
void list_append(List* seq,LinkType val){
// 创建新节点
Node* n = malloc(sizeof(Node));
n->val = val;
n->next = NULL;
if(NULL == seq->head){ // 第一个节点
seq->head = n;
seq->tail = n;
}else{
seq->tail->next = n;
seq->tail = n;
}
seq->size++;
}
(4.2)头部添加关键:找到头结点)
4.2.1 n->next表示头指针
void list_prepend(List* seq,LinkType val){
// 创建新节点
Node* n = malloc(sizeof(Node));
n->val = val;
n->next = seq->head;
if(NULL == seq->head){
seq->tail = n;
}
seq->head = n;
seq->size++;
}
4.2.2 n表示尾指针
void list_prepend(List* seq,LinkType val){
// 创建新节点
Node* n = malloc(sizeof(Node));
n->val = val;
n->next = NULL;
if(NULL == seq->head){ // 第一个节点
seq->head = n;
}else{
n->next = seq->head;
seq->head = n;
}
seq->size++;
}
- 插入元素
// 插入数据
void list_insert(List* seq,int index,LinkType val){
// 创建新节点
Node* n = malloc(sizeof(Node));
n->val = val;
n->next = NULL;
// 在头部插入
if(NULL == seq->head || 0 == index){
list_prepend(seq,val);
return;
}
// for(Node* p=seq->head;NULL!=p;p=p->next){}
// 找到前一个节点
Node* p = seq->head;
int count = 0;
while(NULL!= p){
if(index-1 == count) break;
++count;
p = p->next;
}
// 在尾部插入
if(NULL == p){
list_append(seq,val);
return;
}
// 中间部插入
Node* q = p->next;
p->next = n;
n->next = q;
seq->size++;
}
- 删除元素
// 删除数据
void list_delete(List* seq,int index){
// 删除第一个节点
if(0 == index){
Node* del = seq->head;
seq->head = del->next;
free(del);
seq->size--;
if(0==seq->size){// 删除最后一个节点
seq->head = seq->tail = NULL;
}
return;
}
// 找到前一个节点
Node* p = seq->head;
int count = 0;
while(NULL!= p){
if(index-1 == count) break;
++count;
p = p->next;
}
if(NULL == p){// index不在删除范围
return;
}
// 删除其他节点
Node* del = p->next;
if(NULL == del->next){// 删除尾节点
seq->tail = p;
}
p->next = del->next;
free(del);
seq->size--;
}
-
获取元素
用index和循环的次数进行对比;
if(index == count) return &(p->val);
LinkType* list_at(List* seq,int index){
int count = 0;
Node* p = seq->head;
while(NULL!=p){
if(index == count) return &(p->val);
++count;
p = p->next;
}
return NULL;
}
- 返回元素的个数
// 获取元素个数
int list_size(List* seq){
/*
int size = 0;
Node* p = seq->head;
while(NULL!=p){
++size;
p = p->next;
}
*/
return seq->size;
}
- 实例代码
联系起来的方式
//链表表结构体
typedef struct{
char name[20];
bool sex;
int age;
} Student;
typedef Student LinkType;
typedef struct _Node{
struct _Node* next; // 指针域
LinkType val; // 数据域
}Node;
typedef struct {
Node* head;// 头指针
Node* tail;// 尾指针
int size;
} List;
(1)main.c
#include <stdio.h>
#include "LinkList.h"
void PrintElement(LinkType* val){
printf("%d ",*val);
}
int main(){
List l = list_create();
list_append(&l,1);
list_print(&l);
list_append(&l,2);
list_print(&l);
list_append(&l,3);
list_print(&l);
list_append(&l,4);
list_print(&l);
list_append(&l,5);
list_print(&l);
list_prepend(&l,10);
list_print(&l);
list_prepend(&l,11);
list_print(&l);
list_prepend(&l,12);
list_print(&l);
list_prepend(&l,13);
list_print(&l);
list_insert(&l,0,0);
list_print(&l);
list_insert(&l,100,0);
list_print(&l);
list_insert(&l,3,0);
list_print(&l);
list_delete(&l,3);
list_print(&l);
list_delete(&l,0);
list_print(&l);
list_delete(&l,9);
list_print(&l);
printf("size=%d\n",list_size(&l));
LinkType* p = list_at(&l,5);
*p = 500;
list_print(&l);
list_traval(&l,PrintElement);
list_destroy(&l);
}
(2)LinkList.c
#include "LinkList.h"
#include <stdio.h>
#include <stdlib.h>
// 创建
List list_create(){
List l = {NULL,NULL,0};
return l;
}
// 初始化
bool list_init(List* seq){
seq->head = NULL;
seq->tail = NULL;
seq->size = 0;
return true;
}
// 销毁
void list_destroy(List* seq){
free(seq->head);
free(seq->tail);
seq->head = NULL;
seq->tail = NULL;
seq->size = 0;
}
// 基本操作
// 添加数据
void list_append(List* seq,LinkType val){
// 创建新节点
Node* n = malloc(sizeof(Node));
n->val = val;
n->next = NULL;
if(NULL == seq->head) { // 第一个节点
seq->head = n;
seq->tail = n;
}else{
// 找到最后一个节点
/*
Node* p = seq->head;
while(NULL != p->next){
p = p->next;
}
p->next = n;
*/
seq->tail->next = n;
seq->tail = n;
}
seq->size++;
}
void list_prepend(List* seq,LinkType val){
// 创建新节点
Node* n = malloc(sizeof(Node));
n->val = val;
n->next = seq->head;
if(NULL == seq->head){
seq->tail = n;
}
seq->head = n;
seq->size++;
/*
n->next = NULL;
if(NULL == seq->head){ // 第一个节点
seq->head = n;
}else{
n->next = seq->head;
seq->head = n;
}
*/
}
// 插入数据
void list_insert(List* seq,int index,LinkType val){
// 创建新节点
Node* n = malloc(sizeof(Node));
n->val = val;
n->next = NULL;
// 在头部插入
if(NULL == seq->head || 0 == index){
list_prepend(seq,val);
return;
}
// for(Node* p=seq->head;NULL!=p;p=p->next){}
// 找到前一个节点
Node* p = seq->head;
int count = 0;
while(NULL!= p){
if(index-1 == count) break;
++count;
p = p->next;
}
// 在尾部插入
if(NULL == p){
list_append(seq,val);
return;
}
// 中间部插入
Node* q = p->next;
p->next = n;
n->next = q;
seq->size++;
}
// 删除数据
void list_delete(List* seq,int index){
// 删除第一个节点
if(0 == index){
Node* del = seq->head;
seq->head = del->next;
free(del);
seq->size--;
if(0==seq->size){// 删除最后一个节点
seq->head = seq->tail = NULL;
}
return;
}
// 找到前一个节点
Node* p = seq->head;
int count = 0;
while(NULL!= p){
if(index-1 == count) break;
++count;
p = p->next;
}
if(NULL == p){// index不在删除范围
return;
}
// 删除其他节点
Node* del = p->next;
if(NULL == del->next){// 删除尾节点
seq->tail = p;
}
p->next = del->next;
free(del);
seq->size--;
}
// 获取数据
LinkType list_get(List* seq,int index);
LinkType* list_at(List* seq,int index){
int count = 0;
Node* p = seq->head;
while(NULL!=p){
if(index == count) return &(p->val);
++count;
p = p->next;
}
return NULL;
}
// 获取元素个数
int list_size(List* seq){
/*
int size = 0;
Node* p = seq->head;
while(NULL!=p){
++size;
p = p->next;
}
*/
return seq->size;
}
// 打印
void list_print(List* seq){
Node* p = seq->head;
while(NULL != p){
printf("%d ",p->val);
p = p->next;
}
printf("\n");
}
// 遍历
typedef void (*traval_t)(LinkType* val);
void list_traval(List* seq,traval_t func){
Node* p = seq->head;
while(NULL != p){
(*func)(&(p->val));
p=p->next;
}
}
// int list_comp_element(const LinkType* a,const LinkType* b)
void list_sort(List* seq);
// 清空
void list_clear(List* seq);
// 判空
bool list_empty(List* seq);
// 查找
int list_find(List* seq,LinkType val);
LinkType* list_search(List* seq,LinkType val);
(3)LinkList.h
#ifndef _LINKLIST_H
#define _LINKLIST_H
#include <stdbool.h>
// 顺序表结构体
typedef struct{
char name[20];
bool sex;
int age;
} Student;
typedef Student LinkType;
typedef struct _Node{
struct _Node* next; // 指针域
LinkType val; // 数据域
}Node;
typedef struct {
Node* head;// 头指针
Node* tail;// 尾指针
int size;
} List;
// 创建
List list_create();
// 初始化
bool list_init(List* seq);
// 销毁
void list_destroy(List* seq);
// 基本操作
// 添加数据
void list_append(List* seq,LinkType val);
void list_prepend(List* seq,LinkType val);
// 插入数据
void list_insert(List* seq,int index,LinkType val);
// 删除数据
void list_delete(List* seq,int index);
// 获取数据
LinkType list_get(List* seq,int index);
LinkType* list_at(List* seq,int index);
// 获取元素个数
int list_size(List* seq);
// 打印
void list_print(List* seq);
// 遍历
typedef void (*traval_t)(LinkType* val);
void list_traval(List* seq,traval_t func);
// int list_comp_element(const LinkType* a,const LinkType* b)
void list_sort(List* seq);
// 清空
void list_clear(List* seq);
// 判空
bool list_empty(List* seq);
// 查找
int list_find(List* seq,LinkType val);
LinkType* list_search(List* seq,LinkType val);
#endif // _LINKLIST_H
4. 优化
- 创建
用户使用LinkType
忘记初始化,可以把结构体定义和初始化合二为一。
(即把创建和初始化放在LinkList.h文件里面);
亚元(单节点的表示)
// 创建
List list_create(){
Node* dummy = malloc(sizeof(Node));// 头结点
dummy->next = NULL;
List l = {dummy,dummy,0};
return l;
}
- 随机访问元素
获取元素linklist_get()
,只能获取到顺序表中的元素的副本,如果需要改变链表中的元素,可以提供如下函数。
// 获取数据
LinkType list_get(List* seq,int index);
LinkType* list_at(List* seq,int index){
int count = -1;
Node* p = seq->head;
while(NULL!=p){
if(index == count) return &(p->val);
++count;
p = p->next;
}
return NULL;
}
- 遍历
提供一个对链表的整体操作的接口。
// 遍历
typedef void (*traval_t)(LinkType* val);
void list_traval(List* seq,traval_t func){
Node* p = seq->head->next;
while(NULL != p){
(*func)(&(p->val));
p=p->next;
}
}
完整代码
①main.c(同上)
②LinkList.c
#include "LinkList.h"
#include <stdio.h>
#include <stdlib.h>
// 创建
List list_create(){
Node* dummy = malloc(sizeof(Node));// 头结点
dummy->next = NULL;
List l = {dummy,dummy,0};
return l;
}
// 初始化
bool list_init(List* seq){
Node* dummy = malloc(sizeof(Node));// 头结点
dummy->next = NULL;
seq->head = dummy;
seq->tail = dummy;
seq->size = 0;
return true;
}
// 销毁
void list_destroy(List* seq){
Node* p = seq->head;
while(NULL != p){
Node* del =p;
p = p->next;
free(del);
}
seq->size = 0;
seq->head = seq->tail = NULL;
}
// 基本操作
// 添加数据
void list_append(List* seq,LinkType val){
// 创建新节点
Node* n = malloc(sizeof(Node));
n->val = val;
n->next = NULL;
seq->tail->next = n;
seq->tail = n;
seq->size++;
}
void list_prepend(List* seq,LinkType val){
// 创建新节点
Node* n = malloc(sizeof(Node));
n->val = val;
n->next = seq->head->next;
if(seq->tail == seq->head){
seq->tail = n;
}
seq->head->next = n;
seq->size++;
}
// 插入数据
void list_insert(List* seq,int index,LinkType val){
// 创建新节点
Node* n = malloc(sizeof(Node));
n->val = val;
n->next = NULL;
// for(Node* p=seq->head;NULL!=p;p=p->next){}
// 找到前一个节点
Node* p = seq->head;
int count = -1;
while(NULL!= p){
if(index-1 == count) break;
++count;
p = p->next;
}
// 在尾部插入
if(NULL == p){
list_append(seq,val);
return;
}
// 中间部插入
Node* q = p->next;
p->next = n;
n->next = q;
seq->size++;
}
// 删除数据
void list_delete(List* seq,int index){
// 找到前一个节点
Node* p = seq->head;
int count = -1;
while(NULL!= p){
if(index-1 == count) break;
++count;
p = p->next;
}
if(NULL == p){// index不在删除范围
return;
}
// 删除其他节点
Node* del = p->next;
if(NULL == del->next){// 删除尾节点
seq->tail = p;
}
p->next = del->next;
free(del);
seq->size--;
}
// 获取数据
LinkType list_get(List* seq,int index);
LinkType* list_at(List* seq,int index){
int count = -1;
Node* p = seq->head;
while(NULL!=p){
if(index == count) return &(p->val);
++count;
p = p->next;
}
return NULL;
}
// 获取元素个数
int list_size(List* seq){
/*
int size = 0;
Node* p = seq->head;
while(NULL!=p){
++size;
p = p->next;
}
*/
return seq->size;
}
// 打印
void list_print(List* seq){
Node* p = seq->head->next;
while(NULL != p){
printf("%d ",p->val);
p = p->next;
}
printf("\n");
}
// 遍历
typedef void (*traval_t)(LinkType* val);
void list_traval(List* seq,traval_t func){
Node* p = seq->head->next;
while(NULL != p){
(*func)(&(p->val));
p=p->next;
}
}
// int list_comp_element(const LinkType* a,const LinkType* b)
void list_sort(List* seq);
// 清空
void list_clear(List* seq);
// 判空
bool list_empty(List* seq);
// 查找
int list_find(List* seq,LinkType val);
LinkType* list_search(List* seq,LinkType val);
③LinkList.h(同上)
5. 插入和删除操作分析
5.1 插入
-
开头插入新节点
-
末尾插入新节点
5.2 删除
链表指针两大操作:
-
修改指向节点:p = ?
-
修改节点连接:p->next = ?
-
开头删除节点
-
末尾删除节点
-
中间删除节点
注意:
存在两种特殊情况需要处理
- 空链表删除节点
此情况使用断言或者抛出异常。- 待删除节点的链表中只有一个节点
删除后,需要把头指针和尾指针设置为空
6. 练习
- 实现一个清空接口
- 实现一个判空接口
- 实现一个查找指定元素的接口,返回元素下标
- 实现一个查找指定元素的接口,返回元素地址
- 实现比较两个元素的接口
- 实现交换两个元素的接口
Leetcode206. 反转链表
- 静态链表
静态链表相当于用一个数组来实现线性表的链式存储结构,使用下标代替指针。主要被用于没有指针的计算机语言。FAT文件系统是使用静态链表实现的。
6. 其他链表
6.1 循环单链表
单链表有一个特点只能从头指针开始遍历整个链表,循环单链表可以从任意节点开始遍历整个链表。
6.3.1 前端插入节点
6.3.2 末尾插入节点
6.2 双向链表
单链表在末尾删除节点时,需要获得删除节点前面的一个节点。这需要从队列开头一直遍历到结尾,导致效率瓶颈。双向链表很好的解决这个问题。
6.2.1 末尾插入新节点
6.2.2 末尾删除节点
注意:
存在两种特殊情况需要处理
- 空链表删除节点
此情况使用断言或者抛出异常。- 待删除节点的链表中只有一个节点
删除后,需要把头指针和尾指针设置为空