目录
一、单链表的查找:
1.1 通过位序查找和通过元素的值查找:
1.2 判断链表是否为空:
二、单链表的插入和删除(按结点):
示例分析:
2.1 在指定结点后插入元素:
2.2 在指定结点前插入元素(难):
2.3 删除指定结点:
一、单链表的查找:
单链表的查找是什么?
就是通过 链表中的位序 来查找结点的地址,或者通过判断链表中是否存在指定的元素来获取地址;
1.1 通过位序查找和通过元素的值查找:
//通过元素查找结点地址
// 查找元素ee在链表LL中的结点地址,如果没找到返回NULL,否则返回结点的地址。
LNode *LocateElem(LinkList LL, ElemType *ee)
{
if(LL==NULL||ee==NULL){printf("链表不存在或者元素不存在。\n");return NULL; }
LinkList tmp=LL->next; //比较数据元素,头结点的data不存放数据,所以从首节点开始比较
while(tmp!=NULL)
{
if(tmp->data==*ee)return tmp;
tmp=tmp->next;
}
return NULL;
}
//通过位序查找结点地址
LNode *LocateNode(LinkList LL, unsigned int ii)
{
if(LL==NULL){printf("链表不存在,获取失败。\n");return NULL;}
LinkList tmp=LL; //从头结点开始查找,因为位序可以为0
int jj=0; //因为tmp从头结点开始,所以其序号为0,如果tmp从首节点开始,其序号为1;
while(tmp!=NULL && jj<ii )
{
tmp=tmp->next; //取下一结点
jj++;
}
//循环出来的条件
if(jj==ii && tmp!=NULL)return tmp;
return NULL;
}
1.2 判断链表是否为空 :
// 判断链表是否为空,返回值:0-非空或失败,1-空。
int IsEmpty(LinkList LL)
{
if(LL==NULL){printf("链表不存在。\n");return 0; }
if(LL->next==NULL)return 1;
else return 0;
}
二、单链表的插入和删除(按结点):
示例分析:
假定此时有一 链表LL 存在于内存中,其逻辑结构和位序位置为:
链表结构为: LL->A->B->C->D->NULL;
位序位置 | 0(头结点) | 1(首节点) | 2 | 3 | 4 | 5 | |||||||
逻辑结构 | LL | A | B | C | D | NULL |
2.1 在指定结点后插入元素:
假定此时我在往B结点后插入元素H,应该怎么操作?
指定的结点为B,其地址已知,所以我只需要申请一个新结点newnode,将元素HH值赋值给newnode的数据元素即可;
newnode->data=H;
newnode->next=B->next;
B->next=newnode;
代码实现为:
// 在指定结点pp之后插入元素ee,返回值:0-失败;1-成功。
int InsertNextNode(LNode *pp, ElemType *ee)
{
if (pp == NULL) { printf("结点pp不存在。\n"); return 0; }
if(ee==NULL){printf("元素不存在。\n");return 0;}
LNode *newnode = (LNode *)malloc(sizeof(LNode));
if (newnode == NULL) return 0; //内存不足,失败
memcpy(&newnode->data,ee,sizeof(ElemType)); //将元素ee的值复制给新结点
newnode->next=pp->next;
pp->next=newnode;
return 1;
}
2.2 在指定结点前插入元素(难):
假定此时我需要在B结点之前插入元素K,应该怎么操作?
已知结点B的地址,
方法1: 从头结点开始轮训,查找到B结点前一个结点的位置,伪代码如下:
//伪代码 查询已知结点的前一个结点 需要已知链表头结点
//假定 LL为链表头结点指针
//假定 B结点地址为pp
LinkList tmp=LL; //从头结点开始 遍历
while(tmp->next!=pp && tmp!=NULL )
{
tmp=tmp->next;
}
if(tmp==NULL) //说明链表不存在pp结点
else //tmp为pp前一个结点的地址
//找到B元素前一个地址(即tmp)
(LNode*)newnode=(LNode*)malloc(szieof(LNode));
//将元素ee复制给新结点的数据域
memcpy(&(newnode->data),K,sizeof(ElemType));
newnode->next=pp; //新结点next域指向B (在B之前)
tmp->next=newnode; //B之前的结点next指向新结点
已知B结点的地址,
方法2(替代法):此时我们申请一个结点newnode,让其替代B;
替代的含义:将B的next域和数据域全部复制给newnode;
newnode->next=B->next;
memcpy(&newnode->data,&B->data,sizeof(ElemType));
此时newnode已然成为所谓的B结点,它拥有了B的一切,这就相当于武侠小说里的夺舍,亦或是六耳猕猴和孙悟空,newnode是六耳,B是悟空,悟空拥有的,六耳也要都有,这样说应该好理解了吧,西游阴谋论都是真假美猴王死的是孙悟空,所以六耳直接替代了悟空;
因为newnode已经是B结点了,自然而然B结点 则 成为 的 B 的前一个结点:
memcpy(&B->data,K,sizeof(ElemType));
BB->next=newnode;
代码实现:
// 在指定结点pp之前插入元素ee,返回值:0-失败;1-成功。
int InsertPriorNode(LNode *pp, ElemType *ee)
{
if(pp==NULL){printf("结点pp不存在。\n"); return 0;}
if(ee==NULL){printf("元素不存在。\n");return 0;}
/*方法1 LL需要为全局变量
LinkList tmp=LL; //从头结点开始 遍历
while(tmp->next!=pp && tmp!=NULL )
{
tmp=tmp->next;
}
if(tmp==NULL){printf("pp结点不存在。\n");return 0; }
LNode *newnode = (LNode *)malloc(sizeof(LNode));
if (newnode == NULL) return 0;
memcpy(&newnode->data,ee,sizeof(ElemType));
newnode->next=pp;
tmp->next=newnode;
*/
LNode *newnode = (LNode *)malloc(sizeof(LNode));
if (newnode == NULL) return 0;
//将pp结点夺舍 拥有其拥有的一切,相当于newnode是六耳猕猴,而pp是孙悟空,孙悟空会的六耳猕猴都会是吧;
newnode->next=pp->next;
memcpy(&newnode->data,&pp->data,sizeof(ElemType)); //将pp结点的值复制给新结点
//那么此时newnode已经是所谓的pp结点了,理所当然pp结点就应该成为pp的前一个结点
memcpy(&pp->data,ee,sizeof(ElemType));
pp->next=newnode;
return 1;
}
2.3 删除指定结点 :
假定此时有一 链表LL 存在于内存中,其逻辑结构和位序位置为:
链表结构为: LL->A->B->C->D->NULL;
位序位置 | 0(头结点) | 1(首节点) | 2 | 3 | 4 | 5 | |||||||
逻辑结构 | LL | A | B | C | D | NULL |
假定我此时要删除结点B,那么我应该如何操作呢?
可能有人会说使用上面的替代法,直接使B替代C,然后删除C结点即可,那么这种方法是否可行呢?
在结点1到结点3之间,使用这种方法都是可行的,但是结点4无法使用这种方法,为什么呢?
因为结点4无法替代NULL,使用memcpy(&D->data,NULL,sizeof(ElemType));将会直接报错;
所以只能使用从首结点轮训的方法来删除指定结点;
代码实现:
// 删除指定结点。
int DeleteNode1(LNode *pp)
{
if(pp==NULL){printf("结点pp不存在。\n"); return 0;}
LinkList tmp=LL; //从头结点开始 遍历
while(tmp->next!=pp && tmp!=NULL )
{
tmp=tmp->next;
}
if(tmp==NULL){printf("pp结点不存在。\n");return 0; }
tmp->next=pp->next;
free(pp);pp=0;
/*
//pp夺舍其后继结点
// 删除指定结点的思想是:1)把pp后继结点的数据和next指针复制到pp结点;2)删除pp结点的后继结点。
LNode *tmp=pp->next; // tmp指向pp的后继结点。
memcpy(&pp->data,&tmp->data,sizeof(ElemType)); // 把后继结点的数据复制到pp结点中。
pp->next=tmp->next; // 把pp的next指向后继结点的next。
free(tmp); // 释放后继结点。
// 写这个函数的目的是告诉大家这种方法是有问题的。
// 问题:如果当前的pp结点是链表的最后一个结点,那么它的后继结点根本不存在。
// 结论:此法不通,还是乖乖的从链表头部开始扫描。
*/
return 1;
}
main中对于这些函数的调测代码:
int main()
{
// LinkList LL=NULL; //声明链表指针变量 声明为全局变量
LL=InitList1();
printf("LL=%p\n",LL);
LinkList tmp=LocateNode(LL,0);
printf("获取头结点的位置tmp=%p\n",tmp);
printf("插入前链表长度为%d\n",LengthList(LL));
ElemType ee;
printf("向链表中插入元素1,2,3,4,5,6,7,8,9,0\n");
ee=1;InsertList(LL,1,&ee);
ee=2;InsertList(LL,1,&ee);
ee=3;InsertList(LL,1,&ee);
ee=4;InsertList(LL,1,&ee);
ee=5;InsertList(LL,1,&ee);
ee=6;InsertList(LL,1,&ee);
ee=7;InsertList(LL,1,&ee);
ee=8;InsertList(LL,1,&ee);
ee=9;InsertList(LL,1,&ee);
ee=0;InsertList(LL,1,&ee);
printf("插入后链表长度为%d\n",LengthList(LL));
PrintList(LL);
printf("在第5个位置插入元素25。\n");
ee=25;InsertList(LL,5,&ee);
PrintList(LL);
printf("在链表头部插入元素21。\n");
ee=21;PushFront(LL,&ee);
printf("在链表尾部插入元素30。\n");
ee=30;PushBack(LL,&ee);
PrintList(LL);
printf("删除第5个位置元素。\n");
DeleteNode(LL,5);
PrintList(LL);
printf("删除链表第一个元素。\n");
PopFront(LL);
PrintList(LL);
printf("删除链表尾部元素。\n");
PopBack(LL);
PrintList(LL);
LinkList tmp0=LocateNode(LL,6);
printf("通过序号定位结点位置,第6个元素为%d,位置是%p\n",tmp0->data,tmp0);
printf("通过元素定位结点位置,元素为上述查找元素的位置是%p\n",LocateElem(LL,&tmp0->data));
printf("在指定结点%p后插入元素55。\n",tmp0);
ee=55;
InsertNextNode(tmp0,&ee);
PrintList(LL);
printf("在指定结点%p前插入元素66。\n",tmp0);
ee=66;
InsertPriorNode(tmp0,&ee);
PrintList(LL);
ee=1;
printf("删除结点%p。\n",tmp0);
DeleteNode1(tmp0);
PrintList(LL);
tmp0=LocateNode(LL,LengthList(LL));
printf("删除尾结点%d,%p。\n",tmp0->data,tmp0);
DeleteNode1(tmp0);
PrintList(LL);
//ClearList(LL); //清空链表
//printf("LL=%p\n",LL);
DestroyList1(LL);LL=NULL; //销毁链表 LL=NULL步骤不可以省略,防止野指针产生
printf("LL=%p\n",LL);
return 0;
}