目录
什么是链表和链表的分类:
链表:
- 线性表可分为顺序存储结构和链式存储结构两种。
- 顺序存储结构的特点是逻辑关系上相邻的两个元素在物理位置上也相邻;
- 链式存储结构的特点是不需要逻辑上相邻的元素在物理位置上也相邻。
- 线性链表可分为单链表,双链表,带头结点单链表,不带头结点单链表,循环单链表,非循环单链表,无头单向非循环链表,带头双向循环链表;
概念:
- 链表是一种物理存储结构上非连续,非顺序的存储结构;
- 链表是一个练市
- 数据元素的逻辑顺序,是通过链表的指针链接次序实现的;
数据结构中链表示意图:
-
无头单向非循环链表:
- 结构简单,一般不会单独用来存数据。
- 实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。
-
带头双向循环链表:
- 结构最复杂,一股用在单独存储数据。
- 实际中使用的链表数据结构,都是带头双向循环链表。
- 另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单;
单链表逻辑结构示意图:
带头结点和不带头结点链表:
概念:
- 头指针:通常使用“头指针”来标识一个链表,头指针为NULL的时表示一个空链表。
- 头结点:在单链表的第一个结点之前附加一个结点,称为头结点。
- 头结点的Data域可以不设任何信息,也可以记录表长等相关信息。
- 链表可以没有头结点,但是必须要有头指针;
- 因为要用头指针来标识一个链表。设链表的头指针为pHead。
- 除了头结点之外,还需要一个指向链表一般元素的指针pNode
- 因为pHead只能指向表头,不能指向其他元素,故需要另设指针;
带头结点单链表:
- 若使用头结点,则第1个位置的插入和删除都是对p—>next进行操作,而不用动p本身;
-
插入操作如下
- p指向要插入结点的前驱结点,若要插入的结点为第1个位置,则其前驱结点就是头结点,此时p指向头结点。
- 让新结点s的next指向p的next,即s—>next = p—>next;
- 让p—>next指向s,即p—>next = s;
-
删除操作如下
- p指向要删除结点的前驱结点,若要删除的结点为第1个位置,则其前驱结点就是头结点,此时p指向头结点。
- 让临时指针q指向要删除的结点,即q = p—>next;
- 让p的next指向要删除结点的下一个结点,即p—>next = q—>next;
- 释放q的空间,即free(q);
-
统一空表和非空表的处理
- 若使用头结点,无论表是否为空,头指针都指向头结点,也就是*LNode类型,对于空表和非空表的操作是一致的。
-
不带头结点单链表:
若没有头结点,在第1个位置插入或删除时,需要动头指针。
-
插入操作如下
- 判断要插入的是否是第1个位置,若是需要特殊处理。
- 若是第1个位置,让新结点s的next指向头指针PtrL。
- return s,此时s作为链表的头指针。此时的更新了链表的头指针。
- 若不是第1个位置,首先找到要插入结点的前驱结点,让p指向这个前驱结点。
- 让新结点s的next指向p的next,即s—>next = p—>next;
- 让p—>next指向s,即p—>next = s;
- return PtrL,此时PtrL还是作为链表的头指针,没有被修改,但考虑到一致性需要这样写。
-
删除操作如下
- 判断要删除的是否是第1个位置,若是需要特殊处理。
- 若是第1个位置,让s指向要删除的结点。首先判断PtrL是否为空,若是直接return NULL;若不为空,则将链表的头结点挪到下一个位置,即PtrL = PtrL—>next;
- free(s);然后return PtrL
- 若不是第1个位置,首先找到要删除结点的前驱结点,让p指向这个前驱结点。
- 让临时指针q指向要删除的结点,即q = p—>next;
- 让p的next指向要删除结点的下一个结点,即p—>next = q—>next;
- 释放q的空间,即free(q);
- return PtrL
统一空表和非空表的处理:
- 若不使用头结点,当表非空时,头指针指向第1个结点的地址,即*LNode类型;
- 但是对于空表,头指针指向的是NULL,此时空表和非空表的操作是不一致的。
单链表基本操作:
#pragma once
typedef int SDataType; //int 数据类型重定义
// 链表的节点
typedef struct SListNode
{
SDataType _data; //数据域
struct SListNode* _pNext; //指针域
}Node, *PNode;
// 链表的结构,给一个头指针保存链表第一个节点的地址
typedef struct SList
{
PNode _pHead; // 指向链表中的第一个节点
}SList, *PSList;
-
链表初始化:
- 链表初始化;
- 创建新结点:
// 链表的初始化
void SListInit(SList* s) {
assert(s);
s->_pHead = NULL;
}
//创建新的节点
PNode BuySListNode(SDataType data) {
PNode pNewNode = (PNode)malloc(sizeof(Node));
if (NULL == pNewNode) {
assert(0);
return;
}
pNewNode->_data = data;
pNewNode->_pNext = NULL;
return pNewNode;
}
-
头插:
// 在链表s第一个节点前插入值为data的节点
void SListPushFront(SList* s, SDataType data) {
assert(s);
PNode pNewNode = BuySListNode(data);
pNewNode->_pNext = s->_pHead;
s->_pHead = pNewNode;
}
-
尾插:
// 在链表s最后一个节点后插入值为data的节点
void SListPushBack(SList* s, SDataType data) {
assert(s);
PNode pNewNode = BuySListNode(data);//创建一个新的节点
if (NULL == s->_pHead) {
s->_pHead = pNewNode;//如果链表为空,则让头节点指向新建立的节点
}
else
{
PNode pCur = s->_pHead;//创建一个变量
while (pCur->_pNext) {
pCur = pCur->_pNext;//搜索最后一个节点为空时停下
}
pCur->_pNext = pNewNode;
}
}
-
头删:
// 删除链表s的第一个节点
void SListPopFront(SList* s) {
assert(s);
if (NULL == s->_pHead) {
return;
}
PNode pDelNode = s->_pHead;
s->_pHead = s->_pHead->_pNext;
free(pDelNode);
}
-
尾删:
// 删除链表s最后一个节点
void SListPopBack(SList* s) {
assert(s);
PNode pCur = s->_pHead;
if (pCur == NULL) {
return;
}
if (pCur->_pNext == NULL) {
free(pCur);
s->_pHead = NULL;
return;
}
while (pCur->_pNext->_pNext) {
pCur = pCur->_pNext;//寻找下下一个元素
}
free(pCur->_pNext);//删除指针后需将其置空
pCur->_pNext = NULL;
}
普通插入和删除:
// 在链表的pos位置后插入值为data的节点
void SListInsert(PNode pos, SDataType data) {
if (pos == NULL) {
return;
}
PNode pNewNode = BuySListNode(data);
pNewNode->_pNext = pos->_pNext;
pos->_pNext = pNewNode;
}
// 删除链表s中pos位置的节点
void SListErase(SList* s, PNode pos) {
assert(s);
if (NULL == pos || NULL == s->_pHead) {
return;
}
if (pos == s->_pHead) {
s->_pHead = s->_pHead->_pNext;
}
else
{
PNode pPrePos = s->_pHead;
while (pPrePos && pPrePos->_pNext != pos)
pPrePos = pPrePos->_pNext;
if (pPrePos)
pPrePos->_pNext = pPrePos->_pNext->_pNext;
}
free(pos);
}
-
结点查找:
// 在链表中查找值为data的节点,找到返回该节点的地址,否则返回NULL
PNode SListFind(SList* s, SDataType data) {
assert(s);
PNode pCur = s->_pHead;
while (pCur) {
//pCur = pCur->_pNext;
//查找时考虑每个元素都被遍历到,条件顺序考虑好
if (data == pCur->_data) {
return pCur;
}
pCur = pCur->_pNext;
}
return NULL;
}
-
结点个数:
// 获取链表中有效节点的个数
size_t SListSize(SList* s) {
assert(s);
size_t count = 0;
PNode pCur = s->_pHead;
while (pCur) {
count++;
pCur = pCur->_pNext;
}
return count;
}
-
链表判空和清空:
// 检测链表是否为空
int SListEmpty(SList* s) {
assert(s);
return NULL == s->_pHead;
}
// 将链表中有效节点清空
void SListClear(SList* s) {
assert(s);
while (SListSize(s)) {
SListPopBack(s);
}
free(s->_pHead);
}
-
链表销毁:
-
// 销毁链表 void SListDestroy(SList* s) { assert(s); while (SListSize(s)) { SListPopBack(s); } free(s->_pHead); } void PritfSList(SList* s) { assert(s); if (s->_pHead == NULL) { printf("NULL\n"); return; } PNode pCur = s->_pHead; while (pCur) { printf("%d---->", pCur->_data); pCur = pCur->_pNext; } printf("\n"); }
SList.h:
#pragma once
typedef int SDataType; //int 数据类型重定义
// 链表的节点
//定义一个结构体
typedef struct SListNode
{
SDataType _data; //_date 中存放结点数据域
struct SListNode* _pNext; //指针域,指向后继结点的指针
}Node, *PNode;
//自定义方式将结构定义为Node类型 在定义一个指向Node的指针类型变量Node *L 等价于 PNode L
// 链表的结构,给一个头指针保存链表第一个节点的地址
typedef struct SList
{
PNode _pHead; // 指向链表中的第一个节点
}SList, *PSList;
// 链表的初始化
void SListInit(SList* s);
// 在链表s最后一个节点后插入值为data的节点
void SListPushBack(SList* s, SDataType data);
// 删除链表s最后一个节点
void SListPopBack(SList* s);
// 在链表s第一个节点前插入值为data的节点
void SListPushFront(SList* s, SDataType data);
// 删除链表s的第一个节点
void SListPopFront(SList* s);
// 在链表的pos位置后插入值为data的节点
void SListInsert(PNode pos, SDataType data);
// 删除链表s中pos位置的节点
void SListErase(SList* s, PNode pos);
// 在链表中查找值为data的节点,找到返回该节点的地址,否则返回NULL
PNode SListFind(SList* s, SDataType data);
// 获取链表中有效节点的个数
size_t SListSize(SList* s);
// 检测链表是否为空
int SListEmpty(SList* s);
// 将链表中有效节点清空
void SListClear(SList* s);
// 销毁链表
void SListDestroy(SList* s);
//打印函数
void PritfSList(SList* s);
-
main.c:
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "SList.h"
// 链表的初始化
void SListInit(SList* s) {
assert(s);
s->_pHead = NULL;
}
//创建新的节点
PNode BuySListNode(SDataType data) {
PNode pNewNode = (PNode)malloc(sizeof(Node));
if (NULL == pNewNode) {
assert(0);
return;
}
pNewNode->_data = data;
pNewNode->_pNext = NULL;
return pNewNode;
}
// 在链表s最后一个节点后插入值为data的节点
void SListPushBack(SList* s, SDataType data) {
assert(s);
PNode pNewNode = BuySListNode(data);//创建一个新的节点
if (NULL == s->_pHead) {
s->_pHead = pNewNode;//如果链表为空,则让头节点指向新建立的节点
}
else
{
PNode pCur = s->_pHead;//创建一个变量用于寻找
while (pCur->_pNext) {
pCur = pCur->_pNext;//搜索最后一个节点为空时停下
}
pCur->_pNext = pNewNode;
}
}
// 删除链表s最后一个节点
void SListPopBack(SList* s) {
assert(s);
PNode pCur = s->_pHead;
if (pCur == NULL) {
return;
}
if (pCur->_pNext == NULL) {
free(pCur);
s->_pHead = NULL;
return;
}
while (pCur->_pNext->_pNext) {
pCur = pCur->_pNext;//寻找下下一个元素
}
free(pCur->_pNext);//删除指针后需将其置空
pCur->_pNext = NULL;
}
// 在链表s第一个节点前插入值为data的节点
void SListPushFront(SList* s, SDataType data) {
assert(s);
PNode pNewNode = BuySListNode(data);
pNewNode->_pNext = s->_pHead;
s->_pHead = pNewNode;
}
// 删除链表s的第一个节点
void SListPopFront(SList* s) {
assert(s);
if (NULL == s->_pHead) {
return;
}
PNode pDelNode = s->_pHead;
s->_pHead = s->_pHead->_pNext;
free(pDelNode);
}
// 在链表的pos位置后插入值为data的节点
void SListInsert(PNode pos, SDataType data) {
if (pos == NULL) {
return;
}
PNode pNewNode = BuySListNode(data);
pNewNode->_pNext = pos->_pNext;
pos->_pNext = pNewNode;
}
// 删除链表s中pos位置的节点
void SListErase(SList* s, PNode pos) {
assert(s);
if (NULL == pos || NULL == s->_pHead) {
return;
}
if (pos == s->_pHead) {
s->_pHead = s->_pHead->_pNext;
}
else
{
PNode pPrePos = s->_pHead;
while (pPrePos && pPrePos->_pNext != pos)
pPrePos = pPrePos->_pNext;
if (pPrePos)
pPrePos->_pNext = pPrePos->_pNext->_pNext;
}
free(pos);
}
// 在链表中查找值为data的节点,找到返回该节点的地址,否则返回NULL
PNode SListFind(SList* s, SDataType data) {
assert(s);
PNode pCur = s->_pHead;
while (pCur) {
//pCur = pCur->_pNext;
//查找时考虑每个元素都被遍历到,条件顺序考虑好
if (data == pCur->_data) {
return pCur;
}
pCur = pCur->_pNext;
}
return NULL;
}
// 获取链表中有效节点的个数
size_t SListSize(SList* s) {
assert(s);
size_t count = 0;
PNode pCur = s->_pHead;
while (pCur) {
count++;
pCur = pCur->_pNext;
}
return count;
}
// 检测链表是否为空
int SListEmpty(SList* s) {
assert(s);
return NULL == s->_pHead;
}
// 将链表中有效节点清空
void SListClear(SList* s) {
assert(s);
while (SListSize(s)) {
SListPopBack(s);
}
free(s->_pHead);
}
// 销毁链表
void SListDestroy(SList* s) {
assert(s);
while (SListSize(s)) {
SListPopBack(s);
}
free(s->_pHead);
}
void PritfSList(SList* s) {
assert(s);
if (s->_pHead == NULL) {
printf("NULL\n");
return;
}
PNode pCur = s->_pHead;
while (pCur) {
printf("%d-->", pCur->_data);
pCur = pCur->_pNext;
}
printf("\n");
}
int main() {
SList s;
SListInit(&s);//链表初始化
//尾插
SListPushBack(&s, 1);
SListPushBack(&s, 2);
SListPushBack(&s, 3);
SListPushBack(&s, 4);
SListPushBack(&s, 5);
SListPushBack(&s, 6);
PritfSList(&s);
//头删
SListPopFront(&s);
PritfSList(&s);
//尾删
SListPopBack(&s);
PritfSList(&s);
//头插
SListPushFront(&s, 9);
PritfSList(&s);
printf("%d\n", SListEmpty(&s));
printf("%d\n", SListSize(&s));
printf("%p\n", SListFind(&s, 9));
printf("%p\n", SListFind(&s, 2));
printf("%p\n", SListFind(&s, 3));
printf("%p\n", SListFind(&s, 4));
printf("%p\n", SListFind(&s, 5));
//指定位置插入
SListInsert(SListFind(&s, 3), 8);
PritfSList(&s);
//指定位置删除
SListErase(&s, SListFind(&s, 3));
PritfSList(&s);
SListClear(&s);
PritfSList(&s);
SListDestroy(&s);
PritfSList(&s);
system("pause");
return 0;
}
-
运行结果: