/* 1、单链表的优点:方便插删,但插入,删除速度为O(n) (因为前提是需要进行查找后再插删,查找的复杂度为O(n)) 2、单链表的缺点:查找,存取(查找基础上)的速度为O(n)(绝大部分操作都基于查找之上) 3、单链表有”动态链表“(指针)和“静态链表”(数组)两种实现方式,单链表更注重一个结点的位置(指针或者索引值),而不注重列表的位序 4、“循环链表”与“单链表”的区别为最后一个结点指向头结点(头结点不存储数据),循环时要判断是否指向头指针来结束循环。 也有循环链表不包含头结点,只有一个尾指针(更方便两个循环链表的合并) 5、链表一般都具有一个头结点,头结点不存储数据,空表正好可以用一个头结点表示,同时在头结点的基础上还可以存在一个表头, 用于存储该链表的信息(如链表长度,链表头结点地址以及尾结点地址等等) */
#include<stdio.h> #define OK 1 #define ERROR 0 typedef int Status;//表示返回的状态值 typedef int ElemType; //”动态“单链表的结构体定义 //结构体定义完后声明变量需带上struct关键字 //typedef的作用是将数据类型用一个标志符代替,此处的结构体也是一种数据类型 //该typedef定义了两种类型,一种是该结构体类型Node(用于表示节点),一种是该结构体指针类型LinkList(用于表示链表的头结点) //Node* 就等于 LinkList typedef struct Node //此处使用非匿名定义结构体是因为 结构体内部 需要用到该结构体 { //数据域,该结点存储的数据 ElemType data; //指针域,指向下一个结点的指针 struct Node *node; }Node,*LinkList; //两种定义 //从表尾到表头逆向建立一个长度为n的空的单链表(头结点相当于一个中转站) //这种方式创建链表比从表头向表尾创建更方便 // void CreateLinkList(LinkList &L,int n)//因为要修改指针的指向,因此此处需要&来返回数据到实参中 { //创建一个结点指针 Node *p; //给结点头开辟内存 L=(Linklist)malloc(sizeof(Node)); L->node=NULL;//先指空 int i=1; for(;i<=n;i++) { p=(Node*)malloc(sizeof(Node));//开辟一个结点的内存 //插入结点,顺序是越先进越靠后 p->node=L->node; L->node=p; } } //从链表中得到第i位的元素,即数据的存取 //算法循环次数由i决定,平均时间复杂度为O(n) Status GetElem(LinkList L,int i,ElemType &e) { //获得第一个结点 Node *p=L->node; int j=1; while(j<i&&p)//若p为空则退出,p为空即到达指针尾部 { p=p->node; j++; } if(!p)//如果为空 { return ERROR;//表明超出检索范围,返回错误状态 } else { e=p->data;//将值返回给e return OK;//返回正确状态 } } //在链表插入元素到第i个位置,最多插入到length+1的地方(末尾) //由于插入之前需要进行查找,因此时间复杂度为O(n) Status LinkListInsert(LinkList L,ElemType e,int i) { Node* p=L; //获得头结点 int j=0;//标记i-1位的结点 Node* newNode=(Node*)malloc(sizeof(Node));//创建一个结点 while(j<i-1&&p) { p=p->node;//得到下一个结点的地址 j++;//得到结点对应的序号 } //循环结束后的p为指向i-1位结点的指针 if(!p||i<1)//如果p为空表明i大于length+1,i必须大于0才可以插入 { return ERROR;//报错插入失败 } //给结点赋值 newNode->data=e; //插入结点 newNode->node=p->node; p->node=newNode; //返回插入成功 return OK; } //删除单链表中第i位元素的结点,并返回其值 //删除结点同样要进行查找,因此时间复杂度为O(n) Status LinkListDelete(LinkList L,int i,ElemType &e) { int j=0; Node* p=L; //循环找第i-1位的结点的地址p while(j<i-1&&p) { p=p->node; j++; } if(!p||i<1) { return ERROR; } //获取删除的元素值 e=p->node->data; //删除结点 p->node=p->node->node; //释放被删除结点内存 free(p->node); //返回删除成功 return OK; } //合并两个非递减顺序排序的单链表 //实质上是对表链的指针导向进行更改形成一条新的路线 //相比列表的合并的优点为:时间复杂度差不多,但是空间复杂度却更小,几乎不用多余空间 //时间复杂度取决于两个表的表长,所以平均时间复杂度为O(n) void MergeLinkList(LinkList &L1,LinkList &L2,LinkList &L3)//因为要释放L1和L2,因此需要&传递回去将L1L2指针指空 { Node *p1,*p2,*p3; p1=Li->node; p2=L2->node; p3=L3=(LinkList)malloc(sizeof(Node)); //循环比较大小 while(p1&&p2)//两个同时不为空时继续 { if(p1->data>p2->data) { //若p2的元素比较小,则p3指向p2的结点指针,同时p3前进一步变成p2的结点指针, //p2则更进一步变成下一个结点的指针,因此上一个指针就被P3拿走了, //下一次循环p3再决定这个结点接下来指向哪一个结点 p3=p3->node=p2; p2=p2->node; } else { p3=p1->node=p1; p1=p1->node; } } //插入剩余段 p3->node=p1?p1:p2;//如果p1没有空则指向p1 //释放之前的头结点的内存 free(L1); free(L2); } //------------------------------------------------------------------------------------- //定义最大长度 #define MAXSIZE 1000 //”静态“单链表的结构体定义 //静态链表使用 数组结构 来实现,虽然空间上是静态的,且空间大小会提前开辟,但是每一个元素结点仍具有动态特性 //SLinkList相当于struct Node [MAXSIZE]结构体数组数据类型,其中数组的[0]位为头结点,data不存储,只在node中存储第一个结点的索引 typedef struct Node { //数据域 ElemType data; //指针域 int node; }SLinkList[MAXSIZE],Node; //从静态单链表中取得第i位元素 //平均时间复杂度为O(n) Status GetElem(SLinkList L,int i,ElemType &e) { int k=L[0].node,j=1;//得到第一个结点的地址,和它对应的索引(排在第几位) while(j<i&&L[j].node!=0) { j++; k=L[k].node;//得到下一个结点的地址 } if(L[j].node==0||i<1) { return ERROR; } e=L[k].data return OK; } //得到元素在链表中对应的位置 //时间复杂度为O(n) int LocateElem(SLinkList L,ElemType e) { int i=1,k=L[0].node; while(L[k].data!=e&&L[k].node!=0) { k=L[k].node; i++; } if(L[k].node==0) { return 0; } return i; } //得到元素在相对于SLinkList这个数组的索引值,没有则返回0 //时间复杂度为O(n) int LocateElem(SLinkList L,ElemType e) { int k=L[0].node; while(L[k].data!=e&&k)//当k为0时即遍历完毕,退出循环,返回0表示无此元素 { k=L[k].node; } return k; } /*下面开始 ,L[0]将用作备用链表的头指针,链表地址则用自定义变量来指向 (第一次获得链表的头地址可以用mallocSLinkList函数来分配结点) */ //插入删除在最后的AUB函数中体现(通过备用链表和已使用的链表两条链表来实现,备用链表每次尾部或头部提供新的插入结点, //备用链表也将删除的不用的结点插入备用链表的头尾) //初始化静态链表(开始链表为空,备用链表为满,因此将所有的结点先串成一个备用链表) //用L[0]来指向备用链表头地址,而链表的头地址用变量来指 //O(n) void InitialSLinkList(SLinkList &L) { int i=0; while(i<MAXSIZE) { L[i].node=i+1; i++; } L[i].node=0; } //给静态链表分配内存(相当于自己实现的malloc,实际上是从备用链表中取出一个结点用到链表中去) //分配成功则返回分配的结点的地址 //O(1) int MallocSLinkList(SLinkList &L) { int i=L[0].node;//得到备用空间的头指针 if(L[0].node)//若为0则表明备用空间为空 { L[0].node=L[i].node; } L[i].node=0;//分配的结点指向0 return i; } //将地址为k的结点回收到备用链表中去 //O(1) void FreeSLinkList(SLinkList &L,int k) { L[k].node=L[0].node;//将新的结点插入到备用链表的头指针中去 L[0].node=k; } //实现将A与B集合的交集剔除,再合并两个集合,即(A-B)U(B-A),而A和B通过scanf函数输入 //限定条件为:AB中没有重复元素 //k返回合并后的链表头地址 //平均时间复杂度为O(la*lb) void AUB(SLinkList &L,int &k) { int p,q,q1,la,lb,i,j;//r用于标记链表最后一个结点的地址 ElemType b; InitialSLinkList(L);//初始化链表将备用链表连成串 //MallocSLinkList函数分配的新结点指向0 k=MallocSLinkList(L); //k存储链表头结点的地址,头结点不存储数据. p=k; //p标记末尾结点地址用于插入结点, scanf(la,lb); //输入两个集合的长度 for(i=0;i<la;i++) //将A集合插入到链表中去 { p=L[p].node=MallocSLinkList(L);//链表指向一个新分配的结点,然后p向前进一步指向新的结点 scanf(L[p].data);//输入一个元素到新的结点中 } for(i=0;i<lb;i++) { scanf(b);//获得集合B的元素 q=L[k].node;//指向第一个结点,q用于遍历链表 q1=k;//q1指向q的前一个结点,主要在删除结点时,单链表不需要从头开始遍历到q-1这个结点 while(L[q].node&&L[q].data!=b)//若未到达尾部(下个结点地址不为0)且元素不相等则继续循环 { q=L[q].node;//指向下一个结点 q1=L[q1].node; } if(L[q].node==0)//即没有相等元素,此时将元素插入链表 (静态链表的插入) { //开辟结点,并将其插入到链表的结尾(由于MallocSLinkList分配的地址指向0,因此不需要改变新开辟的结点的指向) p=L[p].node=MallocSLinkList(L); L[p].data=b; } else//存在相等元素则将原链表中结点删除 (静态链表的删除) { L[q1].node=L[q].node; FreeSLinkList(L,q)//回收废弃的q这个结点 if(q==p) { p=q1;//如果删除的是尾结点,则刷新用于下一次插入的p的地址 } } } }
单链表LinkList定义与操作
最新推荐文章于 2024-07-15 22:08:02 发布