目录
始
特征:物理结构不一定连续,逻辑结构一定连续。物理结构:地址不是连续的,
逻辑结构:通过某几个地址能够找到一些参数,并把连接起来。
这些玩意儿叫节点(多半是结构体类型), 节点里面会放一些数据,以及 下一个节点的地址
节点:分为两部分,数据(可以放很多也可以很少,具体看需求)以及下一个节点的地址。
打印
首先,创建一个结构体类型的指针,赋值为NULL(表示为空链表),在插入几个数据之后打印。
我们直接传递的是这个变量,这个变量里面存的是地址,由于我们不需要修改实参的数据,所以可以直接使用变量访问。
创建一个临时变量存放p里面的地址,用 cp 访问,每次进来打印里面的数据 a ,再将下一位的地址赋给 p ,上来判断是否为 NULL
也可以不用创建临时变量不会影响实参的。
void SLNprint(SLN* p)//打印链表里的数据 { assert(p); while (p) { printf("%d->", p->a); p = p->Next; } }
开辟节点
用malloc开辟一个节点的空间,判断是否为空,最后是返回开辟成功的地址
SLN* SLNinvent(void) { SLN* iplist = (SLN*)malloc(sizeof(SLN)); if (iplist == NULL) { perror("malloc"); exit(EOF); } return iplist; }
尾插
大致可以把尾插分两部分开辟节点,和找到尾部
开辟节点后给节点赋值上你要插入的值,将下个节点的指针置NULL
第二部分:
创建一个临时节点变量,用来存放头结点的的地址
判断头链表是否是空链表(人话:头结点是否是空指针)
else遍历链表,直到遇见某个节点的 Next 是NULL,此时的slntail 变量放的是最后一个节点的地址(一个结构体空间的起始地址)。再将这个节点里面的Next 赋值为刚刚开辟的节点地址。
void SLNpushback(SLN** phead,int a) { assert(phead); SLN* list = SLNinvent(); list->a = a; list->Next = NULL; SLN* slntail = *phead; if (*phead == NULL) { *phead = list; } else { while (slntail->Next)//plist->间接访问Next { slntail = slntail->Next; } slntail->Next = list; } }
头插
头插还是比较简单一点
函数进来和尾插一样都需要先判断有没有节点,没有就需要申请一块空间,将地址直接赋给 *phead 即可,*phead 才是保存节点的,phead 放的是 plist 的地址。
else (表示不为空链表),我们需要将原头结点的地址放入刚才开辟的空的 Next 里面,再将*phead 赋值为新的节点的地址。
void SLNpushfront(SLN** phead, int a)//头插 { assert(phead); SLN* list = SLNinvent(); list->a = a; list->Next = NULL; SLN* slnfront = *phead; if (*phead == NULL) { *phead = list; } else { list->Next = slnfront; *phead = list; } }
尾删
两种情况:
1、如果只有一个节点只需要释放该节点,然后将节点地址置空2、则需要找到最后一个节点,当我们的判断语句为空的时候,表示 Tail 指向的是最后一节点的地址,Tail 指向最后一个节点的操作是 ,当 Tail 为上一个节点的地址时,进入循环,然后将 Tail(此时为最后一个节点的上一个节点的地址) 的地址赋给 Last ,Last就表示上一个节点的地址,然后将 Tail 变成最后一个节点地址
最后将Taill释放,taill 置为空 (图里没写代码里补充),再将上一个节点里面的Next置为空。尾删结束
错误更改:SLN* Last = 的后面应该是 *phead
void SLNdetback(SLN** phead)//尾删 { assert(phead && *phead);//表示plist的地址不能为空, //plist里面不能没有节点 SLN* Tail = *phead; SLN* Last = *phead; if ((*phead)->Next == NULL) { free(*phead); *phead = NULL; } else { while (Tail->Next) { Last = Tail; Tail = Tail->Next; } free(Tail); Tail = NULL; Last->Next = NULL; } }
头删
头删也有两种情况:
1、只一有个节点,我们直接释放然后将地址置为空指针
2、多个,先拷贝一份头节点的地址,然后将头节点赋值为Next 的地址(把第二节点当做头节点了),然后释放拷贝的原头节点,最将他置为空
void SLNdetfront(SLN** phead)//头删 { assert(phead && *phead); if ((*phead)->Next == NULL) { free(*phead); *phead = NULL; } else { SLN* cphead = *phead; (*phead) = (*phead)->Next; free(cphead); cphead = NULL; } }
代码二、
可以不需要上面的if语句直接在断言后使用就行
查找
由于查找不需要操作实参所以直接传节点就行。x 为查找的要求(由于这里的 x 实际是 Int 类型所以下面的if判断语句在某些情况下可能不能用)
while 节点的遍历,直到遇见NULL停止,所以这里其实还能改一下(代码放下面),遍历都没有怎么可以直接打印找不到并返回空,接收find的变量再来个 if 判断不能为空。
在使用find函数返回的地址时应该判断是否为NULL
SLN* SLNfind(SLN* phead,SLNtype x) { assert(phead); while (phead->Next) { if (phead->a == x) return phead; phead = phead->Next; } perror("not find\a"); return phead; }
纠错:while 的判断语句时有问题的,
他查不到最后一个节点,当我们把最后一个节点的地址赋给 phead 时(phead=phead->Next),此时上来判断,最后一个节点里的 Next 确实为NULL,不会进入循环也就查不了,应改为 while(phead),当 phead= 最后一个节点的地址时p 判断不为NULL,执行 if 判断语句,找到最后一个数据,直接返回了。
SLN* SLNfind(SLN* phead,SLNtype x)//查找 { assert(phead); while (phead) { if (phead->a == x) return phead; phead = phead->Next; } printf("找不到\n"); return phead; }
指定位置前插入节点
指定位置前依然要考虑头节点的问题。
如果是头节点直接调用头插函数
如果不是那么,需要涉及到 pos 的上个节点和下个节点,我们需要先把下一个节点的地址给到Pos->Next ,然后 while 循环找到上一个节点,判断为 cpphead(拷贝了一份头节点地址用来找到 pos 上一个节点并且不会修改 plist 里面的头节点,),cpphead->Next 不等于pos,此时的 cpphead 的地址就是上一个节点的地址,操作他,cpphead->Next赋值为新节点的地址。
void SLTInsert(SLN** pphead, SLN* pos, SLNtype x) { assert(pphead && *pphead && pos); if ((*pphead) == pos) { SLNpushfront(pphead,x); } else { SLN* cpphead = *pphead; SLN* Anewcode = SLNinvent(); Anewcode->a = x; Anewcode->Next = pos; while ((cpphead)->Next != pos) { (cpphead) = (cpphead)->Next; } cpphead->Next = Anewcode; } }
指定位置后插入
很简单奥,创建一个新的节点(建议在插入输入时在创建节点函数里面直接插入 x 我懒得改了,事实证明那样简单一点),创建完节点后,我们copy 一份 pos->Next 的地址将它赋给新节点,再将 newcode 的地址赋给 pos->Next.
给出了两种可行的方案,第二种要方便一点,节省了一丢丢空间和速度
第二种的逻辑是,将pos下一个节点的地址给新节点(newcode)的Next,再将newcode的地址给pos当中的next。
void SLTInsertAfter(SLN* pos, SLNtype x) { assert(pos); SLN* newcode = SLNinvent(); newcode->a = x; //SLN* cpos = pos->Next; //pos->Next = newcode; //newcode ->Next= cpos; newcode->Next = pos->Next; pos->Next = newcode; }
指定位置删除
只有一个节点的话直接头删
cpphead的目的是找到pos的上一个节点
多个:先拷贝一份头结点,用拷贝的头节点找到 pos 的上一个节点,然后将 pos 下个节点的地址放入 pos 的上一个节点的 Next 中,cpphead->Next(pos的上一个节点中的Next) = pos->Next(下一个节点的地址),最后释放掉 pos 和置为空
void SLTErase(SLN** pphead, SLN* pos) { assert(pphead && *pphead && pos); if ((*pphead) == pos) { SLNdetfront(pphead); } else { SLN* cpphead = *pphead; while (cpphead->Next != pos) { cpphead = cpphead->Next; } cpphead->Next = pos->Next; free(pos); pos = NULL; } }
删除pos之后的节点
将 pos 的下一个节点的地址先拿出来,然后然后将 pos 的 Next (pos->Next)与 pos 的下一个节点中的 Next (pos->Next->Next)连接起来,此时 pos 的下一个节点的地址就变成后面第二个结节的地址 (pos->Next中放的就是后面第二个节点地址),代码是:pos->Next = Cpos->Next ,Cpos->Next 解析:因为 Cpos 是后一个节点的地址,那么Cpos->Next 就是后两个的地址,赋值完后再释放点Cpos就完成了。
void SLTEraseAfter(SLN* pos)//删除pos之后的一个几点 { assert(pos&&pos->Next); SLN* Cpos = pos->Next; pos->Next = Cpos->Next; free(Cpos); Cpos = NULL; }
销毁
拷贝一份头节点,其实 SLN* cpphead = *pphead 可以赋值为空指针反正也只是一个初始化,在循环里还会赋头节点的地址的。
在循环里,先拷贝头节点,然后将头节点变成下一个节点,然后释放的头节点的地址,再将它置为NULL,此时,*pphead 等于第二节点的地址,上来判断是否为空,这里用*pphead 而不用*pphead->Next的原因是 ,当*pphead 来到最后一个节点时如果是*pphead->Next 那么循环就进不去,也就不会销毁最后一个节点
void SListDesTroy(SLN ** pphead) { assert(pphead && *pphead); SLN* cpphead = *pphead; while (*pphead) { cpphead = *pphead; *pphead = (*pphead)->Next; free(cpphead); cpphead = NULL; } }