文章目录

  • ​​1.链表​​
  • ​​2.单链表​​
  • ​​3.将新节点插入到一个有序的单链表中?​​
  • ​​4.将一个值插入到一个有序的双链表中​​
  • ​​5.总结​​

1.链表

  • 链表中的每个节点通过指针连接在一起,程序通过指针来访问链表中节点
  • 通常,节点是动态分配的

2.单链表

  • 链表的最后一个节点的指针字段的值为NULL,提示链表后面不再有其他节点
  • 根指针root pointer:是链表的起始位置,根指针指向链表的第1个节点。注意:根指针只是一个指针,它不包含任何数据。
  • 节点声明创建的结构如下:
typede struct NODE
{
struct NODE *link;
int value;
}node;
  • 单链表的图如下所示:

3.将新节点插入到一个有序的单链表中?

  • 方法1:大多数情况下,这已经是最佳的方案

    链表的最终样子
//若插入的是3,下面的代码是不能改变root的。
//下面是用node的遍历方法,root指的是:指向一个Node的指针
result=sll_insert(root,12);

#include <stdlib.h>
#include <stdio.h>
#include "sll_node.h"
#define true 1
#define flase 0

int sll_insert(Node *current,int new_value)
{
Node *previous;
Node *New;

//寻找正确的插入位置,方法是:按序访问链表,直到到达一个其值大于或等于新值的节点
while (current!=NULL && current->value<new_value)
{
previous=current;
current=current->link;
}
//为新增节点分配内存,并把新值存储到新节点中,如果内存分配失败,函数返回False
New=(Node *)malloc(sizeof(Node));
if (NULL==New)
return false;
New->value=new_value;

//把新节点插入到链表中,并返回true
new->link=current;
previous->link=new;
return true;
}



//下面的代码是指向node的指针的指针的遍历方法
编程要点:
(1)while循环不要越过链表的尾部,对一个NULL指针执行间接访问的操作
(2)若把value=3插入到链表中,会发生什么?
为了在链表的起始位置插入一个节点,函数必须修改根指针。但是,函数不能访问变量root。
所以,可以把一个指向root的指针作为参数传递给函数,然后使用间接访问,函数不仅可以获得root的值(根指针),也可以向他
存储一个新的指针值。
(3)若将value=12插入,若按照顺序访问链表。当到达15的node就会停下来,但是前一个节点的指针字段必须进行修改以实现插入。
解决的办法就是:始终保存一个指向链表当前节点之前的那个节点的指针。

//root指的是:指向一个Node的指针,所以参数类型是:Node **
result=sll_insert(&root,12);

#include <stdlib.h>
#include <stdio.h>
#include "sll_node.h"

#define 0
#define TRUE 1

int sll_insert(Node **rootpt, int value)
{
Node *current;
Node *previous;
Node *new;

//得到指向第一个节点的指针
current=*rootpt;
previous=NULL;//检查新值,是否应为链表的第1个节点

//寻找正确的插入位置,方法是:按序访问链表,直到到达一个其值大于或等于新值的节点
while (current !=NULL && current->value < value)
{
previous=current;
current=current->link;
}

//为新增节点分配内存,并把新值存储到新节点中,如果内存分配失败,函数返回False
new=(Node *)malloc(sizeof(Node));
if (new==NULL)
return false;
new->value=value;

//把新节点插入到链表中,并返回TRUE
new->link=current;

//检查新值是否应该被添加到链表的起始位置,若是,则修改根指针,使他指向新节点
if (previos==NULL)//将一个节点插入到链表的起始位置作为一种特殊情况进行处理
*rootpt=new;//new成为一个新root,但是new的value却没了啊。。。。
else
previous->link=new;

return TURE;
}
  • 方法2:优化

    下面是第2个节点和指向它的指针,若新值要插入到第2个节点之前,那么这个指针必须修改,我们只考虑指向这个节点的指针,至于哪个节点包含这个指针,则无关紧要。对于链表中的其他节点,都可以应用这个模式!!
编程要点:
(1)对于任何节点,对指针进行修改时,实际修改的是前一个节点的link字段
(2)消除上面if (previos==NULL)的异常情况的关键在于:要意识到,链表中的每个节点都有一个指向它的指针。
对于第一个节点,这个指针是根指针,对于其他节点,这个指针是前一个节点的link字段!!
(3)优化的关键:这里的rootpt并不是指向节点本身,而是指向节点内部的link字段,即:必须能够取得当前节点的link字段的地址。


//root指的是:指向一个Node的指针,所以参数类型是:Node **
result=sll_insert(&root,12);
//插入到一个有序单链表,函数的参数是一个指向链表第一个节点的指针,以及一个需要插入的新值
#include <stdlib.h>
#include <stdio.h>
#include "sll_node.h"

#define FALSE 0
#define TRUE 1
//在函数的指针变量中增加register的声明,用于提高代码的效率
int sll_insert(Node **rootpt, int value)
{
register Node *current;
register Node *new;


//寻找正确的插入位置,方法是:按序访问链表,直到到达一个其值大于或等于新值的节点
current=*rootpt;
while (current!=NULL && current->value<value)
{
rootpt=&(current->link);//rootpt为指向当前节点的link字段
current=*rootpt;//current指向下一个节点,current=current->link;这样写好理解这行代码所要干的事情
}

/*
等价于下面的写法:
while ((current=*rootpt)!=NULL && current->value<value)
{
rootpt=&(current->link);
}
*/

//为新节点分配内存,并把新值存储到新节点中,如果内存分配失败,函数返回FALSE
new=(Node *)malloc(sizeof(Node));
if (new==NULL)
return false;
new->value=value;

//在链表中插入新节点,并返回TRUE
new->link=current;
*rootpt=new;//这里的rootpt并不是指向节点本身,而是指向节点内部的link字段,new是新增node的自己的地址,
new->link保存的是下一个node的地址
return TRUE;
}
  • 从一个单链表中删除一个节点
函数的第一个参数是指向链表根指针的指针,第2个参数是一个指向欲移除的节点的指针。
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include "singly_linked_list_node.h"

#define true 1
#define false 0
int sll_remove(struct NODE **rootp, struct NODE *delete)
{
register struct node *current;
assert(node!=NULL);
while ((current=*rootp)!=NULL && current!=delete)
{
rootp=&(current->link);
}

if (current==delete)
{
*rootp=current->link;
free(delete);
return true;
}
else
return false;
}

4.将一个值插入到一个有序的双链表中

  • 在一个双链表中,每个节点都包含两个指针:指向前一个节点的指针和指向后一个节点的指针。这可以使我们以任何方向遍历双向链表。
  • 根节点的fwd字段指向链表的第1个节点,根节点的bwd字段指向链表的最后一个节点。若链表为空,则这两个字段都为NULL。
  • 链表第一个节点的bwd字段和最后一个节点的rwd字段都是NULL
节点类型的声明如下:
typedef struct NODE
{
struct NODE *fwd;
struct NODE *bwd;
int value;
}Node;

(第12章)使用结构和链表_#include

  • for循环终止之后的几个变量地状态如下:
编写程序要点:
(1)dll_insert函数接受两个参数:一个指向根节点的指针和一个整型值
(2)dll_insert只有当预先插入的值原先不存在于链表中,才将其插入
(3)一开始,函数使this指向根节点,next指针始终指向this之后的那个节点。它的思路是这两个指针同步前进,直到新节点应该
插入到这两者之间。for循环检查next所指节点的值,判断是否达到需要插入的位置。
如果在链路中找到新值,函数就简单地返回。否则,当到达链表尾部或者找到适当地插入位置时,循环终止。
在任何一种情况下,新节点都应该插入到this所指地节点后面。

/*
把一个值插入到一个双链表中,rootp是一个指向根节点的指针,
value是欲插入的新值。
返回值:如果预插值原先已存在于链表中,函数返回0;
如果内存不足,导致无法插入,函数返回-1;
如果插入成功,函数返回0;
*/

/*
把一个节点插入到一个链表时,可能出现4种情况:
(1)新值可能插入到链表的中间位置
(2)新值可能插入到链表的起始位置
(3)新值可能插入到链表的结束位置
(4)新值可能既插入到链表的起始位置,又插入到链表的结束位置(即:原链表为空)

*/
#include <stdlib.h>
#include <stdio.h>
#include "double_linked_list_node.h"

int dll_insert(Node *rootp,int value)
{
Node *this;
Node *next;
Node *newnode;

/*
查看value是否已存在于链表中,如果是就返回。
否则,为新值创建一个新节点newnode。
”this“将指向应该在新节点之前的那个节点,”next“将指向应该在新节点之后的那个节点

this=rootp;
while ((next=this->fwd)!=NULL)
{
if (next->nalue==value)
return 0;
if (next->value>value)
break;
this=next;
}

*/
for (this=rootpt;(next=this->fwd)!=NULL;this=next)
{
if (next->value==value)
return 0;
if (next->value > value)
break;
}

/*
在我们决定新值是否应该插入到链表之前,并不为它分配内存。
如果事先分配内存,若发现新值原先已经存在于链表中,就有可能发生内存泄漏。
*/
newnode=(Node *)malloc(sizeof(Node));
if (newnode==NULL)
return -1;

newnode->value=value;

//把新值添加到链表中
if (next!=NULL)
{
//对于情况1或情况2,并非位于链表的尾部
if (this!=rootp)//情况1:若不在链表的起始处
{
newnode->fwd=next;
this->fwd=newnode;
newnode->bwd=this;
next->bwd=newnode;
}
else//情况2:位于链表的起始位置
{
newnode->fwd=next;
rootp->fwd=newnode;
newnode->bwd=NULL;
next->bwd=newnode;
}
}
else//情况3或4:位于链表的尾部
{
if (this!=rootp)//情况3:并非位于链表的起始位置
{
newnode->fwd=NULL;
this->fwd=newnode;
newnode->bwd=this;
rootp->bwd=newnode;
}
else//情况4:位于链表的起始位置
{
newnode->fwd=NULL;
rootp->fwd=newnode;
newnode->bwd=NULL;
rootp->bwd=newnode;
}

}
return 1;
}

(第12章)使用结构和链表_字段_02

5.总结

  • 单链表
    (1)链表中的每个节点包含一个节点,用于指向链表的下一个节点;
    (2)独立的根指针指向链表的第1个节点
    (3)每个节点采用动态分配内存的方式
    (4)遍历链表是根据指针进行的,单链表只能以一个方向进行遍历
    (5)将一个新值插入到一个有序的单链表过程:将新节点的link字段设置为指向它的的目标后续节点,其次,前一个节点的link字段必须设置为指向这个新节点。当时,该技巧,会使得插入到链表的起始位置成为一种特殊情况!!在C语言中,可以通过保存一个指向必须进行修改的link字段的指针,而不是保存一个指向一个节点的指针,从而消除这种特殊情况。
  • 双链表
    (1)有两个link字段:其中一个指向链表的下一个节点,另一个指向链表的前一个节点
    (2)双链表有两个根指针,分别指向第1个节点和最后一个节点。因此,遍历双链表可以从任何一端开始,而且在遍历过程中可以改变方向
    (3)将一个新值插入到双链表的过程:新节点的前向和后向link字段必须被设置,前一个节点的后向link字段和后一个节点的前向link字段也必须进行修改,使他们指向这个新节点。

参考:<C和指针>