文章目录
一、概念
概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
二、分类
根据单向双向、带头不带头、循环不循环可以分为8种链表结构。
1.单向或双向:
2.带头或不带头:
3.循环或不循环
虽然链表结构众多,但实际中最常用还是两种结构:无头单向不循环链表和带头双向循环链表。接下来我们来实现这两种链表。
三、无头单向不循环链表
1.SList.h
先在SList.h中声明需要用到的函数和结构体。
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLDataType;
typedef struct SListNode
{
struct SListNode* next;
SLDataType val;
}SLNode;
void SLPrint(SLNode* phead);
void SLDestory(SLNode* phead);
void SLPushBack(SLNode** phead, SLDataType x);
void SLPushFront(SLNode** phead, SLDataType x);
void SLPopBack(SLNode** phead);
void SLPopFront(SLNode** phead);
void SLInsert(SLNode** phead, SLNode* pos, SLDataType x);
void SLErase(SLNode** phead, SLNode* pos);
定义节点
typedef struct SListNode
{
struct SListNode* next;
SLDataType val;
}SLNode;
在单链表的节点的结构中我们需要一个指向下一节点的指针next,和存数据的val,这里用结构体实现,最后一个节点中的指针存NULL。
2.Slist.c
a.创建节点
SLNode* CreateSLNode(SLDataType x)
{
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
else
{
newnode->next = NULL;
newnode->val = x;
}
return newnode;
}
将节点的指针域置空,数据域存放指定的数据x。
b.打印链表
void SLPrint(SLNode* phead)
{
assert(phead);
SLNode* cur = phead;
while (cur != NULL)
{
printf("%d=>", cur->val);
cur = cur->next;
}
printf("\n");
}
从头节点开始打印节点中的数据,while循环保证打印到尾节点,此时cur为尾节点的下一个,为空。
c.销毁链表
void SLDestroy(SLNode** phead)
{
assert(phead);
SLNode* cur = *phead;
while (cur)
{
SLNode* next = cur->next;
free(cur);
cur = next;
}
*pphead = NULL;
}
free后记得置空,防止后续错误的解引用导致程序崩溃。
d.尾插
void SLPushBack(SLNode** phead, SLDataType x)
{
SLNode* newnode = CreateSLNode(x);
if (*phead == NULL)
{
*phead = newnode;
}
else
{
SLNode* cur = *phead;
while (cur->next)
{
cur = cur->next;
}
cur->next = newnode;
}
}
先判断链表中有没有节点,没有就令首节点为要插入的节点,有的话先找到尾节点,再使原先尾节点的指针指向要插入的节点。(创建节点时已经让节点指向空,所以不用再让插入的节点指向空)。这里用二级指针做形参是为了改变节点的指针,不使用二级指针的话也可以将函数的类型改写为返回节点一级指针的类型。
e.头插
void SLPushFront(SLNode** phead, SLDataType x)
{
SLNode* newnode = CreateSLNode(x);
if (*phead == NULL)
{
*phead = newnode;
}
else
{
newnode->next = *phead;
*phead = newnode;
}
}
和尾插同理,先检查首节点是否为空,注意头插后新节点成为首节点,所以要将新节点赋值给首节点。(*phead = newnode;)
f.尾删
void SLPopBack(SLNode** phead)
{
assert(*phead);
SLNode* cur = *phead;
while (cur->next->next!=NULL)
{
cur = cur->next;
}
SLNode*next = cur->next;
free(next);
next = NULL;
cur->next = NULL;
}
尾删找尾节点的前一个节点,将其指向空,原来的尾节点释放并置空。
g.头删
void SLPopFront(SLNode** phead)
{
assert(*phead);
SLNode* next = (*phead)->next;
free(*phead);
*phead = next;
}
将第二个节点赋值给原来的首节点。
h.找节点
SLNode* SLTFind(SLNode* phead, SLNDataType x)
{
SLNode* cur = phead;
while (cur)
{
if (cur->val == x)
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL;
}
i.定点插
void SLInsert(SLNode** phead, SLNode* pos, SLDataType x)
{
assert(*phead && phead);
if (pos == *phead)
{
SLPushFront(phead, x);
}
else
{
SLNode* cur = *phead;
while (cur->next != pos)
{
cur = cur->next;
}
SLNode* newnode = CreateSLNode(x);
cur->next = newnode;
newnode->next = pos;
}
}
先确保链表不为空,如果插入位置是首节点就直接调用前面的头插函数,不是就先找到pos的前一个节点,让pos前一个节点指向我们要插入的新节点(cur->next = newnode;),新节点指向pos节点(newnode->next = pos;)。
j.定点删
void SLErase(SLNode** phead,SLNode*pos)
{
assert(*phead && phead);
if (*phead == pos)
{
SLPopFront(phead);
}
else
{
SLNode* cur = *phead;
while (cur->next != pos)
{
cur = cur->next;
}
cur->next = pos->next;
}
}
先判断pos节点是不是首节点,是就调用前面的头删函数,不是就找到pos前一个节点,让pos前一个节点指向pos的后一个节点( cur->next = pos->next;)。
整体代码
#include"SList.h"
SLNode* CreateSLNode(SLDataType x)
{
SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
else
{
newnode->next = NULL;
newnode->val = x;
}
return newnode;
}
void SLDeastory(SLNode*phead)
{
assert(phead);
SLNode* cur = phead;
while (cur != NULL)
{
SLNode* next = cur->next;
free(cur);
cur = next;
}
}
void SLPrint(SLNode* phead)
{
assert(phead);
SLNode* cur = phead;
while (cur != NULL)
{
printf("%d=>", cur->val);
cur = cur->next;
}
printf("\n");
}
void SLPushBack(SLNode** phead, SLDataType x)
{
SLNode* newnode = CreateSLNode(x);
if (*phead == NULL)
{
*phead = newnode;
}
else
{
SLNode* cur = *phead;
while (cur->next)
{
cur = cur->next;
}
cur->next = newnode;
}
}
void SLPushFront(SLNode** phead, SLDataType x)
{
SLNode* newnode = CreateSLNode(x);
if (*phead == NULL)
{
*phead = newnode;
}
else
{
newnode->next = *phead;
*phead = newnode;
}
}
void SLPopBack(SLNode** phead)
{
assert(*phead);
SLNode* cur = *phead;
while (cur->next->next!=NULL)
{
cur = cur->next;
}
SLNode*next = cur->next;
free(next);
next = NULL;
cur->next = NULL;
}
void SLPopFront(SLNode** phead)
{
assert(*phead);
SLNode* next = (*phead)->next;
free(*phead);
*phead = next;
}
void SLInsert(SLNode** phead, SLNode* pos, SLDataType x)
{
assert(*phead && phead);
if (pos == *phead)
{
SLPushFront(phead, x);
}
else
{
SLNode* cur = *phead;
while (cur->next != pos)
{
cur = cur->next;
}
SLNode* newnode = CreateSLNode(x);
cur->next = newnode;
newnode->next = pos;
}
}
void SLErase(SLNode** phead,SLNode*pos)
{
assert(*phead && phead);
if (*phead == pos)
{
SLPopFront(phead);
}
else
{
SLNode* cur = *phead;
while (cur->next != pos)
{
cur = cur->next;
}
cur->next = pos->next;
}
}
3.test.c
下面我们来测试一下
先是尾插:
#include"SList.h"
int main()
{
SLNode* plist = NULL;
SLPushBack(&plist, 1);
SLPushBack(&plist, 2);
SLPushBack(&plist, 3);
SLPushBack(&plist, 4);
/*SLPushFront(&plist, 5);
SLPushFront(&plist, 6);
SLPopBack(&plist);
SLPopFront(&plist);
SLInsert(&plist, plist->next->next, 7);
SLErase(&plist,plist);*/
SLPrint(plist);
return 0;
}
头插:
#include"SList.h"
int main()
{
SLNode* plist = NULL;
SLPushBack(&plist, 1);
SLPushBack(&plist, 2);
SLPushBack(&plist, 3);
SLPushBack(&plist, 4);
SLPushFront(&plist, 5);
SLPushFront(&plist, 6);
/*SLPopBack(&plist);
SLPopFront(&plist);
SLInsert(&plist, plist->next->next, 7);
SLErase(&plist,plist);*/
SLPrint(plist);
return 0;
}
头删尾删:
int main()
{
SLNode* plist = NULL;
SLPushBack(&plist, 1);
SLPushBack(&plist, 2);
SLPushBack(&plist, 3);
SLPushBack(&plist, 4);
SLPushFront(&plist, 5);
SLPushFront(&plist, 6);
SLPopBack(&plist);
SLPopFront(&plist);
/*SLInsert(&plist, plist->next->next, 7);
SLErase(&plist,plist);*/
SLPrint(plist);
return 0;
}
定点插:
int main()
{
SLNode* plist = NULL;
SLPushBack(&plist, 1);
SLPushBack(&plist, 2);
SLPushBack(&plist, 3);
SLPushBack(&plist, 4);
SLPushFront(&plist, 5);
SLPushFront(&plist, 6);
SLPopBack(&plist);
SLPopFront(&plist);
SLInsert(&plist, plist->next->next, 7);
/*SLErase(&plist,plist);*/
SLPrint(plist);
return 0;
}
定点删:
int main()
{
SLNode* plist = NULL;
SLPushBack(&plist, 1);
SLPushBack(&plist, 2);
SLPushBack(&plist, 3);
SLPushBack(&plist, 4);
SLPushFront(&plist, 5);
SLPushFront(&plist, 6);
SLPopBack(&plist);
SLPopFront(&plist);
SLInsert(&plist, plist->next->next, 7);
SLErase(&plist,plist->next);
SLPrint(plist);
return 0;
}
四、带头双向循环链表
1.List.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;
struct ListNode* prev;
LTDataType val;
}LTNode;
LTNode* Init();
LTNode* LTFind(LTNode* phead, LTDataType x);
void LTPrint(LTNode* phead);
void LTDestory(LTNode* phead);
void LTInsert(LTNode* phead, LTDataType x);
void LTErase(LTNode* phead);
void LTPushBack(LTNode* phead, LTDataType x);
void LTPushFront(LTNode* phead, LTDataType x);
void LTPopBack(LTNode* phead);
void LTPopFront(LTNode* phead);
循环双向链表中我们需要节点中存放两个指针和一个数据,两个指针一个指向前面的节点,一个指向后面的节点。
2.List.c
a.创建节点
LTNode* CreateLTNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail");
exit(-1);
}
newnode->next = NULL;
newnode->prev = NULL;
newnode->val = x;
return newnode;
}
节点中的两个指针置空,数据放指定的x。
b.初始化哨兵位
LTNode* Init()
{
LTNode* phead = CreateLTNode(0);
phead->next = phead;
phead->prev = phead;
return phead;
}
先将哨兵位前后节点都指向自己。
c.打印链表
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
printf("哨兵位<=>");
while (cur != phead)
{
printf("%d<=>", cur->val);
cur = cur->next;
}
printf("\n");
}
d.销毁链表
void LTDestory(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);//调用销毁函数后置空phead或用二级指针写销毁函数
}
j.定点插
带头双向循环链表虽然结构复杂,但实现很简单,头尾的插入删除都可以通过定点的插入删除实现,所以我们先实现定点的插入删除。
void LTInsert(LTNode* pos, LTDataType x)
{
LTNode* newnode = CreateLTNode(x);
LTNode* posprev = pos->prev;
newnode->next = pos;
pos->prev = newnode;
newnode->prev = posprev;
posprev->next = newnode;
}
让插入的新节点在pos与pos前一节点间创建相互的指向关系。
k.定点删
void LTErase(LTNode* pos)
{
LTNode* prev = pos->prev;
LTNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
}
让要删除的pos的前后节点间形成相互指向关系
e.尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTInsert(phead, x);
}
先判断链表不为空,再在首节点前差入节点。
f.头插
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTInsert(phead->next, x);
}
g.尾删
void LTPopBack(LTNode* phead)
{
assert(phead);
LTErase(phead->prev);
}
同理尾插,调用定点删除。
h.头删
void LTPopFront(LTNode* phead)
{
assert(phead);
LTErase(phead->next);
}
i.找节点
LTNode* LTFind(LTNode* phead, LTDataType x)
{
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->val == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
整体代码:
#include"List.h"
//创建节点
LTNode* CreateLTNode(LTDataType x)
{
LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
if (newnode == NULL)
{
perror("malloc fail");
}
newnode->next = NULL;
newnode->prev = NULL;
newnode->val = x;
return newnode;
}
//初始化哨兵位
LTNode* Init()
{
LTNode* phead = CreateLTNode(0);
phead->next = phead;
phead->prev = phead;
return phead;
}
//打印链表
void LTPrint(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
printf("哨兵位<=>");
while (cur != phead)
{
printf("%d<=>", cur->val);
cur = cur->next;
}
printf("\n");
}
//销毁
void LTDestory(LTNode* phead)
{
assert(phead);
LTNode* cur = phead->next;
while (cur != phead)
{
LTNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);//调用销毁函数后置空phead或用二级指针写销毁函数
}
//定点插入
void LTInsert(LTNode* pos, LTDataType x)
{
LTNode* newnode = CreateLTNode(x);
LTNode* posprev = pos->prev;
newnode->next = pos;
pos->prev = newnode;
newnode->prev = posprev;
posprev->next = newnode;
}
void LTPushBack(LTNode* phead, LTDataType x)
{
assert(phead);
LTInsert(phead, x);
}
void LTPushFront(LTNode* phead, LTDataType x)
{
assert(phead);
LTInsert(phead->next, x);
}
void LTErase(LTNode* pos)
{
LTNode* prev = pos->prev;
LTNode* next = pos->next;
prev->next = next;
next->prev = prev;
free(pos);
}
void LTPopBack(LTNode* phead)
{
assert(phead);
LTErase(phead->prev);
}
void LTPopFront(LTNode* phead)
{
assert(phead);
LTErase(phead->next);
}
LTNode* LTFind(LTNode* phead, LTDataType x)
{
LTNode* cur = phead->next;
while (cur != phead)
{
if (cur->val == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
3.test.c
下面测试一下:
#include"List.h"
int main()
{
LTNode* plist = Init();
LTPushBack(plist, 1);
LTPushBack(plist, 2);
LTPushBack(plist, 3);
LTPushBack(plist, 4);
LTPushFront(plist, 234);
LTNode* pos = LTFind(plist, 2);
if (pos)
{
pos->val *= 1000;
}
LTPrint(plist);
LTDestory(plist);
plist = NULL;
return 0;
}