单链表—线性表的链式存储结构实现(代码)
单链表—线性表的链式存储结构实现(代码)
单链表的操作还挺多的,代码也不少,注释写了很久,不过都明白了,明白了还是要忘记呀,所以要多复习
#include<stdio.h> #include<stdlib.h> //以下操作均基于带头节点的单链表 //且我们把头节点视作第0个结点 typedef int ElemType; //定义单链表的结点类型 typedef struct LNode { ElemType data; //定义结点的数据域 struct LNode* next; //定义结点的指针域 }LNode,*LinkList; //为结构体起别名,前者突出是一个结点,后者突出一个单链表 //—————————————————————————————————————————————————————————————————————————————————————————————— //头插法新建链表 时间复杂度O(n) LinkList List_HeadInsert(LinkList& L) { int x; //需要插入的元素 LNode* s; //定义一个结点,用于后续存放被插入元素的值 L = (LinkList)malloc(sizeof(LNode));//申请一个空间,用于存放头结点(使用LinkList体现这是一个链表) L->next = NULL; //将头结点的指针域置空,因为目前单链表中尚无元素 scanf("%d", &x); //读入元素 while (x != 9999) //当读到9999表示停止读入 { s = (LNode*)malloc(sizeof(LNode)); //申请一个新空间s用于存放需要插入的元素 s->data = x; //将s的数据域置为x,即将x填入s的data域中 s->next = L->next; //将s的指针域与L的指针域指向同一个空间 L->next = s; //再将L的指针域指向s scanf("%d", &x); //继续读入元素 } return L; //插入完毕返回头插法新建好的单链表 } //尾插法新建链表 时间复杂度O(n) LinkList List_TailInsert(LinkList& L) { int x; //需要插入的元素 L = (LinkList)malloc(sizeof(LNode)); //申请一个头节点(利用LinkList体现这是一个单链表) LNode* s, * r = L; //定义一个指针s用于存放需要插入的结点,定义一个尾指针r始终指向单链表尾部 scanf("%d", &x); //读入元素 while (x != 9999) //当读到9999时表示停止读入 { s = (LNode*)malloc(sizeof(LNode));//申请一个新空间用于存放需要插入的元素 s->data = x; //将s的指针域置为x r->next = s; //将r的指针域指向s,也就是说将头节点的指针域指向s r = s; //将r=s,即尾指针r始终指向单链表尾部 scanf("%d", &x); //继续读入元素 } r->next = NULL; //所有元素插入完毕后,将尾指针置为空 return L; //返回利用尾插法新建好的单链表 } //—————————————————————————————————————————————————————————————————————————————————————————————— //按值查找 时间复杂度O(n) LNode* LocateElem(LinkList L,ElemType e)//查找给定的一个值e,然后返回数据域为e的指针(利用LNode*体现返回的是结点) { LNode* j; //新定义一个指针j j = L->next; //将j指向单链表的第一个元素 while (j != NULL && j->data != e) //从第一个结点开始查找数据域为e的结点 { j = j->next; //没找到,j就指向下一个结点 } return j; //找到了就跳出循环,并返回该结点的指针 } //按位查找 时间复杂度O(n) LNode* GetElem(LinkList L, ElemType i)//从单链表的第一个结点出发,顺指针next依序向后查找,直到找到第i个结点为止 { //否则返回最后一个结点的指针域NULL int j = 1; //定义一个int型变量j,用于计数 LNode* p = L->next; //将p指向第一个结点 if (i == 0) //如果i==0,表示查找的是头节点, return L; //就返回L(即头节点, 不过应该是一个奇怪的值,因为我们没有对头节点的数据域进行操作) if (i < 1) //即i为负值 return NULL; //i无效,返回NULL while (p != NULL && j < i) //从第一个结点开始找,找第i个结点 { p = p->next; //没找到就将p指向下一个结点 j++; //并且将j++ } return p; //当j==i时,表示找到第i个结点,返回结点指针p } //—————————————————————————————————————————————————————————————————————————————————————————————— //插入结点,按位序插入 时间复杂度O(n) bool ListInsert(LinkList& L, int i, ElemType e) //在单链表L中,第i个位置插入元素e { if (i < 1) //i<1,i无效 return false; LNode* p; //定义一个新结点p int j = 0; //定义一个整型变量j,用于计数 p = L; //将p指向头结点 while (p != NULL && j < i - 1) //查找被插入位置之前的一个元素 { p = p->next; //没找到p就指向下一个 j++; //并且将j++ } if (p == NULL) //因为i值的不合法导致了p为空(eg:i超过了(表长+1)) return false; LNode* s = (LNode*)malloc(sizeof(LNode)); //申请一个新空间s用于存放需要插入的元素 s->data = e; //这几步和头插法新建链表类似,不多赘述 s->next = p->next; p->next = s; return true; //插入成功 } //指定结点的后插操作 时间复杂度O(n) bool InsertNextNode(LNode* p, ElemType e) { if (p == NULL) //如果p为空,即在在一个空间点后面插入元素,这显然是不正确的 return false; LNode* s = (LNode*)malloc(sizeof(LNode)); //申请一个新空间用于存放需要插入的元素 if (s == NULL) //这一步没什么用,是想说明申请空间可能会失败, return false; //即内存已满,无空闲空间 s->data = e; //这几步和头插法新建链表类似,不多赘述 s->next = p->next; p->next = s; return true; //插入成功 } //指定结点的前插操作 时间复杂度O(1) //在一个结点之前插入一个结点 bool InsertPriorNode(LNode* p, LNode* s)//因为单链表无法逆向检索,即不能知道前一个元素的值, { //只能通过next指针知道下一个结点的值,所以我们需要"偷天换日" if (p == NULL || s == NULL) //如果指定结点或者需要插入的结点有一个为空,返回false return false; ElemType temp; //定义一个ElemType型变量(int型),用于暂存p的数据域 s->next = p->next; //使s的指针域与指向的空间与p的指针域指向的空间相同 p->next = s; //将s连在p之后 temp = p->data; //将p的数据域中元素暂存到变量temp p->data = s->data; //将p的数据域中的元素修改为s的数据域中的元素 s->data = temp; //将s的数据域中的元素修改位p的数据域中的元素,实现了p和s的数据域元素的交换(偷天换日) return true; //插入成功 } //插入结点,按位序插入———利用函数封装 bool ListInsert2(LinkList& L, int i, ElemType e) { if (i < 1) return false; LNode* p; int j = 0; p = L; while (p != NULL && j < i - 1) { p = p->next; j++; } InsertNextNode(p, e); } //—————————————————————————————————————————————————————————————————————————————————————————————— //按位序删除 bool ListDelete(LinkList& L, int i, ElemType& e) { if (i < 1) //删除头结点是万万不行的 return false; LNode* p; //定义一个新结点p p = L; //使得p等于头结点L int j = 0; //定义一个int型变量j while (p != NULL && j < i - 1) //查找被删除元素前一个位置的元素 { p = p->next; //没找到,p就指向下一个结点 j++; //并且将j++ } if (p == NULL) //i值不合法导致p为空(但是我没有看出来这一步的用处) return false; if (p->next == NULL) //第i-1个结点之后已无其他结点 return false; LNode* q = p->next; //定义一个新指针q,令q指向被删除的节点 e = q->data; //用e返回被删除元素的数据域的值 p->next = q->next; //将*q结点从单链表中断开 free(q); //释放q结点 return true; //删除成功 } //指定结点的删除 时间复杂度O(1) bool DeleteNode(LNode* p) //删除一个指定结点p { if (p == NULL) //p为空,删除失败(空结点,就不用删除了呗) return false; LNode* q = p->next; //定义一个新指针q,并令其指向*q的后继结点 p->data = p->next->data; //将结点p的数据域替换为结点q的数据域(此时p中存放的元素值为q中的元素值,即进行了替换) p->next = q->next; //使得p的指针域指向的空间与q的指针域指向的空间相同(实现了将q从单链表中断开) free(q); //释放q return true; //删除成功 } //—————————————————————————————————————————————————————————————————————————————————————————————— //求表长 ElemType Length(LinkList L) { int len = 0; //定义一个int型变量len,将其置零 LNode* j; //定义一个指针j,用于指向各个数据元素 j = L->next; //将j指向第一个元素 while (j != NULL) { j = j->next; //一直遍历到单链表的末尾 len++; //将len++ } return len; } //打印链表 void PrintList(LinkList L) { LNode* j; j = L->next; while (j != NULL) { printf("%5d", j->data); j=j->next; } printf("\n"); } //主函数 int main() { LinkList L; LNode* p; ElemType e; int len; //List_HeadInsert(L); List_TailInsert(L); //1 2 3 4 5 6 7 8 9999 //ListInsert(L, 7, 108); ListInsert2(L, 7, 108); ListDelete(L, 7, e); printf("被删除的元素是%d\n", e); PrintList(L); p=LocateElem(L, 3); printf("%3d", p->data); printf("\n"); p = GetElem(L, 2); printf("%3d", p->data); printf("\n"); len=Length(L); printf("表的长度为%d", len); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)