赞
踩
目录
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序时通过链表中的指针链接次序实现的。
从上图可以看出:
1.链式结构在逻辑上时连续的,但在物理上不一定连续
2.显示中的节点一般都是从堆上申请出来的
3.从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续
- #pragma once
-
- #include <stdio.h>
- #include <string.h>
- #include <stdlib.h>
- #include <assert.h>
-
- typedef int SLTDataType;
-
- typedef struct SListNode
- {
- SLTDataType data;
- struct SListNode* next;
- }SListNode;
-
-
- //单链表打印
- void SListPrint(SListNode* phead);
- //单链表销毁
- void SListDestory(SListNode** pphead);
-
-
- //单链表尾插
- void SListPushBack(SListNode** pphead, SLTDataType x);
- //单链表头插
- void SListPushFront(SListNode** pphead, SLTDataType x);
- //单链表尾删
- void SListPopBack(SListNode** pphead);
- //单链表头删
- void SListPopFront(SListNode** pphead);
-
-
- //找到x数据元素的位置
- SListNode* SListFind(SListNode* phead, SLTDataType x);
-
-
- //在pos位置之前插入x
- void SListInsertBefore(SListNode** pphead, SListNode* pos, SLTDataType x);
- //在pos位置之后插入x
- void SListInsertAfter(SListNode* pos, SLTDataType x);
- //删除pos位置的数据
- void SListErase(SListNode** pphead, SListNode* pos);
- //单链表打印
- void SListPrint(SListNode* phead)
- {
- //创建新指针cur,并用phead赋值
- SListNode* cur = phead;
-
- //当cur为NULL时循环结束
- while (cur)
- {
- printf("%d->", cur->data);
-
- //将cur下一个结点赋值给cur
- cur = cur->next;
- }
-
- printf("NULL\n");
- }
- #include "SList.h"
-
- int main()
- {
- //创建头节点
- SListNode* slist;
- //创建结点
- SListNode* n1 = (SListNode*)malloc(sizeof(SListNode));
- SListNode* n2 = (SListNode*)malloc(sizeof(SListNode));
- SListNode* n3 = (SListNode*)malloc(sizeof(SListNode));
- n1->data = 1;
- n2->data = 2;
- n3->data = 3;
- n1->next = n2;
- n2->next = n3;
- n3->next = NULL;
-
-
- //将n1赋值给头节点
- slist = n1;
- SListPrint(slist);
-
- return 0;
- }
结果:
由于头插、尾插等都需要建立新结点,所以将结点功能作为独立函数,方便后续调用,避免函数之间的冗余。
- //创建新节点
- SListNode* BuySListNode(SLDataType x)
- {
- SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
- if (newnode == NULL)
- {
- printf("malloc fail\n");
- exit(-1);
- }
- else
- {
- newnode->data = x;
- newnode->next = NULL;
- }
- }
1.若phead指向的头节点为空,则要将创建的新节点赋值给phead
2.若phead指向的头节点不为空,则要找尾(tail),并将新节点赋值给tail->next
- //单链表尾插
- void SListPushBack(SListNode* phead, SLDataType x)
- {
- SListNode* newnode = BuySListNode(x);
- if (phead == NULL)
- {
- phead = newnode;
- }
- else
- {
- //找尾
- SListNode* tail = phead;
- while (tail->next)
- {
- tail = tail->next;
- }
- tail->next = newnode;
- }
- }
既然尾插函数写完了,那么让我们来进行测试。
Test.c
我们进行了4次尾插,原本期待的结果是 0->1->2->3->NULL,但是运行出来的结果却是NULL。
那么是什么错误导致结果与预想有出入呢?
这个错误涉及到函数的传值传址。
我们将头结点slist传给SListNode* phead 实际上只是传了值。phead作为形参改变,不影响到实参。当链表为空时,我们将新结点赋值给phead影响不了原来的slist,那么后续操作也就都无效了。
解决方法:进行传址,将slist的地址传给SListNode** pphead,对pphead进行操作。
- //单链表尾插 correct
- void SListPushBack(SListNode** pphead, SLDataType x)
- {
- SListNode* newnode = BuySListNode(x);
- if (*pphead == NULL)
- {
- *pphead = newnode;
- }
- else
- {
- //找尾
- SListNode* tail = *pphead;
- while (tail->next)
- {
- tail = tail->next;
- }
- tail->next = newnode;
- }
- }
- //单链表头插
- void SListPushFront(SListNode** pphead, SLDataType x)
- {
- SListNode* newnode = BuySListNode(x);
- newnode->next = *pphead;
- *pphead = newnode;
- }
尾删分为三种情况:
1.当链表为空的时候,直接返回 return
2.当链表只有一个节点的时候,直接释放并置空头节点
3.当链表有两个以上节点的时候,用tail遍历链表,当tail->next->next ==NULL时,遍历结束并将tail->next释放置空。
- //单链表尾删
- void SListPopBack(SListNode** pphead)
- {
- if (*pphead == NULL)
- {
- return;
- }
- else if ((*pphead)->next == NULL)
- {
- free(*pphead);
- *pphead = NULL;
- }
- else
- {
- SListNode* tail = *pphead;
- while (tail->next->next)
- {
- tail = tail->next;
- }
-
- free(tail->next);
- tail->next = NULL;
- }
- }
单链表头删分两种情况:
1.当单链表为空时,直接return
2.当单链表不为空时,将头指针置空,并将头指针next赋值给头指针
- //单链表头删
- void SListPopFront(SListNode** pphead)
- {
- if (*pphead == NULL)
- {
- return;
- }
- else
- {
- SListNode* next = (*pphead)->next;
- free(*pphead);
- *pphead = next;
- }
- }
- //单链表查找
- SListNode* SListFind(SListNode* phead, SLDataType x)
- {
- SListNode* cur = phead;
- while (cur)
- {
- if (cur->data == x)
- {
- return cur;
- }
-
- cur = cur->next;
- }
-
- return NULL;
- }
分为两种情况:
1.当pos位置为头节点时,使用头插函数。
2.当pos为头节点之外的其它位置时,用prev->next遍历链表找到pos,创建新节点插入到prev和prev->next之间。
- //在pos位置之前插入x
- void SListInsertBefore(SListNode** pphead, SListNode* pos, SLDataType x)
- {
- assert(pphead);
- assert(pos);
- if (pos == *pphead)
- {
- SListNodeFront(pphead, x);
- }
- else
- {
- SListNode* prev = *pphead;
- while (prev->next != pos)
- {
- prev = prev->next;
- }
-
- SListNode* newnode = BuySListNode(x);
- prev->next = newnode;
- newnode->next = pos;
- }
- }
- //在pos位置之后插入x
- void SListInsertAfter(SListNode* pos, SLDataType x)
- {
- assert(pos);
-
- SListNode* newnode = BuySListNode(x);
- newnode->next = pos->next;
- pos->next = newnode;
- }
分为两种情况:
1.当pos为头节点时,调用头删。
2.当pos不为头节点时,用prev->next指向pos->next,释放并置空pos
- //删除pos位置的数据
- void SListErase(SListNode** pphead, SListNode* pos)
- {
- assert(pphead);
- assert(pos);
-
- if (*pphead == pos)
- {
- SListNodePopFront(pphead);
- }
- else
- {
- SListNode* prev = *pphead;
- while (prev->next != pos)
- {
- prev = prev->next;
- }
- prev->next = pos->next;
- free(pos);
- pos = NULL;
- }
- }
小结:单链表结构适合头插,尾插或任意插入不适合。
如果要使用链表单独存储数据,后续学习的双向链表更加合适
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。