声明单链表的结构体成员
struct LNode {
int data;
struct LNode *next;
};
typedef struct LNode LNode;
// 或者: 两者是等价的
typedef struct LNode {
int data;
struct LNode *next;
}LNode;
按位序插入元素e:就是在第i个位置插入新结点,数据域为e
以下带头结点的:
思路:在第i个位置插入,就要找到第i-1个位置,然后在它的后面分配存储空间,创建新结点,再改变新结点以及其左右结点的指针指向,完成插入
代码:
bool ListInsert(LinkList &L, int i, int e) {
LNode *p;
int j=0;
p=L;
if(i<1) {
return false; // 位序不合法,返回false停止指向插入操作
}
while(p!=NULL && jnext; // p结点的指针域存储的下一结点的地址赋值给p结点,表示p结点此时就走到了原p的下一节点,此时的P的指针域存的就是原p下一结点的指针域存的结点地址
// 不能写成p->next=p,因为p->next=p表示p指针域指向的元素是p结点,就是自己指向自己了
j++;
}
// 如果循环完整个单链表都没有找到要找的位序,最后p应该是指向null,如果p指向null,就不应该再指向插入操作了
if(p==NULL) {
// 我感觉,p最后会走向NULL,说明要找的位序都超过单链表的长度了,其实也可以在i<1的时候加个条件判断i>L.length也可以吧
return false;
}
// 找到i-1,跳出循环,此时p指针指向i-1
// 假设原顺序是p-q-r,现在想在p和q之间插入s,值为e
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
// p->next:p结点的指针域,存储下一结点的地址,即q结点的地址
// s->next:s结点的指针域,存储下一结点的地址
// 将p结点的下一结点地址即q结点的地址,赋值给s结点的指针域存储,也就是说此时s指向的下一结点是q
p-next=s; // s表示新结点,p->next表示p的指针域,存储下一结点的地址
// s赋值给p->next,就是说p指向的下一节点是s,即形成了p-s-q-r
return true;
}
分析一下时间复杂度:
以下是不带头结点的:
请注意,头指针L和头结点是两个东西,一个单链表,它可以没有头结点,但它一定有头指针,头指针指向链表的第一个结点,如果链表带头结点,头指针L就指向头结点,头结点不存储数据,如果链表不带头结点,头指针L就指向链表的第一个实际结点
思路:在第i个位置插入结点,就要找到第i-1个结点,在它的后面创建新结点,给新结点分配存储空间,然后通过改变指针指向来实现,与带头结点的步骤一致。
bool inserList_Nohead(LinkList &L, int i, int e) {
if(i<1) {
return false;
}
if(i==1) {
LNode *s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=L;
L=s;
}
int j=1;
LNode *p;
p=L;
// 以下这段和上面带头结点的插入其实是一样的,可以用一个函数调用,就不要写那么多代码了
while(p!=NULL && jnext;
// p->next:p结点的指针域,存储下一结点的地址,赋值给p结点就表示此时p结点变成了下一节点,
// 如果写成p->next=p,就表示:p结点的指针域存储的结点是p结点,就是自己指向自己,这个链表就断了
j++;
}
if(p==NULL) {
return false;
}
LNode *s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
return true;
}