boolListInsert(LinkList& L, int i, ElemType e){
if (i < 1) //检查位序合法性 returnfalse;
LNode* p = L; //定义遍历指针 p,初始指向链表的头结点 int j = 0; //定义计数器 j,记录当前 p 指向的节点序号,初始 0 对应头结点 while (p != NULL && j < i - 1) //循环找到第 i-1 个前驱节点:只要 p 不为空,且还没到第 i-1 个节点,就继续遍历
{
p = p->next; //指针向后移动,走到下一个节点
j++; //计数器加 1,更新当前节点的序号
}
if (p == NULL) //如果 p 为空,说明 i 超过了链表的长度,位置不合法,返回 false returnfalse;
LNode* s = (LNode*)malloc(sizeof(LNode)); //申请内存,创建一个新的节点 s
s->data = e; //给新节点的 data 域赋值,存入要插入的元素 e
s->next = p->next; //新节点的 next 指向 p 原来的下一个节点,把链表后续的节点接在新节点后面
p->next = s; //p 的 next 指向新节点,把新节点接在 p 的后面,完成插入 returntrue; //插入成功,返回 true
}
boolListInsert(LinkList& L, int i, ElemType e){
if (i == 1) //单独处理插入第 1 个位置的情况(不带头结点的链表,插第一个位置要修改头指针)
{
LNode* s = (LNode*)malloc(sizeof(LNode)); //申请内存,创建新节点 s
s->data = e; //给新节点赋值元素 e
s->next = L; //新节点的 next 指向原来的头节点,把原来的链表接在新节点后面
L = s; //头指针 L 指向新节点,新节点成为新的头节点 returntrue;
}
int j = 1; //处理插入位置不是第 1 位的情况:定义计数器 j,初始 1 对应第一个节点的序号
LNode* p = L; //定义遍历指针 p,初始指向链表的第一个节点(头指针 L) while (p != NULL && j < i - 1) //循环找到第 i-1 个前驱节点:只要 p 不为空,且还没到第 i-1 个节点,就继续遍历
{
p = p->next; //指针向后移动,走到下一个节点
j++; //计数器加 1,更新当前节点的序号
}
if (p == NULL) //如果 p 为空,说明 i 超过了链表的长度,位置不合法,返回 false returnfalse;
LNode* s = (LNode*)malloc(sizeof(LNode)); //申请内存,创建新节点 s
s->data = e; //给新节点赋值元素 e
s->next = p->next; //新节点的 next 指向 p 原来的下一个节点,接好后续链表
p->next = s; //p 的 next 指向新节点,完成插入 returntrue;
}
7. 插入结点操作(指定结点)
这是给已知节点前插元素的函数,传入已知的节点指针 p,有要插入的数 e,它就能直接在 p 的前面插入新节点,不用从头遍历链表。操作成功就返回 true,要是你给的 p 是空的,就返回 false。
boolInsertPriorNode(LNode* p, ElemType e){
if (p == NULL) //如果节点 p 为空,位置不合法,直接返回 false returnfalse;
LNode* s = (LNode*)malloc(sizeof(LNode)); //申请内存,创建新节点 s
s->next = p->next; //新节点的 next 指向 p 的下一个节点,把新节点插在 p 的后面
p->next = s;
s->data = p->data; //把 p 原来的数据复制到新节点 s 里
p->data = e; //把要插入的元素 e 存入 p 节点,相当于 p 变成了新插入的节点,s 变成了原来的 p 节点,实现前插效果 returntrue;
}
这是给已知节点后插元素的函数,传入已知节点的指针 p、要插入的元素 e,函数会在节点 p 的后面插入新元素 e。不用从头遍历链表,直接修改节点的指针即可完成插入,操作成功返回 true,若节点 p 为空则返回 false。
boolInsertNextNode(LNode* p, ElemType e){
if (p == NULL) //如果传入的节点 p 为空,位置不合法,直接返回 false,终止操作 returnfalse;
LNode* s = (LNode*)malloc(sizeof(LNode)); //申请内存,创建一个新的节点 s,用来存放要插入的元素
s->data = e; //给新节点的 data 域赋值,存入要插入的元素 e
s->next = p->next; //新节点 s 的 next 指向 p 原来的下一个节点,把 p 后续的链表接在新节点后面
p->next = s; //把 p 的 next 指向新节点 s,将新节点接在 p 的后面,完成后插操作 returntrue;
}
8. 删除结点操作
入链表头指针 L、要删除的位置 i、用来接收被删除元素的变量 e,函数会检查删除位置的合法性,找到第 i-1 个前驱节点,删除第 i 个节点并释放内存,删除成功返回 true,位置不合法返回 false。
boolListDelete(LinkList& L , int i, ElemType& e){
LNode* p = L; //定义遍历指针 p,初始指向链表的头结点 int j = 0; //定义计数器 j,记录当前 p 指向的节点序号,初始 0 对应头结点 while (p->next != NULL && j < i - 1) //循环找到第 i-1 个前驱节点:只要 p 的下一个节点不为空,且还没到第 i-1 个节点,就继续遍历
{
p = p->next; //指针向后移动,走到下一个节点
j++; //计数器加 1,更新当前节点的序号
}
if (p->next == NULL || j > i - 1) //校验位置合法性:如果 p 的下一个节点为空(要删除的节点不存在),或者 j 超过了 i-1(位置非法),返回 false returnfalse;
LNode* q = p->next; //用指针 q 指向要删除的第 i 个节点(p 的下一个节点)
e = q->data; //把被删除节点的元素值赋值给 e,带出去给调用者
p->next = q->next; //把 p 的 next 指向 q 的下一个节点,跳过要删除的节点,把链表接好 free(q); //释放 q 节点的内存,避免内存泄漏 returntrue;
}
LinkList List_HeadInsert(LinkList& L){
LNode* s; //定义新节点指针 s,和用来接收用户输入的变量 x int x;
L = (LNode*)malloc(sizeof(LNode)); //申请内存,创建链表的头结点
L->next = NULL; //头结点的 next 置为 NULL,初始化空链表 scanf("%d", &x); //读取用户输入的第一个整数,存入 x while (x != 9999) //循环:只要输入的 x 不是 9999,就继续插入节点
{
s = (LNode*)malloc(sizeof(LNode)); //申请内存,创建新节点 s
s->data = x; //给新节点赋值,存入用户输入的 x
s->next = L->next; //新节点的 next 指向头结点原来的下一个节点,接好链表的后续部分
L->next = s; //头结点的 next 指向新节点,把新节点插到链表头部 scanf("%d", &x); //读取下一个用户输入的整数,继续循环
}
return L;
}
10. 采用尾插法建立单链表
传入链表头指针 L,函数会初始化带头结点的空链表,用尾指针 r 记录链表的最后一个节点,循环读取用户输入的整数,每读一个数就把它作为新节点插到链表的尾部,直到输入 9999 停止,最终返回建立好的链表头指针。
LinkList List_TailInsert(LinkList& L){
int x; //定义用来接收用户输入的变量 x
L = (LNode*)malloc(sizeof(LNode)); //申请内存,创建链表的头结点
LNode* s, * r = L; //定义新节点指针 s,和尾指针 r,初始 r 指向头结点(空链表的尾部就是头结点) scanf("%d", &x); //读取用户输入的第一个整数,存入 x while (x != 9999) //循环:只要输入的 x 不是 9999,就继续插入节点
{
s = (LNode*)malloc(sizeof(LNode)); //申请内存,创建新节点 s
s->data = x; //给新节点赋值,存入用户输入的 x
r->next = s; //尾指针 r 的 next 指向新节点,把新节点接到链表的尾部
r = s; //尾指针 r 移动到新节点,更新为新的链表尾部 scanf("%d", &x); //读取下一个用户输入的整数,继续循环
}
r->next = NULL; //循环结束,把链表最后一个节点的 next 置为 NULL,标记链表结束 return L; //链表建立完成,返回链表的头指针
}
三、双链表的定义
单链表中只有一个指向后继结点的指针,这导致其在执行查找、插入和删除等操作的时候只能从头开始遍历,时间复杂度为 O(n) 。所以双链表的产生就是为了解决这种问题,其拥有 prior 指针和 next 指针,分别指向其直接前驱和直接后继。双链表结点结构图示如下:
这是带头结点的双链表初始化函数,说白了就是给双链表建一个头结点,先申请一块内存当这个头结点,要是内存申请失败就返回 false;申请成功的话,就把头结点的前驱指针 prior 和后继指针 next 都设成 NULL,代表现在链表是空的,最后返回初始化成功的 true。
boolInitDLinklist(DLinklist& L){
L = (DNode*)malloc(sizeof(DNode)); //申请一块内存,大小是一个 DNode 节点的大小,把申请到的地址赋值给头指针 L,也就是创建头结点 if (L == NULL) //如果 L 是 NULL,说明刚才申请内存失败了,直接返回 false returnfalse;
L->prior = NULL; //把头结点的前驱指针 prior 设为 NULL,头结点前面没有节点
L->next = NULL; //把头结点的后继指针 next 设为 NULL,头结点后面也没有节点,现在链表是空的 returntrue;
}
2. 双链表的插入
这是双链表的节点后插函数,给它一个已知的节点 p,还有要插入的新节点 s,它就能把 s 插到 p 的后面。因为双链表有前后两个指针,所以要把新节点和前后的节点的指针都接好,要是 p 或者 s 是空的,就直接返回 false,操作成功就返回 true。
boolInsertNextDNode(DNode* p, DNode* s){
if (p == NULL || s == NULL) //如果 p 是空的,或者 s 是空的,位置不合法,直接返回 false returnfalse;
s->next = p->next; //让新节点 s 的后继指针,指向 p 原来的下一个节点,先把 p 后面的链表接在 s 的后面 if (p->next != NULL) //如果 p 原来的下一个节点不是空的
p->next->prior = s; //让 p 原来的后继节点的前驱指针,指向新节点 s,把后面节点的前向指针接好
s->prior = p; //让新节点 s 的前驱指针,指向节点 p,把 s 和 p 的前向关系接好
p->next = s; //让 p 的后继指针,指向新节点 s,把 p 和 s 的后向关系接好,完成插入 returntrue;
}
3. 双链表的删除
这是双链表的节点删除函数,作用是删掉节点 p 后面的那个节点。它会先检查 p 是不是空的,还有 p 后面有没有节点可以删,要是没的删就返回 false;有的话就把链表的前后指针重新接好,跳过要删的节点,再释放掉这个节点的内存,避免内存泄漏,删成功了就返回 true。
boolDeleteNextNode(DNode* p){
if (p == NULL) //如果 p 是空的,位置不合法,直接返回 false returnfalse;
DNode* q = p->next; //用指针 q 指向 p 的后继节点,也就是我们要删掉的节点 if (q == NULL) //如果 q 是空的,说明 p 后面没有节点,没的可删,返回 false returnfalse;
p->next = q->next; //让 p 的后继指针,直接指向 q 的下一个节点,跳过要删的 q 节点,把链表的后向接好 if (q->next != NULL) //如果 q 的下一个节点不是空的(也就是 q 不是链表最后一个节点)
q->next->prior = p; //让 q 的后继节点的前驱指针,指向 p,把链表的前向接好 free(q); //释放掉 q 节点的内存,把要删的节点彻底清掉,避免内存泄漏 returntrue;
}