1. 概念
链表是一种 物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
2. 链表的分类
根据链表是否是单向的、是否带头结点、是否循环可以将量表分为 8 种不同的结构。但是这里我们只研究最简单的 不带头结点的单向不循环链表,以及最难的带头结点的双向循环链表。
1. 不带头的单向不循环链表,结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试的重点和常考点。
2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了。
3. 无头单向不循环链表基本操作的实现
SList.h
#pragma once
#include<stdio.h>
#include<malloc.h>
#include<assert.h>
#include<stdlib.h>
typedef int SListDataType;
// 链表中的一个节点
typedef struct Node{
SListDataType value; // 值
struct Node *next; // 下一个节点的地址
} Node;
// Single List
typedef struct SList{
Node *first; // *head; 第一个节点的地址
} SList;
// 初始化 / 销毁
void SListInit(SList *slist);
void SListDestory(SList *slist);
// 增 / 删 / 查 / 改
// 头插
void SListPushFront(SList *slist, SListDataType data);
// 尾插
void SListPushBack(SList *slist, SListDataType data);
// 头删
void SListPopFront(SList *slist);
// 尾删
void SListPopBack(SList *slist);
// 查找,只能遍历查找 时间复杂度O(n)
Node *SListFind(SList *slist, SListDataType data);
// 在 pos 结点的后面插入一个节点
void SListInsertAfter(SList *slist, Node *pos, SListDataType data);
// 在 pos 结点的后面删除一个节点
void SListEraseAfter(SList *slist, Node *pos);
// 删除第一个结点值为 data 的结点
void SListRemove(SList *slist, SListDataType data);
// 删除所有值为 data 的结点
void SListRemoveAll(SList *slist, SListDataType data);
//
// 测试专用打印函数
//
void SListPrint(SList *slist);
SList.c
#include"SList.h"
// 单链表初始化
void SListInit(SList *slist){
slist->first = NULL;
}
// 单链表头插
// 注意问题:
// 1.链表为空 first --> NULL
// 2.链表不为空 first -- > 1 -- > 2 --> 3 --> 4 --> NULL
//经过思考,链表为空和不为空的情形是一样的,所以不需要做特殊处理
void SListPushFront(SList *slist, SListDataType data){
// 1.创建新结点,就是给结点开辟新空间
Node *node = (Node*)malloc(sizeof(Node));
node->value = data;
// 2.更改链表的关系
node->next = slist->first;
slist->first = node;
}
// 尾插
void SListPushBack(SList *slist, SListDataType data){
// 1.创建新结点
Node *node = (Node*)malloc(sizeof(Node));
node->value = data;
node->next = NULL;
// 2.遍历链表,找到最后一个元素
if(slist->first == NULL){
slist->first = node;
}
else{
// 找原来链表的最后一个结点
Node *current = slist->first;
while(current->next != NULL){
current = current->next;
}
// 循环结束,此时 current->next 一定是 NULL,也就是链表最后一个节点
current->next = node;
}
}
// 头删
void SListPopFront(SList *slist){
// 1.链表中没有节点
assert(slist->first != NULL);
// 2.链表中有结点
// 由于是头删,如果直接删除链表第一个结点
// 就会导致链表的头指针找不到链表的第二个结点
// 为了避免这种情况,先定义一个指针,用来存放链表第二个节点的位置
Node *second = slist->first->next;
free(slist->first);
slist->first = second;
}
// 尾删
void SListPopBack(SList *slist){
// 排除链表中没有结点的情况
assert(slist->first != NULL);
// 只有一个节点的情况
if(slist->first->next == NULL){
free(slist->first);
slist->first = NULL;
return;
}
// 有两个即两个结点以上的情况
// 找到倒数第二个结点,做记录
Node *current;
for(current = slist->first; current->next->next != NULL; current = current->next);
free(current->next);
current->next = NULL;
}
// 查找
Node *SListFind(SList *slist, SListDataType data){
for(Node *current = slist->first; current != NULL; current = current->next){
if(current->value == data){
return current;
}
}
return NULL;
}
// 在 pos 结点的后面插入一个结点
void SListInsertAfter(SList *slist, Node *pos, SListDataType data){
(void)slist;
// 1.创建新结点
Node *node = (Node*)malloc(sizeof(Node));
node->value = data;
node->next = pos->next;
pos->next = node;
}
// 在 pos 结点的后面删除一个结点
void SListEraseAfter(SList *slist, Node *pos){
// 这句代码是为了消除 slist 变量未被使用的警告
(void)slist;
// 定义一个指针,保留 pos 结点之后的第二个结点的地址
Node *next = pos->next->next;
free(pos->next);
pos->next = next;
}
// 删除第一个结点值为 data 的结点
void SListRemove(SList *slist, SListDataType data){
// 1.先确认第一个节点的值是不是 data
// 如果是:头删
// 如果不是:继续
// 2.拿一个指针遍历链表的结点,判断指针指向结点的下一个结点保存的值是不是 data
// 指针符号:current
// 指针范围[first, 倒数第二个结点]
// 3.如果找到了,进行删除
// current = current->next->next;
// 释放原来的结点 current->next;
// 第一种情况:链表中一个结点也没有
if(slist->first == NULL){
return;
}
// 第二种情况:链表中有结点
// 判断链表中的第一个结点的值是不是 data
// 链表中的一个结点的值是 data
if(slist->first->value == data){
// 记录链表的第二个节点
Node *second = slist->first->next;
// 释放第一个结点
free(slist->first);
slist->first = second;
}
// 链表中的第一个结点的值不是 data
else{
// 遍历链表,查找链表中结点的值为 data 的结点
Node* current = slist->first;
while(current != NULL){
if(current->next->value == data){
Node* next = current->next;
current->next = current->next->next;
free(next);
return;
}
current = current->next;
}
}
}
// 删除所有值为 data 的结点
void SListRemoveAll(SList *slist, SListDataType data){
if(slist->first == NULL){
return;
}
Node* current = slist->first;
while(current->next != NULL){
if(current->next->value == data){
Node* next = current->next;
current->next = current->next->next;
free(next);
}
else{
current = current->next;
}
}
// 除了第一个结点没有检查,其他的结点都已经检查并删除了
if(slist->first->value == data){
// 头删
Node* first = slist->first;
slist->first = slist->first->next;
free(first);
}
}
// 销毁
void SListDestory(SList *slist){
while(slist->first != NULL){
SListPopBack(slist);
}
}
/
// 这是一个测试专用的打印函数
/
void SListPrint(SList *slist){
assert(slist);
Node* current = slist->first;
while(current != NULL){
printf("%d ",current->value);
current = current->next;
}
printf("\n");
}
main.c
#include"SList.h"
int main(){
SList sl;
SListInit(&sl);
SListPushFront(&sl, 1);
SListPushFront(&sl, 2);
SListPushFront(&sl, 3);
SListPushFront(&sl, 4);
SListPushFront(&sl, 5);
SListPrint(&sl);
//SListPushBack(&sl, 1);
//SListPushBack(&sl, 2);
//SListPushBack(&sl, 3);
//SListPushBack(&sl, 4);
//SListPrint(&sl);
//SListPopFront(&sl);
//SListPopFront(&sl);
//SListPopFront(&sl);
//SListPrint(&sl);
//SListPopBack(&sl);
//SListPopBack(&sl);
//SListPrint(&sl);
//Node *SListFind(&sl, 2);
//
//SListPushFront(&sl, 3);
//SListPushFront(&sl, 4);
//SListPushFront(&sl, 5);
//SListPushFront(&sl, 6);
//SListPrint(&s);
//SListInsertAfter(&sl, 3, 7);
//SListPrint(&sl);
//SListEraseAfter(&sl, 3);
//SListPrint(&sl);
//SListPushBack(&sl, 5);
//SListPrint(&sl);
//SListRemove(&sl, 5);
//SListPrint(&sl);
//SListRemoveAll(&sl, 1);
//SListPrint(&sl);
//system("pause");
//return 0;
}
- 4. 带头双向循环链表基本操作的实现