单链表
单链表
概念:链表是一个物理存储结构非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
链表不单这一种,还分为带头和不带头,单向和双向,循环和非循环,组合下来一共8种。
但常用的就是单链表和带头双向循环链表
上图中的例子就是一个单链表
- 无头单向非循环链表:结构简单,一般不会用来单独存储数据。实际中更多的是作为其他数据的子结构。
- 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表,虽然结构复杂,但是在代码实现中有很多优势,反而变得简单了。
单链表实现
//定义结点
typedef int SLTDataType
typedef struct SListNode
{
SLTDataType data;
struct SListNode* next;
}SListNode;
//定义链表的头和尾
typedef struct SList
{
SListNode* head;
SListNode* tail;
}SList;
单链表打印
void SListPrint(SList* plist)
{
SListNode* cur = plist->head;
while(cur != plist->tail->next)//注意这里cur一定是走到空时才打印完毕
{
printf("%d ",cur->data);
cur=cur->next;
}
printf("\n");
}
单链表初始化
void SListInit(SList* plist)
{
assert(plist);
plist->head=plist->tail=NULL;
}
单链表销毁
void SListDestory(SList* plist)
{
SListNode* cur;
assert(plist);
cur = plist->_head;//将cur作为销毁的对象,初始化为头节点
while (cur != NULL)
{
SListNode* next = cur->next;//用next记录cur的下一个,使cur依次向后移动
free(cur);
cur = next;//让节点依次向后走,达到逐一释放的目的
}
plist->head = plist->tail = NULL;//所有节点释放之后,将头尾结点置为空
}
创建一个结点
SListNode* BuySListNode(SLTDataType x)
{
SListNode* node = malloc(sizeof(SListNode));
assert(node);
node->data = x;
node->next = NULL;
return node;
}
单链表尾插结点
void SListPushBack(SList* plist, SLTDataType x)
{
assert(plist);
if (plist->tail == NULL)//说明链表为空
{
plist->head =plist->tail = BuySListNode(x);//即尾插数据即是头又是尾
}
else
{ //说明链表不为空
SListNode* newnode = BuySListNode(x);//为新节点创建数据部分和指针部分
plist->_tail->next=newnode;//链表的尾节点的指针指向新节点
plist->_tail= newnode;//新节点就成为新的尾节点
}
}
单链表尾删结点
void SListPopBack(SList* plist)
{
SListNode* prev, *cur;//prev的作用是为了记录cur前一个节点,cur作为删除目标
assert(plist);
prev = NULL;//prev初始化为NULL
cur = plist->head;//将cur初始化为头结点
if (cur->next == NULL)//说明只有一个节点
{
free(cur);//直接释放cur
plist->head = NULL;//将头结点置空
}
else
{
while (cur->next)//说明节点不止一个,cur,prev依次向后走,直到cur走到链表末尾
{
prev = cur;
cur = cur->next;
}
free(cur);//此时cur在末尾位置,将它释放
prev->next = NULL;//将prev的指针置为NULL
}
}
单链表头插结点
void SListPushFront(SList* plist, SLTDataType x)
{
SListNode* newnode;
assert(plist);
newnode = BuySListNode(x);//为新节点赋数值和指针
newnode->next = plist->head;//将新节点的指针部分指向头结点
plist->head = newnode;//将新节点做为新的头结点
}
单链表头删结点
void SListPopFront(SList* plist)
{
SListNode* cur;
assert(plist);
cur = plist->head->next;//用cur保存头结点的下一个结点
free(plist->head);//将头结点释放
plist->head = cur;//将cur变成新的头结点
}
单链表查找一个结点
SListNode* SListFind(SList* plist, SLTDataType x)
{
assert(plist);
SListNode* cur = plist->head;
while (cur)
{
if (cur->data == x)
{
return cur;
}
else
{
cur = cur->next;
}
}
return NULL;
}
在指定位置的后面插入一个结点
void SListInsertAfter(SListNode* pos, SLTDataType x)
{
SListNode* cur, *newnode;
assert(pos);
cur = pos->next;//将cur记录为pos的下一个结点
newnode = BuySListNode(x);
pos->next = newnode;//将新节点赋给pos的下一个
newnode->next = cur;//将新节点的下一个置为cur
}
删除指定位置的后面的结点
void SListEraseAfter(SListNode* pos)
{
SListNode* cur;
assert(pos);
if (pos->next == NULL)
{
return ;
}
cur = pos->next;
pos->next = cur->next;
free(cur);
cur = NULL;
}
删除链表中指定值第一次出现的结点
void SListRemove(SList* plist, SLTDataType x)
{
assert(plist);
if (plist->head->data == x)
{
SListPopFront(plist);//如果头结点就是指定值,直接头删
return ;
}
SListNode* prev;
SListNode* cur = plist->head;
while (cur)
{
if (cur->data == x)
{
prev->next = cur->next;//当前prev在cur的前一个节点
free(cur);
cur = NULL;
break;
}
else
{
prev = cur;//prev保存当前cur所在节点
cur = cur->next;//cur走向下一个节点,此时prev在cur的前一个节点
}
}
}
删除链表中指定值的所有节点
void SListALLRemove(SList* plist, SLTDataType x)
{
assert(plist);
if (plist->_head->_data == x)
{
SListPopFront(plist);
return;
}
SListNode* prev;
SListNode* cur = plist->_head;
while (cur != plist->_tail->_next)
{
if (cur->_data == x)
{
prev->_next = cur->_next;
cur = prev->_next;
}
else
{
prev = cur;
cur = cur->_next;
}
}
}
测试函数
void TestSList()
{
SList sl;
SListInit(&sl);
SListPushBack(&sl, 1);
SListPushBack(&sl, 2);
SListPushBack(&sl, 3);
SListPushBack(&sl, 2);
SListPushBack(&sl, 4);
SListPushBack(&sl, 2);
SListPushBack(&sl, 5);
SListPushBack(&sl, 2);
SListPrint(&sl);
SListPopBack(&sl);
SListPushFront(&sl, 7);
SListPopFront(&sl);
SListNode* pos = SListFind(&sl, 2);
SListInsertAfter(pos,100);
SListEraseAfter(pos);
SListRemove(&sl, 3);
SListRemove(&sl,2);
SListPrint(&sl);
SListDestory(&sl);
}
链表增删一定要兼顾前后,处理好前后结点的链接关系,一定要保持链表连通