前前后后看了四天左右吧,一方面把单链表过了几遍,另一方面也补全了一些基础,诸如 &引用,结构体指针,封装 等等内容,感觉难点不是代码怎么敲,而是要想明白每个操作的逻辑以及一些容易忽略的边界条件,为什么要有这些边界条件,没有这个条件会影响到哪些特殊情况?想明白这些很重要。
typedef struct LNode{
int data;//数据域
struct LNode *next;//指向下一个结点的指针域
}LNode,*LinkList;
单链表本质就是一个结点指向下一个结点
LNode * L;
LinkList L;
所以上面这两个语句在含义上是相同的,都表示L是指向这个结构的指针
只不过
int HeadInsert(LinkList &L){
int x;//你要插入的数据
L = (LNode*) malloc(sizeof (LNode*));
L->next = NULL;//创建头结点,并初始化为空链表
LNode *s;
scanf("%d",&x);
while(x!=9999) {
s = (LNode *) malloc(sizeof(LNode *));
s->data = x;
s->next = L->next;
L->next = s;
scanf("%d",&x);
}
return OK;
}
头插,顾名思义就是每次插入新的结点时,都插在头结点的后面,所以对应步骤为
int Insert (LinkList &L,int i,int e){
if(i<1){
return ERROR;
}
LNode* p;
int j = 0;//p当前指向第j个结点,0就是指向头结点
p = L;//p先指向头指针
while(p!=NULL && j<i-1){
p = p->next;
j++;
}
if(p==NULL){
return ERROR;//假设一共有8个结点,要在i=10处插,则循环完一遍后j=9,但是链表中第9位没有结点
}
LNode *s = (LNode *) malloc(sizeof (LNode*));
s->next = p->next;
p->next = s;
s->data = e;
return OK;
}
找到插入位置的前一个节点p,新建s插进去即可
注意边界条件:p == NULL 的情况(你要进入位置的前一个结点是空结点)
int InsertNext(LNode *p,int e){
if(p == NULL){
return ERROR;//再议
}
LNode *s = (LNode*) malloc(sizeof (LNode*));
s->data = e;
s->next = p->next;
p->next = s;
return OK;
}
int InsertPrior (LNode *p,int e){
if(p == NULL){
return ERROR;
}
LNode *s;
s = (LNode *) malloc(sizeof (LNode*));
if(s == NULL){
return ERROR;//内存分配失败,是每个场景都可能出现的问题
}
s->next = p->next;
p->next = s;
s->data = p->data;
p->data = e;
return OK;
}
单链表只能窥探一个指定结点之后的所有结点,之前的结点是未知的,所有想要在一个结点的前面插入一个元素,就要进行一波偷天换日,具体步骤如下:
int Delete(LinkList &L,int i,int *e){
if(i<1){
return ERROR;
}
LNode *p;
int j = 0;
p = L;
if(p!=NULL && j<i-1){
p = p->next;
j++;
}
if(p == NULL){
return ERROR;//p是头结点
}
if(p->next == NULL){
return ERROR;//要删除的结点不存在
}
LNode *s = p->next;//为什么不用开空间:目前理解:插入是新插的结点,是新结点,所以需要申请空间,这里只是定义s就是p的后继结点,原本就有
*e = s->data;
p->next = s->next;
free(s);
return OK;
}
找到要删除结点的前一个结点p,删除它的后继s即可
边界情况
int Deletespecial(LNode *p){
if(p == NULL){
return ERROR;
}
LNode *s = p->next;//4
p->data = s->data;//p不能是最后一个结点//3变为4
p->next = s->next;//3的后面是5
free(s);//删除原本的4
return OK;
}
int GetElem(LinkList L,int i,int *e){
if(i<0){
return NULL;
}
LNode *p;
int j = 0;
p = L;
while(p!=NULL && j<i){
p = p->next;
j++;
}
*e = p->data;
printf("第%d个元素是%d\n",i,*e);
return OK;
}
循环找到第i个结点返回其值即可
int LocateElem(LinkList &L,int e){
LNode *p;
int j = 0;
p = L;
while(p!=NULL){
p = p->next;
j++;
if(p->data == e){
break;
}
}
printf("值为%d的结点的位序是%d\n",e,j);
return j;
}
从第一个结点开始循环,直到p->data == e时结束循环,返回j值
int Print(LinkList L){
LNode *p = L->next;//p是第一个结点
while(p!=NULL){
printf("%d\t",p->data);
p = p->next;
}
printf("\n");
return OK;
}
#include <stdio.h>
#include <stdlib.h>
#include "L.h"
int main (){
LinkList L;
int e;
HeadInsert(L);//输入1,2,3
Delete(L,2,&e);//删除2
Insert(L,2,4);//在第2位插入4: 3 4 1
InsertNext(L->next,5);//在第2位后面插入5 3 5 4 1
InsertPrior(L->next->next,6);//在第2位前面插入6 3 6 5 4 1
Deletespecial(L->next->next->next->next);//删除第四个结点
InsertNext(L,9);
Print(L);
GetElem(L,2,&e);
LocateElem(L,5);
}
不难发现,有一些操作的具体实现可以引用另一个操作,直接举栗子🌰,插入操作就是先找到插入结点的前一个结点,再在它的后面插一个新的结点,这不就是GetElem + InsertNext,就是先按值查找再后插,所以我们注意到,在后插操作有一个边界条件是
if(p==NULL){
return ERROR;
}
当时的我一直在想,为什么输入的结点还可以为NULL,但如果把按位查找和后插操作直接拿来放在插入操作中,可以写为:
//使用前需要声明这两个函数
int InsertNext(LNode *p,int e);
LNode *GetElem(LinkList L,int i);
int Insert (LinkList &L,int i,int e){
if(i<1){
return ERROR;
}
LNode *p = GetElem(L,i-1);//这里需要把函数的返回值改为返回结点类型
InsertNext(p,e);
}
//把按位查找函数的返回值改为返回结点类型
LNode * GetElem(LinkList L,int i){
if(i<0){
return NULL;
}
LNode *p;
int j = 0;
p = L;
while(p!=NULL && j<i){
p = p->next;
j++;
}
return p;
}
那么看一下按位查找函数 ,如果i<0,会返回NULL值,就会出现p==NULL传给后插 操作,那么这个边界条件的作用就会显现。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有