链表是一种在物理存储结构上非连续、非顺序的存储结构数据元素的逻辑顺序通过链表中的指针来连接实现的。
如上如所示,链表的结构在逻辑上是连续的,但是在物理地址上并非连续(图示是连续的,失误)。而且链表还分为单向或双向、带头或不带头、循环或非循环 。
接下来介绍无头单向非循环链表:
typedef int SLTDateType; typedef struct SListNode { SLTDateType data; struct SListNode* next; }SListNode;
这里,date是用来存储数据,next用来找到下一个节点。这里还要注意一个点对于结构体内部依然要struct SListNode* next;因为在结构体内部无法识别SListNode,因此要加上struct。
// 动态申请一个节点
SListNode* BuySListNode(SLTDateType x);
// 打印
void SListPrint(SListNode* plist);
// 尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 尾删
void SListPopBack(SListNode** pplist);
// 头删
void SListPopFront(SListNode** pplist);
// 查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// pos后插x
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 删除pos位置之后数据
void SListEraseAfter(SListNode* pos);
单向链表的基本接口实现:
#define _CRT_SECURE_NO_WARNINGS 1
#include "SingleList.h"
//动态申请一个节点
SListNode* BuySListNode(SLTDateType x) {
SListNode* node = (SListNode*)malloc(sizeof(SListNode));
if (node == NULL) {
printf("malloc fail\n");
exit(-1);
}
node->val = x;
node->next = NULL;
return node;
}
//打印
void SListPrint(SListNode* plist) {
assert(plist);
SListNode* tmp = plist;
while (tmp != NULL) {
printf("%d ", tmp->val);
tmp = tmp->next;
}
free(tmp);
tmp = NULL;
printf("\n");
}
//尾插
void SListPushBack(SListNode** pplist, SLTDateType x) {
assert(pplist);
//创建节点:
SListNode* node = BuySListNode(x);
//找到最后一个节点,连接即可
// 考虑空链表的情况
if (*pplist == NULL) {
*pplist = node;
}
else {
//找尾结点:
SListNode* tmp = *pplist;
while (tmp->next != NULL) {
tmp = tmp->next;
}
//连接
tmp->next = node;
}
}
//头插
void SListPushFront(SListNode** pplist, SLTDateType x) {
assert(pplist);
//创建节点
SListNode* node = BuySListNode(x);
node->next = *pplist;
*pplist = node;
}
//尾删
void SListPopBack(SListNode** pplist) {
assert(pplist);
//尾删要分为为多个情况:
// 1、空
// 2、一个节点
// 3、多个节点
if (*pplist == NULL) {
return NULL;
}
else if ((*pplist)->next == NULL) {
free(*pplist);
*pplist = NULL;
}
else {
//多个节点情况:找到尾节点前一个节点
SListNode* prev = *pplist;
SListNode* tmp = (*pplist)->next;
while (tmp->next != NULL) {
prev = prev->next;
tmp = tmp->next;
}
prev->next = NULL;
free(tmp);
tmp = NULL;
}
}
//头删
void SListPopFront(SListNode** pplist) {
assert(pplist);
//找到头结点的下一个节点
// 空链表
if ((*pplist)==NULL) {
return;
}
else {
//SListNode* node = (*pplist)->next;
//free(*pplist);
//*pplist = node;
SListNode* node = *pplist;
*pplist = (*pplist)->next;
free(node);
node = NULL;
}
}
//查找
SListNode* SListFind(SListNode* plist, SLTDateType x) {
assert(plist);
SListNode* node = plist;
while (node != NULL) {
if (node->val == x) {
return node;
}
node = node->next;
}
return NULL;
}
//插入:pos位置之后插入
void SListInsertAfter(SListNode* pos, SLTDateType x) {
assert(pos);
SListNode* tmp = BuySListNode(x);
//pos后面节点:
SListNode* afterPos = pos->next;
//连接:
pos->next = tmp;
tmp->next = afterPos;
}
//pos位置之后节点消除:
void SListEraseAfter(SListNode* pos) {
assert(pos);
if (pos->next == NULL) {
return;
}
else {
SListNode* node = pos->next;
pos->next = node->next;
free(node);
node = NULL;
}
}
//单链表销毁
void SListDestroy(SListNode** pplist) {
assert(pplist);
SListNode* tmp = *pplist;
while (tmp) {
SListNode* node = tmp->next;
free(tmp);
tmp = node;
}
*pplist = NULL;
}
int main() {
SListNode* n1 = (SListNode*)malloc(sizeof(SListNode));
SListNode* n2 = (SListNode*)malloc(sizeof(SListNode));
SListNode* n3 = (SListNode*)malloc(sizeof(SListNode));
n1->next = n2;
n2->next = n3;
n3->next = NULL;
n1->val = 1;
n2->val = 1;
n3->val = 1;
return 0;
}
在尾插法中, 要考虑链表为空和不为空的情况;
在尾插法中,要考虑空链表、单个节点、多个节点三种情况;
在实现单链表的增删操作中,
- 如果链表的头节点不为空时,可以使用一级指针实现增删操作,因为头节点不为空时,可以直接通过一级指针phead对next进行操作。
- 如果链表头节点为空时,也就是说链表中无节点,即phead=NULL,因此需要使用二级指针pphead,通过二级指针对phead内部的next进行修改操作。
双向链表的基本接口实现
在此实现的是带头、双向、循环链表;
typedef int LTDataType; typedef struct ListNode { LTDataType _data; struct ListNode* next; struct ListNode* prev; }ListNode;
如上,在双向循环的链表中既记录了前一个节点,也记录了后一个节点,这样使得链表的效率得到提升。具体结构如下图所示:next记录了下一个节点的地址,prev记录了上一个节点的地址。
如上,带头的双向循环链表,第一个节点不存在任何数据,充当一个领头的节点。
// 创建返回链表的头结点.
ListNode* ListCreate();
// 双向链表销毁
void ListDestory(ListNode* plist);
// 双向链表打印
void ListPrint(ListNode* plist);
// 双向链表尾插
void ListPushBack(ListNode* plist, LTDataType x);
// 双向链表尾删
void ListPopBack(ListNode* plist);
// 双向链表头插
void ListPushFront(ListNode* plist, LTDataType x);
// 双向链表头删
void ListPopFront(ListNode* plist);
// 双向链表查找
ListNode* ListFind(ListNode* plist, LTDataType x);
// 双向链表在pos的前面进行插入
void ListInsert(ListNode* pos, LTDataType x);
// 双向链表删除pos位置的节点
void ListErase(ListNode* pos);
双向链表的接口实现:
//创建并返回链表的头结点
ListNode* ListCreate() {
ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
if (phead == NULL) {
printf("malloc fail;\n");
exit(-1);
}
phead->val = 0;
phead->next = phead;
phead->prev = phead;
}
// 双向链表销毁
void ListDestory(ListNode* plist) {
ListNode* node = plist->next;
while (node != plist) {
ListNode* tmp = node->next;
free(node);
tmp = node;
}
free(plist);
}
// 创建节点
ListNode* BuyListNode(LTDataType x) {
ListNode* node = (ListNode*)malloc(sizeof(ListNode));
if (node == NULL) {
printf("malloc fail;\n");
exit(-1);
}
node->next = NULL;
node->prev = NULL;
node->val = x;
}
// 链表打印
void ListPrint(ListNode* plist) {
assert(plist);
ListNode* node = plist->next;
while (node != plist) {
printf("%d ", node->val);
node = node->next;
}
printf("\n");
}
// 链表尾插
void ListPushBack(ListNode* plist, LTDataType x) {
ListNode* node = BuyListNode(x);
//记录后一个节点
ListNode* end = plist->prev;
//连接
end->next = node;
node->prev = end;
node->next = plist;
plist->prev = node;
}
// 尾删
void ListPopBack(ListNode* plist) {
assert(plist);
//只有一个节点
if (plist->next == plist) {
printf("单节点无法尾删\n");
//exit(-1);
return;
}
//找到 最后一个节点 与 倒数第二个节点
ListNode* end = plist->prev;
ListNode* endPrev = end->prev;
plist->prev = endPrev;
endPrev->next = plist;
free(end);
end = NULL;
}
// 头插
void ListPushFront(ListNode* plist, LTDataType x) {
assert(plist);
//创建节点:
ListNode* node = BuyListNode(x);
//连接:
ListNode* tmp = plist->next;
node->next = tmp;
tmp->prev = node;
plist->next = node;
node->prev = plist;
}
//头删
void ListPopFront(ListNode* plist) {
assert(plist);
if (plist->next == plist) {
printf("单个节点无法头删;\n");
return;
}
//记录 第一个 和 第二个节点
ListNode* head = plist->next;
ListNode* sec = head->next;
plist->next = sec;
sec->prev = plist;
free(head);
head = NULL;
}
// 查找
ListNode* ListFind(ListNode* plist, LTDataType x) {
assert(plist);
ListNode* tmp = plist->next;
while (tmp != plist) {
if (tmp->val == x) {
return tmp;
}
tmp = tmp->next;
}
return NULL;
}
// pos前插
void ListInsert(ListNode* pos, LTDataType x) {
assert(pos);
ListNode* node = BuyListNode(x);
//记录pos前一个节点
ListNode* posPrev = pos->prev;
//连接
posPrev->next = node;
node->prev = posPrev;
pos->prev = node;
node->next = pos;
}
// pos删除
void ListErase(ListNode* pos) {
assert(pos);
if (pos->next == pos) {
printf("单个节点无法删除;\n");
return;
}
//找到pos前后接点
ListNode* posPrev = pos->next;
ListNode* posAfter = pos->prev;
posPrev->next = posAfter;
posAfter->prev = posPrev;
free(pos);
pos = NULL;
}
事实上,在头插、尾插和头删、尾删操作中可以调用Insert和Erase函数简化代码操作。